Skip to content

Commit d251e2f

Browse files
committed
feat: add image rm command
1 parent 8b3ab19 commit d251e2f

File tree

3 files changed

+118
-3
lines changed

3 files changed

+118
-3
lines changed

pkg/cmd/image/image.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func NewCmdImage() *cobra.Command {
1616
}
1717

1818
cmd.AddCommand(NewCmdList())
19-
//cmd.AddCommand(NewCmdRM())
19+
cmd.AddCommand(NewCmdRM())
2020

2121
return cmd
2222
}

pkg/cmd/image/list.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"encoding/json"
77
"fmt"
88
"os"
9+
"sort"
10+
"strings"
911
"time"
1012

1113
"connectrpc.com/connect"
@@ -18,7 +20,6 @@ import (
1820
"github.com/depot/cli/pkg/proto/depot/build/v1/buildv1connect"
1921
"github.com/pkg/errors"
2022
"github.com/spf13/cobra"
21-
"sort"
2223
)
2324

2425
func NewCmdList() *cobra.Command {
@@ -158,8 +159,14 @@ func fetchAllImages(ctx context.Context, projectID, token string, client buildv1
158159
t := img.PushedAt.AsTime()
159160
pushedAt = &t
160161
}
162+
// The API returns tags in format: registry.depot.dev/PROJECT:TAG
163+
// We want to show them as PROJECT:TAG
164+
tag := img.Tag
165+
if strings.HasPrefix(tag, "registry.depot.dev/") {
166+
tag = strings.TrimPrefix(tag, "registry.depot.dev/")
167+
}
161168
allImages = append(allImages, DepotImage{
162-
Tag: img.Tag,
169+
Tag: tag,
163170
Digest: img.Digest,
164171
SizeBytes: img.SizeBytes,
165172
PushedAt: pushedAt,

pkg/cmd/image/rm.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package image
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
"connectrpc.com/connect"
10+
"github.com/depot/cli/pkg/api"
11+
"github.com/depot/cli/pkg/helpers"
12+
v1 "github.com/depot/cli/pkg/proto/depot/build/v1"
13+
"github.com/pkg/errors"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
func NewCmdRM() *cobra.Command {
18+
var token string
19+
var force bool
20+
var digests []string
21+
22+
cmd := &cobra.Command{
23+
Use: "rm PROJECT --digest DIGEST [--digest DIGEST...]",
24+
Aliases: []string{"remove", "delete"},
25+
Short: "Remove images from the registry by digest",
26+
Long: `Remove images from the registry by digest.
27+
28+
To find image digests, use 'depot image list --project PROJECT'.
29+
Images are referenced by their sha256 digest.`,
30+
Example: ` # Delete a single image by digest
31+
depot image rm myproject --digest sha256:abc123...
32+
33+
# Delete multiple images
34+
depot image rm myproject --digest sha256:abc123... --digest sha256:def456...
35+
36+
# Force deletion without confirmation
37+
depot image rm myproject --digest sha256:abc123... --force`,
38+
Args: cobra.ExactArgs(1),
39+
RunE: func(cmd *cobra.Command, args []string) error {
40+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
41+
defer cancel()
42+
43+
projectID := args[0]
44+
45+
if len(digests) == 0 {
46+
return errors.New("at least one --digest is required")
47+
}
48+
49+
// Convert digests to the format ECR expects
50+
var imageTags []string
51+
for _, digest := range digests {
52+
// Remove sha256: prefix if present and convert to sha256- format
53+
digest = strings.TrimPrefix(digest, "sha256:")
54+
imageTags = append(imageTags, "sha256-"+digest)
55+
}
56+
57+
token, err := helpers.ResolveProjectAuth(context.Background(), token)
58+
if err != nil {
59+
return err
60+
}
61+
62+
if token == "" {
63+
return fmt.Errorf("missing API token, please run `depot login`")
64+
}
65+
66+
totalImages := len(digests)
67+
if !force {
68+
fmt.Printf("Are you sure you want to delete %d image(s)? [y/N]: ", totalImages)
69+
var response string
70+
fmt.Scanln(&response)
71+
if response != "y" && response != "Y" {
72+
fmt.Println("Operation cancelled")
73+
return nil
74+
}
75+
}
76+
77+
client := api.NewRegistryClient()
78+
79+
req := connect.NewRequest(&v1.DeleteImageRequest{
80+
ProjectId: projectID,
81+
ImageTags: imageTags,
82+
})
83+
84+
req = api.WithAuthentication(req, token)
85+
_, err = client.DeleteImage(ctx, req)
86+
if err != nil {
87+
return fmt.Errorf("failed to delete images: %v", err)
88+
}
89+
90+
if totalImages == 1 {
91+
fmt.Printf("Successfully deleted image with digest: %s\n", digests[0])
92+
} else {
93+
fmt.Printf("Successfully deleted %d images\n", totalImages)
94+
}
95+
96+
return nil
97+
},
98+
}
99+
100+
flags := cmd.Flags()
101+
flags.StringSliceVar(&digests, "digest", []string{}, "Image digest(s) to delete (can be specified multiple times)")
102+
flags.StringVar(&token, "token", "", "Depot token")
103+
flags.BoolVarP(&force, "force", "f", false, "Force deletion without confirmation")
104+
105+
cmd.MarkFlagRequired("digest")
106+
107+
return cmd
108+
}

0 commit comments

Comments
 (0)