diff --git a/internal/provider/resource_secret_backend_role.go b/internal/provider/resource_secret_backend_role.go index 23bd5f0..47133aa 100644 --- a/internal/provider/resource_secret_backend_role.go +++ b/internal/provider/resource_secret_backend_role.go @@ -25,7 +25,7 @@ type secretBackendRoleResource struct { type secretBackendRoleModel struct { Backend types.String `tfsdk:"backend"` Name types.String `tfsdk:"name"` - Apps types.List `tfsdk:"apps"` + Apps types.Set `tfsdk:"apps"` TTL types.Int64 `tfsdk:"ttl"` MaxTTL types.Int64 `tfsdk:"max_ttl"` } @@ -56,7 +56,7 @@ func (r *secretBackendRoleResource) Schema(_ context.Context, _ resource.SchemaR stringplanmodifier.RequiresReplace(), }, }, - "apps": schema.ListAttribute{ + "apps": schema.SetAttribute{ Description: "arr apps a generated key may access (subset of sonarr, radarr, prowlarr).", ElementType: types.StringType, Required: true, @@ -212,9 +212,9 @@ func applyRoleData(m *secretBackendRoleModel, role map[string]interface{}) diag. var diags diag.Diagnostics apps := toStringSlice(role["apps"]) - appList, appDiags := types.ListValueFrom(context.Background(), types.StringType, apps) + appSet, appDiags := types.SetValueFrom(context.Background(), types.StringType, apps) diags.Append(appDiags...) - m.Apps = appList + m.Apps = appSet if n, ok := toInt64(role["ttl"]); ok && n != 0 { m.TTL = types.Int64Value(n) diff --git a/internal/provider/resource_secret_backend_role_test.go b/internal/provider/resource_secret_backend_role_test.go index c42b111..ce1e5a2 100644 --- a/internal/provider/resource_secret_backend_role_test.go +++ b/internal/provider/resource_secret_backend_role_test.go @@ -8,18 +8,18 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -func listOf(t *testing.T, vals ...string) types.List { +func setOf(t *testing.T, vals ...string) types.Set { t.Helper() - l, diags := types.ListValueFrom(context.Background(), types.StringType, vals) + s, diags := types.SetValueFrom(context.Background(), types.StringType, vals) if diags.HasError() { - t.Fatalf("building list: %v", diags) + t.Fatalf("building set: %v", diags) } - return l + return s } func TestRoleDataOmitsUnsetTTLs(t *testing.T) { m := secretBackendRoleModel{ - Apps: listOf(t, "sonarr", "radarr", "prowlarr"), + Apps: setOf(t, "sonarr", "radarr", "prowlarr"), TTL: types.Int64Null(), MaxTTL: types.Int64Null(), } @@ -41,7 +41,7 @@ func TestRoleDataOmitsUnsetTTLs(t *testing.T) { func TestRoleDataIncludesTTLs(t *testing.T) { m := secretBackendRoleModel{ - Apps: listOf(t, "prowlarr"), + Apps: setOf(t, "prowlarr"), TTL: types.Int64Value(60), MaxTTL: types.Int64Value(86400), } @@ -70,16 +70,34 @@ func TestApplyRoleDataMapsEngineResponse(t *testing.T) { if diags := applyRoleData(&m, role); diags.HasError() { t.Fatalf("applyRoleData: %v", diags) } - var apps []string - m.Apps.ElementsAs(context.Background(), &apps, false) - if len(apps) != 3 || apps[2] != "prowlarr" { - t.Errorf("apps = %v, want [sonarr radarr prowlarr]", apps) + if !m.Apps.Equal(setOf(t, "sonarr", "radarr", "prowlarr")) { + t.Errorf("apps = %v, want {sonarr radarr prowlarr}", m.Apps) } if m.TTL.ValueInt64() != 60 || m.MaxTTL.ValueInt64() != 86400 { t.Errorf("ttl/max_ttl = %d/%d, want 60/86400", m.TTL.ValueInt64(), m.MaxTTL.ValueInt64()) } } +func TestApplyRoleDataOrderInsensitive(t *testing.T) { + // The engine returns apps alphabetically sorted regardless of the order + // they were written in; the read-back must still equal the config value. + configApps := setOf(t, "sonarr", "prowlarr", "radarr") + role := map[string]interface{}{ + "apps": []interface{}{"prowlarr", "radarr", "sonarr"}, + } + m := secretBackendRoleModel{ + Apps: configApps, + TTL: types.Int64Null(), + MaxTTL: types.Int64Null(), + } + if diags := applyRoleData(&m, role); diags.HasError() { + t.Fatalf("applyRoleData: %v", diags) + } + if !m.Apps.Equal(configApps) { + t.Errorf("apps after read-back = %v, not equal to config value %v", m.Apps, configApps) + } +} + func TestApplyRoleDataZeroTTLLeavesNull(t *testing.T) { // The engine returns 0 for an unset TTL, which must not clobber the null // model value into a spurious 0.