mirror of https://github.com/docker/compose.git
Merge pull request #786 from docker/feat_prune
This commit is contained in:
commit
2f64376f84
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -122,6 +122,7 @@ func main() {
|
|||
cmd.StopCommand(),
|
||||
cmd.KillCommand(),
|
||||
cmd.SecretCommand(),
|
||||
cmd.PruneCommand(),
|
||||
|
||||
// Place holders
|
||||
cmd.EcsCommand(),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
|
||||
res = c.RunDockerCmd("rm", container)
|
||||
res.Assert(t, icmd.Expected{Out: container})
|
||||
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("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))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue