Browse Source

Remove example backend.

Signed-off-by: Guillaume Tardif <[email protected]>
Guillaume Tardif 4 years ago
parent
commit
aca816d5d6

+ 59 - 0
.github/stale.yml

@@ -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

+ 1 - 2
.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

+ 1 - 3
.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

+ 0 - 16
BUILDING.md

@@ -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

+ 2 - 3
Makefile

@@ -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
 

+ 0 - 3
api/context/store/contextmetadata.go

@@ -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{}{}

+ 0 - 6
api/context/store/store.go

@@ -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{}
-		},
 	}
 }

+ 2 - 2
api/context/store/store_test.go

@@ -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")
 }
 

+ 1 - 18
cli/cmd/context/create.go

@@ -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(

+ 0 - 1
cli/main.go

@@ -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"
 )
 

+ 0 - 1
docs/architecture.md

@@ -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)

+ 0 - 187
example/backend.go

@@ -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
-}

+ 0 - 17
example/doc.go

@@ -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

+ 0 - 9
import-restrictions.yaml

@@ -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

+ 19 - 65
tests/e2e/e2e_test.go

@@ -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")

+ 0 - 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"
-}

+ 0 - 3
tests/e2e/testdata/ls-out-test-example-windows.golden

@@ -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                                                                                                              

+ 0 - 1
tests/e2e/testdata/ps-out-example-json.golden

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

+ 0 - 3
tests/e2e/testdata/ps-out-example.golden

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

+ 0 - 3
tests/e2e/testdata/ps-quiet-all-out-example.golden

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

+ 0 - 2
tests/e2e/testdata/ps-quiet-out-example.golden

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

+ 56 - 0
tests/skip-win-ci-e2e/skip_win_ci_test.go

@@ -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 - 1
tests/e2e/testdata/ls-out-test-example.golden → tests/skip-win-ci-e2e/testdata/ls-out-test-local.golden

@@ -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