From a4c6628ace9ce52ac3b3e9709cc9265b6432f0bf Mon Sep 17 00:00:00 2001 From: David Liu Date: Mon, 27 Jul 2026 17:39:19 -0400 Subject: [PATCH 1/2] Add azure support for export --- go.mod | 2 +- go.sum | 4 +- temporalcloudcli/commands.gen.go | 172 +++++++- temporalcloudcli/commands.namespace.export.go | 211 +++++++++ .../commands.namespace.export_test.go | 403 +++++++++++++++++- temporalcloudcli/commands.yml | 134 +++++- 6 files changed, 913 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 7ecd41e..69215ff 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/temporalio/cli/cliext v0.0.0-20260602200703-8bb57b77ad55 go.temporal.io/api v1.62.13 - go.temporal.io/cloud-sdk v0.15.0 + go.temporal.io/cloud-sdk v0.16.0 go.temporal.io/sdk v1.44.1 go.temporal.io/sdk/contrib/envconfig v1.0.0 golang.org/x/oauth2 v0.36.0 diff --git a/go.sum b/go.sum index 2116e89..1169c40 100644 --- a/go.sum +++ b/go.sum @@ -154,8 +154,8 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09 go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.temporal.io/api v1.62.13 h1:xMa8Nt5oAMX+LvlCJA44wjTCc1H09i2rG9poB1/xvH4= go.temporal.io/api v1.62.13/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= -go.temporal.io/cloud-sdk v0.15.0 h1:TwlGhTVnF7d9uIP5wjV/vr7TBYsPEpJw/MNuiylX3DQ= -go.temporal.io/cloud-sdk v0.15.0/go.mod h1:W2O9t9tvo3Q/LhGgYdj8JijWbN5C84os+cz/BadIHYI= +go.temporal.io/cloud-sdk v0.16.0 h1:8EgFMyc3M1XV2+OIOOuKeyhUI8Kjy7YCCbmGYlRw1No= +go.temporal.io/cloud-sdk v0.16.0/go.mod h1:W2O9t9tvo3Q/LhGgYdj8JijWbN5C84os+cz/BadIHYI= go.temporal.io/sdk v1.44.1 h1:Mt2OZLZpqkzDIdg9YyQzO0Rb/HqCDnnqHlIAGAJ5gqM= go.temporal.io/sdk v1.44.1/go.mod h1:vkApR12F9/Y8OR+hkxe7WyXQFuCX6clhzqnAk6rzDAM= go.temporal.io/sdk/contrib/envconfig v1.0.0 h1:1Q/swVgB4EW/p3k7rI9/4hpU4/DC57FSRbU90+UisXw= diff --git a/temporalcloudcli/commands.gen.go b/temporalcloudcli/commands.gen.go index 4bfc133..561ce30 100644 --- a/temporalcloudcli/commands.gen.go +++ b/temporalcloudcli/commands.gen.go @@ -222,6 +222,40 @@ func (v *ExportGcsRegionOptions) BuildFlags(f *pflag.FlagSet) { _ = cobra.MarkFlagRequired(f, "region") } +type ExportAzureOptions struct { + TenantId string + SubscriptionId string + ResourceGroup string + StorageAccount string + ContainerName string + FlagSet *pflag.FlagSet +} + +func (v *ExportAzureOptions) BuildFlags(f *pflag.FlagSet) { + v.FlagSet = f + f.StringVar(&v.TenantId, "tenant-id", "", "The Azure tenant ID where the storage account exists and where Temporal's app registration is consented/granted access. Required.") + _ = cobra.MarkFlagRequired(f, "tenant-id") + f.StringVar(&v.SubscriptionId, "subscription-id", "", "The Azure subscription ID that contains the storage account. Required.") + _ = cobra.MarkFlagRequired(f, "subscription-id") + f.StringVar(&v.ResourceGroup, "resource-group", "", "The Azure resource group that contains the storage account. Required.") + _ = cobra.MarkFlagRequired(f, "resource-group") + f.StringVar(&v.StorageAccount, "storage-account", "", "The name of the destination Azure storage account where Temporal will send data. Required.") + _ = cobra.MarkFlagRequired(f, "storage-account") + f.StringVar(&v.ContainerName, "container-name", "", "The name of the destination Azure Blob container where Temporal will send data. Required.") + _ = cobra.MarkFlagRequired(f, "container-name") +} + +type ExportAzureRegionOptions struct { + Region string + FlagSet *pflag.FlagSet +} + +func (v *ExportAzureRegionOptions) BuildFlags(f *pflag.FlagSet) { + v.FlagSet = f + f.StringVar(&v.Region, "region", "", "The region where the Azure storage account is located. Required.") + _ = cobra.MarkFlagRequired(f, "region") +} + type CloudCommand struct { Command cobra.Command ClientOptions @@ -2488,8 +2522,9 @@ func NewCloudNamespaceExportCommand(cctx *CommandContext, parent *CloudNamespace s.Parent = parent s.Command.Use = "export" s.Command.Short = "Manage workflow history export sinks for namespaces" - s.Command.Long = "Commands for managing workflow history export sinks for Temporal Cloud namespaces.\n\nExport sinks define destinations (S3 or GCS) to which workflow history is exported." + s.Command.Long = "Commands for managing workflow history export sinks for Temporal Cloud namespaces.\n\nExport sinks define destinations (S3, GCS, or Azure Blob) to which workflow history is exported." s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewCloudNamespaceExportAzureCommand(cctx, &s).Command) s.Command.AddCommand(&NewCloudNamespaceExportDeleteCommand(cctx, &s).Command) s.Command.AddCommand(&NewCloudNamespaceExportDisableCommand(cctx, &s).Command) s.Command.AddCommand(&NewCloudNamespaceExportEnableCommand(cctx, &s).Command) @@ -2500,6 +2535,141 @@ func NewCloudNamespaceExportCommand(cctx *CommandContext, parent *CloudNamespace return &s } +type CloudNamespaceExportAzureCommand struct { + Parent *CloudNamespaceExportCommand + Command cobra.Command +} + +func NewCloudNamespaceExportAzureCommand(cctx *CommandContext, parent *CloudNamespaceExportCommand) *CloudNamespaceExportAzureCommand { + var s CloudNamespaceExportAzureCommand + s.Parent = parent + s.Command.Use = "azure" + s.Command.Short = "Manage Azure Blob workflow history export sinks" + s.Command.Long = "Commands for managing Azure Blob workflow history export sinks for Temporal Cloud namespaces." + s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewCloudNamespaceExportAzureCreateCommand(cctx, &s).Command) + s.Command.AddCommand(&NewCloudNamespaceExportAzureUpdateCommand(cctx, &s).Command) + s.Command.AddCommand(&NewCloudNamespaceExportAzureValidateCommand(cctx, &s).Command) + return &s +} + +type CloudNamespaceExportAzureCreateCommand struct { + Parent *CloudNamespaceExportAzureCommand + Command cobra.Command + ClientOptions + NamespaceOptions + AsyncOperationOptions + ExportSinkOptions + ExportAzureOptions + ExportAzureRegionOptions +} + +func NewCloudNamespaceExportAzureCreateCommand(cctx *CommandContext, parent *CloudNamespaceExportAzureCommand) *CloudNamespaceExportAzureCreateCommand { + var s CloudNamespaceExportAzureCreateCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "create [flags]" + s.Command.Short = "Create an Azure Blob workflow history export sink" + if hasHighlighting { + s.Command.Long = "Create a new Azure Blob workflow history export sink for a Temporal Cloud namespace.\nThe sink is created in the enabled state.\n\nExample:\n\n\x1b[1mtemporal cloud namespace export azure create --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\x1b[0m" + } else { + s.Command.Long = "Create a new Azure Blob workflow history export sink for a Temporal Cloud namespace.\nThe sink is created in the enabled state.\n\nExample:\n\n```\ntemporal cloud namespace export azure create --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\n```" + } + s.Command.Args = cobra.NoArgs + s.ClientOptions.BuildFlags(s.Command.Flags()) + s.NamespaceOptions.BuildFlags(s.Command.Flags()) + s.AsyncOperationOptions.BuildFlags(s.Command.Flags()) + s.ExportSinkOptions.BuildFlags(s.Command.Flags()) + s.ExportAzureOptions.BuildFlags(s.Command.Flags()) + s.ExportAzureRegionOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type CloudNamespaceExportAzureUpdateCommand struct { + Parent *CloudNamespaceExportAzureCommand + Command cobra.Command + ClientOptions + NamespaceOptions + AsyncOperationOptions + ResourceVersionOptions + ExportSinkOptions + TenantId string + SubscriptionId string + ResourceGroup string + StorageAccount string + ContainerName string +} + +func NewCloudNamespaceExportAzureUpdateCommand(cctx *CommandContext, parent *CloudNamespaceExportAzureCommand) *CloudNamespaceExportAzureUpdateCommand { + var s CloudNamespaceExportAzureUpdateCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "update [flags]" + s.Command.Short = "Update an Azure Blob workflow history export sink" + if hasHighlighting { + s.Command.Long = "Update the configuration of an existing Azure Blob workflow history export sink.\nOnly the flags you provide are changed; omitted flags keep their current\nvalues. The enabled/disabled state and region are also preserved.\n\nExample (rotate storage account only):\n\n\x1b[1mtemporal cloud namespace export azure update --namespace my-namespace.my-account --sink-name my-sink \\\n --storage-account my-new-storage-account\x1b[0m" + } else { + s.Command.Long = "Update the configuration of an existing Azure Blob workflow history export sink.\nOnly the flags you provide are changed; omitted flags keep their current\nvalues. The enabled/disabled state and region are also preserved.\n\nExample (rotate storage account only):\n\n```\ntemporal cloud namespace export azure update --namespace my-namespace.my-account --sink-name my-sink \\\n --storage-account my-new-storage-account\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVar(&s.TenantId, "tenant-id", "", "The Azure tenant ID where the storage account exists and where Temporal's app registration is consented/granted access. If omitted, the current value is kept.") + s.Command.Flags().StringVar(&s.SubscriptionId, "subscription-id", "", "The Azure subscription ID that contains the storage account. If omitted, the current value is kept.") + s.Command.Flags().StringVar(&s.ResourceGroup, "resource-group", "", "The Azure resource group that contains the storage account. If omitted, the current value is kept.") + s.Command.Flags().StringVar(&s.StorageAccount, "storage-account", "", "The name of the destination Azure storage account where Temporal will send data. If omitted, the current value is kept.") + s.Command.Flags().StringVar(&s.ContainerName, "container-name", "", "The name of the destination Azure Blob container where Temporal will send data. If omitted, the current value is kept.") + s.ClientOptions.BuildFlags(s.Command.Flags()) + s.NamespaceOptions.BuildFlags(s.Command.Flags()) + s.AsyncOperationOptions.BuildFlags(s.Command.Flags()) + s.ResourceVersionOptions.BuildFlags(s.Command.Flags()) + s.ExportSinkOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type CloudNamespaceExportAzureValidateCommand struct { + Parent *CloudNamespaceExportAzureCommand + Command cobra.Command + ClientOptions + NamespaceOptions + ExportSinkOptions + ExportAzureOptions + ExportAzureRegionOptions +} + +func NewCloudNamespaceExportAzureValidateCommand(cctx *CommandContext, parent *CloudNamespaceExportAzureCommand) *CloudNamespaceExportAzureValidateCommand { + var s CloudNamespaceExportAzureValidateCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "validate [flags]" + s.Command.Short = "Validate an Azure Blob workflow history export sink configuration" + if hasHighlighting { + s.Command.Long = "Validate an Azure Blob workflow history export sink configuration without creating or updating it.\nA successful response means the configuration is valid.\n\nExample:\n\n\x1b[1mtemporal cloud namespace export azure validate --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\x1b[0m" + } else { + s.Command.Long = "Validate an Azure Blob workflow history export sink configuration without creating or updating it.\nA successful response means the configuration is valid.\n\nExample:\n\n```\ntemporal cloud namespace export azure validate --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\n```" + } + s.Command.Args = cobra.NoArgs + s.ClientOptions.BuildFlags(s.Command.Flags()) + s.NamespaceOptions.BuildFlags(s.Command.Flags()) + s.ExportSinkOptions.BuildFlags(s.Command.Flags()) + s.ExportAzureOptions.BuildFlags(s.Command.Flags()) + s.ExportAzureRegionOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type CloudNamespaceExportDeleteCommand struct { Parent *CloudNamespaceExportCommand Command cobra.Command diff --git a/temporalcloudcli/commands.namespace.export.go b/temporalcloudcli/commands.namespace.export.go index 4307c5e..9189373 100644 --- a/temporalcloudcli/commands.namespace.export.go +++ b/temporalcloudcli/commands.namespace.export.go @@ -143,6 +143,52 @@ type ( Cloud cloudservice.CloudServiceClient Printer *printer.Printer } + + CreateAzureBlobExportSinkParams struct { + Namespace string + SinkName string + TenantID string + SubscriptionID string + ResourceGroup string + StorageAccount string + ContainerName string + Region string + AsyncOperationID string + + Cloud cloudservice.CloudServiceClient + Prompter Prompter + OperationHandler AsyncOperationHandler + } + + UpdateAzureBlobExportSinkParams struct { + Namespace string + SinkName string + TenantID string + SubscriptionID string + ResourceGroup string + StorageAccount string + ContainerName string + ResourceVersion string + AsyncOperationID string + + Cloud cloudservice.CloudServiceClient + Prompter Prompter + OperationHandler AsyncOperationHandler + } + + ValidateAzureBlobExportSinkParams struct { + Namespace string + SinkName string + TenantID string + SubscriptionID string + ResourceGroup string + StorageAccount string + ContainerName string + Region string + + Cloud cloudservice.CloudServiceClient + Printer *printer.Printer + } ) func GetExportSink(ctx context.Context, params GetExportSinkParams) error { @@ -476,6 +522,110 @@ func ValidateGCSExportSink(ctx context.Context, params ValidateGCSExportSinkPara ) } +func CreateAzureBlobExportSink(ctx context.Context, params CreateAzureBlobExportSinkParams) error { + spec := &namespacev1.ExportSinkSpec{ + Name: params.SinkName, + Enabled: true, + AzureBlob: &sinkv1.AzureBlobSpec{ + TenantId: params.TenantID, + SubscriptionId: params.SubscriptionID, + ResourceGroup: params.ResourceGroup, + StorageAccount: params.StorageAccount, + ContainerName: params.ContainerName, + Region: params.Region, + }, + } + if err := params.Prompter.PromptApply(&namespacev1.ExportSinkSpec{}, spec, false); err != nil { + return err + } + createSink := wrapCreateOperation( + params.Cloud.CreateNamespaceExportSink, + params.OperationHandler, + func(_ *cloudservice.CreateNamespaceExportSinkResponse) string { return params.SinkName }, + ) + return createSink(ctx, &cloudservice.CreateNamespaceExportSinkRequest{ + Namespace: params.Namespace, + Spec: spec, + AsyncOperationId: params.AsyncOperationID, + }) +} + +func UpdateAzureBlobExportSink(ctx context.Context, params UpdateAzureBlobExportSinkParams) error { + sinkRes, err := params.Cloud.GetNamespaceExportSink(ctx, &cloudservice.GetNamespaceExportSinkRequest{ + Namespace: params.Namespace, + Name: params.SinkName, + }) + if err != nil { + return err + } + sink := sinkRes.Sink + + rv := sink.ResourceVersion + if params.ResourceVersion != "" { + rv = params.ResourceVersion + } + + oldSpec := sink.Spec + newAzureBlob := proto.Clone(oldSpec.GetAzureBlob()).(*sinkv1.AzureBlobSpec) + if params.TenantID != "" { + newAzureBlob.TenantId = params.TenantID + } + if params.SubscriptionID != "" { + newAzureBlob.SubscriptionId = params.SubscriptionID + } + if params.ResourceGroup != "" { + newAzureBlob.ResourceGroup = params.ResourceGroup + } + if params.StorageAccount != "" { + newAzureBlob.StorageAccount = params.StorageAccount + } + if params.ContainerName != "" { + newAzureBlob.ContainerName = params.ContainerName + } + newSpec := &namespacev1.ExportSinkSpec{ + Name: params.SinkName, + Enabled: oldSpec.GetEnabled(), + AzureBlob: newAzureBlob, + } + + if err := params.Prompter.PromptApply(oldSpec, newSpec, false); err != nil { + return err + } + + updateSink := wrapUpdateOperation(params.Cloud.UpdateNamespaceExportSink, params.OperationHandler, params.SinkName) + return updateSink(ctx, &cloudservice.UpdateNamespaceExportSinkRequest{ + Namespace: params.Namespace, + Spec: newSpec, + ResourceVersion: rv, + AsyncOperationId: params.AsyncOperationID, + }) +} + +func ValidateAzureBlobExportSink(ctx context.Context, params ValidateAzureBlobExportSinkParams) error { + spec := &namespacev1.ExportSinkSpec{ + Name: params.SinkName, + AzureBlob: &sinkv1.AzureBlobSpec{ + TenantId: params.TenantID, + SubscriptionId: params.SubscriptionID, + ResourceGroup: params.ResourceGroup, + StorageAccount: params.StorageAccount, + ContainerName: params.ContainerName, + Region: params.Region, + }, + } + _, err := params.Cloud.ValidateNamespaceExportSink(ctx, &cloudservice.ValidateNamespaceExportSinkRequest{ + Namespace: params.Namespace, + Spec: spec, + }) + if err != nil { + return err + } + return params.Printer.PrintStructured( + struct{ Status string }{Status: fmt.Sprintf("Export sink %q configuration is valid.", params.SinkName)}, + printer.StructuredOptions{}, + ) +} + func (c *CloudNamespaceExportGetCommand) run(cctx *CommandContext, _ []string) error { cloudClient, err := cctx.BuildCloudClient(c.ClientOptions) if err != nil { @@ -693,3 +843,64 @@ func (c *CloudNamespaceExportGcsValidateCommand) run(cctx *CommandContext, _ []s Printer: cctx.Printer, }) } + +func (c *CloudNamespaceExportAzureCreateCommand) run(cctx *CommandContext, _ []string) error { + cloudClient, err := cctx.BuildCloudClient(c.ClientOptions) + if err != nil { + return err + } + return CreateAzureBlobExportSink(cctx.Context, CreateAzureBlobExportSinkParams{ + Namespace: c.Namespace, + SinkName: c.SinkName, + TenantID: c.TenantId, + SubscriptionID: c.SubscriptionId, + ResourceGroup: c.ResourceGroup, + StorageAccount: c.StorageAccount, + ContainerName: c.ContainerName, + Region: c.Region, + AsyncOperationID: c.AsyncOperationId, + Cloud: cloudClient.CloudService(), + Prompter: newPrompter(cctx), + OperationHandler: NewOperationHandler(cctx, c.AsyncOperationOptions, c.ClientOptions), + }) +} + +func (c *CloudNamespaceExportAzureUpdateCommand) run(cctx *CommandContext, _ []string) error { + cloudClient, err := cctx.BuildCloudClient(c.ClientOptions) + if err != nil { + return err + } + return UpdateAzureBlobExportSink(cctx.Context, UpdateAzureBlobExportSinkParams{ + Namespace: c.Namespace, + SinkName: c.SinkName, + TenantID: c.TenantId, + SubscriptionID: c.SubscriptionId, + ResourceGroup: c.ResourceGroup, + StorageAccount: c.StorageAccount, + ContainerName: c.ContainerName, + ResourceVersion: c.ResourceVersion, + AsyncOperationID: c.AsyncOperationId, + Cloud: cloudClient.CloudService(), + Prompter: newPrompter(cctx), + OperationHandler: NewOperationHandler(cctx, c.AsyncOperationOptions, c.ClientOptions), + }) +} + +func (c *CloudNamespaceExportAzureValidateCommand) run(cctx *CommandContext, _ []string) error { + cloudClient, err := cctx.BuildCloudClient(c.ClientOptions) + if err != nil { + return err + } + return ValidateAzureBlobExportSink(cctx.Context, ValidateAzureBlobExportSinkParams{ + Namespace: c.Namespace, + SinkName: c.SinkName, + TenantID: c.TenantId, + SubscriptionID: c.SubscriptionId, + ResourceGroup: c.ResourceGroup, + StorageAccount: c.StorageAccount, + ContainerName: c.ContainerName, + Region: c.Region, + Cloud: cloudClient.CloudService(), + Printer: cctx.Printer, + }) +} diff --git a/temporalcloudcli/commands.namespace.export_test.go b/temporalcloudcli/commands.namespace.export_test.go index cb4d806..59bea21 100644 --- a/temporalcloudcli/commands.namespace.export_test.go +++ b/temporalcloudcli/commands.namespace.export_test.go @@ -38,6 +38,18 @@ func testGCSSpec() *sinkv1.GCSSpec { } } +// testAzureBlobSpec returns a sample Azure Blob spec for use in tests. +func testAzureBlobSpec() *sinkv1.AzureBlobSpec { + return &sinkv1.AzureBlobSpec{ + TenantId: "my-tenant", + SubscriptionId: "my-subscription", + ResourceGroup: "my-resource-group", + StorageAccount: "my-storage-account", + ContainerName: "my-container", + Region: "eastus", + } +} + // testExportSink returns a sample ExportSink with an S3 spec for use in tests. func testExportSink(enabled bool) *namespacev1.ExportSink { return &namespacev1.ExportSink{ @@ -520,14 +532,14 @@ func TestCreateS3ExportSink_Success(t *testing.T) { Return(nil) err := temporalcloudcli.CreateS3ExportSink(context.Background(), temporalcloudcli.CreateS3ExportSinkParams{ - Namespace: "my-namespace", - SinkName: "my-sink", - RoleName: "my-role", - BucketName: "my-bucket", - Region: "us-east-1", - AwsAccountID: "123456789012", - Cloud: mockCloud, - Prompter: mockPrompter, + Namespace: "my-namespace", + SinkName: "my-sink", + RoleName: "my-role", + BucketName: "my-bucket", + Region: "us-east-1", + AwsAccountID: "123456789012", + Cloud: mockCloud, + Prompter: mockPrompter, OperationHandler: mockHandler, }) require.NoError(t, err) @@ -1209,3 +1221,378 @@ func TestValidateGCSExportSink_Error(t *testing.T) { }) require.ErrorIs(t, err, apiErr) } + +// --- CreateAzureBlobExportSink --- + +func TestCreateAzureBlobExportSink_Success(t *testing.T) { + mockCloud := cloudmock.NewMockCloudServiceClient(t) + mockPrompter := cmdmock.NewMockPrompter(t) + mockHandler := cmdmock.NewMockAsyncOperationHandler(t) + + expectedSpec := &namespacev1.ExportSinkSpec{ + Name: "my-sink", + Enabled: true, + AzureBlob: testAzureBlobSpec(), + } + mockPrompter.EXPECT(). + PromptApply(&namespacev1.ExportSinkSpec{}, expectedSpec, false). + Return(nil) + + op := &operation.AsyncOperation{Id: "op-123"} + mockCloud.EXPECT(). + CreateNamespaceExportSink(context.Background(), &cloudservice.CreateNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Spec: expectedSpec, + }). + Return(&cloudservice.CreateNamespaceExportSinkResponse{AsyncOperation: op}, nil) + + mockHandler.EXPECT(). + HandleOperation(op, "my-sink"). + Return(nil) + + err := temporalcloudcli.CreateAzureBlobExportSink(context.Background(), temporalcloudcli.CreateAzureBlobExportSinkParams{ + Namespace: "my-namespace", + SinkName: "my-sink", + TenantID: "my-tenant", + SubscriptionID: "my-subscription", + ResourceGroup: "my-resource-group", + StorageAccount: "my-storage-account", + ContainerName: "my-container", + Region: "eastus", + Cloud: mockCloud, + Prompter: mockPrompter, + OperationHandler: mockHandler, + }) + require.NoError(t, err) +} + +func TestCreateAzureBlobExportSink_PromptDeclined(t *testing.T) { + mockCloud := cloudmock.NewMockCloudServiceClient(t) + mockPrompter := cmdmock.NewMockPrompter(t) + mockHandler := cmdmock.NewMockAsyncOperationHandler(t) + promptErr := errors.New("Aborting apply.") + + expectedSpec := &namespacev1.ExportSinkSpec{ + Name: "my-sink", + Enabled: true, + AzureBlob: testAzureBlobSpec(), + } + mockPrompter.EXPECT(). + PromptApply(&namespacev1.ExportSinkSpec{}, expectedSpec, false). + Return(promptErr) + + err := temporalcloudcli.CreateAzureBlobExportSink(context.Background(), temporalcloudcli.CreateAzureBlobExportSinkParams{ + Namespace: "my-namespace", + SinkName: "my-sink", + TenantID: "my-tenant", + SubscriptionID: "my-subscription", + ResourceGroup: "my-resource-group", + StorageAccount: "my-storage-account", + ContainerName: "my-container", + Region: "eastus", + Cloud: mockCloud, + Prompter: mockPrompter, + OperationHandler: mockHandler, + }) + require.ErrorIs(t, err, promptErr) +} + +func TestCreateAzureBlobExportSink_CreateError(t *testing.T) { + mockCloud := cloudmock.NewMockCloudServiceClient(t) + mockPrompter := cmdmock.NewMockPrompter(t) + mockHandler := cmdmock.NewMockAsyncOperationHandler(t) + createErr := errors.New("create error") + + expectedSpec := &namespacev1.ExportSinkSpec{ + Name: "my-sink", + Enabled: true, + AzureBlob: testAzureBlobSpec(), + } + mockPrompter.EXPECT(). + PromptApply(&namespacev1.ExportSinkSpec{}, expectedSpec, false). + Return(nil) + + mockCloud.EXPECT(). + CreateNamespaceExportSink(context.Background(), &cloudservice.CreateNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Spec: expectedSpec, + }). + Return(nil, createErr) + + mockHandler.EXPECT(). + HandleCreateErr(createErr). + Return(createErr) + + err := temporalcloudcli.CreateAzureBlobExportSink(context.Background(), temporalcloudcli.CreateAzureBlobExportSinkParams{ + Namespace: "my-namespace", + SinkName: "my-sink", + TenantID: "my-tenant", + SubscriptionID: "my-subscription", + ResourceGroup: "my-resource-group", + StorageAccount: "my-storage-account", + ContainerName: "my-container", + Region: "eastus", + Cloud: mockCloud, + Prompter: mockPrompter, + OperationHandler: mockHandler, + }) + require.ErrorIs(t, err, createErr) +} + +// --- UpdateAzureBlobExportSink --- + +func TestUpdateAzureBlobExportSink_Success(t *testing.T) { + mockCloud := cloudmock.NewMockCloudServiceClient(t) + mockPrompter := cmdmock.NewMockPrompter(t) + mockHandler := cmdmock.NewMockAsyncOperationHandler(t) + + existingSink := &namespacev1.ExportSink{ + ResourceVersion: "rv-1", + Spec: &namespacev1.ExportSinkSpec{ + Name: "my-sink", + Enabled: true, + AzureBlob: testAzureBlobSpec(), + }, + } + mockCloud.EXPECT(). + GetNamespaceExportSink(context.Background(), &cloudservice.GetNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Name: "my-sink", + }). + Return(&cloudservice.GetNamespaceExportSinkResponse{Sink: existingSink}, nil) + + newSpec := &namespacev1.ExportSinkSpec{ + Name: "my-sink", + Enabled: true, + AzureBlob: &sinkv1.AzureBlobSpec{ + TenantId: "new-tenant", + SubscriptionId: "my-subscription", + ResourceGroup: "my-resource-group", + StorageAccount: "my-storage-account", + ContainerName: "my-container", + Region: "eastus", + }, + } + mockPrompter.EXPECT(). + PromptApply(existingSink.Spec, newSpec, false). + Return(nil) + + op := &operation.AsyncOperation{Id: "op-123"} + mockCloud.EXPECT(). + UpdateNamespaceExportSink(context.Background(), &cloudservice.UpdateNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Spec: newSpec, + ResourceVersion: "rv-1", + }). + Return(&cloudservice.UpdateNamespaceExportSinkResponse{AsyncOperation: op}, nil) + + mockHandler.EXPECT(). + HandleOperation(op, "my-sink"). + Return(nil) + + err := temporalcloudcli.UpdateAzureBlobExportSink(context.Background(), temporalcloudcli.UpdateAzureBlobExportSinkParams{ + Namespace: "my-namespace", + SinkName: "my-sink", + TenantID: "new-tenant", + Cloud: mockCloud, + Prompter: mockPrompter, + OperationHandler: mockHandler, + }) + require.NoError(t, err) +} + +func TestUpdateAzureBlobExportSink_PromptDeclined(t *testing.T) { + mockCloud := cloudmock.NewMockCloudServiceClient(t) + mockPrompter := cmdmock.NewMockPrompter(t) + mockHandler := cmdmock.NewMockAsyncOperationHandler(t) + promptErr := errors.New("Aborting apply.") + + existingSink := &namespacev1.ExportSink{ + ResourceVersion: "rv-1", + Spec: &namespacev1.ExportSinkSpec{ + Name: "my-sink", + Enabled: true, + AzureBlob: testAzureBlobSpec(), + }, + } + mockCloud.EXPECT(). + GetNamespaceExportSink(context.Background(), &cloudservice.GetNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Name: "my-sink", + }). + Return(&cloudservice.GetNamespaceExportSinkResponse{Sink: existingSink}, nil) + + newSpec := &namespacev1.ExportSinkSpec{ + Name: "my-sink", + Enabled: true, + AzureBlob: &sinkv1.AzureBlobSpec{ + TenantId: "new-tenant", + SubscriptionId: "my-subscription", + ResourceGroup: "my-resource-group", + StorageAccount: "my-storage-account", + ContainerName: "my-container", + Region: "eastus", + }, + } + mockPrompter.EXPECT(). + PromptApply(existingSink.Spec, newSpec, false). + Return(promptErr) + + err := temporalcloudcli.UpdateAzureBlobExportSink(context.Background(), temporalcloudcli.UpdateAzureBlobExportSinkParams{ + Namespace: "my-namespace", + SinkName: "my-sink", + TenantID: "new-tenant", + Cloud: mockCloud, + Prompter: mockPrompter, + OperationHandler: mockHandler, + }) + require.ErrorIs(t, err, promptErr) +} + +func TestUpdateAzureBlobExportSink_GetSinkError(t *testing.T) { + mockCloud := cloudmock.NewMockCloudServiceClient(t) + mockPrompter := cmdmock.NewMockPrompter(t) + mockHandler := cmdmock.NewMockAsyncOperationHandler(t) + apiErr := errors.New("api error") + + mockCloud.EXPECT(). + GetNamespaceExportSink(context.Background(), &cloudservice.GetNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Name: "my-sink", + }). + Return(nil, apiErr) + + err := temporalcloudcli.UpdateAzureBlobExportSink(context.Background(), temporalcloudcli.UpdateAzureBlobExportSinkParams{ + Namespace: "my-namespace", + SinkName: "my-sink", + TenantID: "my-tenant", + Cloud: mockCloud, + Prompter: mockPrompter, + OperationHandler: mockHandler, + }) + require.ErrorIs(t, err, apiErr) +} + +func TestUpdateAzureBlobExportSink_PartialUpdate(t *testing.T) { + mockCloud := cloudmock.NewMockCloudServiceClient(t) + mockPrompter := cmdmock.NewMockPrompter(t) + mockHandler := cmdmock.NewMockAsyncOperationHandler(t) + + existingSink := &namespacev1.ExportSink{ + ResourceVersion: "rv-1", + Spec: &namespacev1.ExportSinkSpec{ + Name: "my-sink", + Enabled: true, + AzureBlob: testAzureBlobSpec(), + }, + } + mockCloud.EXPECT(). + GetNamespaceExportSink(context.Background(), &cloudservice.GetNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Name: "my-sink", + }). + Return(&cloudservice.GetNamespaceExportSinkResponse{Sink: existingSink}, nil) + + // Only the storage account and container name change; the rest must be preserved. + expectedNewSpec := &namespacev1.ExportSinkSpec{ + Name: "my-sink", + Enabled: true, + AzureBlob: &sinkv1.AzureBlobSpec{ + TenantId: "my-tenant", + SubscriptionId: "my-subscription", + ResourceGroup: "my-resource-group", + StorageAccount: "new-storage-account", + ContainerName: "new-container", + Region: "eastus", + }, + } + mockPrompter.EXPECT(). + PromptApply(existingSink.Spec, expectedNewSpec, false). + Return(nil) + + op := &operation.AsyncOperation{Id: "op-partial-azure"} + mockCloud.EXPECT(). + UpdateNamespaceExportSink(context.Background(), &cloudservice.UpdateNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Spec: expectedNewSpec, + ResourceVersion: "rv-1", + }). + Return(&cloudservice.UpdateNamespaceExportSinkResponse{AsyncOperation: op}, nil) + + mockHandler.EXPECT(). + HandleOperation(op, "my-sink"). + Return(nil) + + err := temporalcloudcli.UpdateAzureBlobExportSink(context.Background(), temporalcloudcli.UpdateAzureBlobExportSinkParams{ + Namespace: "my-namespace", + SinkName: "my-sink", + StorageAccount: "new-storage-account", + ContainerName: "new-container", + Cloud: mockCloud, + Prompter: mockPrompter, + OperationHandler: mockHandler, + }) + require.NoError(t, err) +} + +// --- ValidateAzureBlobExportSink --- + +func TestValidateAzureBlobExportSink_Success(t *testing.T) { + mockCloud := cloudmock.NewMockCloudServiceClient(t) + + mockCloud.EXPECT(). + ValidateNamespaceExportSink(context.Background(), &cloudservice.ValidateNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Spec: &namespacev1.ExportSinkSpec{ + Name: "my-sink", + AzureBlob: testAzureBlobSpec(), + }, + }). + Return(&cloudservice.ValidateNamespaceExportSinkResponse{}, nil) + + var buf bytes.Buffer + err := temporalcloudcli.ValidateAzureBlobExportSink(context.Background(), temporalcloudcli.ValidateAzureBlobExportSinkParams{ + Namespace: "my-namespace", + SinkName: "my-sink", + TenantID: "my-tenant", + SubscriptionID: "my-subscription", + ResourceGroup: "my-resource-group", + StorageAccount: "my-storage-account", + ContainerName: "my-container", + Region: "eastus", + Cloud: mockCloud, + Printer: newTestPrinter(&buf), + }) + require.NoError(t, err) + assert.Contains(t, buf.String(), "my-sink") +} + +func TestValidateAzureBlobExportSink_Error(t *testing.T) { + mockCloud := cloudmock.NewMockCloudServiceClient(t) + apiErr := errors.New("invalid config") + + mockCloud.EXPECT(). + ValidateNamespaceExportSink(context.Background(), &cloudservice.ValidateNamespaceExportSinkRequest{ + Namespace: "my-namespace", + Spec: &namespacev1.ExportSinkSpec{ + Name: "my-sink", + AzureBlob: testAzureBlobSpec(), + }, + }). + Return(nil, apiErr) + + var buf bytes.Buffer + err := temporalcloudcli.ValidateAzureBlobExportSink(context.Background(), temporalcloudcli.ValidateAzureBlobExportSinkParams{ + Namespace: "my-namespace", + SinkName: "my-sink", + TenantID: "my-tenant", + SubscriptionID: "my-subscription", + ResourceGroup: "my-resource-group", + StorageAccount: "my-storage-account", + ContainerName: "my-container", + Region: "eastus", + Cloud: mockCloud, + Printer: newTestPrinter(&buf), + }) + require.ErrorIs(t, err, apiErr) +} diff --git a/temporalcloudcli/commands.yml b/temporalcloudcli/commands.yml index 30623b1..55b95f8 100644 --- a/temporalcloudcli/commands.yml +++ b/temporalcloudcli/commands.yml @@ -1009,7 +1009,7 @@ commands: description: | Commands for managing workflow history export sinks for Temporal Cloud namespaces. - Export sinks define destinations (S3 or GCS) to which workflow history is exported. + Export sinks define destinations (S3, GCS, or Azure Blob) to which workflow history is exported. has-init: false - name: cloud namespace export get summary: Get a workflow history export sink @@ -1256,6 +1256,103 @@ commands: - export-sink - export-gcs - export-gcs-region + - name: cloud namespace export azure + summary: Manage Azure Blob workflow history export sinks + description: | + Commands for managing Azure Blob workflow history export sinks for Temporal Cloud namespaces. + has-init: false + - name: cloud namespace export azure create + summary: Create an Azure Blob workflow history export sink + description: | + Create a new Azure Blob workflow history export sink for a Temporal Cloud namespace. + The sink is created in the enabled state. + + Example: + + ``` + temporal cloud namespace export azure create --namespace my-namespace.my-account --sink-name my-sink \ + --tenant-id 11111111-1111-1111-1111-111111111111 \ + --subscription-id 22222222-2222-2222-2222-222222222222 \ + --resource-group my-resource-group --storage-account my-storage-account \ + --container-name my-container --region eastus + ``` + has-init: false + option-sets: + - client + - namespace + - async-operation + - export-sink + - export-azure + - export-azure-region + - name: cloud namespace export azure update + summary: Update an Azure Blob workflow history export sink + description: | + Update the configuration of an existing Azure Blob workflow history export sink. + Only the flags you provide are changed; omitted flags keep their current + values. The enabled/disabled state and region are also preserved. + + Example (rotate storage account only): + + ``` + temporal cloud namespace export azure update --namespace my-namespace.my-account --sink-name my-sink \ + --storage-account my-new-storage-account + ``` + has-init: false + option-sets: + - client + - namespace + - async-operation + - resource-version + - export-sink + options: + - name: tenant-id + type: string + description: | + The Azure tenant ID where the storage account exists and where Temporal's + app registration is consented/granted access. + If omitted, the current value is kept. + - name: subscription-id + type: string + description: | + The Azure subscription ID that contains the storage account. + If omitted, the current value is kept. + - name: resource-group + type: string + description: | + The Azure resource group that contains the storage account. + If omitted, the current value is kept. + - name: storage-account + type: string + description: | + The name of the destination Azure storage account where Temporal will send data. + If omitted, the current value is kept. + - name: container-name + type: string + description: | + The name of the destination Azure Blob container where Temporal will send data. + If omitted, the current value is kept. + - name: cloud namespace export azure validate + summary: Validate an Azure Blob workflow history export sink configuration + description: | + Validate an Azure Blob workflow history export sink configuration without creating or updating it. + A successful response means the configuration is valid. + + Example: + + ``` + temporal cloud namespace export azure validate --namespace my-namespace.my-account --sink-name my-sink \ + --tenant-id 11111111-1111-1111-1111-111111111111 \ + --subscription-id 22222222-2222-2222-2222-222222222222 \ + --resource-group my-resource-group --storage-account my-storage-account \ + --container-name my-container --region eastus + ``` + has-init: false + option-sets: + - client + - namespace + - export-sink + - export-azure + - export-azure-region # Namespace codec commands - name: cloud namespace codec @@ -4383,3 +4480,38 @@ option-sets: required: true description: | The GCS bucket region. + - name: export-azure + options: + - name: tenant-id + type: string + required: true + description: | + The Azure tenant ID where the storage account exists and where Temporal's + app registration is consented/granted access. + - name: subscription-id + type: string + required: true + description: | + The Azure subscription ID that contains the storage account. + - name: resource-group + type: string + required: true + description: | + The Azure resource group that contains the storage account. + - name: storage-account + type: string + required: true + description: | + The name of the destination Azure storage account where Temporal will send data. + - name: container-name + type: string + required: true + description: | + The name of the destination Azure Blob container where Temporal will send data. + - name: export-azure-region + options: + - name: region + type: string + required: true + description: | + The region where the Azure storage account is located. From 80f500d28ca9c8325004fb56dbcfc9d985974a70 Mon Sep 17 00:00:00 2001 From: David Liu Date: Tue, 28 Jul 2026 11:37:07 -0400 Subject: [PATCH 2/2] azureblob --- temporalcloudcli/commands.gen.go | 76 +++++++++---------- temporalcloudcli/commands.namespace.export.go | 6 +- temporalcloudcli/commands.yml | 26 +++---- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/temporalcloudcli/commands.gen.go b/temporalcloudcli/commands.gen.go index 561ce30..a7a8f1e 100644 --- a/temporalcloudcli/commands.gen.go +++ b/temporalcloudcli/commands.gen.go @@ -222,7 +222,7 @@ func (v *ExportGcsRegionOptions) BuildFlags(f *pflag.FlagSet) { _ = cobra.MarkFlagRequired(f, "region") } -type ExportAzureOptions struct { +type ExportAzureBlobOptions struct { TenantId string SubscriptionId string ResourceGroup string @@ -231,7 +231,7 @@ type ExportAzureOptions struct { FlagSet *pflag.FlagSet } -func (v *ExportAzureOptions) BuildFlags(f *pflag.FlagSet) { +func (v *ExportAzureBlobOptions) BuildFlags(f *pflag.FlagSet) { v.FlagSet = f f.StringVar(&v.TenantId, "tenant-id", "", "The Azure tenant ID where the storage account exists and where Temporal's app registration is consented/granted access. Required.") _ = cobra.MarkFlagRequired(f, "tenant-id") @@ -245,12 +245,12 @@ func (v *ExportAzureOptions) BuildFlags(f *pflag.FlagSet) { _ = cobra.MarkFlagRequired(f, "container-name") } -type ExportAzureRegionOptions struct { +type ExportAzureBlobRegionOptions struct { Region string FlagSet *pflag.FlagSet } -func (v *ExportAzureRegionOptions) BuildFlags(f *pflag.FlagSet) { +func (v *ExportAzureBlobRegionOptions) BuildFlags(f *pflag.FlagSet) { v.FlagSet = f f.StringVar(&v.Region, "region", "", "The region where the Azure storage account is located. Required.") _ = cobra.MarkFlagRequired(f, "region") @@ -2524,7 +2524,7 @@ func NewCloudNamespaceExportCommand(cctx *CommandContext, parent *CloudNamespace s.Command.Short = "Manage workflow history export sinks for namespaces" s.Command.Long = "Commands for managing workflow history export sinks for Temporal Cloud namespaces.\n\nExport sinks define destinations (S3, GCS, or Azure Blob) to which workflow history is exported." s.Command.Args = cobra.NoArgs - s.Command.AddCommand(&NewCloudNamespaceExportAzureCommand(cctx, &s).Command) + s.Command.AddCommand(&NewCloudNamespaceExportAzureBlobCommand(cctx, &s).Command) s.Command.AddCommand(&NewCloudNamespaceExportDeleteCommand(cctx, &s).Command) s.Command.AddCommand(&NewCloudNamespaceExportDisableCommand(cctx, &s).Command) s.Command.AddCommand(&NewCloudNamespaceExportEnableCommand(cctx, &s).Command) @@ -2535,53 +2535,53 @@ func NewCloudNamespaceExportCommand(cctx *CommandContext, parent *CloudNamespace return &s } -type CloudNamespaceExportAzureCommand struct { +type CloudNamespaceExportAzureBlobCommand struct { Parent *CloudNamespaceExportCommand Command cobra.Command } -func NewCloudNamespaceExportAzureCommand(cctx *CommandContext, parent *CloudNamespaceExportCommand) *CloudNamespaceExportAzureCommand { - var s CloudNamespaceExportAzureCommand +func NewCloudNamespaceExportAzureBlobCommand(cctx *CommandContext, parent *CloudNamespaceExportCommand) *CloudNamespaceExportAzureBlobCommand { + var s CloudNamespaceExportAzureBlobCommand s.Parent = parent - s.Command.Use = "azure" + s.Command.Use = "azure-blob" s.Command.Short = "Manage Azure Blob workflow history export sinks" s.Command.Long = "Commands for managing Azure Blob workflow history export sinks for Temporal Cloud namespaces." s.Command.Args = cobra.NoArgs - s.Command.AddCommand(&NewCloudNamespaceExportAzureCreateCommand(cctx, &s).Command) - s.Command.AddCommand(&NewCloudNamespaceExportAzureUpdateCommand(cctx, &s).Command) - s.Command.AddCommand(&NewCloudNamespaceExportAzureValidateCommand(cctx, &s).Command) + s.Command.AddCommand(&NewCloudNamespaceExportAzureBlobCreateCommand(cctx, &s).Command) + s.Command.AddCommand(&NewCloudNamespaceExportAzureBlobUpdateCommand(cctx, &s).Command) + s.Command.AddCommand(&NewCloudNamespaceExportAzureBlobValidateCommand(cctx, &s).Command) return &s } -type CloudNamespaceExportAzureCreateCommand struct { - Parent *CloudNamespaceExportAzureCommand +type CloudNamespaceExportAzureBlobCreateCommand struct { + Parent *CloudNamespaceExportAzureBlobCommand Command cobra.Command ClientOptions NamespaceOptions AsyncOperationOptions ExportSinkOptions - ExportAzureOptions - ExportAzureRegionOptions + ExportAzureBlobOptions + ExportAzureBlobRegionOptions } -func NewCloudNamespaceExportAzureCreateCommand(cctx *CommandContext, parent *CloudNamespaceExportAzureCommand) *CloudNamespaceExportAzureCreateCommand { - var s CloudNamespaceExportAzureCreateCommand +func NewCloudNamespaceExportAzureBlobCreateCommand(cctx *CommandContext, parent *CloudNamespaceExportAzureBlobCommand) *CloudNamespaceExportAzureBlobCreateCommand { + var s CloudNamespaceExportAzureBlobCreateCommand s.Parent = parent s.Command.DisableFlagsInUseLine = true s.Command.Use = "create [flags]" s.Command.Short = "Create an Azure Blob workflow history export sink" if hasHighlighting { - s.Command.Long = "Create a new Azure Blob workflow history export sink for a Temporal Cloud namespace.\nThe sink is created in the enabled state.\n\nExample:\n\n\x1b[1mtemporal cloud namespace export azure create --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\x1b[0m" + s.Command.Long = "Create a new Azure Blob workflow history export sink for a Temporal Cloud namespace.\nThe sink is created in the enabled state.\n\nExample:\n\n\x1b[1mtemporal cloud namespace export azure-blob create --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\x1b[0m" } else { - s.Command.Long = "Create a new Azure Blob workflow history export sink for a Temporal Cloud namespace.\nThe sink is created in the enabled state.\n\nExample:\n\n```\ntemporal cloud namespace export azure create --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\n```" + s.Command.Long = "Create a new Azure Blob workflow history export sink for a Temporal Cloud namespace.\nThe sink is created in the enabled state.\n\nExample:\n\n```\ntemporal cloud namespace export azure-blob create --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\n```" } s.Command.Args = cobra.NoArgs s.ClientOptions.BuildFlags(s.Command.Flags()) s.NamespaceOptions.BuildFlags(s.Command.Flags()) s.AsyncOperationOptions.BuildFlags(s.Command.Flags()) s.ExportSinkOptions.BuildFlags(s.Command.Flags()) - s.ExportAzureOptions.BuildFlags(s.Command.Flags()) - s.ExportAzureRegionOptions.BuildFlags(s.Command.Flags()) + s.ExportAzureBlobOptions.BuildFlags(s.Command.Flags()) + s.ExportAzureBlobRegionOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) @@ -2590,8 +2590,8 @@ func NewCloudNamespaceExportAzureCreateCommand(cctx *CommandContext, parent *Clo return &s } -type CloudNamespaceExportAzureUpdateCommand struct { - Parent *CloudNamespaceExportAzureCommand +type CloudNamespaceExportAzureBlobUpdateCommand struct { + Parent *CloudNamespaceExportAzureBlobCommand Command cobra.Command ClientOptions NamespaceOptions @@ -2605,16 +2605,16 @@ type CloudNamespaceExportAzureUpdateCommand struct { ContainerName string } -func NewCloudNamespaceExportAzureUpdateCommand(cctx *CommandContext, parent *CloudNamespaceExportAzureCommand) *CloudNamespaceExportAzureUpdateCommand { - var s CloudNamespaceExportAzureUpdateCommand +func NewCloudNamespaceExportAzureBlobUpdateCommand(cctx *CommandContext, parent *CloudNamespaceExportAzureBlobCommand) *CloudNamespaceExportAzureBlobUpdateCommand { + var s CloudNamespaceExportAzureBlobUpdateCommand s.Parent = parent s.Command.DisableFlagsInUseLine = true s.Command.Use = "update [flags]" s.Command.Short = "Update an Azure Blob workflow history export sink" if hasHighlighting { - s.Command.Long = "Update the configuration of an existing Azure Blob workflow history export sink.\nOnly the flags you provide are changed; omitted flags keep their current\nvalues. The enabled/disabled state and region are also preserved.\n\nExample (rotate storage account only):\n\n\x1b[1mtemporal cloud namespace export azure update --namespace my-namespace.my-account --sink-name my-sink \\\n --storage-account my-new-storage-account\x1b[0m" + s.Command.Long = "Update the configuration of an existing Azure Blob workflow history export sink.\nOnly the flags you provide are changed; omitted flags keep their current\nvalues. The enabled/disabled state and region are also preserved.\n\nExample (rotate storage account only):\n\n\x1b[1mtemporal cloud namespace export azure-blob update --namespace my-namespace.my-account --sink-name my-sink \\\n --storage-account my-new-storage-account\x1b[0m" } else { - s.Command.Long = "Update the configuration of an existing Azure Blob workflow history export sink.\nOnly the flags you provide are changed; omitted flags keep their current\nvalues. The enabled/disabled state and region are also preserved.\n\nExample (rotate storage account only):\n\n```\ntemporal cloud namespace export azure update --namespace my-namespace.my-account --sink-name my-sink \\\n --storage-account my-new-storage-account\n```" + s.Command.Long = "Update the configuration of an existing Azure Blob workflow history export sink.\nOnly the flags you provide are changed; omitted flags keep their current\nvalues. The enabled/disabled state and region are also preserved.\n\nExample (rotate storage account only):\n\n```\ntemporal cloud namespace export azure-blob update --namespace my-namespace.my-account --sink-name my-sink \\\n --storage-account my-new-storage-account\n```" } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.TenantId, "tenant-id", "", "The Azure tenant ID where the storage account exists and where Temporal's app registration is consented/granted access. If omitted, the current value is kept.") @@ -2635,33 +2635,33 @@ func NewCloudNamespaceExportAzureUpdateCommand(cctx *CommandContext, parent *Clo return &s } -type CloudNamespaceExportAzureValidateCommand struct { - Parent *CloudNamespaceExportAzureCommand +type CloudNamespaceExportAzureBlobValidateCommand struct { + Parent *CloudNamespaceExportAzureBlobCommand Command cobra.Command ClientOptions NamespaceOptions ExportSinkOptions - ExportAzureOptions - ExportAzureRegionOptions + ExportAzureBlobOptions + ExportAzureBlobRegionOptions } -func NewCloudNamespaceExportAzureValidateCommand(cctx *CommandContext, parent *CloudNamespaceExportAzureCommand) *CloudNamespaceExportAzureValidateCommand { - var s CloudNamespaceExportAzureValidateCommand +func NewCloudNamespaceExportAzureBlobValidateCommand(cctx *CommandContext, parent *CloudNamespaceExportAzureBlobCommand) *CloudNamespaceExportAzureBlobValidateCommand { + var s CloudNamespaceExportAzureBlobValidateCommand s.Parent = parent s.Command.DisableFlagsInUseLine = true s.Command.Use = "validate [flags]" s.Command.Short = "Validate an Azure Blob workflow history export sink configuration" if hasHighlighting { - s.Command.Long = "Validate an Azure Blob workflow history export sink configuration without creating or updating it.\nA successful response means the configuration is valid.\n\nExample:\n\n\x1b[1mtemporal cloud namespace export azure validate --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\x1b[0m" + s.Command.Long = "Validate an Azure Blob workflow history export sink configuration without creating or updating it.\nA successful response means the configuration is valid.\n\nExample:\n\n\x1b[1mtemporal cloud namespace export azure-blob validate --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\x1b[0m" } else { - s.Command.Long = "Validate an Azure Blob workflow history export sink configuration without creating or updating it.\nA successful response means the configuration is valid.\n\nExample:\n\n```\ntemporal cloud namespace export azure validate --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\n```" + s.Command.Long = "Validate an Azure Blob workflow history export sink configuration without creating or updating it.\nA successful response means the configuration is valid.\n\nExample:\n\n```\ntemporal cloud namespace export azure-blob validate --namespace my-namespace.my-account --sink-name my-sink \\\n --tenant-id 11111111-1111-1111-1111-111111111111 \\\n --subscription-id 22222222-2222-2222-2222-222222222222 \\\n --resource-group my-resource-group --storage-account my-storage-account \\\n --container-name my-container --region eastus\n```" } s.Command.Args = cobra.NoArgs s.ClientOptions.BuildFlags(s.Command.Flags()) s.NamespaceOptions.BuildFlags(s.Command.Flags()) s.ExportSinkOptions.BuildFlags(s.Command.Flags()) - s.ExportAzureOptions.BuildFlags(s.Command.Flags()) - s.ExportAzureRegionOptions.BuildFlags(s.Command.Flags()) + s.ExportAzureBlobOptions.BuildFlags(s.Command.Flags()) + s.ExportAzureBlobRegionOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) diff --git a/temporalcloudcli/commands.namespace.export.go b/temporalcloudcli/commands.namespace.export.go index 9189373..38ee15c 100644 --- a/temporalcloudcli/commands.namespace.export.go +++ b/temporalcloudcli/commands.namespace.export.go @@ -844,7 +844,7 @@ func (c *CloudNamespaceExportGcsValidateCommand) run(cctx *CommandContext, _ []s }) } -func (c *CloudNamespaceExportAzureCreateCommand) run(cctx *CommandContext, _ []string) error { +func (c *CloudNamespaceExportAzureBlobCreateCommand) run(cctx *CommandContext, _ []string) error { cloudClient, err := cctx.BuildCloudClient(c.ClientOptions) if err != nil { return err @@ -865,7 +865,7 @@ func (c *CloudNamespaceExportAzureCreateCommand) run(cctx *CommandContext, _ []s }) } -func (c *CloudNamespaceExportAzureUpdateCommand) run(cctx *CommandContext, _ []string) error { +func (c *CloudNamespaceExportAzureBlobUpdateCommand) run(cctx *CommandContext, _ []string) error { cloudClient, err := cctx.BuildCloudClient(c.ClientOptions) if err != nil { return err @@ -886,7 +886,7 @@ func (c *CloudNamespaceExportAzureUpdateCommand) run(cctx *CommandContext, _ []s }) } -func (c *CloudNamespaceExportAzureValidateCommand) run(cctx *CommandContext, _ []string) error { +func (c *CloudNamespaceExportAzureBlobValidateCommand) run(cctx *CommandContext, _ []string) error { cloudClient, err := cctx.BuildCloudClient(c.ClientOptions) if err != nil { return err diff --git a/temporalcloudcli/commands.yml b/temporalcloudcli/commands.yml index 55b95f8..9de0d89 100644 --- a/temporalcloudcli/commands.yml +++ b/temporalcloudcli/commands.yml @@ -1256,12 +1256,12 @@ commands: - export-sink - export-gcs - export-gcs-region - - name: cloud namespace export azure + - name: cloud namespace export azure-blob summary: Manage Azure Blob workflow history export sinks description: | Commands for managing Azure Blob workflow history export sinks for Temporal Cloud namespaces. has-init: false - - name: cloud namespace export azure create + - name: cloud namespace export azure-blob create summary: Create an Azure Blob workflow history export sink description: | Create a new Azure Blob workflow history export sink for a Temporal Cloud namespace. @@ -1270,7 +1270,7 @@ commands: Example: ``` - temporal cloud namespace export azure create --namespace my-namespace.my-account --sink-name my-sink \ + temporal cloud namespace export azure-blob create --namespace my-namespace.my-account --sink-name my-sink \ --tenant-id 11111111-1111-1111-1111-111111111111 \ --subscription-id 22222222-2222-2222-2222-222222222222 \ --resource-group my-resource-group --storage-account my-storage-account \ @@ -1282,9 +1282,9 @@ commands: - namespace - async-operation - export-sink - - export-azure - - export-azure-region - - name: cloud namespace export azure update + - export-azure-blob + - export-azure-blob-region + - name: cloud namespace export azure-blob update summary: Update an Azure Blob workflow history export sink description: | Update the configuration of an existing Azure Blob workflow history export sink. @@ -1294,7 +1294,7 @@ commands: Example (rotate storage account only): ``` - temporal cloud namespace export azure update --namespace my-namespace.my-account --sink-name my-sink \ + temporal cloud namespace export azure-blob update --namespace my-namespace.my-account --sink-name my-sink \ --storage-account my-new-storage-account ``` has-init: false @@ -1331,7 +1331,7 @@ commands: description: | The name of the destination Azure Blob container where Temporal will send data. If omitted, the current value is kept. - - name: cloud namespace export azure validate + - name: cloud namespace export azure-blob validate summary: Validate an Azure Blob workflow history export sink configuration description: | Validate an Azure Blob workflow history export sink configuration without creating or updating it. @@ -1340,7 +1340,7 @@ commands: Example: ``` - temporal cloud namespace export azure validate --namespace my-namespace.my-account --sink-name my-sink \ + temporal cloud namespace export azure-blob validate --namespace my-namespace.my-account --sink-name my-sink \ --tenant-id 11111111-1111-1111-1111-111111111111 \ --subscription-id 22222222-2222-2222-2222-222222222222 \ --resource-group my-resource-group --storage-account my-storage-account \ @@ -1351,8 +1351,8 @@ commands: - client - namespace - export-sink - - export-azure - - export-azure-region + - export-azure-blob + - export-azure-blob-region # Namespace codec commands - name: cloud namespace codec @@ -4480,7 +4480,7 @@ option-sets: required: true description: | The GCS bucket region. - - name: export-azure + - name: export-azure-blob options: - name: tenant-id type: string @@ -4508,7 +4508,7 @@ option-sets: required: true description: | The name of the destination Azure Blob container where Temporal will send data. - - name: export-azure-region + - name: export-azure-blob-region options: - name: region type: string