From 149c16668212e471bccb0cad41bfd3cf537acf79 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 23 Sep 2025 16:54:51 -0400 Subject: [PATCH 1/9] [CLDGR-749] Use GetNamespace from Cloud API instead --- app/namespace.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/namespace.go b/app/namespace.go index c2f7ae4a..ae0761f4 100644 --- a/app/namespace.go +++ b/app/namespace.go @@ -326,6 +326,21 @@ func (c *NamespaceClient) listNamespaces() error { } } +func (c *NamespaceClient) getNamespaceCloudApi(namespace string) (*cloudNamespace.Namespace, error) { + res, err := c.cloudAPIClient.GetNamespace(c.ctx, &cloudservice.GetNamespaceRequest{ + Namespace: namespace, + }) + if err != nil { + return nil, err + } + if res.Namespace == nil || res.Namespace.Namespace == "" { + // this should never happen, the server should return an error when the namespace is not found + return nil, fmt.Errorf("invalid namespace returned by server") + } + return res.Namespace, nil +} + +// TODO: deprecate this and use getNamespaceCloudApi everywhere func (c *NamespaceClient) getNamespace(namespace string) (*namespace.Namespace, error) { res, err := c.client.GetNamespace(c.ctx, &namespaceservice.GetNamespaceRequest{ Namespace: namespace, @@ -856,7 +871,7 @@ func NewNamespaceCommand(getNamespaceClientFn GetNamespaceClientFn) (CommandOut, }, }, Action: func(ctx *cli.Context) error { - n, err := c.getNamespace(ctx.String(NamespaceFlagName)) + n, err := c.getNamespaceCloudApi(ctx.String(NamespaceFlagName)) if err != nil { return err } @@ -2243,6 +2258,7 @@ func NewNamespaceCommand(getNamespaceClientFn GetNamespaceClientFn) (CommandOut, }, }, + {}, }, } From b63208ae5de92c30e131bed2630bed0aae75e698 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 23 Sep 2025 17:31:13 -0400 Subject: [PATCH 2/9] fix --- app/namespace.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/namespace.go b/app/namespace.go index ae0761f4..07659984 100644 --- a/app/namespace.go +++ b/app/namespace.go @@ -995,7 +995,7 @@ func NewNamespaceCommand(getNamespaceClientFn GetNamespaceClientFn) (CommandOut, NamespaceFlag, }, Action: func(ctx *cli.Context) error { - n, err := c.getNamespace(ctx.String(NamespaceFlagName)) + n, err := c.getNamespaceCloudApi(ctx.String(NamespaceFlagName)) if err != nil { return err } @@ -2258,7 +2258,6 @@ func NewNamespaceCommand(getNamespaceClientFn GetNamespaceClientFn) (CommandOut, }, }, - {}, }, } From 533e1ee9e1947363ade28add0b7ff7d5c884526e Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 23 Sep 2025 18:03:15 -0400 Subject: [PATCH 3/9] testfix --- app/connection.go | 2 +- app/namespace.go | 2 +- app/namespace_test.go | 24 ++++++++++++++---------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/connection.go b/app/connection.go index 946f24cc..4135cbfb 100644 --- a/app/connection.go +++ b/app/connection.go @@ -25,7 +25,7 @@ const ( CommitHeader = "tcld-commit" TemporalCloudAPIVersionHeader = "temporal-cloud-api-version" LegacyTemporalCloudAPIVersion = "2025-07-09-00" - TemporalCloudAPIVersion = "v0.5.1" + TemporalCloudAPIVersion = "v9999.0.0" userAgentTemplate = "tcld/%s" ) diff --git a/app/namespace.go b/app/namespace.go index 07659984..0e1593b7 100644 --- a/app/namespace.go +++ b/app/namespace.go @@ -871,7 +871,7 @@ func NewNamespaceCommand(getNamespaceClientFn GetNamespaceClientFn) (CommandOut, }, }, Action: func(ctx *cli.Context) error { - n, err := c.getNamespaceCloudApi(ctx.String(NamespaceFlagName)) + n, err := c.getNamespace(ctx.String(NamespaceFlagName)) if err != nil { return err } diff --git a/app/namespace_test.go b/app/namespace_test.go index e1ab1339..5309792e 100644 --- a/app/namespace_test.go +++ b/app/namespace_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/temporalio/tcld/protogen/api/cloud/operation/v1" + "github.com/temporalio/tcld/protogen/api/cloud/resource/v1" "github.com/temporalio/tcld/protogen/api/common/v1" "github.com/temporalio/tcld/protogen/api/auth/v1" @@ -19,13 +20,14 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/suite" + "github.com/urfave/cli/v2" + "github.com/temporalio/tcld/protogen/api/namespace/v1" "github.com/temporalio/tcld/protogen/api/namespaceservice/v1" "github.com/temporalio/tcld/protogen/api/request/v1" authservicemock "github.com/temporalio/tcld/protogen/apimock/authservice/v1" apimock "github.com/temporalio/tcld/protogen/apimock/cloudservice/v1" namespaceservicemock "github.com/temporalio/tcld/protogen/apimock/namespaceservice/v1" - "github.com/urfave/cli/v2" ) func TestNamespace(t *testing.T) { @@ -79,24 +81,26 @@ func (s *NamespaceTestSuite) AfterTest(suiteName, testName string) { func (s *NamespaceTestSuite) TestGet() { s.Error(s.RunCmd("namespace", "get")) - s.mockService.EXPECT().GetNamespace(gomock.Any(), &namespaceservice.GetNamespaceRequest{ + s.mockCloudApiClient.EXPECT().GetNamespace(gomock.Any(), &cloudservice.GetNamespaceRequest{ Namespace: "ns1", }).Return(nil, errors.New("some error")).Times(1) s.Error(s.RunCmd("namespace", "get", "--namespace", "ns1")) - s.mockService.EXPECT().GetNamespace(gomock.Any(), &namespaceservice.GetNamespaceRequest{ + s.mockCloudApiClient.EXPECT().GetNamespace(gomock.Any(), &cloudservice.GetNamespaceRequest{ Namespace: "ns1", - }).Return(&namespaceservice.GetNamespaceResponse{ - Namespace: &namespace.Namespace{ + }).Return(&cloudservice.GetNamespaceResponse{ + Namespace: &cloudNamespace.Namespace{ Namespace: "ns1", - Spec: &namespace.NamespaceSpec{ - AcceptedClientCa: "cert1", - SearchAttributes: map[string]namespace.SearchAttributeType{ - "attr1": namespace.SEARCH_ATTRIBUTE_TYPE_BOOL, + Spec: &cloudNamespace.NamespaceSpec{ + MtlsAuth: &cloudNamespace.MtlsAuthSpec{ + AcceptedClientCa: []byte("cert1"), + }, + SearchAttributes: map[string]cloudNamespace.NamespaceSpec_SearchAttributeType{ + "attr1": cloudNamespace.SEARCH_ATTRIBUTE_TYPE_BOOL, }, RetentionDays: 7, }, - State: namespace.STATE_UPDATING, + State: resource.RESOURCE_STATE_UPDATING, ResourceVersion: "ver1", }, }, nil).Times(1) From d98e965df34cf3662d590ad6da8d7e23319a9124 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 23 Sep 2025 18:04:23 -0400 Subject: [PATCH 4/9] revert connection.go --- app/connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/connection.go b/app/connection.go index 4135cbfb..946f24cc 100644 --- a/app/connection.go +++ b/app/connection.go @@ -25,7 +25,7 @@ const ( CommitHeader = "tcld-commit" TemporalCloudAPIVersionHeader = "temporal-cloud-api-version" LegacyTemporalCloudAPIVersion = "2025-07-09-00" - TemporalCloudAPIVersion = "v9999.0.0" + TemporalCloudAPIVersion = "v0.5.1" userAgentTemplate = "tcld/%s" ) From f91e683a133629958f58b9b7a961f924fed2e4d1 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Wed, 24 Sep 2025 20:50:53 -0400 Subject: [PATCH 5/9] refactor] --- app/namespace.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/namespace.go b/app/namespace.go index 0e1593b7..dd5c041f 100644 --- a/app/namespace.go +++ b/app/namespace.go @@ -995,7 +995,7 @@ func NewNamespaceCommand(getNamespaceClientFn GetNamespaceClientFn) (CommandOut, NamespaceFlag, }, Action: func(ctx *cli.Context) error { - n, err := c.getNamespaceCloudApi(ctx.String(NamespaceFlagName)) + n, err := c.getNamespace(ctx.String(NamespaceFlagName)) if err != nil { return err } @@ -1827,6 +1827,28 @@ func NewNamespaceCommand(getNamespaceClientFn GetNamespaceClientFn) (CommandOut, }, }, }, + { + Name: "capacity", + Usage: "Manage namespace capacity settings", + Aliases: []string{"cap"}, + Subcommands: []*cli.Command{ + { + Name: "get", + Usage: "Get namespace capacity settings", + Aliases: []string{"g"}, + Flags: []cli.Flag{ + NamespaceFlag, + }, + Action: func(ctx *cli.Context) error { + n, err := c.getNamespaceCloudApi(ctx.String(NamespaceFlagName)) + if err != nil { + return err + } + return PrintProto(n.GetCapacity()) + }, + }, + }, + }, } // Export Related Command From fcec79503972e5199673146ee71e06ab26fdd93d Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Wed, 24 Sep 2025 21:12:32 -0400 Subject: [PATCH 6/9] testfix --- app/namespace_test.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/namespace_test.go b/app/namespace_test.go index 5309792e..f7620cc1 100644 --- a/app/namespace_test.go +++ b/app/namespace_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/temporalio/tcld/protogen/api/cloud/operation/v1" - "github.com/temporalio/tcld/protogen/api/cloud/resource/v1" "github.com/temporalio/tcld/protogen/api/common/v1" "github.com/temporalio/tcld/protogen/api/auth/v1" @@ -81,26 +80,24 @@ func (s *NamespaceTestSuite) AfterTest(suiteName, testName string) { func (s *NamespaceTestSuite) TestGet() { s.Error(s.RunCmd("namespace", "get")) - s.mockCloudApiClient.EXPECT().GetNamespace(gomock.Any(), &cloudservice.GetNamespaceRequest{ + s.mockService.EXPECT().GetNamespace(gomock.Any(), &namespaceservice.GetNamespaceRequest{ Namespace: "ns1", }).Return(nil, errors.New("some error")).Times(1) s.Error(s.RunCmd("namespace", "get", "--namespace", "ns1")) - s.mockCloudApiClient.EXPECT().GetNamespace(gomock.Any(), &cloudservice.GetNamespaceRequest{ + s.mockService.EXPECT().GetNamespace(gomock.Any(), &namespaceservice.GetNamespaceRequest{ Namespace: "ns1", - }).Return(&cloudservice.GetNamespaceResponse{ - Namespace: &cloudNamespace.Namespace{ + }).Return(&namespaceservice.GetNamespaceResponse{ + Namespace: &namespace.Namespace{ Namespace: "ns1", - Spec: &cloudNamespace.NamespaceSpec{ - MtlsAuth: &cloudNamespace.MtlsAuthSpec{ - AcceptedClientCa: []byte("cert1"), - }, - SearchAttributes: map[string]cloudNamespace.NamespaceSpec_SearchAttributeType{ - "attr1": cloudNamespace.SEARCH_ATTRIBUTE_TYPE_BOOL, + Spec: &namespace.NamespaceSpec{ + AcceptedClientCa: "cert1", + SearchAttributes: map[string]namespace.SearchAttributeType{ + "attr1": namespace.SEARCH_ATTRIBUTE_TYPE_BOOL, }, RetentionDays: 7, }, - State: resource.RESOURCE_STATE_UPDATING, + State: namespace.STATE_UPDATING, ResourceVersion: "ver1", }, }, nil).Times(1) From d1d8f6f85e5cec704fc829228022a398be48a8af Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Fri, 26 Sep 2025 09:51:59 -0400 Subject: [PATCH 7/9] unit tests --- app/namespace_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/app/namespace_test.go b/app/namespace_test.go index f7620cc1..910b30fa 100644 --- a/app/namespace_test.go +++ b/app/namespace_test.go @@ -3265,3 +3265,59 @@ func (s *NamespaceTestSuite) TestSetConnectivityRules() { }) } } + +func (s *NamespaceTestSuite) TestGetNamespaceCapacity() { + tests := []struct { + name string + args []string + mock func() + expectErr bool + }{ + { + name: "get namespace capacity - success", + args: []string{"namespace", "capacity", "get", "--namespace", "ns1"}, + mock: func() { + s.mockCloudApiClient.EXPECT(). + GetNamespace(gomock.Any(), &cloudservice.GetNamespaceRequest{ + Namespace: "ns1", + }).Return(&cloudservice.GetNamespaceResponse{ + Namespace: &cloudNamespace.Namespace{ + Namespace: "ns1", + Capacity: &cloudNamespace.Capacity{ + CurrentMode: &cloudNamespace.Capacity_Provisioned_{ + Provisioned: &cloudNamespace.Capacity_Provisioned{ + CurrentValue: 16.0, + }, + }, + }, + }, + }, nil).Times(1) + }, + }, + { + name: "get namespace capacity - fail", + args: []string{"namespace", "capacity", "get", "--namespace", "ns1"}, + expectErr: true, + mock: func() { + s.mockCloudApiClient.EXPECT(). + GetNamespace(gomock.Any(), &cloudservice.GetNamespaceRequest{ + Namespace: "ns1", + }).Return(nil, errors.New("some error")).Times(1) + }, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + if tc.mock != nil { + tc.mock() + } + err := s.RunCmd(tc.args...) + if tc.expectErr { + s.Error(err) + } else { + s.NoError(err) + } + }) + } +} From 3375e894820e74ae34b7916c23a3d930509c90e5 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Wed, 24 Sep 2025 21:08:57 -0400 Subject: [PATCH 8/9] [CLDGR-748] Adding capacity operations for ns update --- app/namespace.go | 100 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/app/namespace.go b/app/namespace.go index dd5c041f..d48bdb8c 100644 --- a/app/namespace.go +++ b/app/namespace.go @@ -46,6 +46,12 @@ const ( disableFailoverFlagName = "disable-auto-failover" enableDeleteProtectionFlagName = "enable-delete-protection" tagFlagName = "tag" + + capacityModeFlagName = "capacity-mode" + capacityValueFlagName = "capacity-value" + + provisionedCapacityMode = "provisioned" + onDemandCapacityMode = "on_demand" ) const ( @@ -166,6 +172,28 @@ var ( Aliases: []string{"ids"}, Required: false, } + + capacityModeFlag = &cli.StringFlag{ + Name: capacityModeFlagName, + Usage: fmt.Sprintf("The capacity mode to use for the namespace. Valid values are '%s' and '%s'", onDemandCapacityMode, provisionedCapacityMode), + Aliases: []string{"cm"}, + Required: false, + Action: func(_ *cli.Context, s string) error { + switch s { + case "", onDemandCapacityMode, provisionedCapacityMode: + return nil + default: + return fmt.Errorf("invalid capacity mode %s, valid values are 'on_demand' and 'provisioned'", s) + } + }, + } + + capacityValueFlag = &cli.Float64Flag{ + Name: capacityValueFlagName, + Usage: "The capacity value to use for the namespace. Required if capacity mode is 'provisioned', ignored otherwise", + Aliases: []string{"cv"}, + Required: false, + } ) type NamespaceClient struct { @@ -377,6 +405,28 @@ func (c *NamespaceClient) updateNamespace(ctx *cli.Context, n *namespace.Namespa return PrintProto(res) } +func (c *NamespaceClient) updateNamespaceCloudApi(ctx *cli.Context, n *cloudNamespace.Namespace) error { + resourceVersion := n.ResourceVersion + if v := ctx.String(ResourceVersionFlagName); v != "" { + resourceVersion = v + } + + res, err := c.cloudAPIClient.UpdateNamespace(c.ctx, &cloudservice.UpdateNamespaceRequest{ + AsyncOperationId: ctx.String(RequestIDFlagName), + Namespace: n.Namespace, + ResourceVersion: resourceVersion, + Spec: n.Spec, + }) + if err != nil { + if isNothingChangedErr(ctx, err) { + return nil + } + return err + } + + return PrintProto(res) +} + func (c *NamespaceClient) updateNamespaceTags(ctx *cli.Context, tagsToUpsert map[string]string, tagsToRemove []string) error { namespace := ctx.String(NamespaceFlagName) if len(namespace) == 0 { @@ -1847,6 +1897,56 @@ func NewNamespaceCommand(getNamespaceClientFn GetNamespaceClientFn) (CommandOut, return PrintProto(n.GetCapacity()) }, }, + { + Name: "update", + Usage: "Set the capacity of a given namespace.", + Aliases: []string{"u"}, + Flags: []cli.Flag{ + NamespaceFlag, + capacityModeFlag, + capacityValueFlag, + RequestIDFlag, + ResourceVersionFlag, + }, + Action: func(ctx *cli.Context) error { + nsID := ctx.String(NamespaceFlagName) + mode := ctx.String(capacityModeFlagName) + value := ctx.Float64(capacityValueFlagName) + if mode == "" { + return fmt.Errorf("capacity mode must be specified (either '%s' or '%s')", onDemandCapacityMode, provisionedCapacityMode) + } + if mode == provisionedCapacityMode && value <= 0 { + return fmt.Errorf("capacity value must be greater than 0 when capacity mode is '%s'", provisionedCapacityMode) + } + var capacitySpec *cloudNamespace.CapacitySpec + switch mode { + case onDemandCapacityMode: + capacitySpec = &cloudNamespace.CapacitySpec{ + Spec: &cloudNamespace.CapacitySpec_OnDemand_{ + OnDemand: &cloudNamespace.CapacitySpec_OnDemand{}, + }, + } + case provisionedCapacityMode: + capacitySpec = &cloudNamespace.CapacitySpec{ + Spec: &cloudNamespace.CapacitySpec_Provisioned_{ + Provisioned: &cloudNamespace.CapacitySpec_Provisioned{ + Value: value, + }, + }, + } + } + ns, err := c.getNamespaceCloudApi(nsID) + if err != nil { + return err + } + + if ns != nil && ns.Spec == nil { + ns.Spec = &cloudNamespace.NamespaceSpec{} + } + ns.Spec.CapacitySpec = capacitySpec + return c.updateNamespaceCloudApi(ctx, ns) + }, + }, }, }, } From 0ee648af32a5aa961f75a2b2d94f2fd4f5035636 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Fri, 26 Sep 2025 09:59:11 -0400 Subject: [PATCH 9/9] test --- app/namespace_test.go | 98 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/app/namespace_test.go b/app/namespace_test.go index 910b30fa..7fe9be00 100644 --- a/app/namespace_test.go +++ b/app/namespace_test.go @@ -3321,3 +3321,101 @@ func (s *NamespaceTestSuite) TestGetNamespaceCapacity() { }) } } + +func (s *NamespaceTestSuite) TestUpdateNamespaceCapacity() { + tests := []struct { + name string + args []string + mock func() + expectErr bool + }{ + { + name: "update namespace capacity - success", + args: []string{"namespace", "capacity", "update", "--namespace", "ns1", "--capacity-mode", "provisioned", "--capacity-value", "16"}, + mock: func() { + s.mockCloudApiClient.EXPECT(). + GetNamespace(gomock.Any(), &cloudservice.GetNamespaceRequest{ + Namespace: "ns1", + }).Return(&cloudservice.GetNamespaceResponse{ + Namespace: &cloudNamespace.Namespace{ + Namespace: "ns1", + Capacity: &cloudNamespace.Capacity{ + CurrentMode: &cloudNamespace.Capacity_OnDemand_{ + OnDemand: &cloudNamespace.Capacity_OnDemand{}, + }, + }, + }, + }, nil).Times(1) + + s.mockCloudApiClient.EXPECT(). + UpdateNamespace(gomock.Any(), &cloudservice.UpdateNamespaceRequest{ + Namespace: "ns1", + Spec: &cloudNamespace.NamespaceSpec{ + CapacitySpec: &cloudNamespace.CapacitySpec{ + Spec: &cloudNamespace.CapacitySpec_Provisioned_{ + Provisioned: &cloudNamespace.CapacitySpec_Provisioned{ + Value: 16.0, + }, + }, + }, + }, + }). + Return(&cloudservice.UpdateNamespaceResponse{ + AsyncOperation: &operation.AsyncOperation{ + Id: "op-123", + }, + }, nil) + + }, + }, + { + name: "update namespace capacity - failure", + args: []string{"namespace", "capacity", "update", "--namespace", "ns1", "--capacity-mode", "provisioned", "--capacity-value", "16"}, + mock: func() { + s.mockCloudApiClient.EXPECT(). + GetNamespace(gomock.Any(), &cloudservice.GetNamespaceRequest{ + Namespace: "ns1", + }).Return(&cloudservice.GetNamespaceResponse{ + Namespace: &cloudNamespace.Namespace{ + Namespace: "ns1", + Capacity: &cloudNamespace.Capacity{ + CurrentMode: &cloudNamespace.Capacity_OnDemand_{ + OnDemand: &cloudNamespace.Capacity_OnDemand{}, + }, + }, + }, + }, nil).Times(1) + + s.mockCloudApiClient.EXPECT(). + UpdateNamespace(gomock.Any(), &cloudservice.UpdateNamespaceRequest{ + Namespace: "ns1", + Spec: &cloudNamespace.NamespaceSpec{ + CapacitySpec: &cloudNamespace.CapacitySpec{ + Spec: &cloudNamespace.CapacitySpec_Provisioned_{ + Provisioned: &cloudNamespace.CapacitySpec_Provisioned{ + Value: 16.0, + }, + }, + }, + }, + }). + Return(nil, errors.New("some error")) + }, + expectErr: true, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + if tc.mock != nil { + tc.mock() + } + err := s.RunCmd(tc.args...) + if tc.expectErr { + s.Error(err) + } else { + s.NoError(err) + } + }) + } +}