Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 3385987

Browse files
committed
Add command to remove applicationh images
Signed-off-by: Djordje Lukic <[email protected]>
1 parent e6ba410 commit 3385987

File tree

5 files changed

+163
-14
lines changed

5 files changed

+163
-14
lines changed

e2e/images_test.go

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,58 @@ b-simple-app latest simple
2020
`
2121
)
2222

23+
func insertBundles(t *testing.T, cmd icmd.Cmd, dir *fs.Dir, info dindSwarmAndRegistryInfo) string {
24+
// Push an application so that we can later pull it by digest
25+
cmd.Command = dockerCli.Command("app", "push", "--tag", info.registryAddress+"/c-myapp", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"))
26+
r := icmd.RunCmd(cmd).Assert(t, icmd.Success)
27+
28+
// Get the digest from the output of the pull command
29+
out := r.Stdout()
30+
matches := reg.FindAllStringSubmatch(out, 1)
31+
digest := matches[0][1]
32+
33+
// Pull the app by digest
34+
cmd.Command = dockerCli.Command("app", "pull", info.registryAddress+"/c-myapp@"+digest)
35+
icmd.RunCmd(cmd).Assert(t, icmd.Success)
36+
37+
cmd.Command = dockerCli.Command("app", "bundle", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "b-simple-app", "--output", dir.Join("simple-bundle.json"))
38+
icmd.RunCmd(cmd).Assert(t, icmd.Success)
39+
cmd.Command = dockerCli.Command("app", "bundle", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "a-simple-app", "--output", dir.Join("simple-bundle.json"))
40+
icmd.RunCmd(cmd).Assert(t, icmd.Success)
41+
42+
return digest
43+
}
44+
2345
func TestImageList(t *testing.T) {
2446
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
2547
cmd := info.configuredCmd
2648
dir := fs.NewDir(t, "")
2749
defer dir.Remove()
2850

29-
// Push an application so that we can later pull it by digest
30-
cmd.Command = dockerCli.Command("app", "push", "--tag", info.registryAddress+"/c-myapp", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"))
31-
r := icmd.RunCmd(cmd).Assert(t, icmd.Success)
51+
insertBundles(t, cmd, dir, info)
52+
53+
expectedOutput := fmt.Sprintf(expected, info.registryAddress+"/c-myapp")
54+
cmd.Command = dockerCli.Command("app", "image", "ls")
55+
result := icmd.RunCmd(cmd).Assert(t, icmd.Success)
56+
assert.Equal(t, result.Stdout(), expectedOutput)
57+
})
58+
}
59+
60+
func TestImageRm(t *testing.T) {
61+
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
62+
cmd := info.configuredCmd
63+
dir := fs.NewDir(t, "")
64+
defer dir.Remove()
3265

33-
// Get the digest from the output of the pull command
34-
out := r.Stdout()
35-
matches := reg.FindAllStringSubmatch(out, 1)
36-
digest := matches[0][1]
66+
digest := insertBundles(t, cmd, dir, info)
3767

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

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

47-
expectedOutput := fmt.Sprintf(expected, info.registryAddress+"/c-myapp")
74+
expectedOutput := "REPOSITORY TAG APP NAME\n"
4875
cmd.Command = dockerCli.Command("app", "image", "ls")
4976
result := icmd.RunCmd(cmd).Assert(t, icmd.Success)
5077
assert.Equal(t, result.Stdout(), expectedOutput)

internal/commands/image/command.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
1212
Use: "image",
1313
}
1414

15-
cmd.AddCommand(listCmd(dockerCli))
15+
cmd.AddCommand(
16+
listCmd(dockerCli),
17+
rmCmd(),
18+
)
1619

1720
return cmd
1821
}

internal/commands/image/rm.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package image
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/docker/app/internal/store"
9+
"github.com/docker/cli/cli"
10+
"github.com/docker/cli/cli/config"
11+
"github.com/docker/distribution/reference"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
func rmCmd() *cobra.Command {
16+
return &cobra.Command{
17+
Use: "rm [APP_IMAGE] [APP_IMAGE...]",
18+
Short: "Remove an application image",
19+
Args: cli.RequiresMinArgs(1),
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
appstore, err := store.NewApplicationStore(config.Dir())
22+
if err != nil {
23+
return err
24+
}
25+
26+
bundleStore, err := appstore.BundleStore()
27+
if err != nil {
28+
return err
29+
}
30+
31+
errs := []string{}
32+
for _, arg := range args {
33+
if err := runRm(bundleStore, arg); err != nil {
34+
errs = append(errs, fmt.Sprintf("Error: %s", err))
35+
}
36+
}
37+
if len(errs) > 0 {
38+
return errors.New(strings.Join(errs, "\n"))
39+
}
40+
return nil
41+
},
42+
}
43+
}
44+
45+
func runRm(bundleStore store.BundleStore, app string) error {
46+
ref, err := reference.ParseNormalizedNamed(app)
47+
if err != nil {
48+
return err
49+
}
50+
51+
err = bundleStore.Remove(ref)
52+
if err != nil {
53+
return err
54+
}
55+
56+
fmt.Println("Deleted: " + ref.String())
57+
return nil
58+
}

internal/store/bundle.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type BundleStore interface {
2424
Store(ref reference.Named, bndle *bundle.Bundle) error
2525
Read(ref reference.Named) (*bundle.Bundle, error)
2626
List() ([]reference.Named, error)
27+
Remove(ref reference.Named) error
2728

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

101+
// Remove removes a bundle from the bundle store.
102+
func (b *bundleStore) Remove(ref reference.Named) error {
103+
path, err := b.storePath(ref)
104+
if err != nil {
105+
return err
106+
}
107+
108+
if _, err := os.Stat(path); os.IsNotExist(err) {
109+
return errors.New("no such image " + ref.String())
110+
}
111+
112+
return os.Remove(path)
113+
}
114+
100115
// LookupOrPullBundle will fetch the given bundle from the local
101116
// bundle store, or if it is missing from the registry, and returns
102117
// it. Always pulls if pullRef is true. If it pulls then the local

internal/store/bundle_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,49 @@ func TestList(t *testing.T) {
233233
assert.Equal(t, bundles[1].String(), "docker.io/my-repo/b-bundle@sha256:"+testSha)
234234
})
235235
}
236+
237+
func TestRemove(t *testing.T) {
238+
dockerConfigDir := fs.NewDir(t, t.Name(), fs.WithMode(0755))
239+
defer dockerConfigDir.Remove()
240+
appstore, err := NewApplicationStore(dockerConfigDir.Path())
241+
assert.NilError(t, err)
242+
bundleStore, err := appstore.BundleStore()
243+
assert.NilError(t, err)
244+
245+
refs := []reference.Named{
246+
parseRefOrDie(t, "my-repo/a-bundle:my-tag"),
247+
parseRefOrDie(t, "my-repo/b-bundle@sha256:"+testSha),
248+
}
249+
250+
bndl := &bundle.Bundle{Name: "bundle-name"}
251+
for _, ref := range refs {
252+
err = bundleStore.Store(ref, bndl)
253+
assert.NilError(t, err)
254+
}
255+
256+
t.Run("error on unknown", func(t *testing.T) {
257+
err := bundleStore.Remove(parseRefOrDie(t, "my-repo/some-bundle:1.0.0"))
258+
assert.Equal(t, err.Error(), "no such image docker.io/my-repo/some-bundle:1.0.0")
259+
})
260+
261+
t.Run("remove tagged and digested", func(t *testing.T) {
262+
bundles, err := bundleStore.List()
263+
assert.NilError(t, err)
264+
assert.Equal(t, len(bundles), 2)
265+
266+
err = bundleStore.Remove(refs[0])
267+
268+
// Once removed there should be none left
269+
assert.NilError(t, err)
270+
bundles, err = bundleStore.List()
271+
assert.NilError(t, err)
272+
assert.Equal(t, len(bundles), 1)
273+
274+
err = bundleStore.Remove(refs[1])
275+
assert.NilError(t, err)
276+
277+
bundles, err = bundleStore.List()
278+
assert.NilError(t, err)
279+
assert.Equal(t, len(bundles), 0)
280+
})
281+
}

0 commit comments

Comments
 (0)