Signed-off-by: Guillaume Tardif <guillaume.tardif@gmail.com>
This commit is contained in:
Guillaume Tardif 2021-01-18 18:35:03 +01:00
Родитель 268b3f65b0
Коммит aca816d5d6
23 изменённых файлов: 142 добавлений и 360 удалений

59
.github/stale.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,59 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 180
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- "enhancement ✨"
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: true
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when removing the stale label.
unmarkComment: >
This issue has been automatically marked as not stale anymore due to the recent activity.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been automatically closed because it had not recent activity during the stale period.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
# daysUntilStale: 30
# markComment: >
# This pull request has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
# issues:
# exemptLabels:
# - confirmed

3
.github/workflows/ci.yml поставляемый
Просмотреть файл

@ -65,12 +65,11 @@ jobs:
- name: Test
env:
BUILD_TAGS: example
run: make -f builder.Makefile test
- name: Build for local E2E
env:
BUILD_TAGS: example,e2e
BUILD_TAGS: e2e
run: make -f builder.Makefile cli
- name: E2E Test

4
.github/workflows/windows-ci.yml поставляемый
Просмотреть файл

@ -48,13 +48,11 @@ jobs:
key: go-${{ hashFiles('**/go.sum') }}
- name: Test
env:
BUILD_TAGS: example,local
run: make -f builder.Makefile test
- name: Build
env:
BUILD_TAGS: example,local,e2e
BUILD_TAGS: e2e
run: make -f builder.Makefile cli
- name: E2E Test

Просмотреть файл

@ -35,22 +35,6 @@ This will create a symbolic link from the existing Docker CLI to
You can statically cross compile the CLI for Windows, macOS, and Linux using the
`cross` target.
### Building with specific backends
You can specify which backends are build using the `BUILD_TAGS` variable.
The available backends are:
* `aci`: For ACI support (always built)
* `ecs`: For ECS support (always built)
* `example`: Testing backend (off by default)
* `local`: Beginnings of a [moby](https://github.com/moby/moby) backend
(off by default)
If you want the ACI, ECS and example backends, then you can build as follows:
```console
make BUILD_TAGS=example cli
```
### Updating the API code
The API provided by the CLI is defined using protobuf. If you make changes to

Просмотреть файл

@ -39,7 +39,7 @@ protos: ## Generate go code from .proto files
cli: ## Compile the cli
@docker build . --target cli \
--platform local \
--build-arg BUILD_TAGS=example,e2e \
--build-arg BUILD_TAGS=e2e \
--build-arg GIT_TAG=$(GIT_TAG) \
--output ./bin
@ -63,7 +63,6 @@ cross: ## Compile the CLI for linux, darwin and windows
test: ## Run unit tests
@docker build . \
--build-arg BUILD_TAGS=example \
--build-arg GIT_TAG=$(GIT_TAG) \
--target test
@ -72,7 +71,7 @@ cache-clear: ## Clear the builder cache
lint: ## run linter(s)
@docker build . \
--build-arg BUILD_TAGS=example,e2e \
--build-arg BUILD_TAGS=e2e \
--build-arg GIT_TAG=$(GIT_TAG) \
--target lint

Просмотреть файл

@ -61,9 +61,6 @@ type AwsContext EcsContext
// LocalContext is the context for the local backend
type LocalContext struct{}
// ExampleContext is the context for the example backend
type ExampleContext struct{}
// MarshalJSON implements custom JSON marshalling
func (dc ContextMetadata) MarshalJSON() ([]byte, error) {
s := map[string]interface{}{}

Просмотреть файл

@ -55,9 +55,6 @@ const (
// LocalContextType is the endpoint key in the context endpoints for a new
// local backend
LocalContextType = "local"
// ExampleContextType is the endpoint key in the context endpoints for an
// example backend
ExampleContextType = "example"
)
const (
@ -331,8 +328,5 @@ func getters() map[string]func() interface{} {
LocalContextType: func() interface{} {
return &LocalContext{}
},
ExampleContextType: func() interface{} {
return &ExampleContext{}
},
}
}

Просмотреть файл

@ -64,8 +64,8 @@ func TestGetEndpoint(t *testing.T) {
assert.NilError(t, err)
assert.Equal(t, ctx.Location, "eu")
var exampleCtx ExampleContext
err = s.GetEndpoint("aci", &exampleCtx)
var localCtx LocalContext
err = s.GetEndpoint("aci", &localCtx)
assert.Error(t, err, "wrong context type")
}

Просмотреть файл

@ -39,7 +39,7 @@ func createCommand() *cobra.Command {
longHelp := fmt.Sprintf(`Create a new context
Create docker engine context:
Create docker engine context:
$ docker context create CONTEXT [flags]
%s
@ -78,7 +78,6 @@ $ docker context create my-context --description "some description" --docker "ho
cmd.AddCommand(
createLocalCommand(),
createExampleCommand(),
)
for _, command := range extraCommands {
cmd.AddCommand(command())
@ -111,22 +110,6 @@ func createLocalCommand() *cobra.Command {
return cmd
}
func createExampleCommand() *cobra.Command {
var opts descriptionCreateOpts
cmd := &cobra.Command{
Use: "example CONTEXT",
Short: "Create a test context returning fixed output",
Args: cobra.ExactArgs(1),
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
return createDockerContext(cmd.Context(), args[0], store.ExampleContextType, opts.description, store.ExampleContext{})
},
}
addDescriptionFlag(cmd, &opts.description)
return cmd
}
func createDockerContext(ctx context.Context, name string, contextType string, description string, data interface{}) error {
s := store.ContextStore(ctx)
result := s.Create(

Просмотреть файл

@ -51,7 +51,6 @@ import (
_ "github.com/docker/compose-cli/aci"
_ "github.com/docker/compose-cli/ecs"
_ "github.com/docker/compose-cli/ecs/local"
_ "github.com/docker/compose-cli/example"
_ "github.com/docker/compose-cli/local"
)

Просмотреть файл

@ -20,7 +20,6 @@ These constraints resulted in the following architecture:
What follows is a list of useful links to help navigate the code:
* The CLI UX code is in [`cli/`](../cli)
* The backend interface is defined in [`backend/`](../backend)
* An example backend can be found in [`example/`](../example)
* The API is defined by protobufs that can be found in [`protos/`](../protos)
* The API server is in [`server/`](../server)
* The context management and interface can be found in [`context/`](../api/context)

Просмотреть файл

@ -1,187 +0,0 @@
// +build example
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package example
import (
"context"
"errors"
"fmt"
"github.com/docker/compose-cli/api/backend"
"github.com/docker/compose-cli/api/cloud"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/api/errdefs"
"github.com/docker/compose-cli/api/resources"
"github.com/docker/compose-cli/api/secrets"
"github.com/docker/compose-cli/api/volumes"
"github.com/compose-spec/compose-go/types"
)
type apiService struct {
containerService
composeService
}
func (a *apiService) ContainerService() containers.Service {
return &a.containerService
}
func (a *apiService) ComposeService() compose.Service {
return &a.composeService
}
func (a *apiService) SecretsService() secrets.Service {
return nil
}
func (a *apiService) VolumeService() volumes.Service {
return nil
}
func (a *apiService) ResourceService() resources.Service {
return nil
}
func init() {
backend.Register("example", "example", service, cloud.NotImplementedCloudService)
}
func service(ctx context.Context) (backend.Service, error) {
return &apiService{}, nil
}
type containerService struct{}
func (cs *containerService) Inspect(ctx context.Context, id string) (containers.Container, error) {
return containers.Container{
ID: "id",
Image: "nginx",
Platform: "Linux",
HostConfig: &containers.HostConfig{
RestartPolicy: "none",
},
}, nil
}
func (cs *containerService) List(ctx context.Context, all bool) ([]containers.Container, error) {
result := []containers.Container{
{
ID: "id",
Image: "nginx",
},
{
ID: "1234",
Image: "alpine",
},
}
if all {
result = append(result, containers.Container{
ID: "stopped",
Image: "nginx",
})
}
return result, nil
}
func (cs *containerService) Run(ctx context.Context, r containers.ContainerConfig) error {
fmt.Printf("Running container %q with name %q\n", r.Image, r.ID)
return nil
}
func (cs *containerService) Start(ctx context.Context, containerID string) error {
return errors.New("not implemented")
}
func (cs *containerService) Stop(ctx context.Context, containerName string, timeout *uint32) error {
return errors.New("not implemented")
}
func (cs *containerService) Kill(ctx context.Context, containerName string, signal string) error {
return errors.New("not implemented")
}
func (cs *containerService) Exec(ctx context.Context, name string, request containers.ExecRequest) error {
fmt.Printf("Executing command %q on container %q", request.Command, name)
return nil
}
func (cs *containerService) Logs(ctx context.Context, containerName string, request containers.LogsRequest) error {
fmt.Fprintf(request.Writer, "Following logs for container %q", containerName)
return nil
}
func (cs *containerService) Delete(ctx context.Context, id string, request containers.DeleteRequest) error {
fmt.Printf("Deleting container %q with force = %t\n", id, request.Force)
return nil
}
type composeService struct{}
func (cs *composeService) Build(ctx context.Context, project *types.Project) error {
fmt.Printf("Build command on project %q", project.Name)
return nil
}
func (cs *composeService) Push(ctx context.Context, project *types.Project) error {
return errdefs.ErrNotImplemented
}
func (cs *composeService) Pull(ctx context.Context, project *types.Project) error {
return errdefs.ErrNotImplemented
}
func (cs *composeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
return errdefs.ErrNotImplemented
}
func (cs *composeService) Start(ctx context.Context, project *types.Project, consumer compose.LogConsumer) error {
return errdefs.ErrNotImplemented
}
func (cs *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
fmt.Printf("Up command on project %q", project.Name)
return nil
}
func (cs *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error {
fmt.Printf("Down command on project %q", projectName)
return nil
}
func (cs *composeService) Ps(ctx context.Context, projectName string) ([]compose.ContainerSummary, error) {
return nil, errdefs.ErrNotImplemented
}
func (cs *composeService) List(ctx context.Context, project string) ([]compose.Stack, error) {
return nil, errdefs.ErrNotImplemented
}
func (cs *composeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error {
return errdefs.ErrNotImplemented
}
func (cs *composeService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) {
return nil, errdefs.ErrNotImplemented
}
func (cs *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
return errdefs.ErrNotImplemented
}

Просмотреть файл

@ -1,17 +0,0 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package example

Просмотреть файл

@ -3,23 +3,14 @@
forbiddenImports:
- github.com/docker/compose-cli/cli
- github.com/docker/compose-cli/ecs
- github.com/docker/compose-cli/example
- github.com/docker/compose-cli/local
- path: ./ecs
forbiddenImports:
- github.com/docker/compose-cli/aci
- github.com/docker/compose-cli/cli
- github.com/docker/compose-cli/example
- github.com/docker/compose-cli/local
- path: ./example
forbiddenImports:
- github.com/docker/compose-cli/aci
- github.com/docker/compose-cli/cli
- github.com/docker/compose-cli/ecs
- github.com/docker/compose-cli/local
- path: ./local
forbiddenImports:
- github.com/docker/compose-cli/aci
- github.com/docker/compose-cli/cli
- github.com/docker/compose-cli/ecs
- github.com/docker/compose-cli/example

Просмотреть файл

@ -189,23 +189,25 @@ func TestContextMetrics(t *testing.T) {
t.Run("metrics on other context type", func(t *testing.T) {
s.ResetUsage()
c.RunDockerCmd("context", "create", "example", "test-example")
c.RunDockerCmd("context", "create", "local", "test-local")
c.RunDockerCmd("ps")
c.RunDockerCmd("context", "use", "test-example")
c.RunDockerCmd("context", "use", "test-local")
c.RunDockerCmd("ps")
c.RunDockerOrExitError("stop", "unknown")
c.RunDockerCmd("context", "use", "default")
c.RunDockerCmd("--context", "test-example", "ps")
c.RunDockerCmd("--context", "test-local", "ps")
c.RunDockerCmd("context", "ls")
usage := s.GetUsage()
assert.DeepEqual(t, []string{
`{"command":"context create","context":"moby","source":"cli","status":"success"}`,
`{"command":"ps","context":"moby","source":"cli","status":"success"}`,
`{"command":"context use","context":"moby","source":"cli","status":"success"}`,
`{"command":"ps","context":"example","source":"cli","status":"success"}`,
`{"command":"stop","context":"example","source":"cli","status":"failure"}`,
`{"command":"context use","context":"example","source":"cli","status":"success"}`,
`{"command":"ps","context":"example","source":"cli","status":"success"}`,
`{"command":"ps","context":"local","source":"cli","status":"success"}`,
`{"command":"stop","context":"local","source":"cli","status":"failure"}`,
`{"command":"context use","context":"local","source":"cli","status":"success"}`,
`{"command":"ps","context":"local","source":"cli","status":"success"}`,
`{"command":"context ls","context":"moby","source":"cli","status":"success"}`,
}, usage)
})
}
@ -418,16 +420,15 @@ func TestLegacy(t *testing.T) {
})
t.Run("host flag overrides context", func(t *testing.T) {
c.RunDockerCmd("context", "create", "example", "test-example")
c.RunDockerCmd("context", "use", "test-example")
c.RunDockerCmd("context", "create", "local", "test-local")
c.RunDockerCmd("context", "use", "test-local")
endpoint := "unix:///var/run/docker.sock"
if runtime.GOOS == "windows" {
endpoint = "npipe:////./pipe/docker_engine"
}
res := c.RunDockerCmd("-H", endpoint, "ps")
// Example backend's ps output includes these strings
assert.Assert(t, !strings.Contains(res.Stdout(), "id"), "%q does not contains %q", res.Stdout(), "id")
assert.Assert(t, !strings.Contains(res.Stdout(), "1234"), "%q does not contains %q", res.Stdout(), "1234")
res := c.RunDockerCmd("-H", endpoint, "images")
// Local backend does not have images command
assert.Assert(t, strings.Contains(res.Stdout(), "IMAGE ID"), res.Stdout())
})
}
@ -459,11 +460,11 @@ func TestLegacyLogin(t *testing.T) {
func TestUnsupportedCommand(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
c.RunDockerCmd("context", "create", "example", "test-example")
res := c.RunDockerOrExitError("--context", "test-example", "images")
c.RunDockerCmd("context", "create", "local", "test-local")
res := c.RunDockerOrExitError("--context", "test-local", "images")
res.Assert(t, icmd.Expected{
ExitCode: 1,
Err: `Command "images" not available in current context (test-example), you can use the "default" context to run this command`,
Err: `Command "images" not available in current context (test-local), you can use the "default" context to run this command`,
})
}
@ -519,60 +520,13 @@ func TestVersion(t *testing.T) {
})
t.Run("delegate version flag", func(t *testing.T) {
c.RunDockerCmd("context", "create", "example", "test-example")
c.RunDockerCmd("context", "use", "test-example")
c.RunDockerCmd("context", "create", "local", "test-local")
c.RunDockerCmd("context", "use", "test-local")
res := c.RunDockerCmd("-v")
res.Assert(t, icmd.Expected{Out: "Docker version"})
})
}
func TestMockBackend(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
c.RunDockerCmd("context", "create", "example", "test-example")
res := c.RunDockerCmd("context", "use", "test-example")
res.Assert(t, icmd.Expected{Out: "test-example"})
t.Run("use", func(t *testing.T) {
res := c.RunDockerCmd("context", "show")
res.Assert(t, icmd.Expected{Out: "test-example"})
res = c.RunDockerCmd("context", "ls")
golden.Assert(t, res.Stdout(), GoldenFile("ls-out-test-example"))
})
t.Run("ps", func(t *testing.T) {
res := c.RunDockerCmd("ps")
golden.Assert(t, res.Stdout(), "ps-out-example.golden")
res = c.RunDockerCmd("ps", "--format", "pretty")
golden.Assert(t, res.Stdout(), "ps-out-example.golden")
res = c.RunDockerCmd("ps", "--format", "json")
golden.Assert(t, res.Stdout(), "ps-out-example-json.golden")
})
t.Run("ps quiet", func(t *testing.T) {
res := c.RunDockerCmd("ps", "-q")
golden.Assert(t, res.Stdout(), "ps-quiet-out-example.golden")
})
t.Run("ps quiet all", func(t *testing.T) {
res := c.RunDockerCmd("ps", "-q", "--all")
golden.Assert(t, res.Stdout(), "ps-quiet-all-out-example.golden")
})
t.Run("inspect", func(t *testing.T) {
res := c.RunDockerCmd("inspect", "id")
golden.Assert(t, res.Stdout(), "inspect-id.golden")
})
t.Run("run", func(t *testing.T) {
res := c.RunDockerCmd("run", "-d", "nginx", "-p", "80:80")
res.Assert(t, icmd.Expected{
Out: `Running container "nginx" with name`,
})
})
}
func TestFailOnEcsUsageAsPlugin(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
res := c.RunDockerCmd("context", "create", "local", "local")

14
tests/e2e/testdata/inspect-id.golden поставляемый
Просмотреть файл

@ -1,14 +0,0 @@
{
"ID": "id",
"Status": "",
"Image": "nginx",
"HostConfig": {
"RestartPolicy": "none",
"CPUReservation": 0,
"CPULimit": 0,
"MemoryReservation": 0,
"MemoryLimit": 0,
"AutoRemove": false
},
"Platform": "Linux"
}

Просмотреть файл

@ -1,3 +0,0 @@
NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
default moby Current DOCKER_HOST based configuration npipe:////./pipe/docker_engine swarm
test-example * example

Просмотреть файл

@ -1 +0,0 @@
[{"ID":"id","Image":"nginx","Status":"","Command":"","Ports":[]},{"ID":"1234","Image":"alpine","Status":"","Command":"","Ports":[]}]

3
tests/e2e/testdata/ps-out-example.golden поставляемый
Просмотреть файл

@ -1,3 +0,0 @@
CONTAINER ID IMAGE COMMAND STATUS PORTS
id nginx
1234 alpine

Просмотреть файл

@ -1,3 +0,0 @@
id
1234
stopped

Просмотреть файл

@ -1,2 +0,0 @@
id
1234

Просмотреть файл

@ -17,6 +17,7 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
@ -27,10 +28,12 @@ import (
"testing"
"time"
"gotest.tools/golden"
"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
"gotest.tools/v3/poll"
"github.com/docker/compose-cli/cli/cmd"
. "github.com/docker/compose-cli/tests/framework"
)
@ -92,6 +95,59 @@ func TestKillChildProcess(t *testing.T) {
poll.WaitOn(t, buildStopped, poll.WithDelay(1*time.Second), poll.WithTimeout(60*time.Second))
}
// no linux containers on GHA Windows CI nodes (windows server)
func TestLocalContainers(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
c.RunDockerCmd("context", "create", "local", "test-local")
res := c.RunDockerCmd("context", "use", "test-local")
res.Assert(t, icmd.Expected{Out: "test-local"})
t.Run("use", func(t *testing.T) {
res := c.RunDockerCmd("context", "show")
res.Assert(t, icmd.Expected{Out: "test-local"})
res = c.RunDockerCmd("context", "ls")
golden.Assert(t, res.Stdout(), GoldenFile("ls-out-test-local"))
})
var nginxContainerName string
t.Run("run", func(t *testing.T) {
res := c.RunDockerCmd("run", "-d", "-p", "85:80", "nginx")
nginxContainerName = strings.TrimSpace(res.Stdout())
})
defer c.RunDockerOrExitError("rm", "-f", nginxContainerName)
var nginxID string
t.Run("inspect", func(t *testing.T) {
res = c.RunDockerCmd("inspect", nginxContainerName)
inspect := &cmd.ContainerInspectView{}
err := json.Unmarshal([]byte(res.Stdout()), inspect)
assert.NilError(t, err)
nginxID = inspect.ID
})
t.Run("ps", func(t *testing.T) {
res = c.RunDockerCmd("ps")
lines := Lines(res.Stdout())
nginxFound := false
for _, line := range lines {
fields := strings.Fields(line)
if fields[0] == nginxID {
nginxFound = true
assert.Equal(t, fields[1], "nginx")
assert.Equal(t, fields[2], "/docker-entrypoint.sh")
}
}
assert.Assert(t, nginxFound, res.Stdout())
res = c.RunDockerCmd("ps", "--format", "json")
res.Assert(t, icmd.Expected{Out: `"Image":"nginx","Status":"Up Less than a second","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Ports":["0.0.0.0:85->80/tcp"`})
res = c.RunDockerCmd("ps", "--quiet")
res.Assert(t, icmd.Expected{Out: nginxID + "\n"})
})
}
func writeDockerfile(t *testing.T) string {
d, err := ioutil.TempDir("", "")
assert.NilError(t, err)

Просмотреть файл

@ -1,3 +1,3 @@
NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
default moby Current DOCKER_HOST based configuration unix:///var/run/docker.sock swarm
test-example * example
test-local * local