Explorar o código

Merge pull request #786 from docker/feat_prune

Nicolas De loof %!s(int64=5) %!d(string=hai) anos
pai
achega
2f64376f84

+ 9 - 0
aci/backend.go

@@ -28,6 +28,7 @@ import (
 	"github.com/docker/compose-cli/aci/login"
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/api/resources"
 	"github.com/docker/compose-cli/api/secrets"
 	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/backend"
@@ -96,6 +97,9 @@ func getAciAPIService(aciCtx store.AciContext) *aciAPIService {
 		aciVolumeService: &aciVolumeService{
 			aciContext: aciCtx,
 		},
+		aciResourceService: &aciResourceService{
+			aciContext: aciCtx,
+		},
 	}
 }
 
@@ -103,6 +107,7 @@ type aciAPIService struct {
 	*aciContainerService
 	*aciComposeService
 	*aciVolumeService
+	*aciResourceService
 }
 
 func (a *aciAPIService) ContainerService() containers.Service {
@@ -123,6 +128,10 @@ func (a *aciAPIService) VolumeService() volumes.Service {
 	return a.aciVolumeService
 }
 
+func (a *aciAPIService) ResourceService() resources.Service {
+	return a.aciResourceService
+}
+
 func getContainerID(group containerinstance.ContainerGroup, container containerinstance.Container) string {
 	containerID := *group.Name + composeContainerSeparator + *container.Name
 	if _, ok := group.Tags[singleContainerTag]; ok {

+ 9 - 4
aci/convert/convert.go

@@ -542,12 +542,17 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
 
 // GetStatus returns status for the specified container
 func GetStatus(container containerinstance.Container, group containerinstance.ContainerGroup) string {
-	status := compose.UNKNOWN
-	if group.InstanceView != nil && group.InstanceView.State != nil {
-		status = "Node " + *group.InstanceView.State
-	}
+	status := GetGroupStatus(group)
 	if container.InstanceView != nil && container.InstanceView.CurrentState != nil {
 		status = *container.InstanceView.CurrentState.State
 	}
 	return status
 }
+
+// GetGroupStatus returns status for the container group
+func GetGroupStatus(group containerinstance.ContainerGroup) string {
+	if group.InstanceView != nil && group.InstanceView.State != nil {
+		return "Node " + *group.InstanceView.State
+	}
+	return compose.UNKNOWN
+}

+ 54 - 0
aci/resources.go

@@ -0,0 +1,54 @@
+/*
+   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 aci
+
+import (
+	"context"
+
+	"github.com/hashicorp/go-multierror"
+
+	"github.com/docker/compose-cli/aci/convert"
+	"github.com/docker/compose-cli/api/resources"
+	"github.com/docker/compose-cli/context/store"
+)
+
+type aciResourceService struct {
+	aciContext store.AciContext
+}
+
+func (cs *aciResourceService) Prune(ctx context.Context, request resources.PruneRequest) ([]string, error) {
+	res, err := getACIContainerGroups(ctx, cs.aciContext.SubscriptionID, cs.aciContext.ResourceGroup)
+	if err != nil {
+		return nil, err
+	}
+	multierr := &multierror.Error{}
+	deleted := []string{}
+	for _, containerGroup := range res {
+		if !request.Force && convert.GetGroupStatus(containerGroup) == "Node "+convert.StatusRunning {
+			continue
+		}
+
+		if !request.DryRun {
+			_, err := deleteACIContainerGroup(ctx, cs.aciContext, *containerGroup.Name)
+			multierr = multierror.Append(multierr, err)
+		}
+		if err == nil {
+			deleted = append(deleted, *containerGroup.Name)
+		}
+	}
+	return deleted, multierr.ErrorOrNil()
+}

+ 10 - 0
api/client/client.go

@@ -21,6 +21,7 @@ import (
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/api/resources"
 	"github.com/docker/compose-cli/api/secrets"
 	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/backend"
@@ -107,3 +108,12 @@ func (c *Client) VolumeService() volumes.Service {
 
 	return &volumeService{}
 }
+
+// ResourceService returns the backend service for the current context
+func (c *Client) ResourceService() resources.Service {
+	if vs := c.bs.ResourceService(); vs != nil {
+		return vs
+	}
+
+	return &resourceService{}
+}

+ 32 - 0
api/client/resources.go

@@ -0,0 +1,32 @@
+/*
+   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 client
+
+import (
+	"context"
+
+	"github.com/docker/compose-cli/api/resources"
+	"github.com/docker/compose-cli/errdefs"
+)
+
+type resourceService struct {
+}
+
+// Prune prune resources
+func (c *resourceService) Prune(ctx context.Context, request resources.PruneRequest) ([]string, error) {
+	return nil, errdefs.ErrNotImplemented
+}

+ 33 - 0
api/resources/api.go

@@ -0,0 +1,33 @@
+/*
+   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 resources
+
+import (
+	"context"
+)
+
+// PruneRequest options on what to prune
+type PruneRequest struct {
+	Force  bool
+	DryRun bool
+}
+
+// Service interacts with the underlying container backend
+type Service interface {
+	// Prune prune resources
+	Prune(ctx context.Context, request PruneRequest) ([]string, error)
+}

+ 2 - 0
backend/backend.go

@@ -25,6 +25,7 @@ import (
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/api/resources"
 	"github.com/docker/compose-cli/api/secrets"
 	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/context/cloud"
@@ -55,6 +56,7 @@ var backends = struct {
 type Service interface {
 	ContainerService() containers.Service
 	ComposeService() compose.Service
+	ResourceService() resources.Service
 	SecretsService() secrets.Service
 	VolumeService() volumes.Service
 }

+ 69 - 0
cli/cmd/prune.go

@@ -0,0 +1,69 @@
+/*
+   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 cmd
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+
+	"github.com/docker/compose-cli/api/client"
+	"github.com/docker/compose-cli/api/resources"
+)
+
+type pruneOpts struct {
+	force  bool
+	dryRun bool
+}
+
+// PruneCommand deletes backend resources
+func PruneCommand() *cobra.Command {
+	var opts pruneOpts
+	cmd := &cobra.Command{
+		Use:   "prune",
+		Short: "prune existing resources in current context",
+		Args:  cobra.MaximumNArgs(0),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runPrune(cmd.Context(), opts)
+		},
+	}
+
+	cmd.Flags().BoolVar(&opts.force, "force", false, "Also prune running containers and Compose applications")
+	cmd.Flags().BoolVar(&opts.dryRun, "dry-run", false, "List resources to be deleted, but do not delete them")
+
+	return cmd
+}
+
+func runPrune(ctx context.Context, opts pruneOpts) error {
+	c, err := client.New(ctx)
+	if err != nil {
+		return errors.Wrap(err, "cannot connect to backend")
+	}
+
+	ids, err := c.ResourceService().Prune(ctx, resources.PruneRequest{Force: opts.force, DryRun: opts.dryRun})
+	if opts.dryRun {
+		fmt.Println("resources that would be deleted:")
+	} else {
+		fmt.Println("deleted resources:")
+	}
+	for _, id := range ids {
+		fmt.Println(id)
+	}
+	return err
+}

+ 1 - 0
cli/main.go

@@ -122,6 +122,7 @@ func main() {
 		cmd.StopCommand(),
 		cmd.KillCommand(),
 		cmd.SecretCommand(),
+		cmd.PruneCommand(),
 
 		// Place holders
 		cmd.EcsCommand(),

+ 5 - 0
ecs/backend.go

@@ -24,6 +24,7 @@ import (
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/api/resources"
 	"github.com/docker/compose-cli/api/secrets"
 	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/backend"
@@ -103,6 +104,10 @@ func (a *ecsAPIService) VolumeService() volumes.Service {
 	return nil
 }
 
+func (a *ecsAPIService) ResourceService() resources.Service {
+	return nil
+}
+
 func getCloudService() (cloud.Service, error) {
 	return ecsCloudService{}, nil
 }

+ 5 - 0
ecs/local/backend.go

@@ -23,6 +23,7 @@ import (
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/api/resources"
 	"github.com/docker/compose-cli/api/secrets"
 	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/backend"
@@ -70,3 +71,7 @@ func (e ecsLocalSimulation) SecretsService() secrets.Service {
 func (e ecsLocalSimulation) ComposeService() compose.Service {
 	return e
 }
+
+func (e ecsLocalSimulation) ResourceService() resources.Service {
+	return nil
+}

+ 8 - 3
example/backend.go

@@ -28,6 +28,7 @@ import (
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/api/resources"
 	"github.com/docker/compose-cli/api/secrets"
 	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/backend"
@@ -56,6 +57,10 @@ 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)
 }
@@ -68,9 +73,9 @@ type containerService struct{}
 
 func (cs *containerService) Inspect(ctx context.Context, id string) (containers.Container, error) {
 	return containers.Container{
-		ID:                     "id",
-		Image:                  "nginx",
-		Platform:               "Linux",
+		ID:       "id",
+		Image:    "nginx",
+		Platform: "Linux",
 		HostConfig: &containers.HostConfig{
 			RestartPolicy: "none",
 		},

+ 5 - 0
local/backend.go

@@ -38,6 +38,7 @@ import (
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/api/resources"
 	"github.com/docker/compose-cli/api/secrets"
 	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/backend"
@@ -80,6 +81,10 @@ func (ms *local) VolumeService() volumes.Service {
 	return nil
 }
 
+func (ms *local) ResourceService() resources.Service {
+	return nil
+}
+
 func (ms *local) Inspect(ctx context.Context, id string) (containers.Container, error) {
 	c, err := ms.apiClient.ContainerInspect(ctx, id)
 	if err != nil {

+ 3 - 0
server/metrics_test.go

@@ -21,6 +21,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/docker/compose-cli/api/resources"
+
 	"github.com/stretchr/testify/mock"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/metadata"
@@ -116,6 +118,7 @@ func (noopService) ContainerService() containers.Service { return nil }
 func (noopService) ComposeService() compose.Service      { return nil }
 func (noopService) SecretsService() secrets.Service      { return nil }
 func (noopService) VolumeService() volumes.Service       { return nil }
+func (noopService) ResourceService() resources.Service   { return nil }
 
 type mockMetricsClient struct {
 	mock.Mock

+ 20 - 6
tests/aci-e2e/e2e-aci_test.go

@@ -488,13 +488,27 @@ func TestContainerRunAttached(t *testing.T) {
 		waitForStatus(t, c, container, convert.StatusRunning)
 	})
 
-	t.Run("kill & rm stopped container", func(t *testing.T) {
-		res := c.RunDockerCmd("kill", container)
-		res.Assert(t, icmd.Expected{Out: container})
-		waitForStatus(t, c, container, "Terminated", "Node Stopped")
+	t.Run("prune dry run", func(t *testing.T) {
+		res := c.RunDockerCmd("prune", "--dry-run")
+		fmt.Println("prune output:")
+		assert.Equal(t, "resources that would be deleted:\n", res.Stdout())
+		res = c.RunDockerCmd("prune", "--dry-run", "--force")
+		assert.Equal(t, "resources that would be deleted:\n"+container+"\n", res.Stdout())
+	})
+
+	t.Run("prune", func(t *testing.T) {
+		res := c.RunDockerCmd("prune")
+		assert.Equal(t, "deleted resources:\n", res.Stdout())
+		res = c.RunDockerCmd("ps")
+		l := lines(res.Stdout())
+		assert.Equal(t, 2, len(l))
 
-		res = c.RunDockerCmd("rm", container)
-		res.Assert(t, icmd.Expected{Out: container})
+		res = c.RunDockerCmd("prune", "--force")
+		assert.Equal(t, "deleted resources:\n"+container+"\n", res.Stdout())
+
+		res = c.RunDockerCmd("ps", "--all")
+		l = lines(res.Stdout())
+		assert.Equal(t, 1, len(l))
 	})
 }