From d1343198ffa37db7f5ee688578b3f089014cfd05 Mon Sep 17 00:00:00 2001 From: Edward J Date: Thu, 8 Jan 2026 15:32:17 -0800 Subject: [PATCH 1/3] Get urls and pass to CD for states and events uploading --- src/pkg/cli/cd.go | 59 ++++++++++++++----- src/pkg/cli/client/byoc/aws/byoc.go | 26 ++++++-- .../cli/client/byoc/aws/validation_test.go | 13 ++-- src/pkg/cli/client/byoc/do/byoc.go | 21 ++++++- src/pkg/cli/client/byoc/gcp/byoc.go | 24 ++++++-- src/pkg/cli/client/client.go | 1 + src/pkg/cli/client/grpc.go | 4 ++ src/pkg/cli/client/mock.go | 15 +++++ src/pkg/cli/client/playground.go | 6 +- src/pkg/cli/client/provider.go | 17 ++++-- src/pkg/cli/common.go | 4 ++ src/pkg/cli/composeUp.go | 24 ++++++-- src/pkg/cli/composeUp_test.go | 4 +- 13 files changed, 171 insertions(+), 47 deletions(-) diff --git a/src/pkg/cli/cd.go b/src/pkg/cli/cd.go index 9d88bbef4..44501ca48 100644 --- a/src/pkg/cli/cd.go +++ b/src/pkg/cli/cd.go @@ -3,6 +3,7 @@ package cli import ( "context" "errors" + "fmt" "strings" "time" @@ -26,13 +27,34 @@ func CdCommand(ctx context.Context, projectName string, provider client.Provider return "", dryrun.ErrDryRun } - etag, err := provider.CdCommand(ctx, client.CdCommandRequest{Project: projectName, Command: command}) + var statesUrl, eventsUrl string + if _, ok := provider.(*client.PlaygroundProvider); !ok { // Do not need upload URLs for Playground + var err error + statesUrl, eventsUrl, err = GetStatesAndEventsUploadUrls(ctx, projectName, provider, fabric) + if err != nil { + return "", err + } + } + + etag, err := provider.CdCommand(ctx, client.CdCommandRequest{Project: projectName, Command: command, StatesUrl: statesUrl, EventsUrl: eventsUrl}) if err != nil || etag == "" { return "", err } + // Update deployment table to mark deployment as destroyed only after successful deletion of the subdomain + err = putDeployment(ctx, provider, fabric, putDeploymentParams{ + Action: defangv1.DeploymentAction_DEPLOYMENT_ACTION_DOWN, + ETag: etag, + ProjectName: projectName, + StatesUrl: statesUrl, + EventsUrl: eventsUrl, + }) + if err != nil { + return "", err + } + if command == client.CdCommandDestroy || command == client.CdCommandDown { - err := postDown(ctx, projectName, provider, fabric, etag) + err := postDown(ctx, projectName, provider, fabric) if err != nil { term.Debugf("postDown failed: %v", err) term.Warn("Unable to update deployment history; deployment will proceed anyway.") @@ -41,7 +63,7 @@ func CdCommand(ctx context.Context, projectName string, provider client.Provider return etag, nil } -func postDown(ctx context.Context, projectName string, provider client.Provider, fabric client.FabricClient, etag types.ETag) error { +func postDown(ctx context.Context, projectName string, provider client.Provider, fabric client.FabricClient) error { // Special bookkeeping for "down" commands: delete the subdomain zone and mark deployment as destroyed err := fabric.DeleteSubdomainZone(ctx, &defangv1.DeleteSubdomainZoneRequest{ Project: projectName, @@ -55,17 +77,6 @@ func postDown(ctx context.Context, projectName string, provider client.Provider, } return err } - - // Update deployment table to mark deployment as destroyed only after successful deletion of the subdomain - err = putDeployment(ctx, provider, fabric, putDeploymentParams{ - Action: defangv1.DeploymentAction_DEPLOYMENT_ACTION_DOWN, - ETag: etag, - ProjectName: projectName, - }) - if err != nil { - term.Debug("Failed to record deployment:", err) - return err - } return nil } @@ -144,3 +155,23 @@ func CdListLocal(ctx context.Context, provider client.Provider, allRegions bool) } return nil } + +func GetStatesAndEventsUploadUrls(ctx context.Context, projectName string, provider client.Provider, fabric client.FabricClient) (statesUrl string, eventsUrl string, err error) { + statesResp, err := fabric.CreateUploadURL(ctx, &defangv1.UploadURLRequest{ + Project: projectName, + Stack: provider.GetStackName(), + Filename: "states.json", + }) + if err != nil { + return "", "", fmt.Errorf("failed to create states upload URL: %w", err) + } + eventsResp, err := fabric.CreateUploadURL(ctx, &defangv1.UploadURLRequest{ + Project: projectName, + Stack: provider.GetStackName(), + Filename: "events.log", + }) + if err != nil { + return "", "", fmt.Errorf("failed to create events upload URL: %w", err) + } + return statesResp.Url, eventsResp.Url, nil +} diff --git a/src/pkg/cli/client/byoc/aws/byoc.go b/src/pkg/cli/client/byoc/aws/byoc.go index 4a9a89335..d98735bf6 100644 --- a/src/pkg/cli/client/byoc/aws/byoc.go +++ b/src/pkg/cli/client/byoc/aws/byoc.go @@ -172,15 +172,15 @@ func (b *ByocAws) GetDeploymentStatus(ctx context.Context) error { return nil } -func (b *ByocAws) Deploy(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (b *ByocAws) Deploy(ctx context.Context, req *client.DeployRequest) (*defangv1.DeployResponse, error) { return b.deploy(ctx, req, "up") } -func (b *ByocAws) Preview(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (b *ByocAws) Preview(ctx context.Context, req *client.DeployRequest) (*defangv1.DeployResponse, error) { return b.deploy(ctx, req, "preview") } -func (b *ByocAws) deploy(ctx context.Context, req *defangv1.DeployRequest, cmd string) (*defangv1.DeployResponse, error) { +func (b *ByocAws) deploy(ctx context.Context, req *client.DeployRequest, cmd string) (*defangv1.DeployResponse, error) { cfg, err := b.driver.LoadConfig(ctx) if err != nil { return nil, AnnotateAwsError(err) @@ -246,6 +246,8 @@ func (b *ByocAws) deploy(ctx context.Context, req *defangv1.DeployRequest, cmd s delegationSetId: req.DelegationSetId, mode: req.Mode, project: project.Name, + statesUrl: req.StatesUrl, + eventsUrl: req.EventsUrl, } if b.needDockerHubCreds { @@ -502,6 +504,9 @@ type cdCommand struct { dockerHubUsername string dockerHubAccessToken string + + statesUrl string + eventsUrl string } func (b *ByocAws) runCdCommand(ctx context.Context, cmd cdCommand) (ecs.TaskArn, error) { @@ -542,6 +547,15 @@ func (b *ByocAws) runCdCommand(ctx context.Context, cmd cdCommand) (ecs.TaskArn, return nil, err } } + + if statesUrl := pkg.Getenv("DEFANG_STATES_UPLOAD_URL", cmd.statesUrl); statesUrl != "" { + env["DEFANG_STATES_UPLOAD_URL"] = statesUrl + } + + if eventsUrl := pkg.Getenv("DEFANG_EVENTS_UPLOAD_URL", cmd.eventsUrl); eventsUrl != "" { + env["DEFANG_EVENTS_UPLOAD_URL"] = eventsUrl + } + return b.driver.Run(ctx, env, cmd.command...) } @@ -904,8 +918,10 @@ func (b *ByocAws) CdCommand(ctx context.Context, req client.CdCommandRequest) (s return "", err } cmd := cdCommand{ - project: req.Project, - command: []string{string(req.Command)}, + project: req.Project, + command: []string{string(req.Command)}, + statesUrl: req.StatesUrl, + eventsUrl: req.EventsUrl, } cdTaskArn, err := b.runCdCommand(ctx, cmd) // TODO: make domain optional for defang cd if err != nil { diff --git a/src/pkg/cli/client/byoc/aws/validation_test.go b/src/pkg/cli/client/byoc/aws/validation_test.go index 407da8cba..a444f56aa 100644 --- a/src/pkg/cli/client/byoc/aws/validation_test.go +++ b/src/pkg/cli/client/byoc/aws/validation_test.go @@ -5,6 +5,7 @@ import ( "errors" "testing" + "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/cli/client/byoc" "github.com/DefangLabs/defang/src/pkg/clouds/aws" "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs/cfn" @@ -131,7 +132,7 @@ func TestDeployValidateGPUResources(t *testing.T) { b.ByocBaseClient.SetupDone = true t.Run("no errors", func(t *testing.T) { - testDeploy := defangv1.DeployRequest{ + testDeploy := client.DeployRequest{DeployRequest: defangv1.DeployRequest{ Compose: []byte( `name: project services: @@ -144,7 +145,7 @@ services: - capabilities: [gpu] count: 1 `), - } + }} quotaClient = nil _, err := b.Deploy(ctx, &testDeploy) @@ -154,7 +155,7 @@ services: }) t.Run("error on too many gpu", func(t *testing.T) { - testDeploy := defangv1.DeployRequest{ + testDeploy := client.DeployRequest{DeployRequest: defangv1.DeployRequest{ Compose: []byte( `name: project services: @@ -167,7 +168,7 @@ services: - capabilities: [gpu] count: 24 `), - } + }} _, err := b.Deploy(ctx, &testDeploy) if err != nil && !errors.Is(err, ErrGPUQuotaZero) && !errors.As(err, &errAWSOperation) { @@ -176,7 +177,7 @@ services: }) t.Run("no error on non-gpu resource", func(t *testing.T) { - testDeploy := defangv1.DeployRequest{ + testDeploy := client.DeployRequest{DeployRequest: defangv1.DeployRequest{ Compose: []byte( `name: project services: @@ -189,7 +190,7 @@ services: - capabilities: [not-gpu] count: 24 `), - } + }} _, err := b.Deploy(ctx, &testDeploy) if err != nil && errors.Is(err, ErrGPUQuotaZero) { diff --git a/src/pkg/cli/client/byoc/do/byoc.go b/src/pkg/cli/client/byoc/do/byoc.go index b095ce253..21626d75f 100644 --- a/src/pkg/cli/client/byoc/do/byoc.go +++ b/src/pkg/cli/client/byoc/do/byoc.go @@ -126,15 +126,15 @@ func (b *ByocDo) GetProjectUpdate(ctx context.Context, projectName string) (*def return &projUpdate, nil } -func (b *ByocDo) Preview(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (b *ByocDo) Preview(ctx context.Context, req *client.DeployRequest) (*defangv1.DeployResponse, error) { return b.deploy(ctx, req, "preview") } -func (b *ByocDo) Deploy(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (b *ByocDo) Deploy(ctx context.Context, req *client.DeployRequest) (*defangv1.DeployResponse, error) { return b.deploy(ctx, req, "up") } -func (b *ByocDo) deploy(ctx context.Context, req *defangv1.DeployRequest, cmd string) (*defangv1.DeployResponse, error) { +func (b *ByocDo) deploy(ctx context.Context, req *client.DeployRequest, cmd string) (*defangv1.DeployResponse, error) { // If multiple Compose files were provided, req.Compose is the merged representation of all the files project, err := compose.LoadFromContent(ctx, req.Compose, "") if err != nil { @@ -192,6 +192,8 @@ func (b *ByocDo) deploy(ctx context.Context, req *defangv1.DeployRequest, cmd st delegateDomain: req.DelegateDomain, mode: req.Mode, project: project.Name, + statesUrl: req.StatesUrl, + eventsUrl: req.EventsUrl, } _, err = b.runCdCommand(ctx, cdCmd) if err != nil { @@ -230,6 +232,8 @@ func (b *ByocDo) CdCommand(ctx context.Context, req client.CdCommandRequest) (st command: []string{string(req.Command)}, delegateDomain: "dummy.domain", project: req.Project, + statesUrl: req.StatesUrl, + eventsUrl: req.EventsUrl, } _, err := b.runCdCommand(ctx, cmd) if err != nil { @@ -605,6 +609,8 @@ type cdCommand struct { project string delegateDomain string mode defangv1.DeploymentMode + statesUrl string + eventsUrl string } //nolint:unparam @@ -613,6 +619,15 @@ func (b *ByocDo) runCdCommand(ctx context.Context, cmd cdCommand) (*godo.App, er if err != nil { return nil, err } + + if statesUrl := pkg.Getenv("DEFANG_STATES_UPLOAD_URL", cmd.statesUrl); statesUrl != "" { + env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_STATES_UPLOAD_URL", Value: statesUrl}) + } + + if eventsUrl := pkg.Getenv("DEFANG_EVENTS_UPLOAD_URL", cmd.eventsUrl); eventsUrl != "" { + env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_EVENTS_UPLOAD_URL", Value: eventsUrl}) + } + if term.DoDebug() { // Convert the environment to a human-readable array of KEY=VALUE strings for debugging debugEnv := make([]string, len(env)) diff --git a/src/pkg/cli/client/byoc/gcp/byoc.go b/src/pkg/cli/client/byoc/gcp/byoc.go index 842b9552c..0f51e2c34 100644 --- a/src/pkg/cli/client/byoc/gcp/byoc.go +++ b/src/pkg/cli/client/byoc/gcp/byoc.go @@ -331,8 +331,10 @@ func (b *ByocGcp) CdCommand(ctx context.Context, req client.CdCommandRequest) (t return "", err } cmd := cdCommand{ - project: req.Project, - command: []string{string(req.Command)}, + project: req.Project, + command: []string{string(req.Command)}, + statesUrl: req.StatesUrl, + eventsUrl: req.EventsUrl, } cdExecutionId, err := b.runCdCommand(ctx, cmd) // TODO: make domain optional for defang cd if err != nil { @@ -347,6 +349,8 @@ type cdCommand struct { envOverride map[string]string mode defangv1.DeploymentMode project string + statesUrl string + eventsUrl string } func (b *ByocGcp) runCdCommand(ctx context.Context, cmd cdCommand) (string, error) { @@ -384,6 +388,14 @@ func (b *ByocGcp) runCdCommand(ctx context.Context, cmd cdCommand) (string, erro env["DOMAIN"] = "dummy.domain" } + if statesUrl := pkg.Getenv("DEFANG_STATES_UPLOAD_URL", cmd.statesUrl); statesUrl != "" { + env["DEFANG_STATES_UPLOAD_URL"] = statesUrl + } + + if eventsUrl := pkg.Getenv("DEFANG_EVENTS_UPLOAD_URL", cmd.eventsUrl); eventsUrl != "" { + env["DEFANG_EVENTS_UPLOAD_URL"] = eventsUrl + } + for k, v := range cmd.envOverride { env[k] = v } @@ -424,11 +436,11 @@ func (b *ByocGcp) CreateUploadURL(ctx context.Context, req *defangv1.UploadURLRe } return &defangv1.UploadURLResponse{Url: url}, nil } -func (b *ByocGcp) Deploy(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (b *ByocGcp) Deploy(ctx context.Context, req *client.DeployRequest) (*defangv1.DeployResponse, error) { return b.deploy(ctx, req, "up") } -func (b *ByocGcp) Preview(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (b *ByocGcp) Preview(ctx context.Context, req *client.DeployRequest) (*defangv1.DeployResponse, error) { return b.deploy(ctx, req, "preview") } @@ -467,7 +479,7 @@ func (b *ByocGcp) GetDeploymentStatus(ctx context.Context) error { return nil } -func (b *ByocGcp) deploy(ctx context.Context, req *defangv1.DeployRequest, command string) (*defangv1.DeployResponse, error) { +func (b *ByocGcp) deploy(ctx context.Context, req *client.DeployRequest, command string) (*defangv1.DeployResponse, error) { // If multiple Compose files were provided, req.Compose is the merged representation of all the files project, err := compose.LoadFromContent(ctx, req.Compose, "") if err != nil { @@ -524,6 +536,8 @@ func (b *ByocGcp) deploy(ctx context.Context, req *defangv1.DeployRequest, comma envOverride: map[string]string{"DEFANG_ETAG": etag}, mode: req.Mode, project: project.Name, + statesUrl: req.StatesUrl, + eventsUrl: req.EventsUrl, } execution, err := b.runCdCommand(ctx, cdCmd) if err != nil { diff --git a/src/pkg/cli/client/client.go b/src/pkg/cli/client/client.go index 8b8767c44..a3689ed05 100644 --- a/src/pkg/cli/client/client.go +++ b/src/pkg/cli/client/client.go @@ -13,6 +13,7 @@ type FabricClient interface { CanIUse(context.Context, *defangv1.CanIUseRequest) (*defangv1.CanIUseResponse, error) CheckLoginAndToS(context.Context) error CreateDelegateSubdomainZone(context.Context, *defangv1.DelegateSubdomainZoneRequest) (*defangv1.DelegateSubdomainZoneResponse, error) + CreateUploadURL(context.Context, *defangv1.UploadURLRequest) (*defangv1.UploadURLResponse, error) Debug(context.Context, *defangv1.DebugRequest) (*defangv1.DebugResponse, error) DeleteSubdomainZone(context.Context, *defangv1.DeleteSubdomainZoneRequest) error Estimate(context.Context, *defangv1.EstimateRequest) (*defangv1.EstimateResponse, error) diff --git a/src/pkg/cli/client/grpc.go b/src/pkg/cli/client/grpc.go index d769a1c11..785c19338 100644 --- a/src/pkg/cli/client/grpc.go +++ b/src/pkg/cli/client/grpc.go @@ -112,6 +112,10 @@ func (g GrpcClient) CreateDelegateSubdomainZone(ctx context.Context, req *defang return getMsg(g.client.DelegateSubdomainZone(ctx, connect.NewRequest(req))) } +func (g GrpcClient) CreateUploadURL(ctx context.Context, req *defangv1.UploadURLRequest) (*defangv1.UploadURLResponse, error) { + return getMsg(g.client.CreateUploadURL(ctx, connect.NewRequest(req))) +} + func (g GrpcClient) DeleteSubdomainZone(ctx context.Context, req *defangv1.DeleteSubdomainZoneRequest) error { _, err := getMsg(g.client.DeleteSubdomainZone(ctx, connect.NewRequest(req))) return err diff --git a/src/pkg/cli/client/mock.go b/src/pkg/cli/client/mock.go index e2fccb491..4859f97b9 100644 --- a/src/pkg/cli/client/mock.go +++ b/src/pkg/cli/client/mock.go @@ -4,6 +4,7 @@ import ( "context" "errors" "net/http" + "path" "github.com/DefangLabs/defang/src/pkg/dns" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" @@ -167,6 +168,20 @@ func (m MockFabricClient) ListDeployments(ctx context.Context, req *defangv1.Lis }, nil } +func (m MockFabricClient) CreateUploadURL(ctx context.Context, req *defangv1.UploadURLRequest) (*defangv1.UploadURLResponse, error) { + name := req.Digest + if req.Filename != "" { + name = req.Filename + } + if req.Stack != "" { + name = path.Join(req.Stack, name) + } + if req.Project != "" { + name = path.Join(req.Project, name) + } + return &defangv1.UploadURLResponse{Url: "http://mock-upload-url/" + name}, nil +} + type MockLoader struct { Project composeTypes.Project Error error diff --git a/src/pkg/cli/client/playground.go b/src/pkg/cli/client/playground.go index 7ae6f444f..dd831f75d 100644 --- a/src/pkg/cli/client/playground.go +++ b/src/pkg/cli/client/playground.go @@ -26,18 +26,18 @@ func (g *PlaygroundProvider) GetStackName() string { return "" // Playground does not use stacks } -func (g *PlaygroundProvider) Deploy(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (g *PlaygroundProvider) Deploy(ctx context.Context, req *DeployRequest) (*defangv1.DeployResponse, error) { if os.Getenv("DEFANG_PULUMI_DIR") != "" { return nil, errors.New("DEFANG_PULUMI_DIR is set, but not supported by the Playground provider") } - return getMsg(g.GetFabricClient().Deploy(ctx, connect.NewRequest(req))) + return getMsg(g.GetFabricClient().Deploy(ctx, connect.NewRequest(&req.DeployRequest))) } func (g *PlaygroundProvider) GetDeploymentStatus(ctx context.Context) error { return io.EOF // TODO: implement on fabric, for now assume service is deployed } -func (g *PlaygroundProvider) Preview(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (g *PlaygroundProvider) Preview(ctx context.Context, req *DeployRequest) (*defangv1.DeployResponse, error) { req.Preview = true return g.Deploy(ctx, req) } diff --git a/src/pkg/cli/client/provider.go b/src/pkg/cli/client/provider.go index 842a5b1f0..ecc4a5a98 100644 --- a/src/pkg/cli/client/provider.go +++ b/src/pkg/cli/client/provider.go @@ -29,8 +29,17 @@ const ( ) type CdCommandRequest struct { - Command CdCommand - Project string + Command CdCommand + Project string + StatesUrl string + EventsUrl string +} + +type DeployRequest struct { + defangv1.DeployRequest + + StatesUrl string + EventsUrl string } type PrepareDomainDelegationRequest struct { @@ -59,7 +68,7 @@ type Provider interface { CreateUploadURL(context.Context, *defangv1.UploadURLRequest) (*defangv1.UploadURLResponse, error) DelayBeforeRetry(context.Context) error DeleteConfig(context.Context, *defangv1.Secrets) error - Deploy(context.Context, *defangv1.DeployRequest) (*defangv1.DeployResponse, error) + Deploy(context.Context, *DeployRequest) (*defangv1.DeployResponse, error) GetDeploymentStatus(context.Context) error // nil means deployment is pending/running; io.EOF means deployment is done GetProjectUpdate(context.Context, string) (*defangv1.ProjectUpdate, error) GetService(context.Context, *defangv1.GetRequest) (*defangv1.ServiceInfo, error) @@ -68,7 +77,7 @@ type Provider interface { GetStackNameForDomain() string ListConfig(context.Context, *defangv1.ListConfigsRequest) (*defangv1.Secrets, error) PrepareDomainDelegation(context.Context, PrepareDomainDelegationRequest) (*PrepareDomainDelegationResponse, error) - Preview(context.Context, *defangv1.DeployRequest) (*defangv1.DeployResponse, error) + Preview(context.Context, *DeployRequest) (*defangv1.DeployResponse, error) PutConfig(context.Context, *defangv1.PutConfigRequest) error QueryForDebug(context.Context, *defangv1.DebugRequest) error QueryLogs(context.Context, *defangv1.TailRequest) (ServerStream[defangv1.TailResponse], error) diff --git a/src/pkg/cli/common.go b/src/pkg/cli/common.go index ce8c1238e..ba562aba4 100644 --- a/src/pkg/cli/common.go +++ b/src/pkg/cli/common.go @@ -45,6 +45,8 @@ type putDeploymentParams struct { Mode defangv1.DeploymentMode ProjectName string ServiceCount int + StatesUrl string + EventsUrl string } func putDeployment(ctx context.Context, provider client.Provider, fabric client.FabricClient, req putDeploymentParams) error { @@ -66,6 +68,8 @@ func putDeployment(ctx context.Context, provider client.Provider, fabric client. Stack: provider.GetStackName(), Timestamp: timestamppb.Now(), Mode: req.Mode, + StatesUrl: req.StatesUrl, + EventsUrl: req.EventsUrl, }, }) } diff --git a/src/pkg/cli/composeUp.go b/src/pkg/cli/composeUp.go index 63f5c52fd..02bcf67f2 100644 --- a/src/pkg/cli/composeUp.go +++ b/src/pkg/cli/composeUp.go @@ -85,11 +85,13 @@ func ComposeUp(ctx context.Context, fabric client.FabricClient, provider client. return nil, project, errors.New("failed to get delegate domain") } - deployRequest := &defangv1.DeployRequest{ - Mode: mode.Value(), - Project: project.Name, - Compose: bytes, - DelegateDomain: delegateDomain.Zone, + deployRequest := &client.DeployRequest{ + DeployRequest: defangv1.DeployRequest{ + Mode: mode.Value(), + Project: project.Name, + Compose: bytes, + DelegateDomain: delegateDomain.Zone, + }, } delegation, err := provider.PrepareDomainDelegation(ctx, client.PrepareDomainDelegationRequest{ @@ -103,6 +105,7 @@ func ComposeUp(ctx context.Context, fabric client.FabricClient, provider client. deployRequest.DelegationSetId = delegation.DelegationSetId } + var statesUrl, eventsUrl string var action defangv1.DeploymentAction var resp *defangv1.DeployResponse if upload == compose.UploadModePreview || upload == compose.UploadModeEstimate { @@ -124,6 +127,15 @@ func ComposeUp(ctx context.Context, fabric client.FabricClient, provider client. } } + if _, ok := provider.(*client.PlaygroundProvider); !ok { // Do not need upload URLs for Playground + statesUrl, eventsUrl, err = GetStatesAndEventsUploadUrls(ctx, project.Name, provider, fabric) + if err != nil { + return nil, project, err + } + deployRequest.StatesUrl = statesUrl + deployRequest.EventsUrl = eventsUrl + } + resp, err = provider.Deploy(ctx, deployRequest) if err != nil { return nil, project, err @@ -137,6 +149,8 @@ func ComposeUp(ctx context.Context, fabric client.FabricClient, provider client. Mode: mode.Value(), ProjectName: project.Name, ServiceCount: len(fixedProject.Services), + StatesUrl: statesUrl, + EventsUrl: eventsUrl, }) if err != nil { term.Debug("Failed to record deployment:", err) diff --git a/src/pkg/cli/composeUp_test.go b/src/pkg/cli/composeUp_test.go index 46016bc9b..f15c4418f 100644 --- a/src/pkg/cli/composeUp_test.go +++ b/src/pkg/cli/composeUp_test.go @@ -25,11 +25,11 @@ type mockDeployProvider struct { tailStream *client.MockWaitStream[defangv1.TailResponse] } -func (d mockDeployProvider) Deploy(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (d mockDeployProvider) Deploy(ctx context.Context, req *client.DeployRequest) (*defangv1.DeployResponse, error) { return d.Preview(ctx, req) } -func (mockDeployProvider) Preview(ctx context.Context, req *defangv1.DeployRequest) (*defangv1.DeployResponse, error) { +func (mockDeployProvider) Preview(ctx context.Context, req *client.DeployRequest) (*defangv1.DeployResponse, error) { if len(req.Compose) == 0 { return nil, errors.New("DeployRequest needs Compose") } From 0892a87dd0393292b877bc9985d6f512a891c6a5 Mon Sep 17 00:00:00 2001 From: Edward J Date: Thu, 8 Jan 2026 15:46:32 -0800 Subject: [PATCH 2/3] Correct place to get environment override of the upload URLs --- src/pkg/cli/cd.go | 42 ++++++++++++++++++----------- src/pkg/cli/client/byoc/aws/byoc.go | 8 +++--- src/pkg/cli/client/byoc/do/byoc.go | 8 +++--- src/pkg/cli/client/byoc/gcp/byoc.go | 8 +++--- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/pkg/cli/cd.go b/src/pkg/cli/cd.go index 44501ca48..f0d83e350 100644 --- a/src/pkg/cli/cd.go +++ b/src/pkg/cli/cd.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "os" "strings" "time" @@ -157,21 +158,30 @@ func CdListLocal(ctx context.Context, provider client.Provider, allRegions bool) } func GetStatesAndEventsUploadUrls(ctx context.Context, projectName string, provider client.Provider, fabric client.FabricClient) (statesUrl string, eventsUrl string, err error) { - statesResp, err := fabric.CreateUploadURL(ctx, &defangv1.UploadURLRequest{ - Project: projectName, - Stack: provider.GetStackName(), - Filename: "states.json", - }) - if err != nil { - return "", "", fmt.Errorf("failed to create states upload URL: %w", err) - } - eventsResp, err := fabric.CreateUploadURL(ctx, &defangv1.UploadURLRequest{ - Project: projectName, - Stack: provider.GetStackName(), - Filename: "events.log", - }) - if err != nil { - return "", "", fmt.Errorf("failed to create events upload URL: %w", err) + // Allow overriding upload URLs via environment variables + statesUrl, eventsUrl = os.Getenv("DEFANG_STATES_UPLOAD_URL"), os.Getenv("DEFANG_EVENTS_UPLOAD_URL") + + if statesUrl == "" { + statesResp, err := fabric.CreateUploadURL(ctx, &defangv1.UploadURLRequest{ + Project: projectName, + Stack: provider.GetStackName(), + Filename: "states.json", + }) + if err != nil { + return "", "", fmt.Errorf("failed to create states upload URL: %w", err) + } + statesUrl = statesResp.Url + } + if eventsUrl == "" { + eventsResp, err := fabric.CreateUploadURL(ctx, &defangv1.UploadURLRequest{ + Project: projectName, + Stack: provider.GetStackName(), + Filename: "events.log", + }) + if err != nil { + return "", "", fmt.Errorf("failed to create events upload URL: %w", err) + } + eventsUrl = eventsResp.Url } - return statesResp.Url, eventsResp.Url, nil + return statesUrl, eventsUrl, nil } diff --git a/src/pkg/cli/client/byoc/aws/byoc.go b/src/pkg/cli/client/byoc/aws/byoc.go index d98735bf6..ff0cbb25e 100644 --- a/src/pkg/cli/client/byoc/aws/byoc.go +++ b/src/pkg/cli/client/byoc/aws/byoc.go @@ -548,12 +548,12 @@ func (b *ByocAws) runCdCommand(ctx context.Context, cmd cdCommand) (ecs.TaskArn, } } - if statesUrl := pkg.Getenv("DEFANG_STATES_UPLOAD_URL", cmd.statesUrl); statesUrl != "" { - env["DEFANG_STATES_UPLOAD_URL"] = statesUrl + if cmd.statesUrl != "" { + env["DEFANG_STATES_UPLOAD_URL"] = cmd.statesUrl } - if eventsUrl := pkg.Getenv("DEFANG_EVENTS_UPLOAD_URL", cmd.eventsUrl); eventsUrl != "" { - env["DEFANG_EVENTS_UPLOAD_URL"] = eventsUrl + if cmd.eventsUrl != "" { + env["DEFANG_EVENTS_UPLOAD_URL"] = cmd.eventsUrl } return b.driver.Run(ctx, env, cmd.command...) diff --git a/src/pkg/cli/client/byoc/do/byoc.go b/src/pkg/cli/client/byoc/do/byoc.go index 21626d75f..54e144255 100644 --- a/src/pkg/cli/client/byoc/do/byoc.go +++ b/src/pkg/cli/client/byoc/do/byoc.go @@ -620,12 +620,12 @@ func (b *ByocDo) runCdCommand(ctx context.Context, cmd cdCommand) (*godo.App, er return nil, err } - if statesUrl := pkg.Getenv("DEFANG_STATES_UPLOAD_URL", cmd.statesUrl); statesUrl != "" { - env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_STATES_UPLOAD_URL", Value: statesUrl}) + if cmd.statesUrl != "" { + env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_STATES_UPLOAD_URL", Value: cmd.statesUrl}) } - if eventsUrl := pkg.Getenv("DEFANG_EVENTS_UPLOAD_URL", cmd.eventsUrl); eventsUrl != "" { - env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_EVENTS_UPLOAD_URL", Value: eventsUrl}) + if cmd.eventsUrl != "" { + env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_EVENTS_UPLOAD_URL", Value: cmd.eventsUrl}) } if term.DoDebug() { diff --git a/src/pkg/cli/client/byoc/gcp/byoc.go b/src/pkg/cli/client/byoc/gcp/byoc.go index 0f51e2c34..b9266f7bd 100644 --- a/src/pkg/cli/client/byoc/gcp/byoc.go +++ b/src/pkg/cli/client/byoc/gcp/byoc.go @@ -388,12 +388,12 @@ func (b *ByocGcp) runCdCommand(ctx context.Context, cmd cdCommand) (string, erro env["DOMAIN"] = "dummy.domain" } - if statesUrl := pkg.Getenv("DEFANG_STATES_UPLOAD_URL", cmd.statesUrl); statesUrl != "" { - env["DEFANG_STATES_UPLOAD_URL"] = statesUrl + if cmd.statesUrl != "" { + env["DEFANG_STATES_UPLOAD_URL"] = cmd.statesUrl } - if eventsUrl := pkg.Getenv("DEFANG_EVENTS_UPLOAD_URL", cmd.eventsUrl); eventsUrl != "" { - env["DEFANG_EVENTS_UPLOAD_URL"] = eventsUrl + if cmd.eventsUrl != "" { + env["DEFANG_EVENTS_UPLOAD_URL"] = cmd.eventsUrl } for k, v := range cmd.envOverride { From 0a2b98ffc1d0da4fcf645dd978fb8e4f40852ef0 Mon Sep 17 00:00:00 2001 From: Edward J Date: Fri, 9 Jan 2026 12:52:54 -0800 Subject: [PATCH 3/3] Address coderabbit comments --- src/pkg/cli/client/byoc/aws/byoc_integration_test.go | 5 +++-- src/pkg/cli/client/byoc/do/byoc.go | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pkg/cli/client/byoc/aws/byoc_integration_test.go b/src/pkg/cli/client/byoc/aws/byoc_integration_test.go index 90239fcbc..b8933798b 100644 --- a/src/pkg/cli/client/byoc/aws/byoc_integration_test.go +++ b/src/pkg/cli/client/byoc/aws/byoc_integration_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/cli/client/byoc" "github.com/DefangLabs/defang/src/pkg/cli/compose" "github.com/DefangLabs/defang/src/pkg/clouds/aws" @@ -21,9 +22,9 @@ func TestDeploy(t *testing.T) { t.Run("multiple ingress without domain", func(t *testing.T) { t.Skip("skipping test: delegation enabled") - _, err := b.Deploy(context.Background(), &defangv1.DeployRequest{ + _, err := b.Deploy(context.Background(), &client.DeployRequest{DeployRequest: defangv1.DeployRequest{ Project: "byoc_integration_test", - }) + }}) if err == nil || !strings.Contains(err.Error(), "duplicate endpoint:") { t.Error("expected error") } diff --git a/src/pkg/cli/client/byoc/do/byoc.go b/src/pkg/cli/client/byoc/do/byoc.go index 54e144255..886446a3e 100644 --- a/src/pkg/cli/client/byoc/do/byoc.go +++ b/src/pkg/cli/client/byoc/do/byoc.go @@ -621,11 +621,11 @@ func (b *ByocDo) runCdCommand(ctx context.Context, cmd cdCommand) (*godo.App, er } if cmd.statesUrl != "" { - env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_STATES_UPLOAD_URL", Value: cmd.statesUrl}) + env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_STATES_UPLOAD_URL", Value: cmd.statesUrl, Type: godo.AppVariableType_Secret}) } if cmd.eventsUrl != "" { - env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_EVENTS_UPLOAD_URL", Value: cmd.eventsUrl}) + env = append(env, &godo.AppVariableDefinition{Key: "DEFANG_EVENTS_UPLOAD_URL", Value: cmd.eventsUrl, Type: godo.AppVariableType_Secret}) } if term.DoDebug() {