diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 7ccb14e3d..a0b15c3d0 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -3637,6 +3637,10 @@ type TemporalWorkerDeploymentCreateVersionCommand struct { AwsLambdaFunctionArn string AwsLambdaAssumeRoleArn string AwsLambdaAssumeRoleExternalId string + GcpCloudRunProject string + GcpCloudRunRegion string + GcpCloudRunWorkerPool string + GcpCloudRunServiceAccount string } func NewTemporalWorkerDeploymentCreateVersionCommand(cctx *CommandContext, parent *TemporalWorkerDeploymentCommand) *TemporalWorkerDeploymentCreateVersionCommand { @@ -3646,14 +3650,18 @@ func NewTemporalWorkerDeploymentCreateVersionCommand(cctx *CommandContext, paren s.Command.Use = "create-version [flags]" s.Command.Short = "Create a new Worker Deployment Version" if hasHighlighting { - s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n\x1b[1mtemporal worker deployment create-version [options]\x1b[0m\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n\x1b[1mtemporal worker deployment create-version [options]\x1b[0m\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\x1b[0m\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." } else { - s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n```\ntemporal worker deployment create-version [options]\n```\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\n```\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n```\ntemporal worker deployment create-version [options]\n```\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\n```\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com\n```\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.AwsLambdaFunctionArn, "aws-lambda-function-arn", "", "Qualified (contains version suffix) or unqualified AWS Lambda function ARN to invoke when there are no active pollers for task queue targets in the Worker Deployment.") s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleArn, "aws-lambda-assume-role-arn", "", "AWS IAM role ARN that the Temporal server will assume when invoking the Lambda function that spawns a new Worker in this Worker Deployment Version. Required when --aws-lambda-function-arn is specified.") s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleExternalId, "aws-lambda-assume-role-external-id", "", "Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-lambda-function-arn is specified.") + s.Command.Flags().StringVar(&s.GcpCloudRunProject, "gcp-cloud-run-project", "", "GCP project ID hosting the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") + s.Command.Flags().StringVar(&s.GcpCloudRunRegion, "gcp-cloud-run-region", "", "Region of the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") + s.Command.Flags().StringVar(&s.GcpCloudRunWorkerPool, "gcp-cloud-run-worker-pool", "", "GCP Cloud Run worker pool name to scale when there are no active pollers for task queue targets in the Worker Deployment.") + s.Command.Flags().StringVar(&s.GcpCloudRunServiceAccount, "gcp-cloud-run-service-account", "", "Customer GCP service account the Temporal server impersonates to manage the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") s.DeploymentVersionOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { @@ -3960,6 +3968,10 @@ type TemporalWorkerDeploymentUpdateVersionComputeConfigCommand struct { AwsLambdaFunctionArn string AwsLambdaAssumeRoleArn string AwsLambdaAssumeRoleExternalId string + GcpCloudRunProject string + GcpCloudRunRegion string + GcpCloudRunWorkerPool string + GcpCloudRunServiceAccount string Remove bool } @@ -3970,14 +3982,18 @@ func NewTemporalWorkerDeploymentUpdateVersionComputeConfigCommand(cctx *CommandC s.Command.Use = "update-version-compute-config [flags]" s.Command.Short = "Update compute configuration for a Version" if hasHighlighting { - s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\x1b[0m\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\x1b[0m\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\x1b[0m\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\x1b[0m\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com\x1b[0m\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." } else { - s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\n```\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\n```\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\n```\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\n```\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\n```\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com\n```\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\n```\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.AwsLambdaFunctionArn, "aws-lambda-function-arn", "", "Qualified (contains version suffix) or unqualified AWS Lambda function ARN to invoke when there are no active pollers for task queue targets in the Worker Deployment.") s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleArn, "aws-lambda-assume-role-arn", "", "AWS IAM role ARN that the Temporal server will assume when invoking the Lambda function that spawns a new Worker in this Worker Deployment Version. Required when --aws-lambda-function-arn is specified.") s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleExternalId, "aws-lambda-assume-role-external-id", "", "Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-lambda-function-arn is specified.") + s.Command.Flags().StringVar(&s.GcpCloudRunProject, "gcp-cloud-run-project", "", "GCP project ID hosting the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") + s.Command.Flags().StringVar(&s.GcpCloudRunRegion, "gcp-cloud-run-region", "", "Region of the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") + s.Command.Flags().StringVar(&s.GcpCloudRunWorkerPool, "gcp-cloud-run-worker-pool", "", "GCP Cloud Run worker pool name to scale when there are no active pollers for task queue targets in the Worker Deployment.") + s.Command.Flags().StringVar(&s.GcpCloudRunServiceAccount, "gcp-cloud-run-service-account", "", "Customer GCP service account the Temporal server impersonates to manage the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") s.Command.Flags().BoolVar(&s.Remove, "remove", false, "Removes any compute configuration associated with this Worker Deployment Version.") s.DeploymentVersionOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { diff --git a/internal/temporalcli/commands.worker.deployment.go b/internal/temporalcli/commands.worker.deployment.go index 3dc25be79..a887f7ddb 100644 --- a/internal/temporalcli/commands.worker.deployment.go +++ b/internal/temporalcli/commands.worker.deployment.go @@ -919,6 +919,83 @@ func awsLambdaProviderDetailsPayload( return dc.ToPayload(&providerDetails) } +func validateGCPCloudRunProviderDetails(details map[string]any) error { + for _, key := range []string{"project", "region", "worker_pool", "service_account"} { + if v, ok := details[key].(string); !ok || v == "" { + return fmt.Errorf("missing required GCP Cloud Run provider detail: %s", key) + } + } + return nil +} + +// gcpCloudRunProviderDetailsPayload returns the encoded Payload representing GCP +// Cloud Run compute provider details. All four keys are required: the first +// three name the worker-pool resource and service_account is the impersonation +// target the serverless chain depends on. +func gcpCloudRunProviderDetailsPayload( + project string, + region string, + workerPool string, + serviceAccount string, +) (*commonpb.Payload, error) { + // Map keys from temporal-auto-scaled-workers: + // https://github.com/temporalio/temporal-auto-scaled-workers/blob/d1390d11cb55b4450141ede559f7832e5620c1e4/wci/workflow/compute_provider/gcp_cloudrun.go#L21-L24 + providerDetails := map[string]any{ + "project": project, + "region": region, + "worker_pool": workerPool, + "service_account": serviceAccount, + } + err := validateGCPCloudRunProviderDetails(providerDetails) + if err != nil { + return nil, err + } + dc := converter.GetDefaultDataConverter() + return dc.ToPayload(&providerDetails) +} + +// computeProviderConfig selects the single compute provider for a Worker +// Deployment Version's "default" scaling group from the command's flags. It +// enforces that AWS Lambda and GCP Cloud Run flags are not mixed, then +// dispatches on the trigger flag (--aws-lambda-function-arn / +// --gcp-cloud-run-worker-pool). Returns an empty providerType when no provider +// flags are set, leaving the "no configuration" decision to the caller. +func computeProviderConfig( + awsLambdaFunctionARN string, + awsLambdaAssumeRoleARN string, + awsLambdaAssumeRoleExternalID string, + gcpCloudRunProject string, + gcpCloudRunRegion string, + gcpCloudRunWorkerPool string, + gcpCloudRunServiceAccount string, +) (providerType string, detailsPayload *commonpb.Payload, err error) { + awsSet := awsLambdaFunctionARN != "" || awsLambdaAssumeRoleARN != "" || awsLambdaAssumeRoleExternalID != "" + gcpSet := gcpCloudRunProject != "" || gcpCloudRunRegion != "" || gcpCloudRunWorkerPool != "" || gcpCloudRunServiceAccount != "" + if awsSet && gcpSet { + return "", nil, fmt.Errorf("cannot combine --aws-lambda-* and --gcp-cloud-run-* flags; a Worker Deployment Version supports a single compute provider") + } + + switch { + case awsLambdaFunctionARN != "": + p, err := awsLambdaProviderDetailsPayload( + awsLambdaFunctionARN, + awsLambdaAssumeRoleARN, + awsLambdaAssumeRoleExternalID, + ) + return "aws-lambda", p, err + case gcpCloudRunWorkerPool != "": + p, err := gcpCloudRunProviderDetailsPayload( + gcpCloudRunProject, + gcpCloudRunRegion, + gcpCloudRunWorkerPool, + gcpCloudRunServiceAccount, + ) + return "gcp-cloud-run", p, err + default: + return "", nil, nil + } +} + func (c *TemporalWorkerDeploymentCreateVersionCommand) run(cctx *CommandContext, args []string) error { cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) if err != nil { @@ -932,35 +1009,37 @@ func (c *TemporalWorkerDeploymentCreateVersionCommand) run(cctx *CommandContext, deploymentName := c.DeploymentName requestID := uuid.NewString() - var cc *computepb.ComputeConfig - if c.AwsLambdaFunctionArn != "" { - detailsPayload, err := awsLambdaProviderDetailsPayload( - c.AwsLambdaFunctionArn, - c.AwsLambdaAssumeRoleArn, - c.AwsLambdaAssumeRoleExternalId, - ) - if err != nil { - return err - } - cc = &computepb.ComputeConfig{ - ScalingGroups: map[string]*computepb.ComputeConfigScalingGroup{ - "default": { - Provider: &computepb.ComputeProvider{ - Type: "aws-lambda", - Details: detailsPayload, - }, - Scaler: &computepb.ComputeScaler{ - // Hard-coded: no-sync is the only supported algorithm - // in temporal-auto-scaled-workers as of 2026-04-01. - Type: "no-sync", - }, - }, - }, - } - } else { + providerType, detailsPayload, err := computeProviderConfig( + c.AwsLambdaFunctionArn, + c.AwsLambdaAssumeRoleArn, + c.AwsLambdaAssumeRoleExternalId, + c.GcpCloudRunProject, + c.GcpCloudRunRegion, + c.GcpCloudRunWorkerPool, + c.GcpCloudRunServiceAccount, + ) + if err != nil { + return err + } + if providerType == "" { // We do not allow creation of an "empty" WDV. return fmt.Errorf("missing configuration for compute provider") } + cc := &computepb.ComputeConfig{ + ScalingGroups: map[string]*computepb.ComputeConfigScalingGroup{ + "default": { + Provider: &computepb.ComputeProvider{ + Type: providerType, + Details: detailsPayload, + }, + Scaler: &computepb.ComputeScaler{ + // Hard-coded: no-sync is the only supported algorithm + // in temporal-auto-scaled-workers as of 2026-04-01. + Type: "no-sync", + }, + }, + }, + } request := &workflowservice.CreateWorkerDeploymentVersionRequest{ Namespace: ns, DeploymentVersion: &deployment.WorkerDeploymentVersion{ @@ -1005,22 +1084,30 @@ func (c *TemporalWorkerDeploymentUpdateVersionComputeConfigCommand) run(cctx *Co } if c.Remove { - if c.AwsLambdaFunctionArn != "" || c.AwsLambdaAssumeRoleArn != "" || c.AwsLambdaAssumeRoleExternalId != "" { - return fmt.Errorf("--remove cannot be combined with --aws-lambda-function-arn, --aws-lambda-assume-role-arn, or --aws-lambda-assume-role-external-id") + if c.AwsLambdaFunctionArn != "" || c.AwsLambdaAssumeRoleArn != "" || c.AwsLambdaAssumeRoleExternalId != "" || + c.GcpCloudRunProject != "" || c.GcpCloudRunRegion != "" || c.GcpCloudRunWorkerPool != "" || c.GcpCloudRunServiceAccount != "" { + return fmt.Errorf("--remove cannot be combined with --aws-lambda-* or --gcp-cloud-run-* flags") } request.RemoveComputeConfigScalingGroups = []string{"default"} } else { - detailsPayload, err := awsLambdaProviderDetailsPayload( + providerType, detailsPayload, err := computeProviderConfig( c.AwsLambdaFunctionArn, c.AwsLambdaAssumeRoleArn, c.AwsLambdaAssumeRoleExternalId, + c.GcpCloudRunProject, + c.GcpCloudRunRegion, + c.GcpCloudRunWorkerPool, + c.GcpCloudRunServiceAccount, ) if err != nil { return err } + if providerType == "" { + return fmt.Errorf("missing configuration for compute provider") + } sg := &computepb.ComputeConfigScalingGroup{ Provider: &computepb.ComputeProvider{ - Type: "aws-lambda", + Type: providerType, Details: detailsPayload, }, Scaler: &computepb.ComputeScaler{ diff --git a/internal/temporalcli/commands.worker.deployment_test.go b/internal/temporalcli/commands.worker.deployment_test.go index b1323532f..d7642ee0f 100644 --- a/internal/temporalcli/commands.worker.deployment_test.go +++ b/internal/temporalcli/commands.worker.deployment_test.go @@ -1311,6 +1311,49 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { s.Error(res.Err) s.ErrorContains(res.Err, "missing required AWS Lambda provider detail: role") + // --gcp-cloud-run-worker-pool requires project, region, and + // service-account; the first missing detail key is reported. + missingGCPProjectBuildID := uuid.NewString() + + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", missingGCPProjectBuildID, + "--gcp-cloud-run-worker-pool", "my-worker-pool", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "missing required GCP Cloud Run provider detail: project") + + // The impersonation target (service-account) is required too. + missingGCPServiceAccountBuildID := uuid.NewString() + + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", missingGCPServiceAccountBuildID, + "--gcp-cloud-run-project", "my-gcp-project", + "--gcp-cloud-run-region", "us-central1", + "--gcp-cloud-run-worker-pool", "my-worker-pool", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "missing required GCP Cloud Run provider detail: service_account") + + // AWS Lambda and GCP Cloud Run providers are mutually exclusive on create. + mixedProvidersBuildID := uuid.NewString() + + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", mixedProvidersBuildID, + "--aws-lambda-function-arn", invokeARN, + "--gcp-cloud-run-worker-pool", "my-worker-pool", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "cannot combine --aws-lambda-* and --gcp-cloud-run-* flags") + // Attempting to update the compute config for a non-existent WDV // should fail. nonExistingBuildID := "non-existing" @@ -1336,6 +1379,32 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { ) s.Error(res.Err) s.ErrorContains(res.Err, "build ID 'non-existing' not found") + + // AWS Lambda and GCP Cloud Run providers are mutually exclusive on update + // too. This is rejected client-side, before the RPC, so the existing + // (lazily-created) build ID is fine to target. + res = s.Execute( + "worker", "deployment", "update-version-compute-config", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", lazyCreatedBuildID, + "--aws-lambda-function-arn", invokeARN, + "--gcp-cloud-run-worker-pool", "my-worker-pool", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "cannot combine --aws-lambda-* and --gcp-cloud-run-* flags") + + // --remove cannot be combined with GCP Cloud Run flags. + res = s.Execute( + "worker", "deployment", "update-version-compute-config", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", lazyCreatedBuildID, + "--gcp-cloud-run-worker-pool", "my-worker-pool", + "--remove", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "--remove cannot be combined with") } // TODO(jaypipes): Enable this test when we have a way of ensuring AWS resource @@ -1455,3 +1524,121 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_LambdaComputeConfi s.NoError(res.Err) s.Contains(res.Stdout.String(), "Successfully removed worker deployment version compute config") } + +// TODO(jaypipes): Enable this test when we have a way of ensuring GCP resource +// fixtures since the CLI test harness uses a real Temporal Server and a real +// Temporal Server validates that any supplied GCP Cloud Run worker pool and +// service account are good (the server impersonates the service account to +// manage the worker pool). +func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_GCPCloudRunComputeConfig() { + s.T().Skip("GCP Cloud Run worker pool and service account fixtures needed.") + deploymentName := uuid.NewString() + taskQueue := uuid.NewString() + + lazyCreatedBuildID := uuid.NewString() + lazyCreatedVer := worker.WorkerDeploymentVersion{ + DeploymentName: deploymentName, + BuildID: lazyCreatedBuildID, + } + + // Create worker with explicit versioning. This will end up creating a + // WorkerDeployment with the specified name. We will then manually create a + // worker deployment version using the `temporal worker deployment + // create-version` command. + w1 := worker.New(s.Client, taskQueue, worker.Options{ + DeploymentOptions: worker.DeploymentOptions{ + UseVersioning: true, + Version: lazyCreatedVer, + }, + }) + + // Register a workflow with explicit Pinned versioning behavior to trigger + // creation of the worker deployment. + w1.RegisterWorkflowWithOptions( + func(ctx workflow.Context, input any) (any, error) { + workflow.GetSignalChannel(ctx, "complete-signal").Receive(ctx, nil) + return nil, nil + }, + workflow.RegisterOptions{ + Name: "TestCreateWorkerDeploymentVersion_GCPCloudRunComputeConfig", + VersioningBehavior: workflow.VersioningBehaviorPinned, + }, + ) + + s.NoError(w1.Start()) + + // Create a WDV with a valid GCP Cloud Run Compute Config specified and + // verify that the compute config provider is displayed in the output of + // `temporal worker deployment describe-version`. + computeConfigBuildID := uuid.NewString() + + project := "my-gcp-project" + region := "us-central1" + workerPool := "my-worker-pool" + serviceAccount := "customer-sa@my-gcp-project.iam.gserviceaccount.com" + + res := s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + "--gcp-cloud-run-project", project, + "--gcp-cloud-run-region", region, + "--gcp-cloud-run-worker-pool", workerPool, + "--gcp-cloud-run-service-account", serviceAccount, + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Successfully created worker deployment version") + + // Wait for the deployment version to appear + s.EventuallyWithT(func(t *assert.CollectT) { + res := s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + ) + assert.NoError(t, res.Err) + }, 30*time.Second, 100*time.Millisecond) + + // Check that the compute config returned for this WDV reports the + // gcp-cloud-run provider. + res = s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + "--output", "json", + ) + s.NoError(res.Err) + jsonOut := jsonDeploymentVersionInfoType{} + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut)) + s.NotNil(jsonOut.ComputeConfig, "ComputeConfig should not be nil.") + s.Len(jsonOut.ComputeConfig.ScalingGroups, 1) + s.Equal("gcp-cloud-run", jsonOut.ComputeConfig.ScalingGroups[0].ProviderType) + + // We should be able to update the compute config. + res = s.Execute( + "worker", "deployment", "update-version-compute-config", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + "--gcp-cloud-run-project", project, + "--gcp-cloud-run-region", region, + "--gcp-cloud-run-worker-pool", "updated-worker-pool", + "--gcp-cloud-run-service-account", serviceAccount, + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Successfully updated worker deployment version compute config") + + // As well as remove the compute config. + res = s.Execute( + "worker", "deployment", "update-version-compute-config", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + "--remove", + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Successfully removed worker deployment version compute config") +} diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 5ec1b9d66..947cf84ae 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -1092,7 +1092,21 @@ commands: --aws-lambda-assume-role-arn LambdaAssumeRoleARN \ --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID ``` - + + Or pass compute provider information for a GCP Cloud Run worker pool + that spawns a Worker in the Worker Deployment: + + ``` + temporal worker deployment create-version \ + --namespace YourNamespaceName \ + --deployment-name YourDeploymentName \ + --build-id YourBuildID \ + --gcp-cloud-run-project YourGCPProject \ + --gcp-cloud-run-region us-central1 \ + --gcp-cloud-run-worker-pool YourWorkerPool \ + --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com + ``` + If a Worker Deployment Version with the supplied BuildID already exists, this command will return an error. @@ -1122,6 +1136,27 @@ commands: with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-lambda-function-arn is specified. + - name: gcp-cloud-run-project + type: string + description: | + GCP project ID hosting the Cloud Run worker pool. Required when + --gcp-cloud-run-worker-pool is specified. + - name: gcp-cloud-run-region + type: string + description: | + Region of the Cloud Run worker pool. Required when + --gcp-cloud-run-worker-pool is specified. + - name: gcp-cloud-run-worker-pool + type: string + description: | + GCP Cloud Run worker pool name to scale when there are no active + pollers for task queue targets in the Worker Deployment. + - name: gcp-cloud-run-service-account + type: string + description: | + Customer GCP service account the Temporal server impersonates to + manage the Cloud Run worker pool. Required when + --gcp-cloud-run-worker-pool is specified. - name: temporal worker deployment describe-version summary: Show properties of a Worker Deployment Version @@ -1350,6 +1385,18 @@ commands: --aws-lambda-assume-role-arn UpdatedRoleARN ``` + To update the GCP Cloud Run worker pool associated with an existing + Worker Deployment Version: + + ``` + temporal worker deployment update-version-compute-config \ + --deployment-name YourDeploymentName --build-id YourBuildID \ + --gcp-cloud-run-project YourGCPProject \ + --gcp-cloud-run-region us-central1 \ + --gcp-cloud-run-worker-pool UpdatedWorkerPool \ + --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com + ``` + If --remove is specified, the compute configuration for the Worker Deployment Version will be removed: @@ -1386,6 +1433,27 @@ commands: with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-lambda-function-arn is specified. + - name: gcp-cloud-run-project + type: string + description: | + GCP project ID hosting the Cloud Run worker pool. Required when + --gcp-cloud-run-worker-pool is specified. + - name: gcp-cloud-run-region + type: string + description: | + Region of the Cloud Run worker pool. Required when + --gcp-cloud-run-worker-pool is specified. + - name: gcp-cloud-run-worker-pool + type: string + description: | + GCP Cloud Run worker pool name to scale when there are no active + pollers for task queue targets in the Worker Deployment. + - name: gcp-cloud-run-service-account + type: string + description: | + Customer GCP service account the Temporal server impersonates to + manage the Cloud Run worker pool. Required when + --gcp-cloud-run-worker-pool is specified. - name: remove type: bool description: |