From 79c687bb6d41afd550dd0aaa08cb782f8d7791f5 Mon Sep 17 00:00:00 2001 From: Jacob Moody Date: Wed, 30 Sep 2026 19:38:46 -0500 Subject: [PATCH 1/2] Omit unset times in worker deployment describe-version A version that was never ramped (or never current) has no RampingSinceTime/CurrentSinceTime. These nil timestamps were converted via AsTime() to the Unix epoch, which the text output rendered as "a long while ago" and JSON output as "1970-01-01T00:00:00Z". Treat nil/epoch timestamps as unset: omit them from text output and from JSON output. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../temporalcli/commands.worker.deployment.go | 89 ++++++++++++------- .../commands.worker.deployment_test.go | 73 +++++++++++++++ 2 files changed, 129 insertions(+), 33 deletions(-) diff --git a/internal/temporalcli/commands.worker.deployment.go b/internal/temporalcli/commands.worker.deployment.go index 6dece0b35..f69daf542 100644 --- a/internal/temporalcli/commands.worker.deployment.go +++ b/internal/temporalcli/commands.worker.deployment.go @@ -23,6 +23,7 @@ import ( "go.temporal.io/sdk/converter" "go.temporal.io/sdk/worker" "google.golang.org/protobuf/types/known/fieldmaskpb" + "google.golang.org/protobuf/types/known/timestamppb" ) type versionSummariesRowType struct { @@ -112,9 +113,9 @@ type formattedWorkerDeploymentVersionInfoType struct { DeploymentName string `json:"deploymentName"` BuildID string `json:"BuildID"` CreateTime time.Time `json:"createTime"` - RoutingChangedTime time.Time `json:"routingChangedTime"` - CurrentSinceTime time.Time `json:"currentSinceTime"` - RampingSinceTime time.Time `json:"rampingSinceTime"` + RoutingChangedTime *time.Time `json:"routingChangedTime,omitempty"` + CurrentSinceTime *time.Time `json:"currentSinceTime,omitempty"` + RampingSinceTime *time.Time `json:"rampingSinceTime,omitempty"` RampPercentage float32 `json:"rampPercentage"` DrainageInfo formattedDrainageInfo `json:"drainageInfo"` TaskQueuesInfos []formattedTaskQueueInfoRowType `json:"taskQueuesInfos"` @@ -368,6 +369,28 @@ func formatTaskQueuesInfosProto(tqis []*workflowservice.DescribeWorkerDeployment return tqiRows, nil } +// optionalTimestampToTime converts a timestamp that the server may leave unset +// (e.g. RampingSinceTime for a version that was never ramped) to a time.Time. +// Both a nil timestamp and the Unix epoch are treated as unset and return the +// zero time.Time, so that text output omits the field instead of rendering it +// as "a long while ago". +func optionalTimestampToTime(t *timestamppb.Timestamp) time.Time { + if t == nil || (t.GetSeconds() == 0 && t.GetNanos() == 0) { + return time.Time{} + } + return t.AsTime() +} + +// optionalTimestampToTimePtr is like optionalTimestampToTime but returns nil for +// an unset timestamp, so that JSON output omits the field. +func optionalTimestampToTimePtr(t *timestamppb.Timestamp) *time.Time { + tm := optionalTimestampToTime(t) + if tm.IsZero() { + return nil + } + return &tm +} + func formatDrainageInfoProto(drainageInfo *deploymentpb.VersionDrainageInfo) (formattedDrainageInfo, error) { if drainageInfo == nil { return formattedDrainageInfo{}, nil @@ -529,9 +552,9 @@ func workerDeploymentVersionInfoProtoToRows(deploymentInfo *deploymentpb.WorkerD DeploymentName: deploymentInfo.GetDeploymentVersion().GetDeploymentName(), BuildID: deploymentInfo.GetDeploymentVersion().GetBuildId(), CreateTime: deploymentInfo.GetCreateTime().AsTime(), - RoutingChangedTime: deploymentInfo.GetRoutingChangedTime().AsTime(), - CurrentSinceTime: deploymentInfo.GetCurrentSinceTime().AsTime(), - RampingSinceTime: deploymentInfo.GetRampingSinceTime().AsTime(), + RoutingChangedTime: optionalTimestampToTimePtr(deploymentInfo.GetRoutingChangedTime()), + CurrentSinceTime: optionalTimestampToTimePtr(deploymentInfo.GetCurrentSinceTime()), + RampingSinceTime: optionalTimestampToTimePtr(deploymentInfo.GetRampingSinceTime()), RampPercentage: deploymentInfo.GetRampPercentage(), DrainageInfo: drainage, TaskQueuesInfos: tqi, @@ -620,9 +643,9 @@ func printWorkerDeploymentVersionInfoProto(cctx *CommandContext, deploymentInfo DeploymentName: deploymentInfo.GetDeploymentVersion().GetDeploymentName(), BuildID: deploymentInfo.GetDeploymentVersion().GetBuildId(), CreateTime: deploymentInfo.GetCreateTime().AsTime(), - RoutingChangedTime: deploymentInfo.GetRoutingChangedTime().AsTime(), - CurrentSinceTime: deploymentInfo.GetCurrentSinceTime().AsTime(), - RampingSinceTime: deploymentInfo.GetRampingSinceTime().AsTime(), + RoutingChangedTime: optionalTimestampToTime(deploymentInfo.GetRoutingChangedTime()), + CurrentSinceTime: optionalTimestampToTime(deploymentInfo.GetCurrentSinceTime()), + RampingSinceTime: optionalTimestampToTime(deploymentInfo.GetRampingSinceTime()), RampPercentage: deploymentInfo.GetRampPercentage(), DrainageStatus: drainageStr, DrainageLastChangedTime: drainageLastChangedTime, @@ -1350,18 +1373,18 @@ func (c *TemporalWorkerDeploymentCreateVersionCommand) run(cctx *CommandContext, requestID := uuid.NewString() providerType, detailsPayload, err := computeProviderConfig(&ComputeConfigArgs{ - awsLambdaFunctionArn: c.AwsLambdaFunctionArn, - awsLambdaAssumeRoleArn: c.AwsLambdaAssumeRoleArn, - awsLambdaAssumeRoleExternalId: c.AwsLambdaAssumeRoleExternalId, - awsLambdaSkipRoleAndExternalId: c.AwsLambdaSkipRoleAndExternalId, - awsAgentcoreEndpointArn: c.AwsAgentcoreEndpointArn, - awsAgentcoreAssumeRoleArn: c.AwsAgentcoreAssumeRoleArn, - awsAgentcoreAssumeRoleExternalId: c.AwsAgentcoreAssumeRoleExternalId, + awsLambdaFunctionArn: c.AwsLambdaFunctionArn, + awsLambdaAssumeRoleArn: c.AwsLambdaAssumeRoleArn, + awsLambdaAssumeRoleExternalId: c.AwsLambdaAssumeRoleExternalId, + awsLambdaSkipRoleAndExternalId: c.AwsLambdaSkipRoleAndExternalId, + awsAgentcoreEndpointArn: c.AwsAgentcoreEndpointArn, + awsAgentcoreAssumeRoleArn: c.AwsAgentcoreAssumeRoleArn, + awsAgentcoreAssumeRoleExternalId: c.AwsAgentcoreAssumeRoleExternalId, awsAgentcoreSkipRoleAndExternalId: c.AwsAgentcoreSkipRoleAndExternalId, - gcpCloudRunProject: c.GcpCloudRunProject, - gcpCloudRunRegion: c.GcpCloudRunRegion, - gcpCloudRunWorkerPool: c.GcpCloudRunWorkerPool, - gcpCloudRunServiceAccount: c.GcpCloudRunServiceAccount, + gcpCloudRunProject: c.GcpCloudRunProject, + gcpCloudRunRegion: c.GcpCloudRunRegion, + gcpCloudRunWorkerPool: c.GcpCloudRunWorkerPool, + gcpCloudRunServiceAccount: c.GcpCloudRunServiceAccount, }) if err != nil { return err @@ -1447,19 +1470,19 @@ func (c *TemporalWorkerDeploymentUpdateVersionComputeConfigCommand) run(cctx *Co } computeConfigArgs := &ComputeConfigArgs{ - awsLambdaFunctionArn: c.AwsLambdaFunctionArn, - awsLambdaAssumeRoleArn: c.AwsLambdaAssumeRoleArn, - awsLambdaAssumeRoleExternalId: c.AwsLambdaAssumeRoleExternalId, - awsLambdaSkipRoleAndExternalId: c.AwsLambdaSkipRoleAndExternalId, - awsAgentcoreEndpointArn: c.AwsAgentcoreEndpointArn, - awsAgentcoreAssumeRoleArn: c.AwsAgentcoreAssumeRoleArn, - awsAgentcoreAssumeRoleExternalId: c.AwsAgentcoreAssumeRoleExternalId, - awsAgentcoreSkipRoleAndExternalId: c.AwsAgentcoreSkipRoleAndExternalId, - gcpCloudRunProject: c.GcpCloudRunProject, - gcpCloudRunRegion: c.GcpCloudRunRegion, - gcpCloudRunWorkerPool: c.GcpCloudRunWorkerPool, - gcpCloudRunServiceAccount: c.GcpCloudRunServiceAccount, - } + awsLambdaFunctionArn: c.AwsLambdaFunctionArn, + awsLambdaAssumeRoleArn: c.AwsLambdaAssumeRoleArn, + awsLambdaAssumeRoleExternalId: c.AwsLambdaAssumeRoleExternalId, + awsLambdaSkipRoleAndExternalId: c.AwsLambdaSkipRoleAndExternalId, + awsAgentcoreEndpointArn: c.AwsAgentcoreEndpointArn, + awsAgentcoreAssumeRoleArn: c.AwsAgentcoreAssumeRoleArn, + awsAgentcoreAssumeRoleExternalId: c.AwsAgentcoreAssumeRoleExternalId, + awsAgentcoreSkipRoleAndExternalId: c.AwsAgentcoreSkipRoleAndExternalId, + gcpCloudRunProject: c.GcpCloudRunProject, + gcpCloudRunRegion: c.GcpCloudRunRegion, + gcpCloudRunWorkerPool: c.GcpCloudRunWorkerPool, + gcpCloudRunServiceAccount: c.GcpCloudRunServiceAccount, + } if c.Remove { if computeConfigArgs.hasAwsLambdaArgs() || computeConfigArgs.hasAwsAgentcoreArgs() || computeConfigArgs.hasGcpCloudRunArgs() || diff --git a/internal/temporalcli/commands.worker.deployment_test.go b/internal/temporalcli/commands.worker.deployment_test.go index db9c62661..827180990 100644 --- a/internal/temporalcli/commands.worker.deployment_test.go +++ b/internal/temporalcli/commands.worker.deployment_test.go @@ -213,6 +213,79 @@ func (s *SharedServerSuite) TestDeployment_Set_Current_Version() { s.ErrorContains(res.Err, "specify either --build-id or --unversioned") } +func (s *SharedServerSuite) TestDeployment_Describe_Version_Omits_Unset_Times() { + deploymentName := uuid.NewString() + currentBuildId := uuid.NewString() + rampingBuildId := uuid.NewString() + + res := s.Execute( + "worker", "deployment", "set-current-version", + "--address", s.Address(), + "--deployment-name", deploymentName, "--build-id", currentBuildId, + "--allow-no-pollers", + "--yes", + ) + s.NoError(res.Err) + + // A version that has only ever been current has no ramping since time, so + // it must not be rendered (previously it showed as "a long while ago"). + res = s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", deploymentName, "--build-id", currentBuildId, + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "CurrentSinceTime") + s.NotContains(res.Stdout.String(), "RampingSinceTime") + s.NotContains(res.Stdout.String(), "a long while ago") + + res = s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", deploymentName, "--build-id", currentBuildId, + "--output", "json", + ) + s.NoError(res.Err) + var rawOut map[string]any + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &rawOut)) + s.Contains(rawOut, "currentSinceTime") + s.NotContains(rawOut, "rampingSinceTime") + + // Once a version is ramping, its ramping since time is shown and the + // never-current version omits its current since time. + res = s.Execute( + "worker", "deployment", "set-ramping-version", + "--address", s.Address(), + "--deployment-name", deploymentName, "--build-id", rampingBuildId, + "--percentage", "5", + "--allow-no-pollers", + "--yes", + ) + s.NoError(res.Err) + + res = s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", deploymentName, "--build-id", rampingBuildId, + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "RampingSinceTime") + s.NotContains(res.Stdout.String(), "CurrentSinceTime") + s.NotContains(res.Stdout.String(), "a long while ago") + + res = s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", deploymentName, "--build-id", rampingBuildId, + "--output", "json", + ) + s.NoError(res.Err) + rawOut = nil + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &rawOut)) + s.Contains(rawOut, "rampingSinceTime") + s.NotContains(rawOut, "currentSinceTime") +} + func (s *SharedServerSuite) TestDeployment_Set_Current_Version_AllowNoPollers() { deploymentName := uuid.NewString() buildId := uuid.NewString() From 39f25ea559f8766bc5ea42d93fa6055564c65cc8 Mon Sep 17 00:00:00 2001 From: Jacob Moody Date: Wed, 30 Sep 2026 19:45:01 -0500 Subject: [PATCH 2/2] Print all important fields in worker deployment describe-version Add the version's Status, FirstActivationTime, LastCurrentTime, LastDeactivationTime and LastModifierIdentity to both text and JSON output. Unset times are omitted, and drainageInfo is omitted from JSON when the server does not report it instead of printing zero times. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../temporalcli/commands.worker.deployment.go | 93 ++++++++++++------- ...ommands.worker.deployment.internal_test.go | 88 ++++++++++++++++++ .../commands.worker.deployment_test.go | 5 + 3 files changed, 153 insertions(+), 33 deletions(-) diff --git a/internal/temporalcli/commands.worker.deployment.go b/internal/temporalcli/commands.worker.deployment.go index f69daf542..95fa09bc4 100644 --- a/internal/temporalcli/commands.worker.deployment.go +++ b/internal/temporalcli/commands.worker.deployment.go @@ -64,9 +64,9 @@ type formattedWorkerDeploymentListEntryType struct { } type formattedDrainageInfo struct { - DrainageStatus string `json:"drainageStatus"` - LastChangedTime time.Time `json:"lastChangedTime"` - LastCheckedTime time.Time `json:"lastCheckedTime"` + DrainageStatus string `json:"drainageStatus"` + LastChangedTime *time.Time `json:"lastChangedTime,omitempty"` + LastCheckedTime *time.Time `json:"lastCheckedTime,omitempty"` } type formattedTaskQueueInfoRowType struct { @@ -110,17 +110,22 @@ type priorityStatsDisplayRow struct { } type formattedWorkerDeploymentVersionInfoType struct { - DeploymentName string `json:"deploymentName"` - BuildID string `json:"BuildID"` - CreateTime time.Time `json:"createTime"` - RoutingChangedTime *time.Time `json:"routingChangedTime,omitempty"` - CurrentSinceTime *time.Time `json:"currentSinceTime,omitempty"` - RampingSinceTime *time.Time `json:"rampingSinceTime,omitempty"` - RampPercentage float32 `json:"rampPercentage"` - DrainageInfo formattedDrainageInfo `json:"drainageInfo"` - TaskQueuesInfos []formattedTaskQueueInfoRowType `json:"taskQueuesInfos"` - Metadata map[string]*common.Payload `json:"metadata"` - ComputeConfig *formattedComputeConfig `json:"computeConfig,omitempty"` + DeploymentName string `json:"deploymentName"` + BuildID string `json:"BuildID"` + Status string `json:"status"` + CreateTime time.Time `json:"createTime"` + RoutingChangedTime *time.Time `json:"routingChangedTime,omitempty"` + CurrentSinceTime *time.Time `json:"currentSinceTime,omitempty"` + RampingSinceTime *time.Time `json:"rampingSinceTime,omitempty"` + FirstActivationTime *time.Time `json:"firstActivationTime,omitempty"` + LastCurrentTime *time.Time `json:"lastCurrentTime,omitempty"` + LastDeactivationTime *time.Time `json:"lastDeactivationTime,omitempty"` + RampPercentage float32 `json:"rampPercentage"` + DrainageInfo *formattedDrainageInfo `json:"drainageInfo,omitempty"` + LastModifierIdentity string `json:"lastModifierIdentity,omitempty"` + TaskQueuesInfos []formattedTaskQueueInfoRowType `json:"taskQueuesInfos"` + Metadata map[string]*common.Payload `json:"metadata"` + ComputeConfig *formattedComputeConfig `json:"computeConfig,omitempty"` } type formattedComputeConfig struct { @@ -309,6 +314,13 @@ func drainageStatusProtoToStr(status enumspb.VersionDrainageStatus) (string, err } } +// versionStatusProtoToStr converts a version status to a lowercase string, e.g. +// WORKER_DEPLOYMENT_VERSION_STATUS_CURRENT becomes "current". Values unknown to +// this CLI fall back to their numeric enum string rather than failing. +func versionStatusProtoToStr(status enumspb.WorkerDeploymentVersionStatus) string { + return strings.ToLower(strings.TrimPrefix(status.String(), "WORKER_DEPLOYMENT_VERSION_STATUS_")) +} + func taskQueueTypeProtoToStr(taskQueueType enumspb.TaskQueueType) (string, error) { switch taskQueueType { case enumspb.TASK_QUEUE_TYPE_UNSPECIFIED: @@ -391,20 +403,20 @@ func optionalTimestampToTimePtr(t *timestamppb.Timestamp) *time.Time { return &tm } -func formatDrainageInfoProto(drainageInfo *deploymentpb.VersionDrainageInfo) (formattedDrainageInfo, error) { +func formatDrainageInfoProto(drainageInfo *deploymentpb.VersionDrainageInfo) (*formattedDrainageInfo, error) { if drainageInfo == nil { - return formattedDrainageInfo{}, nil + return nil, nil } drainageStr, err := drainageStatusProtoToStr(drainageInfo.GetStatus()) if err != nil { - return formattedDrainageInfo{}, err + return nil, err } - return formattedDrainageInfo{ + return &formattedDrainageInfo{ DrainageStatus: drainageStr, - LastChangedTime: drainageInfo.GetLastChangedTime().AsTime(), - LastCheckedTime: drainageInfo.GetLastCheckedTime().AsTime(), + LastChangedTime: optionalTimestampToTimePtr(drainageInfo.GetLastChangedTime()), + LastCheckedTime: optionalTimestampToTimePtr(drainageInfo.GetLastCheckedTime()), }, nil } @@ -549,17 +561,22 @@ func workerDeploymentVersionInfoProtoToRows(deploymentInfo *deploymentpb.WorkerD computeConfig := formatComputeConfigProto(deploymentInfo.GetComputeConfig()) return formattedWorkerDeploymentVersionInfoType{ - DeploymentName: deploymentInfo.GetDeploymentVersion().GetDeploymentName(), - BuildID: deploymentInfo.GetDeploymentVersion().GetBuildId(), - CreateTime: deploymentInfo.GetCreateTime().AsTime(), - RoutingChangedTime: optionalTimestampToTimePtr(deploymentInfo.GetRoutingChangedTime()), - CurrentSinceTime: optionalTimestampToTimePtr(deploymentInfo.GetCurrentSinceTime()), - RampingSinceTime: optionalTimestampToTimePtr(deploymentInfo.GetRampingSinceTime()), - RampPercentage: deploymentInfo.GetRampPercentage(), - DrainageInfo: drainage, - TaskQueuesInfos: tqi, - Metadata: deploymentInfo.GetMetadata().GetEntries(), - ComputeConfig: computeConfig, + DeploymentName: deploymentInfo.GetDeploymentVersion().GetDeploymentName(), + BuildID: deploymentInfo.GetDeploymentVersion().GetBuildId(), + Status: versionStatusProtoToStr(deploymentInfo.GetStatus()), + CreateTime: deploymentInfo.GetCreateTime().AsTime(), + RoutingChangedTime: optionalTimestampToTimePtr(deploymentInfo.GetRoutingChangedTime()), + CurrentSinceTime: optionalTimestampToTimePtr(deploymentInfo.GetCurrentSinceTime()), + RampingSinceTime: optionalTimestampToTimePtr(deploymentInfo.GetRampingSinceTime()), + FirstActivationTime: optionalTimestampToTimePtr(deploymentInfo.GetFirstActivationTime()), + LastCurrentTime: optionalTimestampToTimePtr(deploymentInfo.GetLastCurrentTime()), + LastDeactivationTime: optionalTimestampToTimePtr(deploymentInfo.GetLastDeactivationTime()), + RampPercentage: deploymentInfo.GetRampPercentage(), + DrainageInfo: drainage, + LastModifierIdentity: deploymentInfo.GetLastModifierIdentity(), + TaskQueuesInfos: tqi, + Metadata: deploymentInfo.GetMetadata().GetEntries(), + ComputeConfig: computeConfig, }, nil } @@ -621,35 +638,45 @@ func printWorkerDeploymentVersionInfoProto(cctx *CommandContext, deploymentInfo if err != nil { return err } - drainageLastChangedTime = deploymentInfo.GetDrainageInfo().GetLastChangedTime().AsTime() - drainageLastCheckedTime = deploymentInfo.GetDrainageInfo().GetLastCheckedTime().AsTime() + drainageLastChangedTime = optionalTimestampToTime(deploymentInfo.GetDrainageInfo().GetLastChangedTime()) + drainageLastCheckedTime = optionalTimestampToTime(deploymentInfo.GetDrainageInfo().GetLastCheckedTime()) } computeConfigSummary := computeConfigSummaryStr(deploymentInfo.GetComputeConfig()) printMe := struct { DeploymentName string BuildID string + Status string CreateTime time.Time RoutingChangedTime time.Time `cli:",cardOmitEmpty"` CurrentSinceTime time.Time `cli:",cardOmitEmpty"` RampingSinceTime time.Time `cli:",cardOmitEmpty"` + FirstActivationTime time.Time `cli:",cardOmitEmpty"` + LastCurrentTime time.Time `cli:",cardOmitEmpty"` + LastDeactivationTime time.Time `cli:",cardOmitEmpty"` RampPercentage float32 DrainageStatus string `cli:",cardOmitEmpty"` DrainageLastChangedTime time.Time `cli:",cardOmitEmpty"` DrainageLastCheckedTime time.Time `cli:",cardOmitEmpty"` + LastModifierIdentity string `cli:",cardOmitEmpty"` Metadata map[string]*common.Payload `cli:",cardOmitEmpty"` ComputeConfigSummary string `cli:",cardOmitEmpty"` }{ DeploymentName: deploymentInfo.GetDeploymentVersion().GetDeploymentName(), BuildID: deploymentInfo.GetDeploymentVersion().GetBuildId(), + Status: fDeploymentInfo.Status, CreateTime: deploymentInfo.GetCreateTime().AsTime(), RoutingChangedTime: optionalTimestampToTime(deploymentInfo.GetRoutingChangedTime()), CurrentSinceTime: optionalTimestampToTime(deploymentInfo.GetCurrentSinceTime()), RampingSinceTime: optionalTimestampToTime(deploymentInfo.GetRampingSinceTime()), + FirstActivationTime: optionalTimestampToTime(deploymentInfo.GetFirstActivationTime()), + LastCurrentTime: optionalTimestampToTime(deploymentInfo.GetLastCurrentTime()), + LastDeactivationTime: optionalTimestampToTime(deploymentInfo.GetLastDeactivationTime()), RampPercentage: deploymentInfo.GetRampPercentage(), DrainageStatus: drainageStr, DrainageLastChangedTime: drainageLastChangedTime, DrainageLastCheckedTime: drainageLastCheckedTime, + LastModifierIdentity: deploymentInfo.GetLastModifierIdentity(), Metadata: deploymentInfo.GetMetadata().GetEntries(), ComputeConfigSummary: computeConfigSummary, } diff --git a/internal/temporalcli/commands.worker.deployment.internal_test.go b/internal/temporalcli/commands.worker.deployment.internal_test.go index f25603819..df8be73ce 100644 --- a/internal/temporalcli/commands.worker.deployment.internal_test.go +++ b/internal/temporalcli/commands.worker.deployment.internal_test.go @@ -1,12 +1,18 @@ package temporalcli import ( + "bytes" + "encoding/json" "testing" "time" "github.com/stretchr/testify/require" + "github.com/temporalio/cli/internal/printer" computepb "go.temporal.io/api/compute/v1" + deploymentpb "go.temporal.io/api/deployment/v1" + enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/sdk/converter" + "google.golang.org/protobuf/types/known/timestamppb" ) func TestScalerTypeForProvider(t *testing.T) { @@ -213,3 +219,85 @@ func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { require.Empty(t, sg.Scaler.ScaleDownStabilization) require.Equal(t, "gcp-cloud-run", computeConfigSummaryStr(ccNoBounds)) } + +func TestPrintWorkerDeploymentVersionInfoProto_AllFields(t *testing.T) { + ts := func(sec int64) *timestamppb.Timestamp { return timestamppb.New(time.Unix(sec, 0).UTC()) } + info := &deploymentpb.WorkerDeploymentVersionInfo{ + Status: enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_DRAINING, + DeploymentVersion: &deploymentpb.WorkerDeploymentVersion{DeploymentName: "my-deployment", BuildId: "v1"}, + CreateTime: ts(1000), + RoutingChangedTime: ts(2000), + FirstActivationTime: ts(3000), + LastCurrentTime: ts(4000), + LastDeactivationTime: ts(5000), + DrainageInfo: &deploymentpb.VersionDrainageInfo{ + Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINING, + LastChangedTime: ts(6000), + LastCheckedTime: ts(7000), + }, + LastModifierIdentity: "some-identity", + } + + t.Run("text", func(t *testing.T) { + var buf bytes.Buffer + cctx := &CommandContext{Printer: &printer.Printer{Output: &buf}} + require.NoError(t, printWorkerDeploymentVersionInfoProto(cctx, info, nil, "Worker Deployment Version:", printVersionInfoOptions{})) + out := buf.String() + for _, field := range []string{ + "Status", "FirstActivationTime", "LastCurrentTime", "LastDeactivationTime", + "DrainageStatus", "DrainageLastChangedTime", "DrainageLastCheckedTime", "LastModifierIdentity", + } { + require.Contains(t, out, field) + } + require.Contains(t, out, "draining") + require.Contains(t, out, "some-identity") + require.Contains(t, out, time.Unix(3000, 0).UTC().Format(time.RFC3339)) + // Never current or ramping, so these must not be rendered. + require.NotContains(t, out, "CurrentSinceTime") + require.NotContains(t, out, "RampingSinceTime") + }) + + t.Run("json", func(t *testing.T) { + var buf bytes.Buffer + cctx := &CommandContext{JSONOutput: true, Printer: &printer.Printer{Output: &buf, JSON: true}} + require.NoError(t, printWorkerDeploymentVersionInfoProto(cctx, info, nil, "", printVersionInfoOptions{})) + var out formattedWorkerDeploymentVersionInfoType + require.NoError(t, json.Unmarshal(buf.Bytes(), &out)) + require.Equal(t, "draining", out.Status) + require.Equal(t, time.Unix(3000, 0).UTC(), *out.FirstActivationTime) + require.Equal(t, time.Unix(4000, 0).UTC(), *out.LastCurrentTime) + require.Equal(t, time.Unix(5000, 0).UTC(), *out.LastDeactivationTime) + require.Equal(t, "some-identity", out.LastModifierIdentity) + require.NotNil(t, out.DrainageInfo) + require.Equal(t, "draining", out.DrainageInfo.DrainageStatus) + require.Equal(t, time.Unix(6000, 0).UTC(), *out.DrainageInfo.LastChangedTime) + require.Equal(t, time.Unix(7000, 0).UTC(), *out.DrainageInfo.LastCheckedTime) + require.Nil(t, out.CurrentSinceTime) + require.Nil(t, out.RampingSinceTime) + }) + + t.Run("json omits unset drainage info", func(t *testing.T) { + var buf bytes.Buffer + cctx := &CommandContext{JSONOutput: true, Printer: &printer.Printer{Output: &buf, JSON: true}} + current := &deploymentpb.WorkerDeploymentVersionInfo{ + Status: enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_CURRENT, + DeploymentVersion: &deploymentpb.WorkerDeploymentVersion{DeploymentName: "my-deployment", BuildId: "v2"}, + CreateTime: ts(1000), + } + require.NoError(t, printWorkerDeploymentVersionInfoProto(cctx, current, nil, "", printVersionInfoOptions{})) + var raw map[string]any + require.NoError(t, json.Unmarshal(buf.Bytes(), &raw)) + require.Equal(t, "current", raw["status"]) + require.NotContains(t, raw, "drainageInfo") + }) +} + +func TestVersionStatusProtoToStr(t *testing.T) { + require.Equal(t, "unspecified", versionStatusProtoToStr(enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_UNSPECIFIED)) + require.Equal(t, "inactive", versionStatusProtoToStr(enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_INACTIVE)) + require.Equal(t, "current", versionStatusProtoToStr(enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_CURRENT)) + require.Equal(t, "ramping", versionStatusProtoToStr(enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_RAMPING)) + require.Equal(t, "draining", versionStatusProtoToStr(enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_DRAINING)) + require.Equal(t, "drained", versionStatusProtoToStr(enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_DRAINED)) + require.Equal(t, "created", versionStatusProtoToStr(enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_CREATED)) +} diff --git a/internal/temporalcli/commands.worker.deployment_test.go b/internal/temporalcli/commands.worker.deployment_test.go index 827180990..1c19aa501 100644 --- a/internal/temporalcli/commands.worker.deployment_test.go +++ b/internal/temporalcli/commands.worker.deployment_test.go @@ -235,6 +235,7 @@ func (s *SharedServerSuite) TestDeployment_Describe_Version_Omits_Unset_Times() "--deployment-name", deploymentName, "--build-id", currentBuildId, ) s.NoError(res.Err) + s.ContainsOnSameLine(res.Stdout.String(), "Status", "current") s.Contains(res.Stdout.String(), "CurrentSinceTime") s.NotContains(res.Stdout.String(), "RampingSinceTime") s.NotContains(res.Stdout.String(), "a long while ago") @@ -248,6 +249,8 @@ func (s *SharedServerSuite) TestDeployment_Describe_Version_Omits_Unset_Times() s.NoError(res.Err) var rawOut map[string]any s.NoError(json.Unmarshal(res.Stdout.Bytes(), &rawOut)) + s.Equal("current", rawOut["status"]) + s.NotContains(rawOut, "drainageInfo") s.Contains(rawOut, "currentSinceTime") s.NotContains(rawOut, "rampingSinceTime") @@ -269,6 +272,7 @@ func (s *SharedServerSuite) TestDeployment_Describe_Version_Omits_Unset_Times() "--deployment-name", deploymentName, "--build-id", rampingBuildId, ) s.NoError(res.Err) + s.ContainsOnSameLine(res.Stdout.String(), "Status", "ramping") s.Contains(res.Stdout.String(), "RampingSinceTime") s.NotContains(res.Stdout.String(), "CurrentSinceTime") s.NotContains(res.Stdout.String(), "a long while ago") @@ -282,6 +286,7 @@ func (s *SharedServerSuite) TestDeployment_Describe_Version_Omits_Unset_Times() s.NoError(res.Err) rawOut = nil s.NoError(json.Unmarshal(res.Stdout.Bytes(), &rawOut)) + s.Equal("ramping", rawOut["status"]) s.Contains(rawOut, "rampingSinceTime") s.NotContains(rawOut, "currentSinceTime") }