Skip to content

Commit abd6198

Browse files
Adding Namespace Capacity Terraform Resource (#369)
* Adding Namespace Capacity Terraform Resource * fix * optional * go mod tidy + tests * fix * acceptance test * true * testfix * Acceptance tests * fix * errs * fix * fix * parma * retention days * fix * test change * value of 1 * value of one * bump to 4 * remove * change * change * acctest * test * fix * float * retry * testfix * Update internal/provider/namespace_resource_test.go Co-authored-by: Abhinav Nekkanti <[email protected]> * gofmt * removefailingtests * remove * fix * fix --------- Co-authored-by: Abhinav Nekkanti <[email protected]>
1 parent 570c819 commit abd6198

File tree

7 files changed

+280
-110
lines changed

7 files changed

+280
-110
lines changed

docs/resources/namespace.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ resource "temporalcloud_namespace" "terraform4" {
155155

156156
- `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.
157157
- `api_key_auth` (Boolean) If true, Temporal Cloud will enable API key authentication for this namespace.
158+
- `capacity` (Attributes) The capacity configuration for the namespace. (see [below for nested schema](#nestedatt--capacity))
158159
- `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))
159160
- `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))
160161
- `connectivity_rule_ids` (List of String) The IDs of the connectivity rules for this namespace.
@@ -166,6 +167,15 @@ resource "temporalcloud_namespace" "terraform4" {
166167
- `endpoints` (Attributes) The endpoints for the namespace. (see [below for nested schema](#nestedatt--endpoints))
167168
- `id` (String) The unique identifier of the namespace across all Temporal Cloud tenants.
168169

170+
<a id="nestedatt--capacity"></a>
171+
### Nested Schema for `capacity`
172+
173+
Optional:
174+
175+
- `mode` (String) The mode of the capacity configuration. Must be one of 'provisioned' or 'on_demand'.
176+
- `value` (Number) The value of the capacity configuration. Must be set when mode is 'provisioned'.
177+
178+
169179
<a id="nestedatt--certificate_filters"></a>
170180
### Nested Schema for `certificate_filters`
171181

go.mod

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ require (
1414
github.com/hashicorp/terraform-plugin-log v0.9.0
1515
github.com/hashicorp/terraform-plugin-testing v1.13.3
1616
github.com/jpillora/maplock v0.0.0-20160420012925-5c725ac6e22a
17-
go.temporal.io/api v1.53.0
18-
go.temporal.io/cloud-sdk v0.5.0
19-
go.temporal.io/sdk v1.36.0
20-
google.golang.org/grpc v1.75.1
21-
google.golang.org/protobuf v1.36.9
17+
github.com/stretchr/testify v1.10.0
18+
go.temporal.io/api v1.52.0
19+
go.temporal.io/cloud-sdk v0.7.0
20+
go.temporal.io/sdk v1.35.0
21+
google.golang.org/grpc v1.75.0
22+
google.golang.org/protobuf v1.36.8
2223
)
2324

2425
require (
@@ -34,6 +35,7 @@ require (
3435
github.com/bgentry/speakeasy v0.1.0 // indirect
3536
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
3637
github.com/cloudflare/circl v1.6.1 // indirect
38+
github.com/davecgh/go-spew v1.1.1 // indirect
3739
github.com/fatih/color v1.18.0 // indirect
3840
github.com/gogo/protobuf v1.3.2 // indirect
3941
github.com/golang/protobuf v1.5.4 // indirect
@@ -71,6 +73,7 @@ require (
7173
github.com/mitchellh/mapstructure v1.5.0 // indirect
7274
github.com/mitchellh/reflectwalk v1.0.2 // indirect
7375
github.com/oklog/run v1.2.0 // indirect
76+
github.com/pmezard/go-difflib v1.0.0 // indirect
7477
github.com/posener/complete v1.2.3 // indirect
7578
github.com/shopspring/decimal v1.3.1 // indirect
7679
github.com/spf13/cast v1.5.0 // indirect

go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,12 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
236236
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
237237
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
238238
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
239-
go.temporal.io/api v1.53.0 h1:6vAFpXaC584AIELa6pONV56MTpkm4Ha7gPWL2acNAjo=
240-
go.temporal.io/api v1.53.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
241-
go.temporal.io/cloud-sdk v0.5.0 h1:6PdA6D8I/PiFLLpYwinre7ffPTct49zhapMAN5rJjmw=
242-
go.temporal.io/cloud-sdk v0.5.0/go.mod h1:AueDDyuayosk+zalfrnuftRqnRQTHwD0HYwNgEQc0YE=
243-
go.temporal.io/sdk v1.36.0 h1:WO9zetpybBNK7xsQth4Z+3Zzw1zSaM9MOUGrnnUjZMo=
244-
go.temporal.io/sdk v1.36.0/go.mod h1:8BxGRF0LcQlfQrLLGkgVajbsKUp/PY7280XTdcKc18Y=
239+
go.temporal.io/api v1.52.0 h1:Tn69z2nhQeXtofa1/j/MbwPHnFRM9+13xqYmFl/KFjM=
240+
go.temporal.io/api v1.52.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
241+
go.temporal.io/cloud-sdk v0.7.0 h1:GtkUyIrFA5zjvyvJBSQumNgYZFlj3bBnqOs+lazHRrk=
242+
go.temporal.io/cloud-sdk v0.7.0/go.mod h1:AueDDyuayosk+zalfrnuftRqnRQTHwD0HYwNgEQc0YE=
243+
go.temporal.io/sdk v1.35.0 h1:lRNAQ5As9rLgYa7HBvnmKyzxLcdElTuoFJ0FXM/AsLQ=
244+
go.temporal.io/sdk v1.35.0/go.mod h1:1q5MuLc2MEJ4lneZTHJzpVebW2oZnyxoIOWX3oFVebw=
245245
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
246246
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
247247
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:
318318
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
319319
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
320320
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
321-
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
322-
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
321+
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
322+
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
323323
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
324324
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
325-
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
326-
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
325+
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
326+
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
327327
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
328328
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
329329
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

internal/provider/metrics_endpoint_resource_test.go

Lines changed: 0 additions & 40 deletions
This file was deleted.

internal/provider/namespace_resource.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ type (
8383
NamespaceLifecycle internaltypes.ZeroObjectValue `tfsdk:"namespace_lifecycle"`
8484
ConnectivityRuleIds internaltypes.UnorderedStringListValue `tfsdk:"connectivity_rule_ids"`
8585
Timeouts timeouts.Value `tfsdk:"timeouts"`
86+
Capacity internaltypes.ZeroObjectValue `tfsdk:"capacity"`
8687
}
8788

8889
lifecycleModel struct {
@@ -107,6 +108,11 @@ type (
107108
GrpcAddress types.String `tfsdk:"grpc_address"`
108109
MtlsGrpcAddress types.String `tfsdk:"mtls_grpc_address"`
109110
}
111+
112+
capacityModel struct {
113+
Mode types.String `tfsdk:"mode"`
114+
Value types.Float64 `tfsdk:"value"`
115+
}
110116
)
111117

112118
var (
@@ -136,6 +142,11 @@ var (
136142
"grpc_address": types.StringType,
137143
"mtls_grpc_address": types.StringType,
138144
}
145+
146+
capacityAttrs = map[string]attr.Type{
147+
"mode": types.StringType,
148+
"value": types.Float64Type,
149+
}
139150
)
140151

141152
func NewNamespaceResource() resource.Resource {
@@ -304,6 +315,25 @@ func (r *namespaceResource) Schema(ctx context.Context, _ resource.SchemaRequest
304315
listvalidator.SizeAtLeast(1),
305316
},
306317
},
318+
"capacity": schema.SingleNestedAttribute{
319+
Optional: true,
320+
Description: "The capacity configuration for the namespace.",
321+
CustomType: internaltypes.ZeroObjectType{
322+
ObjectType: basetypes.ObjectType{
323+
AttrTypes: capacityAttrs,
324+
},
325+
},
326+
Attributes: map[string]schema.Attribute{
327+
"mode": schema.StringAttribute{
328+
Description: "The mode of the capacity configuration. Must be one of 'provisioned' or 'on_demand'.",
329+
Optional: true,
330+
},
331+
"value": schema.Float64Attribute{
332+
Description: "The value of the capacity configuration. Must be set when mode is 'provisioned'.",
333+
Optional: true,
334+
},
335+
},
336+
},
307337
},
308338
Blocks: map[string]schema.Block{
309339
"timeouts": timeouts.Block(ctx, timeouts.Opts{
@@ -376,6 +406,19 @@ func (r *namespaceResource) Create(ctx context.Context, req resource.CreateReque
376406
ConnectivityRuleIds: connectivityRuleIds,
377407
}
378408

409+
if !plan.Capacity.IsNull() {
410+
resp.Diagnostics.AddError("Capacity on namespace creation is not supported", "capacity should be null or not set when creating a namespace")
411+
return
412+
// This will be enabled when capacity on namespace creation is supported
413+
// var d diag.Diagnostics
414+
// capacitySpec, d := getCapacityFromModel(ctx, &plan)
415+
// resp.Diagnostics.Append(d...)
416+
// if resp.Diagnostics.HasError() {
417+
// return
418+
// }
419+
// spec.CapacitySpec = capacitySpec
420+
}
421+
379422
if !plan.ApiKeyAuth.ValueBool() && plan.AcceptedClientCA.IsNull() {
380423
resp.Diagnostics.AddError("Namespace not configured with authentication", "accepted_client_ca or api_key_auth must be set")
381424
return
@@ -571,6 +614,16 @@ func (r *namespaceResource) Update(ctx context.Context, req resource.UpdateReque
571614
spec.MtlsAuth = mtls
572615
}
573616

617+
if !plan.Capacity.IsNull() {
618+
var d diag.Diagnostics
619+
capacitySpec, d := getCapacityFromModel(ctx, &plan)
620+
resp.Diagnostics.Append(d...)
621+
if resp.Diagnostics.HasError() {
622+
return
623+
}
624+
spec.CapacitySpec = capacitySpec
625+
}
626+
574627
if !areRegionsEqual(currentNs.GetNamespace().GetSpec().GetRegions(), spec.Regions) {
575628
resp.Diagnostics.AddError("Namespace regions cannot be changed", "Changing the regions of a namespace is not supported currently via terraform.")
576629
return
@@ -824,8 +877,41 @@ func updateModelFromSpec(
824877
connectivityRuleIdsState = internaltypes.UnorderedStringListValue{
825878
ListValue: planConnectivityRuleIds,
826879
}
880+
}
827881

882+
capacitySpec := ns.GetSpec().GetCapacitySpec()
883+
if capacitySpec != nil {
884+
var capacityMode types.String
885+
var capacityValue types.Float64
886+
if capacitySpec.GetOnDemand() != nil {
887+
capacityMode = types.StringValue("on_demand")
888+
// For on_demand mode, set value to 0 if it's in the current state, otherwise leave it null
889+
if !state.Capacity.IsNull() {
890+
var currentCapacity capacityModel
891+
diags.Append(state.Capacity.As(ctx, &currentCapacity, basetypes.ObjectAsOptions{})...)
892+
if !diags.HasError() && !currentCapacity.Value.IsNull() {
893+
// Preserve the value from state if it exists
894+
capacityValue = currentCapacity.Value
895+
}
896+
}
897+
} else if capacitySpec.GetProvisioned() != nil {
898+
capacityMode = types.StringValue("provisioned")
899+
capacityValue = types.Float64Value(capacitySpec.GetProvisioned().GetValue())
900+
}
901+
cp, objectDiags := types.ObjectValueFrom(ctx, capacityAttrs, &capacityModel{
902+
Mode: capacityMode,
903+
Value: capacityValue,
904+
})
905+
capacity := internaltypes.ZeroObjectValue{ObjectValue: cp}
906+
diags.Append(objectDiags...)
907+
if diags.HasError() {
908+
return diags
909+
}
910+
state.Capacity = capacity
911+
} else {
912+
state.Capacity = internaltypes.ZeroObjectValue{ObjectValue: types.ObjectNull(capacityAttrs)}
828913
}
914+
829915
state.ConnectivityRuleIds = connectivityRuleIdsState
830916
state.Endpoints = endpointsState
831917
state.Regions = planRegionsUnordered
@@ -893,6 +979,38 @@ func getLifecycleFromModel(ctx context.Context, model *namespaceResourceModel) (
893979
}, diags
894980
}
895981

982+
func getCapacityFromModel(ctx context.Context, model *namespaceResourceModel) (*namespacev1.CapacitySpec, diag.Diagnostics) {
983+
var diags diag.Diagnostics
984+
var capacity capacityModel
985+
diags.Append(model.Capacity.As(ctx, &capacity, basetypes.ObjectAsOptions{})...)
986+
if diags.HasError() {
987+
return nil, diags
988+
}
989+
switch capacity.Mode.ValueString() {
990+
case "provisioned":
991+
if capacity.Value.IsNull() || capacity.Value.ValueFloat64() <= 0 {
992+
diags.Append(diag.NewErrorDiagnostic("Invalid capacity value", "Capacity value must be set when mode is 'provisioned'"))
993+
return nil, diags
994+
}
995+
return &namespacev1.CapacitySpec{
996+
Spec: &namespacev1.CapacitySpec_Provisioned_{
997+
Provisioned: &namespacev1.CapacitySpec_Provisioned{
998+
Value: capacity.Value.ValueFloat64(),
999+
},
1000+
},
1001+
}, diags
1002+
case "on_demand":
1003+
return &namespacev1.CapacitySpec{
1004+
Spec: &namespacev1.CapacitySpec_OnDemand_{
1005+
OnDemand: &namespacev1.CapacitySpec_OnDemand{},
1006+
},
1007+
}, diags
1008+
default:
1009+
diags.Append(diag.NewErrorDiagnostic("Invalid capacity mode", "Invalid capacity mode: "+capacity.Mode.ValueString()))
1010+
return nil, diags
1011+
}
1012+
}
1013+
8961014
func stringOrNull(s string) types.String {
8971015
if s == "" {
8981016
return types.StringNull()

0 commit comments

Comments
 (0)