Skip to content

Commit b01dc39

Browse files
Merge pull request #391 from depot/feat/improve-depot-pull
feat: improve `depot pull` to support tags
2 parents 7b86958 + 6deae68 commit b01dc39

File tree

1 file changed

+99
-13
lines changed

1 file changed

+99
-13
lines changed

pkg/cmd/pull/pull.go

Lines changed: 99 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package pull
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
"connectrpc.com/connect"
89
depotapi "github.com/depot/cli/pkg/api"
@@ -18,19 +19,23 @@ import (
1819
"golang.org/x/sync/errgroup"
1920
)
2021

22+
const (
23+
depotRegistry = "registry.depot.dev"
24+
)
25+
2126
func NewCmdPull() *cobra.Command {
2227
var (
2328
token string
2429
projectID string
2530
platform string
26-
buildID string
31+
param string
2732
progress string
2833
userTags []string
2934
targets []string
3035
)
3136

3237
cmd := &cobra.Command{
33-
Use: "pull [flags] [buildID]",
38+
Use: "pull [flags] [buildID|tag]",
3439
Short: "Pull a project's build from the Depot registry",
3540
Args: cli.RequiresMaxArgs(1),
3641
RunE: func(cmd *cobra.Command, args []string) error {
@@ -40,7 +45,7 @@ func NewCmdPull() *cobra.Command {
4045
}
4146

4247
if len(args) > 0 {
43-
buildID = args[0]
48+
param = args[0]
4449
}
4550
_, isCI := ci.Provider()
4651
if progress == prog.PrinterModeAuto && isCI {
@@ -49,7 +54,7 @@ func NewCmdPull() *cobra.Command {
4954

5055
ctx := cmd.Context()
5156

52-
token, err := helpers.ResolveProjectAuth(ctx, token)
57+
token, err = helpers.ResolveProjectAuth(ctx, token)
5358
if err != nil {
5459
return err
5560
}
@@ -58,7 +63,7 @@ func NewCmdPull() *cobra.Command {
5863
return fmt.Errorf("missing API token, please run `depot login`")
5964
}
6065

61-
if buildID == "" {
66+
if param == "" {
6267
var selectedProject *helpers.SelectedProject
6368
projectID = helpers.ResolveProjectID(projectID)
6469
if projectID == "" { // No locally saved depot.json.
@@ -85,34 +90,53 @@ func NewCmdPull() *cobra.Command {
8590
return fmt.Errorf("build ID must be specified")
8691
}
8792

88-
buildID, err = helpers.SelectBuildID(ctx, token, projectID, client)
93+
param, err = helpers.SelectBuildID(ctx, token, projectID, client)
8994
if err != nil {
9095
return err
9196
}
9297

93-
if buildID == "" {
94-
return fmt.Errorf("build ID must be specified")
98+
if param == "" {
99+
return fmt.Errorf("build ID or tag must be specified")
95100
}
96101
}
97102

103+
// Check if the buildID is actually a registry reference or tag
104+
if strings.HasPrefix(param, depotRegistry+"/") {
105+
// Extract project ID and tag from the reference
106+
projectID, tag := extractProjectIDAndTag(param)
107+
return pullByTag(ctx, dockerCli, token, projectID, tag, userTags, platform, progress)
108+
}
109+
110+
// Check if the param is in the format "projectID:tag"
111+
if strings.Contains(param, ":") && !strings.HasPrefix(param, depotRegistry+"/") {
112+
parts := strings.SplitN(param, ":", 2)
113+
if len(parts) == 2 {
114+
projectID = parts[0]
115+
tag := parts[1]
116+
return pullByTag(ctx, dockerCli, token, projectID, tag, userTags, platform, progress)
117+
}
118+
}
119+
120+
// Try to get build info first (build ID approach)
98121
client := depotapi.NewBuildClient()
99-
req := &cliv1.GetPullInfoRequest{BuildId: buildID}
122+
req := &cliv1.GetPullInfoRequest{BuildId: param}
100123
res, err := client.GetPullInfo(ctx, depotapi.WithAuthentication(connect.NewRequest(req), token))
101124
if err != nil {
102-
return err
125+
// If GetPullInfo fails, try the direct tag approach
126+
return pullByTag(ctx, dockerCli, token, projectID, param, userTags, platform, progress)
103127
}
104128

105129
buildOptions := res.Msg.Options
106130
savedForLoad := res.Msg.SaveForLoad
107131
if len(buildOptions) > 0 && !isSavedBuild(buildOptions, savedForLoad) {
108-
return fmt.Errorf("build %s is not a saved build. To use the registry use --save when building", buildID)
132+
return fmt.Errorf("build %s is not a saved build. To use the registry use --save when building", param)
109133
}
110134

111135
if isBake(buildOptions) {
112136
return pullBake(ctx, dockerCli, res.Msg, targets, userTags, platform, progress)
113-
} else {
114-
return pullBuild(ctx, dockerCli, res.Msg, userTags, platform, progress)
115137
}
138+
139+
return pullBuild(ctx, dockerCli, res.Msg, userTags, platform, progress)
116140
},
117141
}
118142

@@ -126,6 +150,68 @@ func NewCmdPull() *cobra.Command {
126150
return cmd
127151
}
128152

153+
// extractProjectIDAndTag extracts project ID and tag from a registry reference
154+
func extractProjectIDAndTag(reference string) (projectID string, tag string) {
155+
// Remove the registry prefix
156+
projectAndTag := strings.TrimPrefix(reference, depotRegistry+"/")
157+
158+
// Split on colon to separate project ID and tag
159+
parts := strings.SplitN(projectAndTag, ":", 2)
160+
if len(parts) != 2 {
161+
return "", reference
162+
}
163+
164+
return parts[0], parts[1]
165+
}
166+
167+
// pullByTag handles pulling an image directly by tag when build ID approach fails
168+
func pullByTag(ctx context.Context, dockerCli command.Cli, token, projectID, tag string, userTags []string, platform, progress string) error {
169+
client := depotapi.NewBuildClient()
170+
171+
// Get pull token for the project
172+
req := &cliv1.GetPullTokenRequest{ProjectId: &projectID}
173+
res, err := client.GetPullToken(ctx, depotapi.WithAuthentication(connect.NewRequest(req), token))
174+
if err != nil {
175+
return fmt.Errorf("failed to get pull token for project %s: %w", projectID, err)
176+
}
177+
178+
// Construct the full image reference
179+
imageName := fmt.Sprintf("%s/%s:%s", depotRegistry, projectID, tag)
180+
181+
// Set up pull options
182+
serverAddress := depotRegistry
183+
username := "x-token"
184+
opts := load.PullOptions{
185+
UserTags: userTags,
186+
Quiet: progress == prog.PrinterModeQuiet,
187+
KeepImage: true,
188+
Username: &username,
189+
Password: &res.Msg.Token,
190+
ServerAddress: &serverAddress,
191+
}
192+
if platform != "" {
193+
opts.Platform = &platform
194+
}
195+
196+
// Create a simple pull struct
197+
pull := &pull{
198+
imageName: imageName,
199+
pullOptions: opts,
200+
}
201+
202+
// Set up printer
203+
printer, cancel, err := buildPrinter(ctx, pull, progress)
204+
if err != nil {
205+
return err
206+
}
207+
defer func() {
208+
cancel()
209+
_ = printer.Wait()
210+
}()
211+
212+
return load.PullImages(ctx, dockerCli.Client(), pull.imageName, pull.pullOptions, printer)
213+
}
214+
129215
func pullBuild(ctx context.Context, dockerCli command.Cli, msg *cliv1.GetPullInfoResponse, userTags []string, platform string, progress string) error {
130216
pull := buildPullOpt(msg, userTags, platform, progress)
131217
printer, cancel, err := buildPrinter(ctx, pull, progress)

0 commit comments

Comments
 (0)