Skip to content

Commit 6245a90

Browse files
committed
Add keyed mutex around tenant variables
1 parent 4b6bd3a commit 6245a90

File tree

2 files changed

+128
-66
lines changed

2 files changed

+128
-66
lines changed

octopusdeploy_framework/resource_tenant_common_variable.go

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package octopusdeploy_framework
33
import (
44
"context"
55
"fmt"
6+
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal"
67
"strings"
78

89
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
@@ -95,6 +96,9 @@ func (t *tenantCommonVariableResource) Create(ctx context.Context, req resource.
9596
return
9697
}
9798

99+
internal.KeyedMutex.Lock(plan.TenantID.ValueString())
100+
defer internal.KeyedMutex.Unlock(plan.TenantID.ValueString())
101+
98102
if len(plan.Scope) > 0 && !t.supportsV2() {
99103
resp.Diagnostics.AddError(
100104
"Scoped tenant variables are not supported",
@@ -210,7 +214,7 @@ func (t *tenantCommonVariableResource) createV2(ctx context.Context, plan *tenan
210214
scope.EnvironmentIds = envIDs
211215
}
212216

213-
var payloads []variables.TenantCommonVariablePayload
217+
payloads := []variables.TenantCommonVariablePayload{}
214218

215219
for _, v := range getResp.Variables {
216220
payloads = append(payloads, variables.TenantCommonVariablePayload{
@@ -244,8 +248,35 @@ func (t *tenantCommonVariableResource) createV2(ctx context.Context, plan *tenan
244248
var createdID string
245249
for _, v := range updateResp.Variables {
246250
if v.LibraryVariableSetId == plan.LibraryVariableSetID.ValueString() && v.TemplateID == plan.TemplateID.ValueString() {
247-
createdID = v.GetID()
248-
break
251+
// Also match on scope to handle multiple variables with same template but different scopes
252+
if len(plan.Scope) > 0 {
253+
if len(v.Scope.EnvironmentIds) > 0 {
254+
planEnvs := plan.Scope[0].EnvironmentIDs.Elements()
255+
if len(planEnvs) == len(v.Scope.EnvironmentIds) {
256+
match := true
257+
planEnvSet := make(map[string]bool)
258+
for _, e := range planEnvs {
259+
planEnvSet[e.String()] = true
260+
}
261+
for _, serverEnv := range v.Scope.EnvironmentIds {
262+
if !planEnvSet["\""+serverEnv+"\""] {
263+
match = false
264+
break
265+
}
266+
}
267+
if match {
268+
createdID = v.GetID()
269+
break
270+
}
271+
}
272+
}
273+
} else {
274+
// No scope in plan, match unscoped variable
275+
if len(v.Scope.EnvironmentIds) == 0 {
276+
createdID = v.GetID()
277+
break
278+
}
279+
}
249280
}
250281
}
251282

@@ -269,6 +300,9 @@ func (t *tenantCommonVariableResource) Read(ctx context.Context, req resource.Re
269300
return
270301
}
271302

303+
internal.KeyedMutex.Lock(state.TenantID.ValueString())
304+
defer internal.KeyedMutex.Unlock(state.TenantID.ValueString())
305+
272306
tenant, err := tenants.GetByID(t.Client, state.SpaceID.ValueString(), state.TenantID.ValueString())
273307
if err != nil {
274308
resp.Diagnostics.AddError("Error retrieving tenant", err.Error())
@@ -420,6 +454,9 @@ func (t *tenantCommonVariableResource) Update(ctx context.Context, req resource.
420454
return
421455
}
422456

457+
internal.KeyedMutex.Lock(plan.TenantID.ValueString())
458+
defer internal.KeyedMutex.Unlock(plan.TenantID.ValueString())
459+
423460
// Validate scope block usage on unsupported servers
424461
if len(plan.Scope) > 0 && !t.supportsV2() {
425462
resp.Diagnostics.AddError(
@@ -488,7 +525,7 @@ func (t *tenantCommonVariableResource) updateV2(ctx context.Context, plan *tenan
488525
scope.EnvironmentIds = envIDs
489526
}
490527

491-
var payloads []variables.TenantCommonVariablePayload
528+
payloads := []variables.TenantCommonVariablePayload{}
492529

493530
for _, v := range getResp.Variables {
494531
if v.GetID() == plan.ID.ValueString() {
@@ -585,7 +622,7 @@ func (t *tenantCommonVariableResource) migrateV1ToV2OnUpdate(ctx context.Context
585622
scope.EnvironmentIds = envIDs
586623
}
587624

588-
var payloads []variables.TenantCommonVariablePayload
625+
payloads := []variables.TenantCommonVariablePayload{}
589626

590627
for _, v := range getResp.Variables {
591628
if v.LibraryVariableSetId == plan.LibraryVariableSetID.ValueString() && v.TemplateID == plan.TemplateID.ValueString() {
@@ -645,6 +682,9 @@ func (t *tenantCommonVariableResource) Delete(ctx context.Context, req resource.
645682
return
646683
}
647684

685+
internal.KeyedMutex.Lock(state.TenantID.ValueString())
686+
defer internal.KeyedMutex.Unlock(state.TenantID.ValueString())
687+
648688
tenant, err := tenants.GetByID(t.Client, state.SpaceID.ValueString(), state.TenantID.ValueString())
649689
if err != nil {
650690
resp.Diagnostics.AddError("Error retrieving tenant", err.Error())
@@ -715,7 +755,7 @@ func (t *tenantCommonVariableResource) deleteV2(ctx context.Context, state *tena
715755
return
716756
}
717757

718-
var payloads []variables.TenantCommonVariablePayload
758+
payloads := []variables.TenantCommonVariablePayload{}
719759

720760
for _, v := range getResp.Variables {
721761
if v.GetID() == state.ID.ValueString() {

octopusdeploy_framework/resource_tenant_project_variable.go

Lines changed: 82 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,15 @@ func findProjectVariableByID(variables []variables.TenantProjectVariable, id str
9292
}
9393

9494
func (t *tenantProjectVariableResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
95-
internal.Mutex.Lock()
96-
defer internal.Mutex.Unlock()
9795
var plan tenantProjectVariableResourceModel
9896
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
9997
if resp.Diagnostics.HasError() {
10098
return
10199
}
102100

101+
internal.KeyedMutex.Lock(plan.TenantID.ValueString())
102+
defer internal.KeyedMutex.Unlock(plan.TenantID.ValueString())
103+
103104
tflog.Debug(ctx, "Creating tenant project variable")
104105

105106
// Validate that either environment_id or scope is provided, but not both
@@ -149,6 +150,47 @@ func (t *tenantProjectVariableResource) Create(ctx context.Context, req resource
149150
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
150151
}
151152

153+
func (t *tenantProjectVariableResource) createV1(ctx context.Context, plan *tenantProjectVariableResourceModel, tenant *tenants.Tenant, hasEnvironmentID bool, resp *resource.CreateResponse) {
154+
tflog.Debug(ctx, "Using V1 API for tenant project variable")
155+
156+
if !hasEnvironmentID {
157+
resp.Diagnostics.AddError("Invalid configuration", "environment_id is required for V1 API")
158+
return
159+
}
160+
161+
id := fmt.Sprintf("%s:%s:%s:%s", plan.TenantID.ValueString(), plan.ProjectID.ValueString(), plan.EnvironmentID.ValueString(), plan.TemplateID.ValueString())
162+
163+
tenantVariables, err := t.Client.Tenants.GetVariables(tenant)
164+
if err != nil {
165+
resp.Diagnostics.AddError("Error retrieving tenant variables", err.Error())
166+
return
167+
}
168+
169+
isSensitive, err := checkIfVariableIsSensitive(tenantVariables, *plan)
170+
if err != nil {
171+
resp.Diagnostics.AddError("Error checking if variable is sensitive", err.Error())
172+
return
173+
}
174+
175+
if err := updateTenantProjectVariable(tenantVariables, *plan, isSensitive); err != nil {
176+
resp.Diagnostics.AddError("Error updating tenant project variable", err.Error())
177+
return
178+
}
179+
180+
_, err = t.Client.Tenants.UpdateVariables(tenant, tenantVariables)
181+
if err != nil {
182+
resp.Diagnostics.AddError("Error updating tenant variables", err.Error())
183+
return
184+
}
185+
186+
plan.ID = types.StringValue(id)
187+
plan.SpaceID = types.StringValue(tenant.SpaceID)
188+
189+
tflog.Debug(ctx, "Tenant project variable created with V1 API", map[string]interface{}{
190+
"id": plan.ID.ValueString(),
191+
})
192+
}
193+
152194
func (t *tenantProjectVariableResource) createV2(ctx context.Context, plan *tenantProjectVariableResourceModel, tenant *tenants.Tenant, spaceID string, hasEnvironmentID bool, resp *resource.CreateResponse) {
153195
tflog.Debug(ctx, "Using V2 API for tenant project variable")
154196

@@ -193,7 +235,7 @@ func (t *tenantProjectVariableResource) createV2(ctx context.Context, plan *tena
193235
}
194236

195237
// Build payloads for ALL existing variables plus the new one
196-
var payloads []variables.TenantProjectVariablePayload
238+
payloads := []variables.TenantProjectVariablePayload{}
197239

198240
// Add all existing variables
199241
for _, v := range getResp.Variables {
@@ -229,8 +271,29 @@ func (t *tenantProjectVariableResource) createV2(ctx context.Context, plan *tena
229271
var createdID string
230272
for _, v := range updateResp.Variables {
231273
if v.ProjectID == plan.ProjectID.ValueString() && v.TemplateID == plan.TemplateID.ValueString() {
232-
createdID = v.GetID()
233-
break
274+
// Also match on scope to handle multiple variables with same template but different scopes
275+
if len(plan.Scope) > 0 {
276+
if len(v.Scope.EnvironmentIds) > 0 {
277+
planEnvs := plan.Scope[0].EnvironmentIDs.Elements()
278+
if len(planEnvs) == len(v.Scope.EnvironmentIds) {
279+
match := true
280+
planEnvSet := make(map[string]bool)
281+
for _, e := range planEnvs {
282+
planEnvSet[e.String()] = true
283+
}
284+
for _, serverEnv := range v.Scope.EnvironmentIds {
285+
if !planEnvSet["\""+serverEnv+"\""] {
286+
match = false
287+
break
288+
}
289+
}
290+
if match {
291+
createdID = v.GetID()
292+
break
293+
}
294+
}
295+
}
296+
}
234297
}
235298
}
236299

@@ -254,57 +317,16 @@ func (t *tenantProjectVariableResource) createV2(ctx context.Context, plan *tena
254317
})
255318
}
256319

257-
func (t *tenantProjectVariableResource) createV1(ctx context.Context, plan *tenantProjectVariableResourceModel, tenant *tenants.Tenant, hasEnvironmentID bool, resp *resource.CreateResponse) {
258-
tflog.Debug(ctx, "Using V1 API for tenant project variable")
259-
260-
if !hasEnvironmentID {
261-
resp.Diagnostics.AddError("Invalid configuration", "environment_id is required for V1 API")
262-
return
263-
}
264-
265-
id := fmt.Sprintf("%s:%s:%s:%s", plan.TenantID.ValueString(), plan.ProjectID.ValueString(), plan.EnvironmentID.ValueString(), plan.TemplateID.ValueString())
266-
267-
tenantVariables, err := t.Client.Tenants.GetVariables(tenant)
268-
if err != nil {
269-
resp.Diagnostics.AddError("Error retrieving tenant variables", err.Error())
270-
return
271-
}
272-
273-
isSensitive, err := checkIfVariableIsSensitive(tenantVariables, *plan)
274-
if err != nil {
275-
resp.Diagnostics.AddError("Error checking if variable is sensitive", err.Error())
276-
return
277-
}
278-
279-
if err := updateTenantProjectVariable(tenantVariables, *plan, isSensitive); err != nil {
280-
resp.Diagnostics.AddError("Error updating tenant project variable", err.Error())
281-
return
282-
}
283-
284-
_, err = t.Client.Tenants.UpdateVariables(tenant, tenantVariables)
285-
if err != nil {
286-
resp.Diagnostics.AddError("Error updating tenant variables", err.Error())
287-
return
288-
}
289-
290-
plan.ID = types.StringValue(id)
291-
plan.SpaceID = types.StringValue(tenant.SpaceID)
292-
293-
tflog.Debug(ctx, "Tenant project variable created with V1 API", map[string]interface{}{
294-
"id": plan.ID.ValueString(),
295-
})
296-
}
297-
298320
func (t *tenantProjectVariableResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
299-
internal.Mutex.Lock()
300-
defer internal.Mutex.Unlock()
301-
302321
var state tenantProjectVariableResourceModel
303322
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
304323
if resp.Diagnostics.HasError() {
305324
return
306325
}
307326

327+
internal.KeyedMutex.Lock(state.TenantID.ValueString())
328+
defer internal.KeyedMutex.Unlock(state.TenantID.ValueString())
329+
308330
tenant, err := tenants.GetByID(t.Client, state.SpaceID.ValueString(), state.TenantID.ValueString())
309331
if err != nil {
310332
resp.Diagnostics.AddError("Error retrieving tenant", err.Error())
@@ -482,15 +504,15 @@ func (t *tenantProjectVariableResource) migrateV1ToV2OnRead(ctx context.Context,
482504
}
483505

484506
func (t *tenantProjectVariableResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
485-
internal.Mutex.Lock()
486-
defer internal.Mutex.Unlock()
487-
488507
var plan tenantProjectVariableResourceModel
489508
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
490509
if resp.Diagnostics.HasError() {
491510
return
492511
}
493512

513+
internal.KeyedMutex.Lock(plan.TenantID.ValueString())
514+
defer internal.KeyedMutex.Unlock(plan.TenantID.ValueString())
515+
494516
// Validate that either environment_id or scope is provided, but not both
495517
hasEnvironmentID := !plan.EnvironmentID.IsNull() && plan.EnvironmentID.ValueString() != ""
496518
hasScope := len(plan.Scope) > 0
@@ -575,7 +597,7 @@ func (t *tenantProjectVariableResource) updateV2(ctx context.Context, plan *tena
575597
scope.EnvironmentIds = []string{plan.EnvironmentID.ValueString()}
576598
}
577599

578-
var payloads []variables.TenantProjectVariablePayload
600+
payloads := []variables.TenantProjectVariablePayload{}
579601

580602
for _, v := range getResp.Variables {
581603
if v.GetID() == plan.ID.ValueString() {
@@ -676,7 +698,7 @@ func (t *tenantProjectVariableResource) migrateV1ToV2OnUpdate(ctx context.Contex
676698
scope.EnvironmentIds = []string{plan.EnvironmentID.ValueString()}
677699
}
678700

679-
var payloads []variables.TenantProjectVariablePayload
701+
payloads := []variables.TenantProjectVariablePayload{}
680702

681703
for _, v := range getResp.Variables {
682704
if v.ProjectID == plan.ProjectID.ValueString() && v.TemplateID == plan.TemplateID.ValueString() {
@@ -737,15 +759,15 @@ func (t *tenantProjectVariableResource) migrateV1ToV2OnUpdate(ctx context.Contex
737759
}
738760

739761
func (t *tenantProjectVariableResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
740-
internal.Mutex.Lock()
741-
defer internal.Mutex.Unlock()
742-
743762
var state tenantProjectVariableResourceModel
744763
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
745764
if resp.Diagnostics.HasError() {
746765
return
747766
}
748767

768+
internal.KeyedMutex.Lock(state.TenantID.ValueString())
769+
defer internal.KeyedMutex.Unlock(state.TenantID.ValueString())
770+
749771
tenant, err := tenants.GetByID(t.Client, state.SpaceID.ValueString(), state.TenantID.ValueString())
750772
if err != nil {
751773
resp.Diagnostics.AddError("Error retrieving tenant", err.Error())
@@ -757,8 +779,8 @@ func (t *tenantProjectVariableResource) Delete(ctx context.Context, req resource
757779
spaceID = tenant.SpaceID
758780
}
759781

760-
useV1API := isCompositeID(state.ID.ValueString())
761-
if !useV1API {
782+
isV1ID := isCompositeID(state.ID.ValueString())
783+
if !isV1ID {
762784
t.deleteV2(ctx, &state, spaceID, resp)
763785
} else {
764786
t.deleteV1(ctx, &state, tenant, resp)
@@ -786,7 +808,7 @@ func (t *tenantProjectVariableResource) deleteV2(ctx context.Context, state *ten
786808
return
787809
}
788810

789-
var payloads []variables.TenantProjectVariablePayload
811+
payloads := []variables.TenantProjectVariablePayload{}
790812

791813
for _, v := range getResp.Variables {
792814
if v.GetID() == state.ID.ValueString() {

0 commit comments

Comments
 (0)