Skip to content
This repository has been archived by the owner on Jun 13, 2021. It is now read-only.

Add command to remove application images #648

Merged
merged 1 commit into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 40 additions & 13 deletions e2e/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,58 @@ b-simple-app latest simple
`
)

func insertBundles(t *testing.T, cmd icmd.Cmd, dir *fs.Dir, info dindSwarmAndRegistryInfo) string {
// Push an application so that we can later pull it by digest
cmd.Command = dockerCli.Command("app", "push", "--tag", info.registryAddress+"/c-myapp", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"))
r := icmd.RunCmd(cmd).Assert(t, icmd.Success)

// Get the digest from the output of the pull command
out := r.Stdout()
matches := reg.FindAllStringSubmatch(out, 1)
digest := matches[0][1]

// Pull the app by digest
cmd.Command = dockerCli.Command("app", "pull", info.registryAddress+"/c-myapp@"+digest)
icmd.RunCmd(cmd).Assert(t, icmd.Success)

cmd.Command = dockerCli.Command("app", "bundle", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "b-simple-app", "--output", dir.Join("simple-bundle.json"))
icmd.RunCmd(cmd).Assert(t, icmd.Success)
cmd.Command = dockerCli.Command("app", "bundle", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "a-simple-app", "--output", dir.Join("simple-bundle.json"))
icmd.RunCmd(cmd).Assert(t, icmd.Success)

return digest
}

func TestImageList(t *testing.T) {
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
cmd := info.configuredCmd
dir := fs.NewDir(t, "")
defer dir.Remove()

// Push an application so that we can later pull it by digest
cmd.Command = dockerCli.Command("app", "push", "--tag", info.registryAddress+"/c-myapp", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"))
r := icmd.RunCmd(cmd).Assert(t, icmd.Success)
insertBundles(t, cmd, dir, info)

expectedOutput := fmt.Sprintf(expected, info.registryAddress+"/c-myapp")
cmd.Command = dockerCli.Command("app", "image", "ls")
result := icmd.RunCmd(cmd).Assert(t, icmd.Success)
assert.Equal(t, result.Stdout(), expectedOutput)
})
}

func TestImageRm(t *testing.T) {
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
cmd := info.configuredCmd
dir := fs.NewDir(t, "")
defer dir.Remove()

// Get the digest from the output of the pull command
out := r.Stdout()
matches := reg.FindAllStringSubmatch(out, 1)
digest := matches[0][1]
digest := insertBundles(t, cmd, dir, info)

// Pull the app by digest
cmd.Command = dockerCli.Command("app", "pull", info.registryAddress+"/c-myapp@"+digest)
cmd.Command = dockerCli.Command("app", "image", "rm", info.registryAddress+"/c-myapp@"+digest)
icmd.RunCmd(cmd).Assert(t, icmd.Success)

cmd.Command = dockerCli.Command("app", "bundle", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "b-simple-app", "--output", dir.Join("simple-bundle.json"))
icmd.RunCmd(cmd).Assert(t, icmd.Success)
cmd.Command = dockerCli.Command("app", "bundle", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "a-simple-app", "--output", dir.Join("simple-bundle.json"))
cmd.Command = dockerCli.Command("app", "image", "rm", "a-simple-app:latest", "b-simple-app:latest")
icmd.RunCmd(cmd).Assert(t, icmd.Success)

expectedOutput := fmt.Sprintf(expected, info.registryAddress+"/c-myapp")
expectedOutput := "REPOSITORY TAG APP NAME\n"
cmd.Command = dockerCli.Command("app", "image", "ls")
result := icmd.RunCmd(cmd).Assert(t, icmd.Success)
assert.Equal(t, result.Stdout(), expectedOutput)
Expand Down
5 changes: 4 additions & 1 deletion internal/commands/image/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
Use: "image",
}

cmd.AddCommand(listCmd(dockerCli))
cmd.AddCommand(
listCmd(dockerCli),
rmCmd(),
)

return cmd
}
58 changes: 58 additions & 0 deletions internal/commands/image/rm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package image

import (
"errors"
"fmt"
"strings"

"github.com/docker/app/internal/store"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/config"
rumpl marked this conversation as resolved.
Show resolved Hide resolved
"github.com/docker/distribution/reference"
"github.com/spf13/cobra"
)

func rmCmd() *cobra.Command {
return &cobra.Command{
Use: "rm [APP_IMAGE] [APP_IMAGE...]",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late reply, but could you add remove as alias for this?

Suggested change
Use: "rm [APP_IMAGE] [APP_IMAGE...]",
Use: "rm [APP_IMAGE] [APP_IMAGE...]",
Aliases: []string{"remove"},

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, let me open a quick PR

Short: "Remove an application image",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
appstore, err := store.NewApplicationStore(config.Dir())
if err != nil {
return err
}

bundleStore, err := appstore.BundleStore()
if err != nil {
return err
}

errs := []string{}
for _, arg := range args {
if err := runRm(bundleStore, arg); err != nil {
errs = append(errs, fmt.Sprintf("Error: %s", err))
}
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}
return nil
},
}
}

func runRm(bundleStore store.BundleStore, app string) error {
ref, err := reference.ParseNormalizedNamed(app)
if err != nil {
return err
}

err = bundleStore.Remove(ref)
if err != nil {
return err
}

fmt.Println("Deleted: " + ref.String())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should consistently use dockerCli.Out() / dockerCli.Err() for these; ideally we would print the reference on stdout, and Deleted: on stderr, but I don't think we do that currently on the CLI

return nil
}
15 changes: 15 additions & 0 deletions internal/store/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type BundleStore interface {
Store(ref reference.Named, bndle *bundle.Bundle) error
Read(ref reference.Named) (*bundle.Bundle, error)
List() ([]reference.Named, error)
Remove(ref reference.Named) error

LookupOrPullBundle(ref reference.Named, pullRef bool, config *configfile.ConfigFile, insecureRegistries []string) (*bundle.Bundle, error)
}
Expand Down Expand Up @@ -97,6 +98,20 @@ func (b *bundleStore) List() ([]reference.Named, error) {
return references, nil
}

// Remove removes a bundle from the bundle store.
func (b *bundleStore) Remove(ref reference.Named) error {
path, err := b.storePath(ref)
if err != nil {
return err
}

if _, err := os.Stat(path); os.IsNotExist(err) {
silvin-lubecki marked this conversation as resolved.
Show resolved Hide resolved
return errors.New("no such image " + ref.String())
}

return os.Remove(path)
}

// LookupOrPullBundle will fetch the given bundle from the local
// bundle store, or if it is missing from the registry, and returns
// it. Always pulls if pullRef is true. If it pulls then the local
Expand Down
46 changes: 46 additions & 0 deletions internal/store/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,49 @@ func TestList(t *testing.T) {
assert.Equal(t, bundles[1].String(), "docker.io/my-repo/b-bundle@sha256:"+testSha)
})
}

func TestRemove(t *testing.T) {
dockerConfigDir := fs.NewDir(t, t.Name(), fs.WithMode(0755))
defer dockerConfigDir.Remove()
appstore, err := NewApplicationStore(dockerConfigDir.Path())
assert.NilError(t, err)
bundleStore, err := appstore.BundleStore()
assert.NilError(t, err)

refs := []reference.Named{
parseRefOrDie(t, "my-repo/a-bundle:my-tag"),
parseRefOrDie(t, "my-repo/b-bundle@sha256:"+testSha),
}

bndl := &bundle.Bundle{Name: "bundle-name"}
for _, ref := range refs {
err = bundleStore.Store(ref, bndl)
assert.NilError(t, err)
}

t.Run("error on unknown", func(t *testing.T) {
err := bundleStore.Remove(parseRefOrDie(t, "my-repo/some-bundle:1.0.0"))
assert.Equal(t, err.Error(), "no such image docker.io/my-repo/some-bundle:1.0.0")
})

t.Run("remove tagged and digested", func(t *testing.T) {
bundles, err := bundleStore.List()
assert.NilError(t, err)
assert.Equal(t, len(bundles), 2)

err = bundleStore.Remove(refs[0])

// Once removed there should be none left
assert.NilError(t, err)
bundles, err = bundleStore.List()
assert.NilError(t, err)
assert.Equal(t, len(bundles), 1)

err = bundleStore.Remove(refs[1])
assert.NilError(t, err)

bundles, err = bundleStore.List()
assert.NilError(t, err)
assert.Equal(t, len(bundles), 0)
})
}