Skip to content

Commit ed51354

Browse files
committed
Merge remote-tracking branch 'origin/kelsi-hoyle/TF-24744/validator' into kelsi-hoyle/TF-24744/arm-toolversion-support
2 parents 8437fe1 + 2e69fbd commit ed51354

File tree

3 files changed

+187
-17
lines changed

3 files changed

+187
-17
lines changed

internal/provider/resource_tfe_opa_version.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
// Copyright (c) HashiCorp, Inc.
22
// SPDX-License-Identifier: MPL-2.0
33

4-
// NOTE: This is a legacy resource and should be migrated to the Plugin
5-
// Framework if substantial modifications are planned. See
6-
// docs/new-resources.md if planning to use this code as boilerplate for
7-
// a new resource.
8-
94
package provider
105

116
import (
@@ -19,16 +14,17 @@ import (
1914
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
2015
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
2116
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
22-
// "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier"
17+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier"
2318
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2419
"github.com/hashicorp/terraform-plugin-framework/types"
2520
"github.com/hashicorp/terraform-plugin-log/tflog"
2621
)
2722

2823
var (
29-
_ resource.Resource = &OPAVersionResource{}
30-
_ resource.ResourceWithConfigure = &OPAVersionResource{}
31-
_ resource.ResourceWithImportState = &OPAVersionResource{}
24+
_ resource.Resource = &OPAVersionResource{}
25+
_ resource.ResourceWithConfigure = &OPAVersionResource{}
26+
_ resource.ResourceWithImportState = &OPAVersionResource{}
27+
_ resource.ResourceWithValidateConfig = &OPAVersionResource{} // Add this line
3228
)
3329

3430
type OPAVersionResource struct {
@@ -103,7 +99,7 @@ func (r *OPAVersionResource) Schema(ctx context.Context, req resource.SchemaRequ
10399
"url": schema.StringAttribute{
104100
Required: true,
105101
},
106-
"sha": schema.StringAttribute{ // Ensure lowercase
102+
"sha": schema.StringAttribute{
107103
Required: true,
108104
},
109105
"os": schema.StringAttribute{
@@ -116,7 +112,10 @@ func (r *OPAVersionResource) Schema(ctx context.Context, req resource.SchemaRequ
116112
},
117113
Computed: true,
118114
Optional: true,
119-
// PlanModifiers: []planmodifier.Set{setplanmodifier.UseStateForUnknown()},
115+
PlanModifiers: []planmodifier.Set{
116+
setplanmodifier.UseStateForUnknown(), // This ensures that we don't show a warning for invisible changes in updates when using refresh-only mode
117+
PreserveAMD64ArchsOnChange(), // This ensures that we don't remove AMD64 archs when the URL/SHA changes
118+
},
120119
},
121120
},
122121
}
@@ -284,7 +283,7 @@ func (r *OPAVersionResource) Update(ctx context.Context, req resource.UpdateRequ
284283
opts := tfe.AdminOPAVersionUpdateOptions{
285284
Version: tfe.String(opaVersion.Version.ValueString()),
286285
URL: stringOrNil(opaVersion.URL.ValueString()),
287-
SHA: tfe.String(opaVersion.SHA.ValueString()),
286+
SHA: stringOrNil(opaVersion.SHA.ValueString()),
288287
Official: tfe.Bool(opaVersion.Official.ValueBool()),
289288
Enabled: tfe.Bool(opaVersion.Enabled.ValueBool()),
290289
Beta: tfe.Bool(opaVersion.Beta.ValueBool()),
@@ -391,3 +390,15 @@ func (r *OPAVersionResource) ImportState(ctx context.Context, req resource.Impor
391390
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), versionID)...)
392391
}
393392
}
393+
394+
// Make OPAVersionResource implement the ToolVersionValidator interface
395+
func (r *OPAVersionResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
396+
var config modelAdminOPAVersion
397+
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
398+
if resp.Diagnostics.HasError() {
399+
return
400+
}
401+
402+
// Use the simplified validation function
403+
resp.Diagnostics.Append(ValidateToolVersion(ctx, config.URL, config.SHA, config.Archs, "OPA Version")...)
404+
}

internal/provider/resource_tfe_terraform_version.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
// Copyright (c) HashiCorp, Inc.
22
// SPDX-License-Identifier: MPL-2.0
33

4-
// NOTE: This is a legacy resource and should be migrated to the Plugin
5-
// Framework if substantial modifications are planned. See
6-
// docs/new-resources.md if planning to use this code as boilerplate for
7-
// a new resource.
8-
94
package provider
105

116
import (

internal/provider/tool_helpers.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import (
99

1010
"github.com/hashicorp/terraform-plugin-framework/attr"
1111
"github.com/hashicorp/terraform-plugin-framework/diag"
12+
"github.com/hashicorp/terraform-plugin-framework/path"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1214
"github.com/hashicorp/terraform-plugin-framework/types"
15+
"github.com/hashicorp/terraform-plugin-log/tflog"
1316

1417
tfe "github.com/hashicorp/go-tfe"
1518
)
@@ -222,3 +225,164 @@ func convertToToolVersionArchitectures(ctx context.Context, archs types.Set) ([]
222225

223226
return result, nil
224227
}
228+
229+
// PreserveAMD64ArchsOnChange creates a plan modifier that preserves AMD64 architecture entries
230+
// when top-level URL or SHA changes, to be used across all tool version resources
231+
func PreserveAMD64ArchsOnChange() planmodifier.Set {
232+
return &preserveAMD64ArchsModifier{}
233+
}
234+
235+
// Implement the plan modifier interface
236+
type preserveAMD64ArchsModifier struct{}
237+
238+
// Description provides a plain text description of the plan modifier
239+
func (m *preserveAMD64ArchsModifier) Description(ctx context.Context) string {
240+
return "Preserves AMD64 architecture entries when top-level URL or SHA changes"
241+
}
242+
243+
// MarkdownDescription provides markdown documentation
244+
func (m *preserveAMD64ArchsModifier) MarkdownDescription(ctx context.Context) string {
245+
return "Preserves AMD64 architecture entries when top-level URL or SHA changes"
246+
}
247+
248+
// PlanModifySet modifies the plan to ensure AMD64 architecture entries are preserved
249+
func (m *preserveAMD64ArchsModifier) PlanModifySet(ctx context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) {
250+
// Skip if we're destroying the resource or no state
251+
if req.Plan.Raw.IsNull() || req.State.Raw.IsNull() || req.StateValue.IsNull() {
252+
return
253+
}
254+
255+
var stateURL, planURL, stateSHA, planSHA types.String
256+
// Get values from state and plan
257+
req.State.GetAttribute(ctx, path.Root("url"), &stateURL)
258+
req.Plan.GetAttribute(ctx, path.Root("url"), &planURL)
259+
req.State.GetAttribute(ctx, path.Root("sha"), &stateSHA)
260+
req.Plan.GetAttribute(ctx, path.Root("sha"), &planSHA)
261+
262+
// Check if values are changing
263+
urlChanged := !stateURL.Equal(planURL)
264+
shaChanged := !stateSHA.Equal(planSHA)
265+
266+
// If neither URL nor SHA is changing, do nothing
267+
if !urlChanged && !shaChanged {
268+
return
269+
}
270+
271+
// Extract state archs and plan archs
272+
stateArchs := req.StateValue
273+
planArchs := req.PlanValue
274+
275+
// Extract archs from state
276+
var stateArchsList []ToolArchitecture
277+
diags := stateArchs.ElementsAs(ctx, &stateArchsList, false)
278+
if diags.HasError() {
279+
return
280+
}
281+
282+
// we need to update the plan amd url and sha to match the top level values
283+
var planArchsList []ToolArchitecture
284+
diags = planArchs.ElementsAs(ctx, &planArchsList, false)
285+
if diags.HasError() {
286+
tflog.Debug(ctx, "Error extracting plan architectures", map[string]interface{}{
287+
"diagnostics": diags,
288+
})
289+
return
290+
}
291+
292+
// Check if AMD64 is already in the plan
293+
for i, arch := range planArchsList {
294+
if arch.Arch.ValueString() == "amd64" {
295+
// If URL or SHA is changing, update the AMD64 arch to match
296+
if urlChanged {
297+
arch.URL = planURL
298+
}
299+
if shaChanged {
300+
arch.Sha = planSHA
301+
}
302+
// Update the plan architecture list with the modified AMD64 arch
303+
planArchsList[i] = arch
304+
305+
// Update the plan with the modified AMD64 arch
306+
archObjectType := ObjectTypeForArchitectures()
307+
attrValues := make([]attr.Value, len(planArchsList))
308+
309+
for i, arch := range planArchsList {
310+
attrValues[i] = types.ObjectValueMust(
311+
archObjectType.AttrTypes,
312+
map[string]attr.Value{
313+
"url": arch.URL,
314+
"sha": arch.Sha,
315+
"os": arch.OS,
316+
"arch": arch.Arch,
317+
},
318+
)
319+
}
320+
321+
resp.PlanValue = types.SetValueMust(archObjectType, attrValues)
322+
return
323+
}
324+
}
325+
}
326+
327+
// ValidateToolVersion provides common validation for tool version resources
328+
func ValidateToolVersion(ctx context.Context, url, sha types.String, archs types.Set, resourceType string) diag.Diagnostics {
329+
var diags diag.Diagnostics
330+
331+
urlPresent := !url.IsNull() && !url.IsUnknown()
332+
shaPresent := !sha.IsNull() && !sha.IsUnknown()
333+
334+
// If URL or SHA is not set, we will rely on the archs attribute
335+
if !urlPresent || !shaPresent {
336+
return diags
337+
}
338+
339+
// Check if archs is present
340+
if !archs.IsNull() && !archs.IsUnknown() {
341+
// Extract archs
342+
var archsList []ToolArchitecture
343+
archDiags := archs.ElementsAs(ctx, &archsList, false)
344+
if archDiags.HasError() {
345+
diags.Append(archDiags...)
346+
return diags
347+
}
348+
349+
// Check for AMD64 architecture
350+
var hasAMD64 bool
351+
for _, arch := range archsList {
352+
if arch.Arch.ValueString() == "amd64" {
353+
hasAMD64 = true
354+
355+
// If URL and SHA are set at top level, check they match AMD64 arch
356+
// Check URL matches
357+
if urlPresent && url.ValueString() != arch.URL.ValueString() {
358+
diags.AddError(
359+
fmt.Sprintf("Inconsistent %s URL values", resourceType),
360+
fmt.Sprintf("Top-level URL (%s) doesn't match AMD64 architecture URL (%s)",
361+
url.ValueString(), arch.URL.ValueString()),
362+
)
363+
}
364+
365+
// Check SHA matches
366+
if shaPresent && sha.ValueString() != arch.Sha.ValueString() {
367+
diags.AddError(
368+
fmt.Sprintf("Inconsistent %s SHA values", resourceType),
369+
fmt.Sprintf("Top-level SHA (%s) doesn't match AMD64 architecture SHA (%s)",
370+
sha.ValueString(), arch.Sha.ValueString()),
371+
)
372+
}
373+
374+
break
375+
}
376+
}
377+
378+
// If top-level URL/SHA are set and no AMD64 arch found, add error
379+
if !hasAMD64 && (!url.IsNull() || !sha.IsNull()) {
380+
diags.AddError(
381+
fmt.Sprintf("Missing AMD64 architecture in %s", resourceType),
382+
"When specifying both top-level URL/SHA and archs, an AMD64 architecture entry must be included",
383+
)
384+
}
385+
}
386+
387+
return diags
388+
}

0 commit comments

Comments
 (0)