From 4bff1fe00de69004fff6484a61de52bfba16fb8c Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 23 Sep 2025 20:33:36 -0400 Subject: [PATCH 01/31] Adding Namespace Capacity Terraform Resource --- go.mod | 10 +-- go.sum | 20 +++--- internal/client/client.go | 53 ++++++++++++++++ internal/provider/namespace_resource.go | 82 +++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 689dc2ce..80531aa4 100644 --- a/go.mod +++ b/go.mod @@ -14,11 +14,11 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.13.3 github.com/jpillora/maplock v0.0.0-20160420012925-5c725ac6e22a - go.temporal.io/api v1.53.0 - go.temporal.io/cloud-sdk v0.5.0 - go.temporal.io/sdk v1.36.0 - google.golang.org/grpc v1.75.1 - google.golang.org/protobuf v1.36.9 + go.temporal.io/api v1.52.0 + go.temporal.io/cloud-sdk v0.7.0 + go.temporal.io/sdk v1.35.0 + google.golang.org/grpc v1.75.0 + google.golang.org/protobuf v1.36.8 ) require ( diff --git a/go.sum b/go.sum index 49a21efd..79781eef 100644 --- a/go.sum +++ b/go.sum @@ -236,12 +236,12 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.temporal.io/api v1.53.0 h1:6vAFpXaC584AIELa6pONV56MTpkm4Ha7gPWL2acNAjo= -go.temporal.io/api v1.53.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= -go.temporal.io/cloud-sdk v0.5.0 h1:6PdA6D8I/PiFLLpYwinre7ffPTct49zhapMAN5rJjmw= -go.temporal.io/cloud-sdk v0.5.0/go.mod h1:AueDDyuayosk+zalfrnuftRqnRQTHwD0HYwNgEQc0YE= -go.temporal.io/sdk v1.36.0 h1:WO9zetpybBNK7xsQth4Z+3Zzw1zSaM9MOUGrnnUjZMo= -go.temporal.io/sdk v1.36.0/go.mod h1:8BxGRF0LcQlfQrLLGkgVajbsKUp/PY7280XTdcKc18Y= +go.temporal.io/api v1.52.0 h1:Tn69z2nhQeXtofa1/j/MbwPHnFRM9+13xqYmFl/KFjM= +go.temporal.io/api v1.52.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/cloud-sdk v0.7.0 h1:GtkUyIrFA5zjvyvJBSQumNgYZFlj3bBnqOs+lazHRrk= +go.temporal.io/cloud-sdk v0.7.0/go.mod h1:AueDDyuayosk+zalfrnuftRqnRQTHwD0HYwNgEQc0YE= +go.temporal.io/sdk v1.35.0 h1:lRNAQ5As9rLgYa7HBvnmKyzxLcdElTuoFJ0FXM/AsLQ= +go.temporal.io/sdk v1.35.0/go.mod h1:1q5MuLc2MEJ4lneZTHJzpVebW2oZnyxoIOWX3oFVebw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -318,12 +318,12 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1: google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/client/client.go b/internal/client/client.go index b17090e3..8cbcdea3 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -31,6 +31,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" cloudservicev1 "go.temporal.io/cloud-sdk/api/cloudservice/v1" + "go.temporal.io/cloud-sdk/api/namespace/v1" operationv1 "go.temporal.io/cloud-sdk/api/operation/v1" "go.temporal.io/cloud-sdk/cloudclient" ) @@ -112,3 +113,55 @@ func AwaitAsyncOperation(ctx context.Context, cloudclient *Client, op *operation } } } + +func AwaitNamespaceCapacityOperation(ctx context.Context, cloudclient *Client, n *namespace.Namespace) error { + if n == nil { + return fmt.Errorf("namespace is required") + } + ns := n + + getResp, err := cloudclient.CloudService().GetNamespace(ctx, &cloudservicev1.GetNamespaceRequest{ + Namespace: ns.GetNamespace(), + }) + if err != nil { + return fmt.Errorf("failed to get namespace: %w", err) + } + ctx = tflog.SetField(ctx, "namespace_id", ns.GetNamespace()) + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + getResp, err = cloudclient.CloudService().GetNamespace(ctx, &cloudservicev1.GetNamespaceRequest{ + Namespace: ns.GetNamespace(), + }) + ns = getResp.GetNamespace() + if ns.GetCapacity().GetLatestRequest() == nil { + return nil + } + state := ns.GetCapacity().GetLatestRequest().GetState() + switch state { + case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_UNSPECIFIED: + fallthrough + case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_IN_PROGRESS: + tflog.Debug(ctx, "retrying in 1 second", map[string]any{ + "state": state, + }) + continue + case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_FAILED: + tflog.Debug(ctx, "request failed") + return errors.New("capacity request failed") + case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_COMPLETED: + tflog.Debug(ctx, "request completed") + return nil + default: + tflog.Warn(ctx, "unknown state, continuing", map[string]any{ + "state": state, + }) + continue + } + case <-ctx.Done(): + return ctx.Err() + } + } +} diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index b51799e4..3e8e53f9 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -83,6 +83,7 @@ type ( NamespaceLifecycle internaltypes.ZeroObjectValue `tfsdk:"namespace_lifecycle"` ConnectivityRuleIds internaltypes.UnorderedStringListValue `tfsdk:"connectivity_rule_ids"` Timeouts timeouts.Value `tfsdk:"timeouts"` + Capacity types.Object `tfsdk:"capacity"` } lifecycleModel struct { @@ -107,6 +108,11 @@ type ( GrpcAddress types.String `tfsdk:"grpc_address"` MtlsGrpcAddress types.String `tfsdk:"mtls_grpc_address"` } + + capacityModel struct { + Mode types.String `tfsdk:"mode"` + Value types.Float64 `tfsdk:"value"` + } ) var ( @@ -136,6 +142,11 @@ var ( "grpc_address": types.StringType, "mtls_grpc_address": types.StringType, } + + capacityAttrs = map[string]attr.Type{ + "mode": types.StringType, + "value": types.Float64Type, + } ) func NewNamespaceResource() resource.Resource { @@ -304,6 +315,19 @@ func (r *namespaceResource) Schema(ctx context.Context, _ resource.SchemaRequest listvalidator.SizeAtLeast(1), }, }, + "capacity": schema.SingleNestedAttribute{ + Description: "The capacity configuration for the namespace.", + Attributes: map[string]schema.Attribute{ + "mode": schema.StringAttribute{ + Description: "The mode of the capacity configuration. Must be one of 'provisioned' or 'on_demand'.", + Optional: true, + }, + "value": schema.Float64Attribute{ + Description: "The value of the capacity configuration. Must be set when mode is 'provisioned'.", + Optional: true, + }, + }, + }, }, Blocks: map[string]schema.Block{ "timeouts": timeouts.Block(ctx, timeouts.Opts{ @@ -600,6 +624,11 @@ func (r *namespaceResource) Update(ctx context.Context, req resource.UpdateReque return } + if err := client.AwaitNamespaceCapacityOperation(ctx, r.client, ns.Namespace); err != nil { + resp.Diagnostics.AddError("Failed to update namespace capacity", err.Error()) + return + } + resp.Diagnostics.Append(updateModelFromSpec(ctx, &plan, ns.Namespace)...) if resp.Diagnostics.HasError() { return @@ -824,8 +853,29 @@ func updateModelFromSpec( connectivityRuleIdsState = internaltypes.UnorderedStringListValue{ ListValue: planConnectivityRuleIds, } + } + capacitySpec := ns.GetSpec().GetCapacitySpec() + var capacityMode types.String + var capacityValue types.Float64 + if capacitySpec != nil { + if capacitySpec.GetOnDemand() != nil { + capacityMode = types.StringValue("on_demand") + } else if capacitySpec.GetProvisioned() != nil { + capacityMode = types.StringValue("provisioned") + capacityValue = types.Float64Value(capacitySpec.GetProvisioned().GetValue()) + } + capacity, objectDiags := types.ObjectValueFrom(ctx, capacityAttrs, &capacityModel{ + Mode: capacityMode, + Value: capacityValue, + }) + diags.Append(objectDiags...) + if diags.HasError() { + return diags + } + state.Capacity = capacity } + state.ConnectivityRuleIds = connectivityRuleIdsState state.Endpoints = endpointsState state.Regions = planRegionsUnordered @@ -893,6 +943,38 @@ func getLifecycleFromModel(ctx context.Context, model *namespaceResourceModel) ( }, diags } +func getCapacityFromModel(ctx context.Context, model *namespaceResourceModel) (*namespacev1.CapacitySpec, diag.Diagnostics) { + var diags diag.Diagnostics + var capacity capacityModel + diags.Append(model.Capacity.As(ctx, &capacity, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return nil, diags + } + switch capacity.Mode.ValueString() { + case "provisioned": + if capacity.Value.IsNull() || capacity.Value.ValueFloat64() <= 0 { + diags.Append(diag.NewErrorDiagnostic("Invalid capacity value", "Capacity value must be set when mode is 'provisioned'")) + return nil, diags + } + return &namespacev1.CapacitySpec{ + Spec: &namespacev1.CapacitySpec_Provisioned_{ + Provisioned: &namespacev1.CapacitySpec_Provisioned{ + Value: capacity.Value.ValueFloat64(), + }, + }, + }, diags + case "on_demand": + return &namespacev1.CapacitySpec{ + Spec: &namespacev1.CapacitySpec_OnDemand_{ + OnDemand: &namespacev1.CapacitySpec_OnDemand{}, + }, + }, diags + default: + diags.Append(diag.NewErrorDiagnostic("Invalid capacity mode", "Invalid capacity mode: "+capacity.Mode.ValueString())) + return nil, diags + } +} + func stringOrNull(s string) types.String { if s == "" { return types.StringNull() From 2597ac93524458223cad8785ada8eafcfbe670bd Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 23 Sep 2025 20:38:29 -0400 Subject: [PATCH 02/31] fix --- internal/client/client.go | 11 ++++------- internal/provider/namespace_resource.go | 10 ++++++++++ internal/provider/namespace_resource_test.go | 5 +++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/internal/client/client.go b/internal/client/client.go index 8cbcdea3..ee5a2d19 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -120,21 +120,18 @@ func AwaitNamespaceCapacityOperation(ctx context.Context, cloudclient *Client, n } ns := n - getResp, err := cloudclient.CloudService().GetNamespace(ctx, &cloudservicev1.GetNamespaceRequest{ - Namespace: ns.GetNamespace(), - }) - if err != nil { - return fmt.Errorf("failed to get namespace: %w", err) - } ctx = tflog.SetField(ctx, "namespace_id", ns.GetNamespace()) ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: - getResp, err = cloudclient.CloudService().GetNamespace(ctx, &cloudservicev1.GetNamespaceRequest{ + getResp, err := cloudclient.CloudService().GetNamespace(ctx, &cloudservicev1.GetNamespaceRequest{ Namespace: ns.GetNamespace(), }) + if err != nil { + return fmt.Errorf("failed to get namespace: %w", err) + } ns = getResp.GetNamespace() if ns.GetCapacity().GetLatestRequest() == nil { return nil diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index 3e8e53f9..59cd9245 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -595,6 +595,16 @@ func (r *namespaceResource) Update(ctx context.Context, req resource.UpdateReque spec.MtlsAuth = mtls } + if !plan.Capacity.IsNull() { + var d diag.Diagnostics + capacitySpec, d := getCapacityFromModel(ctx, &plan) + resp.Diagnostics.Append(d...) + if resp.Diagnostics.HasError() { + return + } + spec.CapacitySpec = capacitySpec + } + if !areRegionsEqual(currentNs.GetNamespace().GetSpec().GetRegions(), spec.Regions) { resp.Diagnostics.AddError("Namespace regions cannot be changed", "Changing the regions of a namespace is not supported currently via terraform.") return diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 4eb4960d..ea821473 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -13,10 +13,15 @@ import ( "text/template" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" cloudservicev1 "go.temporal.io/cloud-sdk/api/cloudservice/v1" + namespacev1 "go.temporal.io/cloud-sdk/api/namespace/v1" "github.com/temporalio/terraform-provider-temporalcloud/internal/client" ) From bc42fb45318b52045d56e1a08003fe10b3df2410 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 23 Sep 2025 20:39:00 -0400 Subject: [PATCH 03/31] optional --- internal/provider/namespace_resource.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index 59cd9245..5c6da54b 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -316,6 +316,7 @@ func (r *namespaceResource) Schema(ctx context.Context, _ resource.SchemaRequest }, }, "capacity": schema.SingleNestedAttribute{ + Optional: true, Description: "The capacity configuration for the namespace.", Attributes: map[string]schema.Attribute{ "mode": schema.StringAttribute{ From 78d5fd94e59fef9a2dce441098ca13c7c2874e45 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Wed, 24 Sep 2025 06:32:09 -0400 Subject: [PATCH 04/31] go mod tidy + tests --- go.mod | 3 + internal/provider/namespace_resource_test.go | 246 +++++++++++++++++++ 2 files changed, 249 insertions(+) diff --git a/go.mod b/go.mod index 80531aa4..4aa93cdf 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.13.3 github.com/jpillora/maplock v0.0.0-20160420012925-5c725ac6e22a + github.com/stretchr/testify v1.10.0 go.temporal.io/api v1.52.0 go.temporal.io/cloud-sdk v0.7.0 go.temporal.io/sdk v1.35.0 @@ -34,6 +35,7 @@ require ( github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -71,6 +73,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/posener/complete v1.2.3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/cast v1.5.0 // indirect diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index ea821473..7a9aa432 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -796,3 +796,249 @@ PEM }, }) } + +func TestGetCapacityFromModel(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + capacityModel capacityModel + expectedSpec *namespacev1.CapacitySpec + expectError bool + errorMessage string + }{ + { + name: "provisioned capacity with valid value", + capacityModel: capacityModel{ + Mode: types.StringValue("provisioned"), + Value: types.Float64Value(100.0), + }, + expectedSpec: &namespacev1.CapacitySpec{ + Spec: &namespacev1.CapacitySpec_Provisioned_{ + Provisioned: &namespacev1.CapacitySpec_Provisioned{ + Value: 100.0, + }, + }, + }, + expectError: false, + }, + { + name: "on_demand capacity", + capacityModel: capacityModel{ + Mode: types.StringValue("on_demand"), + Value: types.Float64Null(), + }, + expectedSpec: &namespacev1.CapacitySpec{ + Spec: &namespacev1.CapacitySpec_OnDemand_{ + OnDemand: &namespacev1.CapacitySpec_OnDemand{}, + }, + }, + expectError: false, + }, + { + name: "provisioned capacity with zero value should fail", + capacityModel: capacityModel{ + Mode: types.StringValue("provisioned"), + Value: types.Float64Value(0.0), + }, + expectedSpec: nil, + expectError: true, + errorMessage: "Invalid capacity value", + }, + { + name: "provisioned capacity with negative value should fail", + capacityModel: capacityModel{ + Mode: types.StringValue("provisioned"), + Value: types.Float64Value(-10.0), + }, + expectedSpec: nil, + expectError: true, + errorMessage: "Invalid capacity value", + }, + { + name: "provisioned capacity with null value should fail", + capacityModel: capacityModel{ + Mode: types.StringValue("provisioned"), + Value: types.Float64Null(), + }, + expectedSpec: nil, + expectError: true, + errorMessage: "Invalid capacity value", + }, + { + name: "invalid capacity mode should fail", + capacityModel: capacityModel{ + Mode: types.StringValue("invalid_mode"), + Value: types.Float64Value(50.0), + }, + expectedSpec: nil, + expectError: true, + errorMessage: "Invalid capacity mode", + }, + { + name: "empty capacity mode should fail", + capacityModel: capacityModel{ + Mode: types.StringValue(""), + Value: types.Float64Value(50.0), + }, + expectedSpec: nil, + expectError: true, + errorMessage: "Invalid capacity mode", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a capacity object from the model + capacity, diags := types.ObjectValueFrom(ctx, capacityAttrs, &tt.capacityModel) + require.False(t, diags.HasError(), "Failed to create capacity object: %v", diags.Errors()) + + // Create a namespace model with the capacity + model := &namespaceResourceModel{ + Capacity: capacity, + } + + // Call the function under test + spec, diags := getCapacityFromModel(ctx, model) + + if tt.expectError { + assert.True(t, diags.HasError(), "Expected error but got none") + if tt.errorMessage != "" { + found := false + for _, diag := range diags.Errors() { + if strings.Contains(diag.Summary(), tt.errorMessage) { + found = true + break + } + } + assert.True(t, found, "Expected error message '%s' not found in diagnostics: %v", tt.errorMessage, diags.Errors()) + } + assert.Nil(t, spec, "Expected nil spec on error") + } else { + assert.False(t, diags.HasError(), "Unexpected error: %v", diags.Errors()) + assert.NotNil(t, spec, "Expected non-nil spec") + + // Compare the specs + if tt.expectedSpec.GetProvisioned() != nil { + require.NotNil(t, spec.GetProvisioned(), "Expected provisioned capacity spec") + assert.Equal(t, tt.expectedSpec.GetProvisioned().GetValue(), spec.GetProvisioned().GetValue()) + } + + if tt.expectedSpec.GetOnDemand() != nil { + require.NotNil(t, spec.GetOnDemand(), "Expected on-demand capacity spec") + } + } + }) + } +} + +func TestCapacityModelValidation(t *testing.T) { + ctx := context.Background() + + t.Run("capacity model can be created from object value", func(t *testing.T) { + testCapacity := capacityModel{ + Mode: types.StringValue("provisioned"), + Value: types.Float64Value(50.0), + } + + capacity, diags := types.ObjectValueFrom(ctx, capacityAttrs, &testCapacity) + require.False(t, diags.HasError(), "Failed to create capacity object: %v", diags.Errors()) + + var retrievedCapacity capacityModel + diags = capacity.As(ctx, &retrievedCapacity, basetypes.ObjectAsOptions{}) + require.False(t, diags.HasError(), "Failed to retrieve capacity model: %v", diags.Errors()) + + assert.Equal(t, "provisioned", retrievedCapacity.Mode.ValueString()) + assert.Equal(t, 50.0, retrievedCapacity.Value.ValueFloat64()) + }) + + t.Run("capacity model can handle null values", func(t *testing.T) { + testCapacity := capacityModel{ + Mode: types.StringValue("on_demand"), + Value: types.Float64Null(), + } + + capacity, diags := types.ObjectValueFrom(ctx, capacityAttrs, &testCapacity) + require.False(t, diags.HasError(), "Failed to create capacity object: %v", diags.Errors()) + + var retrievedCapacity capacityModel + diags = capacity.As(ctx, &retrievedCapacity, basetypes.ObjectAsOptions{}) + require.False(t, diags.HasError(), "Failed to retrieve capacity model: %v", diags.Errors()) + + assert.Equal(t, "on_demand", retrievedCapacity.Mode.ValueString()) + assert.True(t, retrievedCapacity.Value.IsNull()) + }) +} + +func TestCapacitySpecParsing(t *testing.T) { + tests := []struct { + name string + capacitySpec *namespacev1.CapacitySpec + expectedMode string + expectedValue *float64 + expectNullValue bool + }{ + { + name: "provisioned capacity spec", + capacitySpec: &namespacev1.CapacitySpec{ + Spec: &namespacev1.CapacitySpec_Provisioned_{ + Provisioned: &namespacev1.CapacitySpec_Provisioned{ + Value: 75.5, + }, + }, + }, + expectedMode: "provisioned", + expectedValue: func() *float64 { v := 75.5; return &v }(), + }, + { + name: "on-demand capacity spec", + capacitySpec: &namespacev1.CapacitySpec{ + Spec: &namespacev1.CapacitySpec_OnDemand_{ + OnDemand: &namespacev1.CapacitySpec_OnDemand{}, + }, + }, + expectedMode: "on_demand", + expectNullValue: true, + }, + { + name: "nil capacity spec", + capacitySpec: nil, + expectedMode: "", + expectNullValue: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test the capacity parsing logic from updateModelFromSpec function + var capacityMode types.String + var capacityValue types.Float64 + + if tt.capacitySpec != nil { + if tt.capacitySpec.GetOnDemand() != nil { + capacityMode = types.StringValue("on_demand") + capacityValue = types.Float64Null() + } else if tt.capacitySpec.GetProvisioned() != nil { + capacityMode = types.StringValue("provisioned") + capacityValue = types.Float64Value(tt.capacitySpec.GetProvisioned().GetValue()) + } + } else { + capacityMode = types.StringNull() + capacityValue = types.Float64Null() + } + + if tt.expectedMode != "" { + assert.Equal(t, tt.expectedMode, capacityMode.ValueString()) + } else { + assert.True(t, capacityMode.IsNull()) + } + + if tt.expectNullValue { + assert.True(t, capacityValue.IsNull()) + } else { + require.NotNil(t, tt.expectedValue) + assert.Equal(t, *tt.expectedValue, capacityValue.ValueFloat64()) + } + }) + } +} From 4c4602e1f86716419d5e7c15591754c0c9ebf8f3 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Wed, 24 Sep 2025 06:33:11 -0400 Subject: [PATCH 05/31] fix --- docs/resources/namespace.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/resources/namespace.md b/docs/resources/namespace.md index 1b9bb37e..e2a0fbd4 100644 --- a/docs/resources/namespace.md +++ b/docs/resources/namespace.md @@ -155,6 +155,7 @@ resource "temporalcloud_namespace" "terraform4" { - `accepted_client_ca` (String) The Base64-encoded CA cert in PEM format that clients use when authenticating with Temporal Cloud. This is a required field when a Namespace uses mTLS authentication. - `api_key_auth` (Boolean) If true, Temporal Cloud will enable API key authentication for this namespace. +- `capacity` (Attributes) The capacity configuration for the namespace. (see [below for nested schema](#nestedatt--capacity)) - `certificate_filters` (Attributes List) A list of filters to apply to client certificates when initiating a connection Temporal Cloud. If present, connections will only be allowed from client certificates whose distinguished name properties match at least one of the filters. Empty lists are not allowed, omit the attribute instead. (see [below for nested schema](#nestedatt--certificate_filters)) - `codec_server` (Attributes) A codec server is used by the Temporal Cloud UI to decode payloads for all users interacting with this namespace, even if the workflow history itself is encrypted. (see [below for nested schema](#nestedatt--codec_server)) - `connectivity_rule_ids` (List of String) The IDs of the connectivity rules for this namespace. @@ -166,6 +167,15 @@ resource "temporalcloud_namespace" "terraform4" { - `endpoints` (Attributes) The endpoints for the namespace. (see [below for nested schema](#nestedatt--endpoints)) - `id` (String) The unique identifier of the namespace across all Temporal Cloud tenants. + +### Nested Schema for `capacity` + +Optional: + +- `mode` (String) The mode of the capacity configuration. Must be one of 'provisioned' or 'on_demand'. +- `value` (Number) The value of the capacity configuration. Must be set when mode is 'provisioned'. + + ### Nested Schema for `certificate_filters` From 457211e7967ccdb85a49fc444cfb4e92d6f6056e Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Wed, 24 Sep 2025 06:43:22 -0400 Subject: [PATCH 06/31] acceptance test --- internal/provider/namespace_resource_test.go | 292 ++++--------------- 1 file changed, 52 insertions(+), 240 deletions(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 7a9aa432..f6771d23 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -13,15 +13,10 @@ import ( "text/template" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" cloudservicev1 "go.temporal.io/cloud-sdk/api/cloudservice/v1" - namespacev1 "go.temporal.io/cloud-sdk/api/namespace/v1" "github.com/temporalio/terraform-provider-temporalcloud/internal/client" ) @@ -797,248 +792,65 @@ PEM }) } -func TestGetCapacityFromModel(t *testing.T) { - ctx := context.Background() - - tests := []struct { - name string - capacityModel capacityModel - expectedSpec *namespacev1.CapacitySpec - expectError bool - errorMessage string - }{ - { - name: "provisioned capacity with valid value", - capacityModel: capacityModel{ - Mode: types.StringValue("provisioned"), - Value: types.Float64Value(100.0), - }, - expectedSpec: &namespacev1.CapacitySpec{ - Spec: &namespacev1.CapacitySpec_Provisioned_{ - Provisioned: &namespacev1.CapacitySpec_Provisioned{ - Value: 100.0, - }, - }, - }, - expectError: false, - }, - { - name: "on_demand capacity", - capacityModel: capacityModel{ - Mode: types.StringValue("on_demand"), - Value: types.Float64Null(), - }, - expectedSpec: &namespacev1.CapacitySpec{ - Spec: &namespacev1.CapacitySpec_OnDemand_{ - OnDemand: &namespacev1.CapacitySpec_OnDemand{}, - }, - }, - expectError: false, - }, - { - name: "provisioned capacity with zero value should fail", - capacityModel: capacityModel{ - Mode: types.StringValue("provisioned"), - Value: types.Float64Value(0.0), - }, - expectedSpec: nil, - expectError: true, - errorMessage: "Invalid capacity value", - }, - { - name: "provisioned capacity with negative value should fail", - capacityModel: capacityModel{ - Mode: types.StringValue("provisioned"), - Value: types.Float64Value(-10.0), - }, - expectedSpec: nil, - expectError: true, - errorMessage: "Invalid capacity value", - }, - { - name: "provisioned capacity with null value should fail", - capacityModel: capacityModel{ - Mode: types.StringValue("provisioned"), - Value: types.Float64Null(), - }, - expectedSpec: nil, - expectError: true, - errorMessage: "Invalid capacity value", - }, - { - name: "invalid capacity mode should fail", - capacityModel: capacityModel{ - Mode: types.StringValue("invalid_mode"), - Value: types.Float64Value(50.0), - }, - expectedSpec: nil, - expectError: true, - errorMessage: "Invalid capacity mode", - }, - { - name: "empty capacity mode should fail", - capacityModel: capacityModel{ - Mode: types.StringValue(""), - Value: types.Float64Value(50.0), - }, - expectedSpec: nil, - expectError: true, - errorMessage: "Invalid capacity mode", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a capacity object from the model - capacity, diags := types.ObjectValueFrom(ctx, capacityAttrs, &tt.capacityModel) - require.False(t, diags.HasError(), "Failed to create capacity object: %v", diags.Errors()) - - // Create a namespace model with the capacity - model := &namespaceResourceModel{ - Capacity: capacity, - } +func TestAccNamespaceWithCapacity(t *testing.T) { + name := fmt.Sprintf("%s-%s", "tf-capacity", randomString(10)) + config := func(name string, retention int, deleteProtection bool, mode string, value int) string { + return fmt.Sprintf(` +provider "temporalcloud" { - // Call the function under test - spec, diags := getCapacityFromModel(ctx, model) - - if tt.expectError { - assert.True(t, diags.HasError(), "Expected error but got none") - if tt.errorMessage != "" { - found := false - for _, diag := range diags.Errors() { - if strings.Contains(diag.Summary(), tt.errorMessage) { - found = true - break - } - } - assert.True(t, found, "Expected error message '%s' not found in diagnostics: %v", tt.errorMessage, diags.Errors()) - } - assert.Nil(t, spec, "Expected nil spec on error") - } else { - assert.False(t, diags.HasError(), "Unexpected error: %v", diags.Errors()) - assert.NotNil(t, spec, "Expected non-nil spec") - - // Compare the specs - if tt.expectedSpec.GetProvisioned() != nil { - require.NotNil(t, spec.GetProvisioned(), "Expected provisioned capacity spec") - assert.Equal(t, tt.expectedSpec.GetProvisioned().GetValue(), spec.GetProvisioned().GetValue()) - } - - if tt.expectedSpec.GetOnDemand() != nil { - require.NotNil(t, spec.GetOnDemand(), "Expected on-demand capacity spec") - } - } - }) - } } -func TestCapacityModelValidation(t *testing.T) { - ctx := context.Background() - - t.Run("capacity model can be created from object value", func(t *testing.T) { - testCapacity := capacityModel{ - Mode: types.StringValue("provisioned"), - Value: types.Float64Value(50.0), - } - - capacity, diags := types.ObjectValueFrom(ctx, capacityAttrs, &testCapacity) - require.False(t, diags.HasError(), "Failed to create capacity object: %v", diags.Errors()) - - var retrievedCapacity capacityModel - diags = capacity.As(ctx, &retrievedCapacity, basetypes.ObjectAsOptions{}) - require.False(t, diags.HasError(), "Failed to retrieve capacity model: %v", diags.Errors()) - - assert.Equal(t, "provisioned", retrievedCapacity.Mode.ValueString()) - assert.Equal(t, 50.0, retrievedCapacity.Value.ValueFloat64()) - }) - - t.Run("capacity model can handle null values", func(t *testing.T) { - testCapacity := capacityModel{ - Mode: types.StringValue("on_demand"), - Value: types.Float64Null(), - } - - capacity, diags := types.ObjectValueFrom(ctx, capacityAttrs, &testCapacity) - require.False(t, diags.HasError(), "Failed to create capacity object: %v", diags.Errors()) - - var retrievedCapacity capacityModel - diags = capacity.As(ctx, &retrievedCapacity, basetypes.ObjectAsOptions{}) - require.False(t, diags.HasError(), "Failed to retrieve capacity model: %v", diags.Errors()) +resource "temporalcloud_namespace" "terraform" { + name = "%s" + regions = ["aws-us-east-1"] + accepted_client_ca = base64encode(< Date: Wed, 24 Sep 2025 09:13:31 -0400 Subject: [PATCH 07/31] true --- internal/provider/namespace_resource.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index 5c6da54b..53c3ae39 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -401,6 +401,16 @@ func (r *namespaceResource) Create(ctx context.Context, req resource.CreateReque ConnectivityRuleIds: connectivityRuleIds, } + if !plan.Capacity.IsNull() { + var d diag.Diagnostics + capacitySpec, d := getCapacityFromModel(ctx, &plan) + resp.Diagnostics.Append(d...) + if resp.Diagnostics.HasError() { + return + } + spec.CapacitySpec = capacitySpec + } + if !plan.ApiKeyAuth.ValueBool() && plan.AcceptedClientCA.IsNull() { resp.Diagnostics.AddError("Namespace not configured with authentication", "accepted_client_ca or api_key_auth must be set") return @@ -872,6 +882,7 @@ func updateModelFromSpec( if capacitySpec != nil { if capacitySpec.GetOnDemand() != nil { capacityMode = types.StringValue("on_demand") + capacityValue = types.Float64Value(0) } else if capacitySpec.GetProvisioned() != nil { capacityMode = types.StringValue("provisioned") capacityValue = types.Float64Value(capacitySpec.GetProvisioned().GetValue()) @@ -885,6 +896,8 @@ func updateModelFromSpec( return diags } state.Capacity = capacity + } else { + state.Capacity = types.ObjectNull(capacityAttrs) } state.ConnectivityRuleIds = connectivityRuleIdsState From 87d52ad60884e7883dcf03eabd07c5f9bb8a7793 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Wed, 24 Sep 2025 09:24:50 -0400 Subject: [PATCH 08/31] testfix --- internal/provider/namespace_resource.go | 1 - internal/provider/namespace_resource_test.go | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index 53c3ae39..d42594c7 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -882,7 +882,6 @@ func updateModelFromSpec( if capacitySpec != nil { if capacitySpec.GetOnDemand() != nil { capacityMode = types.StringValue("on_demand") - capacityValue = types.Float64Value(0) } else if capacitySpec.GetProvisioned() != nil { capacityMode = types.StringValue("provisioned") capacityValue = types.Float64Value(capacitySpec.GetProvisioned().GetValue()) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index f6771d23..33f9e4b4 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -794,13 +794,13 @@ PEM func TestAccNamespaceWithCapacity(t *testing.T) { name := fmt.Sprintf("%s-%s", "tf-capacity", randomString(10)) - config := func(name string, retention int, deleteProtection bool, mode string, value int) string { + config := func(name string, retention int, deleteProtection bool, mode string, value string) string { return fmt.Sprintf(` provider "temporalcloud" { } -resource "temporalcloud_namespace" "terraform" { +resource "temporalcloud_namespace" "capacitytest" { name = "%s" regions = ["aws-us-east-1"] accepted_client_ca = base64encode(< Date: Thu, 25 Sep 2025 09:55:33 -0400 Subject: [PATCH 09/31] Acceptance tests --- internal/client/client.go | 1 + internal/provider/namespace_resource.go | 17 ++++--- internal/provider/namespace_resource_test.go | 51 ++++++++++++++------ 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/internal/client/client.go b/internal/client/client.go index ee5a2d19..f31eebb5 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -133,6 +133,7 @@ func AwaitNamespaceCapacityOperation(ctx context.Context, cloudclient *Client, n return fmt.Errorf("failed to get namespace: %w", err) } ns = getResp.GetNamespace() + n.Capacity = ns.GetCapacity() if ns.GetCapacity().GetLatestRequest() == nil { return nil } diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index d42594c7..381b5f71 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -402,13 +402,16 @@ func (r *namespaceResource) Create(ctx context.Context, req resource.CreateReque } if !plan.Capacity.IsNull() { - var d diag.Diagnostics - capacitySpec, d := getCapacityFromModel(ctx, &plan) - resp.Diagnostics.Append(d...) - if resp.Diagnostics.HasError() { - return - } - spec.CapacitySpec = capacitySpec + resp.Diagnostics.AddError("Capacity on namespace creation is not supported", "capacity should be null or not set when creating a namespace") + return + // This will be enabled when capacity on namespace creation is supported + // var d diag.Diagnostics + // capacitySpec, d := getCapacityFromModel(ctx, &plan) + // resp.Diagnostics.Append(d...) + // if resp.Diagnostics.HasError() { + // return + // } + // spec.CapacitySpec = capacitySpec } if !plan.ApiKeyAuth.ValueBool() && plan.AcceptedClientCA.IsNull() { diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 33f9e4b4..4730b89f 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -794,8 +794,30 @@ PEM func TestAccNamespaceWithCapacity(t *testing.T) { name := fmt.Sprintf("%s-%s", "tf-capacity", randomString(10)) - config := func(name string, retention int, deleteProtection bool, mode string, value string) string { + config := func(name string, retention int, deleteProtection bool, variable string) string { return fmt.Sprintf(` +variable "provisioned" { + type = object({ + mode = string + value = number + }) + default = { + mode = "provisioned" + value = 2 + } +} + +variable "on_demand" { + type = object({ + mode = string + value = number + }) + default = { + mode = "on_demand" + value = 0 + } +} + provider "temporalcloud" { } @@ -823,11 +845,8 @@ PEM namespace_lifecycle = { enable_delete_protection = %t } - capacity = { - mode = "%s" - %s - } -}`, name, retention, deleteProtection, mode, value) + capacity = %s +}`, name, retention, deleteProtection, variable) } resource.ParallelTest(t, resource.TestCase{ @@ -835,20 +854,24 @@ PEM ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - // New namespace with retention of 7 - Config: config(name, 14, true, "provisioned", `value = 16`), - }, - { - Config: config(name, 14, true, "on_demand", ""), + // New namespace with on demand capacity + Config: config(name, 14, false, "null"), }, + // cannot do provisioned capacity because the test environment doesn't have enough capacity + // { + // Config: config(name, 14, false, "var.on_demand"), // disable delete protection for deletion to succeed + // }, + // { + // Config: config(name, 14, true, "var.provisioned"), + // }, { ImportState: true, ImportStateVerify: true, ResourceName: "temporalcloud_namespace.terraform", }, - { - Config: config(name, 14, false, "on_demand", ""), // disable delete protection for deletion to succeed - }, + // { + // Config: config(name, 14, false, "var.on_demand"), // disable delete protection for deletion to succeed + // }, // Delete testing automatically occurs in TestCase }, }) From 0e6687b13ce303375ca4848214f605b3ac782393 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Thu, 25 Sep 2025 10:01:13 -0400 Subject: [PATCH 10/31] fix --- internal/client/client.go | 33 +++++++++++++++++++------ internal/provider/namespace_resource.go | 2 +- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/internal/client/client.go b/internal/client/client.go index f31eebb5..bd068e25 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -28,6 +28,7 @@ import ( "fmt" "time" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-log/tflog" cloudservicev1 "go.temporal.io/cloud-sdk/api/cloudservice/v1" @@ -41,6 +42,8 @@ type Client struct { *cloudclient.Client } +type AwaitPredicate func(ctx context.Context, cloudclient *Client, n *namespace.Namespace) (bool, error) + func NewConnectionWithAPIKey(addrStr string, allowInsecure bool, apiKey string, version string) (*Client, error) { userAgentProject := "terraform-provider-temporalcloud" if version != "" { @@ -114,9 +117,25 @@ func AwaitAsyncOperation(ctx context.Context, cloudclient *Client, op *operation } } -func AwaitNamespaceCapacityOperation(ctx context.Context, cloudclient *Client, n *namespace.Namespace) error { +func AwaitForFulfillment(ctx context.Context, cloudclient *Client, n *namespace.Namespace, predicate ...AwaitPredicate) (bool, error) { + if n == nil { + return false, fmt.Errorf("namespace is required") + } + var bValue bool = true + var errs error + for _, p := range predicate { + b, err := p(ctx, cloudclient, n) + if err != nil { + multierror.Append(errs, err) + } + bValue = bValue && b + } + return bValue, errs +} + +func AwaitNamespaceCapacityOperation(ctx context.Context, cloudclient *Client, n *namespace.Namespace) (bool, error) { if n == nil { - return fmt.Errorf("namespace is required") + return false, fmt.Errorf("namespace is required") } ns := n @@ -130,12 +149,12 @@ func AwaitNamespaceCapacityOperation(ctx context.Context, cloudclient *Client, n Namespace: ns.GetNamespace(), }) if err != nil { - return fmt.Errorf("failed to get namespace: %w", err) + return false, fmt.Errorf("failed to get namespace: %w", err) } ns = getResp.GetNamespace() n.Capacity = ns.GetCapacity() if ns.GetCapacity().GetLatestRequest() == nil { - return nil + return false, nil } state := ns.GetCapacity().GetLatestRequest().GetState() switch state { @@ -148,10 +167,10 @@ func AwaitNamespaceCapacityOperation(ctx context.Context, cloudclient *Client, n continue case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_FAILED: tflog.Debug(ctx, "request failed") - return errors.New("capacity request failed") + return false, errors.New("capacity request failed") case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_COMPLETED: tflog.Debug(ctx, "request completed") - return nil + return true, nil default: tflog.Warn(ctx, "unknown state, continuing", map[string]any{ "state": state, @@ -159,7 +178,7 @@ func AwaitNamespaceCapacityOperation(ctx context.Context, cloudclient *Client, n continue } case <-ctx.Done(): - return ctx.Err() + return false, ctx.Err() } } } diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index 381b5f71..9efadee8 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -648,7 +648,7 @@ func (r *namespaceResource) Update(ctx context.Context, req resource.UpdateReque return } - if err := client.AwaitNamespaceCapacityOperation(ctx, r.client, ns.Namespace); err != nil { + if _, err := client.AwaitForFulfillment(ctx, r.client, ns.Namespace, client.AwaitNamespaceCapacityOperation); err != nil { resp.Diagnostics.AddError("Failed to update namespace capacity", err.Error()) return } From dda636aa5a92892c3f94af5f16ae945965d58e49 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Thu, 25 Sep 2025 10:54:27 -0400 Subject: [PATCH 11/31] errs --- internal/client/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/client/client.go b/internal/client/client.go index bd068e25..df9029a2 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -126,7 +126,7 @@ func AwaitForFulfillment(ctx context.Context, cloudclient *Client, n *namespace. for _, p := range predicate { b, err := p(ctx, cloudclient, n) if err != nil { - multierror.Append(errs, err) + errs = multierror.Append(errs, err) } bValue = bValue && b } From 86aa570c45a81a2032750b4c749f5e9aa5b80605 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Thu, 25 Sep 2025 11:22:07 -0400 Subject: [PATCH 12/31] fix --- internal/provider/namespace_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 4730b89f..d1cba62f 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -867,7 +867,7 @@ PEM { ImportState: true, ImportStateVerify: true, - ResourceName: "temporalcloud_namespace.terraform", + ResourceName: "temporalcloud_namespace.capacitytest", }, // { // Config: config(name, 14, false, "var.on_demand"), // disable delete protection for deletion to succeed From bd2a7daee5ae66359ce70b3ab1a0b39630a536a6 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Thu, 25 Sep 2025 12:57:29 -0400 Subject: [PATCH 13/31] fix --- .vscode/launch.json | 29 ++++++++++++++++++++ internal/provider/namespace_resource_test.go | 15 ++++------ 2 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..e4224657 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + }, + { + "name": "Test Namespace Capacity", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}", + "args": ["-test.run", "TestAccNamespaceWithCapacity"], + "env": { + "TF_ACC": "1", + "TEMPORAL_CLOUD_ENDPOINT": "saas-api.tmprl.cloud:443", + "TEMPORAL_CLOUD_API_KEY": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBwYWtIQSJ9.eyJhY2NvdW50X2lkIjoidGVtcG9yYWwtZGV2IiwiYXVkIjpbInRlbXBvcmFsLXRlc3QuaW8iXSwiZXhwIjoxNzU4ODg5MTMxLCJpc3MiOiJ0ZW1wb3JhbC10ZXN0LmlvIiwianRpIjoiUzRFUGpQdmVDY0c4ZnRGTXloY2x2WW1RU1JGU041SGoiLCJrZXlfaWQiOiJTNEVQalB2ZUNjRzhmdEZNeWhjbHZZbVFTUkZTTjVIaiIsInN1YiI6ImNhMmY3MGJmZjRmMDQ0YzY5YTExMzAxZDFkMmY4MjdhIn0.aruHWBDUklJEwSyr67l-nAqBZbd7JX3oK18tKJIPEN7EzCcl1Wpxj1tVms5PaQPuim5ndkZl0yfvWkTfYY9rTA" + } + } + ] +} \ No newline at end of file diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index d1cba62f..9a8daec8 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -794,7 +794,7 @@ PEM func TestAccNamespaceWithCapacity(t *testing.T) { name := fmt.Sprintf("%s-%s", "tf-capacity", randomString(10)) - config := func(name string, retention int, deleteProtection bool, variable string) string { + config := func(name string, variable string) string { return fmt.Sprintf(` variable "provisioned" { type = object({ @@ -840,11 +840,6 @@ ThGIAJ5f8VReP9T7155ri5sRCUTBdgFHVAIxAOrtnTo8uRjEs8HdUW0e9H7E2nyW -----END CERTIFICATE----- PEM ) - - retention_days = %d - namespace_lifecycle = { - enable_delete_protection = %t - } capacity = %s }`, name, retention, deleteProtection, variable) } @@ -855,14 +850,14 @@ PEM Steps: []resource.TestStep{ { // New namespace with on demand capacity - Config: config(name, 14, false, "null"), + Config: config(name, "null"), }, // cannot do provisioned capacity because the test environment doesn't have enough capacity // { - // Config: config(name, 14, false, "var.on_demand"), // disable delete protection for deletion to succeed + // Config: config(name, "var.on_demand"), // }, // { - // Config: config(name, 14, true, "var.provisioned"), + // Config: config(name, "var.provisioned"), // }, { ImportState: true, @@ -870,7 +865,7 @@ PEM ResourceName: "temporalcloud_namespace.capacitytest", }, // { - // Config: config(name, 14, false, "var.on_demand"), // disable delete protection for deletion to succeed + // Config: config(name, "var.on_demand"), // }, // Delete testing automatically occurs in TestCase }, From a7a5eeb44be9065bbfff06ff3b1f1f7f0f507b0f Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Thu, 25 Sep 2025 12:57:40 -0400 Subject: [PATCH 14/31] parma --- internal/provider/namespace_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 9a8daec8..7bf45784 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -841,7 +841,7 @@ ThGIAJ5f8VReP9T7155ri5sRCUTBdgFHVAIxAOrtnTo8uRjEs8HdUW0e9H7E2nyW PEM ) capacity = %s -}`, name, retention, deleteProtection, variable) +}`, name, variable) } resource.ParallelTest(t, resource.TestCase{ From 7830bcf01e2476c1735f9bf79aabb1daf794a1bf Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Thu, 25 Sep 2025 16:59:30 -0400 Subject: [PATCH 15/31] retention days --- internal/provider/namespace_resource_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 7bf45784..0cbf0575 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -840,6 +840,7 @@ ThGIAJ5f8VReP9T7155ri5sRCUTBdgFHVAIxAOrtnTo8uRjEs8HdUW0e9H7E2nyW -----END CERTIFICATE----- PEM ) + retention_days = 7 capacity = %s }`, name, variable) } From 572361fce06ac126de78d6b7a5e4ba270fad613e Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Thu, 25 Sep 2025 17:02:12 -0400 Subject: [PATCH 16/31] fix --- internal/provider/namespace_resource_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 0cbf0575..33255902 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -872,4 +872,18 @@ PEM }, }) + nameProvisioned := fmt.Sprintf("%s-%s", "tf-capacity-provisioned", randomString(10)) + + // provisioned capacity is not supported on namespace creation + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: config(nameProvisioned, "var.provisioned"), + ExpectError: regexp.MustCompile("Capacity on namespace creation is not supported"), + }, + }, + }) + } From fc9f7b48cdfe6b14b9133857532635afd652a413 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Thu, 25 Sep 2025 19:23:32 -0400 Subject: [PATCH 17/31] test change --- .vscode/launch.json | 29 -------- internal/provider/namespace_resource_test.go | 69 ++++++++++++++++---- 2 files changed, 56 insertions(+), 42 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index e4224657..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - - { - "name": "Launch Package", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}" - }, - { - "name": "Test Namespace Capacity", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}", - "args": ["-test.run", "TestAccNamespaceWithCapacity"], - "env": { - "TF_ACC": "1", - "TEMPORAL_CLOUD_ENDPOINT": "saas-api.tmprl.cloud:443", - "TEMPORAL_CLOUD_API_KEY": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBwYWtIQSJ9.eyJhY2NvdW50X2lkIjoidGVtcG9yYWwtZGV2IiwiYXVkIjpbInRlbXBvcmFsLXRlc3QuaW8iXSwiZXhwIjoxNzU4ODg5MTMxLCJpc3MiOiJ0ZW1wb3JhbC10ZXN0LmlvIiwianRpIjoiUzRFUGpQdmVDY0c4ZnRGTXloY2x2WW1RU1JGU041SGoiLCJrZXlfaWQiOiJTNEVQalB2ZUNjRzhmdEZNeWhjbHZZbVFTUkZTTjVIaiIsInN1YiI6ImNhMmY3MGJmZjRmMDQ0YzY5YTExMzAxZDFkMmY4MjdhIn0.aruHWBDUklJEwSyr67l-nAqBZbd7JX3oK18tKJIPEN7EzCcl1Wpxj1tVms5PaQPuim5ndkZl0yfvWkTfYY9rTA" - } - } - ] -} \ No newline at end of file diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 33255902..43516ed6 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -853,37 +853,80 @@ PEM // New namespace with on demand capacity Config: config(name, "null"), }, - // cannot do provisioned capacity because the test environment doesn't have enough capacity - // { - // Config: config(name, "var.on_demand"), - // }, - // { - // Config: config(name, "var.provisioned"), - // }, + { + Config: config(name, "var.provisioned"), + }, { ImportState: true, ImportStateVerify: true, ResourceName: "temporalcloud_namespace.capacitytest", }, - // { - // Config: config(name, "var.on_demand"), - // }, // Delete testing automatically occurs in TestCase }, }) +} - nameProvisioned := fmt.Sprintf("%s-%s", "tf-capacity-provisioned", randomString(10)) +func TestAccCreateNamespaceWithCapacity(t *testing.T) { + name := fmt.Sprintf("%s-%s", "tf-capacity-create", randomString(10)) + config := func(name string, variable string) string { + return fmt.Sprintf(` +variable "provisioned" { + type = object({ + mode = string + value = number + }) + default = { + mode = "provisioned" + value = 2 + } +} +variable "on_demand" { + type = object({ + mode = string + value = number + }) + default = { + mode = "on_demand" + value = 0 + } +} + +provider "temporalcloud" { + +} + +resource "temporalcloud_namespace" "capacitytest" { + name = "%s" + regions = ["aws-us-east-1"] + accepted_client_ca = base64encode(< Date: Thu, 25 Sep 2025 20:21:10 -0400 Subject: [PATCH 18/31] value of 1 --- internal/provider/namespace_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 43516ed6..4a912bcc 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -877,7 +877,7 @@ variable "provisioned" { }) default = { mode = "provisioned" - value = 2 + value = 1 } } From cc019a5a1f9d866702f57e7df1196236e89432f2 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Thu, 25 Sep 2025 20:38:44 -0400 Subject: [PATCH 19/31] value of one --- internal/provider/namespace_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 4a912bcc..a9223f92 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -803,7 +803,7 @@ variable "provisioned" { }) default = { mode = "provisioned" - value = 2 + value = 1 } } From f6b77a52b613a7f111da7606d5cf3f3d006752a5 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Fri, 26 Sep 2025 16:55:10 -0400 Subject: [PATCH 20/31] bump to 4 --- internal/provider/namespace_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index a9223f92..febb4597 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -803,7 +803,7 @@ variable "provisioned" { }) default = { mode = "provisioned" - value = 1 + value = 4 } } From 9b729cd7e1c19b8960e50e5bafa992327636da2f Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Mon, 6 Oct 2025 12:35:40 -0400 Subject: [PATCH 21/31] remove --- internal/provider/namespace_resource.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index 9efadee8..5957bd6e 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -648,11 +648,6 @@ func (r *namespaceResource) Update(ctx context.Context, req resource.UpdateReque return } - if _, err := client.AwaitForFulfillment(ctx, r.client, ns.Namespace, client.AwaitNamespaceCapacityOperation); err != nil { - resp.Diagnostics.AddError("Failed to update namespace capacity", err.Error()) - return - } - resp.Diagnostics.Append(updateModelFromSpec(ctx, &plan, ns.Namespace)...) if resp.Diagnostics.HasError() { return From 85959cde9bd03306758cb033b2f8d1cd360bd5d9 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Mon, 6 Oct 2025 17:41:23 -0400 Subject: [PATCH 22/31] change --- internal/client/client.go | 70 ------------------------- internal/provider/namespace_resource.go | 7 +-- 2 files changed, 4 insertions(+), 73 deletions(-) diff --git a/internal/client/client.go b/internal/client/client.go index df9029a2..b17090e3 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -28,11 +28,9 @@ import ( "fmt" "time" - "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-log/tflog" cloudservicev1 "go.temporal.io/cloud-sdk/api/cloudservice/v1" - "go.temporal.io/cloud-sdk/api/namespace/v1" operationv1 "go.temporal.io/cloud-sdk/api/operation/v1" "go.temporal.io/cloud-sdk/cloudclient" ) @@ -42,8 +40,6 @@ type Client struct { *cloudclient.Client } -type AwaitPredicate func(ctx context.Context, cloudclient *Client, n *namespace.Namespace) (bool, error) - func NewConnectionWithAPIKey(addrStr string, allowInsecure bool, apiKey string, version string) (*Client, error) { userAgentProject := "terraform-provider-temporalcloud" if version != "" { @@ -116,69 +112,3 @@ func AwaitAsyncOperation(ctx context.Context, cloudclient *Client, op *operation } } } - -func AwaitForFulfillment(ctx context.Context, cloudclient *Client, n *namespace.Namespace, predicate ...AwaitPredicate) (bool, error) { - if n == nil { - return false, fmt.Errorf("namespace is required") - } - var bValue bool = true - var errs error - for _, p := range predicate { - b, err := p(ctx, cloudclient, n) - if err != nil { - errs = multierror.Append(errs, err) - } - bValue = bValue && b - } - return bValue, errs -} - -func AwaitNamespaceCapacityOperation(ctx context.Context, cloudclient *Client, n *namespace.Namespace) (bool, error) { - if n == nil { - return false, fmt.Errorf("namespace is required") - } - ns := n - - ctx = tflog.SetField(ctx, "namespace_id", ns.GetNamespace()) - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - for { - select { - case <-ticker.C: - getResp, err := cloudclient.CloudService().GetNamespace(ctx, &cloudservicev1.GetNamespaceRequest{ - Namespace: ns.GetNamespace(), - }) - if err != nil { - return false, fmt.Errorf("failed to get namespace: %w", err) - } - ns = getResp.GetNamespace() - n.Capacity = ns.GetCapacity() - if ns.GetCapacity().GetLatestRequest() == nil { - return false, nil - } - state := ns.GetCapacity().GetLatestRequest().GetState() - switch state { - case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_UNSPECIFIED: - fallthrough - case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_IN_PROGRESS: - tflog.Debug(ctx, "retrying in 1 second", map[string]any{ - "state": state, - }) - continue - case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_FAILED: - tflog.Debug(ctx, "request failed") - return false, errors.New("capacity request failed") - case namespace.Capacity_Request_STATE_CAPACITY_REQUEST_COMPLETED: - tflog.Debug(ctx, "request completed") - return true, nil - default: - tflog.Warn(ctx, "unknown state, continuing", map[string]any{ - "state": state, - }) - continue - } - case <-ctx.Done(): - return false, ctx.Err() - } - } -} diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index 5957bd6e..a8aefc63 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -83,7 +83,7 @@ type ( NamespaceLifecycle internaltypes.ZeroObjectValue `tfsdk:"namespace_lifecycle"` ConnectivityRuleIds internaltypes.UnorderedStringListValue `tfsdk:"connectivity_rule_ids"` Timeouts timeouts.Value `tfsdk:"timeouts"` - Capacity types.Object `tfsdk:"capacity"` + Capacity internaltypes.ZeroObjectValue `tfsdk:"capacity"` } lifecycleModel struct { @@ -884,17 +884,18 @@ func updateModelFromSpec( capacityMode = types.StringValue("provisioned") capacityValue = types.Float64Value(capacitySpec.GetProvisioned().GetValue()) } - capacity, objectDiags := types.ObjectValueFrom(ctx, capacityAttrs, &capacityModel{ + cp, objectDiags := types.ObjectValueFrom(ctx, capacityAttrs, &capacityModel{ Mode: capacityMode, Value: capacityValue, }) + capacity := internaltypes.ZeroObjectValue{ObjectValue: cp} diags.Append(objectDiags...) if diags.HasError() { return diags } state.Capacity = capacity } else { - state.Capacity = types.ObjectNull(capacityAttrs) + state.Capacity = internaltypes.ZeroObjectValue{ObjectValue: types.ObjectNull(capacityAttrs)} } state.ConnectivityRuleIds = connectivityRuleIdsState From caa7fcf96574242b8e943f6498b4f3a9526d2770 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Mon, 6 Oct 2025 17:43:22 -0400 Subject: [PATCH 23/31] change --- internal/provider/namespace_resource_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index febb4597..4d5b47db 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -11,6 +11,7 @@ import ( "strings" "testing" "text/template" + "time" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -856,6 +857,12 @@ PEM { Config: config(name, "var.provisioned"), }, + { + PreConfig: func() { + time.Sleep(time.Minute * 1) // wait for previous update to finish + }, + Config: config(name, "var.on_demand"), + }, { ImportState: true, ImportStateVerify: true, From 4aa2f2ab0fa052344bc700feb27e6c3ae26e4b7b Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 7 Oct 2025 15:37:43 -0400 Subject: [PATCH 24/31] acctest --- internal/provider/namespace_resource.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index a8aefc63..b5f511c5 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -318,6 +318,11 @@ func (r *namespaceResource) Schema(ctx context.Context, _ resource.SchemaRequest "capacity": schema.SingleNestedAttribute{ Optional: true, Description: "The capacity configuration for the namespace.", + CustomType: internaltypes.ZeroObjectType{ + ObjectType: basetypes.ObjectType{ + AttrTypes: capacityAttrs, + }, + }, Attributes: map[string]schema.Attribute{ "mode": schema.StringAttribute{ Description: "The mode of the capacity configuration. Must be one of 'provisioned' or 'on_demand'.", From 4c4ec9ca4b35febbbc026f887b8503254666876a Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 7 Oct 2025 16:17:19 -0400 Subject: [PATCH 25/31] test --- internal/provider/namespace_resource_test.go | 77 ++++++++++++++++++-- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 4d5b47db..fd79d8b2 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -11,7 +11,6 @@ import ( "strings" "testing" "text/template" - "time" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -793,7 +792,7 @@ PEM }) } -func TestAccNamespaceWithCapacity(t *testing.T) { +func TestAccNamespaceWithProvisionedCapacity(t *testing.T) { name := fmt.Sprintf("%s-%s", "tf-capacity", randomString(10)) config := func(name string, variable string) string { return fmt.Sprintf(` @@ -858,9 +857,77 @@ PEM Config: config(name, "var.provisioned"), }, { - PreConfig: func() { - time.Sleep(time.Minute * 1) // wait for previous update to finish - }, + ImportState: true, + ImportStateVerify: true, + ResourceName: "temporalcloud_namespace.capacitytest", + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccNamespaceWithOnDemandCapacity(t *testing.T) { + name := fmt.Sprintf("%s-%s", "tf-capacity", randomString(10)) + config := func(name string, variable string) string { + return fmt.Sprintf(` +variable "provisioned" { + type = object({ + mode = string + value = number + }) + default = { + mode = "provisioned" + value = 4 + } +} + +variable "on_demand" { + type = object({ + mode = string + value = number + }) + default = { + mode = "on_demand" + value = 0 + } +} + +provider "temporalcloud" { + +} + +resource "temporalcloud_namespace" "capacitytest" { + name = "%s" + regions = ["aws-us-east-1"] + accepted_client_ca = base64encode(< Date: Tue, 7 Oct 2025 16:39:02 -0400 Subject: [PATCH 26/31] fix --- internal/provider/namespace_resource_test.go | 200 ++++++------------- 1 file changed, 63 insertions(+), 137 deletions(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index fd79d8b2..713c3eeb 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -11,10 +11,12 @@ import ( "strings" "testing" "text/template" + "time" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" + "go.temporal.io/cloud-sdk/api/namespace/v1" cloudservicev1 "go.temporal.io/cloud-sdk/api/cloudservice/v1" @@ -792,7 +794,7 @@ PEM }) } -func TestAccNamespaceWithProvisionedCapacity(t *testing.T) { +func TestAccNamespaceWithCapacity(t *testing.T) { name := fmt.Sprintf("%s-%s", "tf-capacity", randomString(10)) config := func(name string, variable string) string { return fmt.Sprintf(` @@ -855,80 +857,69 @@ PEM }, { Config: config(name, "var.provisioned"), - }, - { - ImportState: true, - ImportStateVerify: true, - ResourceName: "temporalcloud_namespace.capacitytest", - }, - // Delete testing automatically occurs in TestCase - }, - }) -} - -func TestAccNamespaceWithOnDemandCapacity(t *testing.T) { - name := fmt.Sprintf("%s-%s", "tf-capacity", randomString(10)) - config := func(name string, variable string) string { - return fmt.Sprintf(` -variable "provisioned" { - type = object({ - mode = string - value = number - }) - default = { - mode = "provisioned" - value = 4 - } -} - -variable "on_demand" { - type = object({ - mode = string - value = number - }) - default = { - mode = "on_demand" - value = 0 - } -} - -provider "temporalcloud" { - -} - -resource "temporalcloud_namespace" "capacitytest" { - name = "%s" - regions = ["aws-us-east-1"] - accepted_client_ca = base64encode(< Date: Tue, 7 Oct 2025 16:46:09 -0400 Subject: [PATCH 27/31] float --- internal/provider/namespace_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 713c3eeb..d7475776 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -880,7 +880,7 @@ PEM } else { value := ns.GetNamespace().GetCapacity().GetProvisioned().GetCurrentValue() if value != 4.0 { - return fmt.Errorf("expected provisioned capacity of 4, got %d", value) + return fmt.Errorf("expected provisioned capacity of 4, got %f", value) } // success return nil From 2b1174a1aafd96525496c9c8a7471d3afd9e0174 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 7 Oct 2025 19:51:40 -0400 Subject: [PATCH 28/31] retry --- internal/provider/namespace_resource.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index b5f511c5..b4b7b7da 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -880,11 +880,19 @@ func updateModelFromSpec( } capacitySpec := ns.GetSpec().GetCapacitySpec() - var capacityMode types.String - var capacityValue types.Float64 if capacitySpec != nil { + var capacityMode types.String + var capacityValue types.Float64 if capacitySpec.GetOnDemand() != nil { capacityMode = types.StringValue("on_demand") + // For on_demand mode, preserve the value from the current state if it exists + if !state.Capacity.IsNull() { + var currentCapacity capacityModel + diags.Append(state.Capacity.As(ctx, ¤tCapacity, basetypes.ObjectAsOptions{})...) + if !diags.HasError() { + capacityValue = currentCapacity.Value + } + } } else if capacitySpec.GetProvisioned() != nil { capacityMode = types.StringValue("provisioned") capacityValue = types.Float64Value(capacitySpec.GetProvisioned().GetValue()) From fde2762386db221d4cef24baf3a2efbe2c69d248 Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Tue, 7 Oct 2025 21:12:33 -0400 Subject: [PATCH 29/31] testfix --- internal/provider/namespace_resource.go | 5 +++-- internal/provider/namespace_resource_test.go | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/provider/namespace_resource.go b/internal/provider/namespace_resource.go index b4b7b7da..c9ca301d 100644 --- a/internal/provider/namespace_resource.go +++ b/internal/provider/namespace_resource.go @@ -885,11 +885,12 @@ func updateModelFromSpec( var capacityValue types.Float64 if capacitySpec.GetOnDemand() != nil { capacityMode = types.StringValue("on_demand") - // For on_demand mode, preserve the value from the current state if it exists + // For on_demand mode, set value to 0 if it's in the current state, otherwise leave it null if !state.Capacity.IsNull() { var currentCapacity capacityModel diags.Append(state.Capacity.As(ctx, ¤tCapacity, basetypes.ObjectAsOptions{})...) - if !diags.HasError() { + if !diags.HasError() && !currentCapacity.Value.IsNull() { + // Preserve the value from state if it exists capacityValue = currentCapacity.Value } } diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index d7475776..4c23b1e5 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -922,9 +922,10 @@ PEM }, }, { - ImportState: true, - ImportStateVerify: true, - ResourceName: "temporalcloud_namespace.capacitytest", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"capacity.value"}, + ResourceName: "temporalcloud_namespace.capacitytest", }, // Delete testing automatically occurs in TestCase }, From 8563099ac9eecab84671d9c469401f39a494af24 Mon Sep 17 00:00:00 2001 From: pauloh-temporal Date: Wed, 8 Oct 2025 12:49:39 -0400 Subject: [PATCH 30/31] Update internal/provider/namespace_resource_test.go Co-authored-by: Abhinav Nekkanti <10552725+anekkanti@users.noreply.github.com> --- internal/provider/namespace_resource_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index 4c23b1e5..ff3b1822 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -921,6 +921,10 @@ PEM return fmt.Errorf("timed out waiting for capacity change") }, }, + { + // Revert namespace back to defaults + Config: config(name, "null"), + }, { ImportState: true, ImportStateVerify: true, From 0e96856b15f97e58e1042acb4484fb65049e4dac Mon Sep 17 00:00:00 2001 From: Paul W Oh Date: Wed, 8 Oct 2025 12:53:13 -0400 Subject: [PATCH 31/31] gofmt --- internal/provider/namespace_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/namespace_resource_test.go b/internal/provider/namespace_resource_test.go index ff3b1822..2465a522 100644 --- a/internal/provider/namespace_resource_test.go +++ b/internal/provider/namespace_resource_test.go @@ -922,7 +922,7 @@ PEM }, }, { - // Revert namespace back to defaults + // Revert namespace back to defaults Config: config(name, "null"), }, {