From b2d3dfe17b4461390d86ef9299896d70953150a4 Mon Sep 17 00:00:00 2001 From: Po-Han Shih Date: Wed, 30 Sep 2026 20:32:33 +0800 Subject: [PATCH] fix(cmd/deployment): resolve service from --deployment-id in log `deployment log --deployment-id ` skipped the service selector but never looked up which service the deployment belongs to, so runtime logs (the default type) always failed with "serviceID is required for runtime logs" unless --service-id was also passed. Fetch the deployment and take its project, service and environment when no service is given. Closes #279 Co-authored-by: Cursor --- internal/cmd/deployment/log/log.go | 18 +++++- internal/cmd/deployment/log/log_test.go | 81 +++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 internal/cmd/deployment/log/log_test.go diff --git a/internal/cmd/deployment/log/log.go b/internal/cmd/deployment/log/log.go index 3c81de3..e242e4c 100644 --- a/internal/cmd/deployment/log/log.go +++ b/internal/cmd/deployment/log/log.go @@ -87,8 +87,22 @@ func runLogNonInteractive(f *cmdutil.Factory, opts *Options) (err error) { opts.serviceID = service.ID } - // When serviceID is available, resolve projectID and environmentID from the service - if opts.serviceID != "" { + if opts.deploymentID != "" && opts.serviceID == "" { + // A deployment ID already identifies its project, service and environment. + deployment, err := f.ApiClient.GetDeployment(context.Background(), opts.deploymentID) + if err != nil { + return fmt.Errorf("failed to get deployment: %w", err) + } + if deployment == nil { + return fmt.Errorf("deployment %s not found", opts.deploymentID) + } + opts.projectID = deployment.ProjectID + opts.serviceID = deployment.ServiceID + if opts.environmentID == "" { + opts.environmentID = deployment.EnvironmentID + } + } else if opts.serviceID != "" { + // When serviceID is available, resolve projectID and environmentID from the service service, err := f.ApiClient.GetService(context.Background(), opts.serviceID, "", "", "") if err != nil { return fmt.Errorf("failed to get service: %w", err) diff --git a/internal/cmd/deployment/log/log_test.go b/internal/cmd/deployment/log/log_test.go new file mode 100644 index 0000000..daa4b58 --- /dev/null +++ b/internal/cmd/deployment/log/log_test.go @@ -0,0 +1,81 @@ +package log_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + deploymentLog "github.com/zeabur/cli/internal/cmd/deployment/log" + "github.com/zeabur/cli/internal/cmdutil" + "github.com/zeabur/cli/pkg/api" + "github.com/zeabur/cli/pkg/model" + "github.com/zeabur/cli/pkg/printer" +) + +// stubClient stubs only the calls a deployment-id lookup should need. Any +// other Client method hits the nil embedded interface and panics. +type stubClient struct { + api.Client + + deployment *model.Deployment + + gotServiceID, gotEnvironmentID, gotDeploymentID string +} + +func (c *stubClient) GetDeployment(_ context.Context, id string) (*model.Deployment, error) { + if c.deployment == nil || c.deployment.ID != id { + return nil, nil + } + return c.deployment, nil +} + +func (c *stubClient) GetRuntimeLogs(_ context.Context, serviceID, environmentID, deploymentID string) (model.Logs, error) { + c.gotServiceID = serviceID + c.gotEnvironmentID = environmentID + c.gotDeploymentID = deploymentID + return model.Logs{}, nil +} + +func TestLogResolvesServiceFromDeploymentID(t *testing.T) { + t.Parallel() + + for _, interactive := range []bool{false, true} { + client := &stubClient{deployment: &model.Deployment{ + ID: "6aa50431fbf9c810b64d3a2f", + ProjectID: "6aa50431fbf9c810b64d3a20", + ServiceID: "6aa50431fbf9c810b64d3a21", + EnvironmentID: "6aa50431fbf9c810b64d3a22", + }} + f := &cmdutil.Factory{ + ApiClient: client, + Log: zap.NewNop().Sugar(), + Printer: printer.New(), + } + f.Interactive = interactive + f.JSON = true + + cmd := deploymentLog.NewCmdLog(f) + cmd.SetArgs([]string{"--deployment-id", "6aa50431fbf9c810b64d3a2f"}) + require.NoError(t, cmd.Execute(), "interactive=%v", interactive) + + require.Equal(t, "6aa50431fbf9c810b64d3a21", client.gotServiceID, "interactive=%v", interactive) + require.Equal(t, "6aa50431fbf9c810b64d3a22", client.gotEnvironmentID, "interactive=%v", interactive) + require.Equal(t, "6aa50431fbf9c810b64d3a2f", client.gotDeploymentID, "interactive=%v", interactive) + } +} + +func TestLogUnknownDeploymentID(t *testing.T) { + t.Parallel() + + f := &cmdutil.Factory{ + ApiClient: &stubClient{}, + Log: zap.NewNop().Sugar(), + Printer: printer.New(), + } + + cmd := deploymentLog.NewCmdLog(f) + cmd.SetArgs([]string{"--deployment-id", "6aa50431fbf9c810b64d3a2f"}) + require.ErrorContains(t, cmd.Execute(), "not found") +}