From afe34001230754b4182ac4c5232741febf06d6f1 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 21 Jan 2025 07:03:01 +0000 Subject: [PATCH 1/2] chore: allow pushing only inactive coderd_template versions --- docs/resources/template.md | 2 +- integration/integration.go | 7 +- integration/integration_test.go | 2 +- internal/provider/template_resource.go | 112 ++++++-- internal/provider/template_resource_test.go | 302 ++++++++++++++++++-- 5 files changed, 382 insertions(+), 43 deletions(-) diff --git a/docs/resources/template.md b/docs/resources/template.md index c0efc4b..0b15d2f 100644 --- a/docs/resources/template.md +++ b/docs/resources/template.md @@ -77,7 +77,7 @@ resource "coderd_template" "ubuntu-main" { - `description` (String) A description of the template. - `display_name` (String) The display name of the template. Defaults to the template name. - `failure_ttl_ms` (Number) (Enterprise) The max lifetime before Coder stops all resources for failed workspaces created from this template, in milliseconds. -- `icon` (String) Relative path or external URL that specifes an icon to be displayed in the dashboard. +- `icon` (String) Relative path or external URL that specifies an icon to be displayed in the dashboard. - `max_port_share_level` (String) (Enterprise) The maximum port share level for workspaces created from this template. Defaults to `owner` on an Enterprise deployment, or `public` otherwise. - `organization_id` (String) The ID of the organization. Defaults to the provider's default organization - `require_active_version` (Boolean) (Enterprise) Whether workspaces must be created from the active version of this template. Defaults to false. diff --git a/integration/integration.go b/integration/integration.go index 89b385d..9f7120f 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -51,9 +51,10 @@ func StartCoder(ctx context.Context, t *testing.T, name string, useLicense bool) ctr, err := cli.ContainerCreate(ctx, &container.Config{ Image: coderImg + ":" + coderVersion, Env: []string{ - "CODER_HTTP_ADDRESS=0.0.0.0:3000", // Listen on all interfaces inside the container - "CODER_ACCESS_URL=http://localhost:3000", // Set explicitly to avoid creating try.coder.app URLs. - "CODER_TELEMETRY_ENABLE=false", // Avoid creating noise. + "CODER_HTTP_ADDRESS=0.0.0.0:3000", // Listen on all interfaces inside the container + "CODER_ACCESS_URL=http://localhost:3000", // Set explicitly to avoid creating try.coder.app URLs. + "CODER_TELEMETRY_ENABLE=false", // Avoid creating noise. + "CODER_DANGEROUS_DISABLE_RATE_LIMITS=true", // Avoid hitting file rate limit in tests. }, Labels: map[string]string{}, ExposedPorts: map[nat.Port]struct{}{nat.Port("3000/tcp"): {}}, diff --git a/integration/integration_test.go b/integration/integration_test.go index 3bfdae4..aeb90a5 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -166,7 +166,7 @@ func TestIntegration(t *testing.T) { tfCmd.Stderr = &buf tt.preF(t, client) if err := tfCmd.Run(); !assert.NoError(t, err) { - t.Logf("%s", buf.String()) + t.Log(buf.String()) t.FailNow() } tt.assertF(t, client) diff --git a/internal/provider/template_resource.go b/internal/provider/template_resource.go index a5834db..d02e12f 100644 --- a/internal/provider/template_resource.go +++ b/internal/provider/template_resource.go @@ -286,7 +286,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques }, }, "icon": schema.StringAttribute{ - MarkdownDescription: "Relative path or external URL that specifes an icon to be displayed in the dashboard.", + MarkdownDescription: "Relative path or external URL that specifies an icon to be displayed in the dashboard.", Optional: true, Computed: true, Default: stringdefault.StaticString(""), @@ -404,7 +404,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques Required: true, Validators: []validator.List{ listvalidator.SizeAtLeast(1), - NewActiveVersionValidator(), + NewVersionsValidator(), }, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ @@ -867,24 +867,24 @@ func (r *TemplateResource) ConfigValidators(context.Context) []resource.ConfigVa return []resource.ConfigValidator{} } -type activeVersionValidator struct{} +type versionsValidator struct{} -func NewActiveVersionValidator() validator.List { - return &activeVersionValidator{} +func NewVersionsValidator() validator.List { + return &versionsValidator{} } // Description implements validator.List. -func (a *activeVersionValidator) Description(ctx context.Context) string { +func (a *versionsValidator) Description(ctx context.Context) string { return a.MarkdownDescription(ctx) } // MarkdownDescription implements validator.List. -func (a *activeVersionValidator) MarkdownDescription(context.Context) string { - return "Validate that exactly one template version has active set to true." +func (a *versionsValidator) MarkdownDescription(context.Context) string { + return "Validate that template version names are unique and that at most one version is active." } // ValidateList implements validator.List. -func (a *activeVersionValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { +func (a *versionsValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { return } @@ -908,13 +908,13 @@ func (a *activeVersionValidator) ValidateList(ctx context.Context, req validator uniqueNames[version.Name.ValueString()] = struct{}{} } - // Check if only one item in Version has active set to true + // Ensure at most one version is active active := false for _, version := range data { - // `active` is required, so if it's null or unknown, this is Terraform + // `active` defaults to false, so if it's null or unknown, this is Terraform // requesting an early validation. if version.Active.IsNull() || version.Active.IsUnknown() { - return + continue } if version.Active.ValueBool() { if active { @@ -924,12 +924,9 @@ func (a *activeVersionValidator) ValidateList(ctx context.Context, req validator active = true } } - if !active { - resp.Diagnostics.AddError("Client Error", "At least one template version must be active.") - } } -var _ validator.List = &activeVersionValidator{} +var _ validator.List = &versionsValidator{} type versionsPlanModifier struct{} @@ -956,6 +953,12 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif return } + hasActiveVersion, diag := hasOneActiveVersion(configVersions) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + for i := range planVersions { hash, err := computeDirectoryHash(planVersions[i].Directory.ValueString()) if err != nil { @@ -974,6 +977,13 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif // If this is the first read, init the private state value if lvBytes == nil { lv = make(LastVersionsByHash) + // If there's no prior private state, this might be resource creation, + // in which case one version must be active. + if !hasActiveVersion { + resp.Diagnostics.AddError("Client Error", "At least one template version must be active when creating a"+ + " `coderd_template` resource.\n(Subsequent resource updates can be made without an active template in the list).") + return + } } else { err := json.Unmarshal(lvBytes, &lv) if err != nil { @@ -982,9 +992,37 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif } } - planVersions.reconcileVersionIDs(lv, configVersions) + diag = planVersions.reconcileVersionIDs(lv, configVersions, hasActiveVersion) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + + resp.PlanValue, diag = types.ListValueFrom(ctx, req.PlanValue.ElementType(ctx), planVersions) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + } +} - resp.PlanValue, resp.Diagnostics = types.ListValueFrom(ctx, req.PlanValue.ElementType(ctx), planVersions) +func hasOneActiveVersion(data Versions) (hasActiveVersion bool, diags diag.Diagnostics) { + active := false + for _, version := range data { + if version.Active.IsNull() || version.Active.IsUnknown() { + // If null or unknown, the value will be defaulted to false + continue + } + if version.Active.ValueBool() { + if active { + diags.AddError("Client Error", "Only one template version can be active at a time.") + return + } + active = true + } + } + if !active { + return false, diags + } + return true, diags } func NewVersionsPlanModifier() planmodifier.List { @@ -1309,6 +1347,7 @@ type PreviousTemplateVersion struct { ID uuid.UUID `json:"id"` Name string `json:"name"` TFVars map[string]string `json:"tf_vars"` + Active bool `json:"active"` } type privateState interface { @@ -1331,6 +1370,7 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d ID: version.ID.ValueUUID(), Name: version.Name.ValueString(), TFVars: tfVars, + Active: version.Active.ValueBool(), }) } else { lv[version.DirectoryHash.ValueString()] = []PreviousTemplateVersion{ @@ -1338,6 +1378,7 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d ID: version.ID.ValueUUID(), Name: version.Name.ValueString(), TFVars: tfVars, + Active: version.Active.ValueBool(), }, } } @@ -1350,7 +1391,7 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d return ps.SetKey(ctx, LastVersionsKey, lvBytes) } -func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVersions Versions) { +func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVersions Versions, hasOneActiveVersion bool) (diag diag.Diagnostics) { // We remove versions that we've matched from `lv`, so make a copy for // resolving tfvar changes at the end. fullLv := make(LastVersionsByHash) @@ -1420,6 +1461,39 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe } } } + + // If a version was deactivated, and no active version was set, we need to + // return an error to avoid a post-apply plan being non-empty. + if !hasOneActiveVersion { + for i := range planVersions { + if !planVersions[i].ID.IsUnknown() { + prevs, ok := fullLv[planVersions[i].DirectoryHash.ValueString()] + if !ok { + continue + } + if versionDeactivated(prevs, &planVersions[i]) { + diag.AddError("Client Error", "Plan could not determine which version should be active.\n"+ + "Either specify an active version or modify the contents of the previously active version before marking it as inactive.") + return diag + } + } + } + } + return diag +} + +func versionDeactivated(prevs []PreviousTemplateVersion, planned *TemplateVersion) bool { + for _, prev := range prevs { + if prev.ID == planned.ID.ValueUUID() { + if prev.Active && + !planned.Active.IsNull() && + !planned.Active.IsUnknown() && + !planned.Active.ValueBool() { + return true + } + } + } + return false } func tfVariablesChanged(prevs []PreviousTemplateVersion, planned *TemplateVersion) bool { diff --git a/internal/provider/template_resource_test.go b/internal/provider/template_resource_test.go index c844da0..5bb1e3b 100644 --- a/internal/provider/template_resource_test.go +++ b/internal/provider/template_resource_test.go @@ -245,7 +245,7 @@ func TestAccTemplateResource(t *testing.T) { Versions: []testAccTemplateVersionConfig{ { // Auto-generated version name - Directory: ptr.Ref("../../integration/template-test/example-template-2/"), + Directory: &exTemplateTwo, TerraformVariables: []testAccTemplateKeyValueConfig{ { Key: ptr.Ref("name"), @@ -256,13 +256,14 @@ func TestAccTemplateResource(t *testing.T) { }, { // Auto-generated version name - Directory: ptr.Ref("../../integration/template-test/example-template-2/"), + Directory: &exTemplateTwo, TerraformVariables: []testAccTemplateKeyValueConfig{ { Key: ptr.Ref("name"), Value: ptr.Ref("world"), }, }, + Active: ptr.Ref(false), }, }, ACL: testAccTemplateACLConfig{ @@ -282,11 +283,11 @@ func TestAccTemplateResource(t *testing.T) { cfg4 := cfg1 cfg4.Versions = slices.Clone(cfg4.Versions) - cfg4.Versions[0].Directory = ptr.Ref("../../integration/template-test/example-template/") + cfg4.Versions[0].Directory = &exTemplateOne cfg5 := cfg4 cfg5.Versions = slices.Clone(cfg5.Versions) - cfg5.Versions[1].Directory = ptr.Ref("../../integration/template-test/example-template/") + cfg5.Versions[1].Directory = &exTemplateOne cfg6 := cfg5 cfg6.Versions = slices.Clone(cfg6.Versions) @@ -373,7 +374,7 @@ func TestAccTemplateResource(t *testing.T) { Versions: []testAccTemplateVersionConfig{ { // Auto-generated version name - Directory: ptr.Ref("../../integration/template-test/example-template-2/"), + Directory: &exTemplateTwo, TerraformVariables: []testAccTemplateKeyValueConfig{ { Key: ptr.Ref("name"), @@ -417,6 +418,222 @@ func TestAccTemplateResource(t *testing.T) { }, }) }) + + t.Run("CreateWithNoActiveVersionErrors", func(t *testing.T) { + cfg1 := testAccTemplateResourceConfig{ + URL: client.URL.String(), + Token: client.SessionToken(), + Name: ptr.Ref("example-template"), + Versions: []testAccTemplateVersionConfig{ + { + // Auto-generated version name + Directory: &exTemplateOne, + Active: ptr.Ref(false), + }, + }, + ACL: testAccTemplateACLConfig{ + null: true, + }, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: cfg1.String(t), + ExpectError: regexp.MustCompile("At least one template version must be active when creating"), + }, + }, + }) + }) + + t.Run("AmbiguousActiveVersionResolvedByModifying", func(t *testing.T) { + cfg1 := testAccTemplateResourceConfig{ + URL: client.URL.String(), + Token: client.SessionToken(), + Name: ptr.Ref("example-template"), + Versions: []testAccTemplateVersionConfig{ + { + // Auto-generated version name + Directory: &exTemplateOne, + Active: ptr.Ref(true), + }, + }, + ACL: testAccTemplateACLConfig{ + null: true, + }, + } + + cfg2 := cfg1 + cfg2.Versions = slices.Clone(cfg2.Versions) + cfg2.Versions[0].Active = ptr.Ref(false) + + cfg3 := cfg2 + cfg3.Versions = slices.Clone(cfg3.Versions) + cfg3.Versions[0].Directory = &exTemplateTwo + + cfg2b := cfg1 + cfg2b.Versions = slices.Clone(cfg2b.Versions) + cfg2b.Versions = append(cfg2b.Versions, testAccTemplateVersionConfig{ + Directory: &exTemplateTwo, + Active: ptr.Ref(false), + }) + + cfg3b := cfg2b + cfg3b.Versions = slices.Clone(cfg3b.Versions) + cfg3b.Versions[1].Active = ptr.Ref(true) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: cfg1.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckNumTemplateVersions(ctx, client, 1), + ), + }, + // With an unmodified version deactivated, it's not clear what + // the active version should be. + { + Config: cfg2.String(t), + ExpectError: regexp.MustCompile("Plan could not determine which version should be active."), + }, + // If we modify the version, a new version will be created on `coderd`, + // and the old version can remain active. + { + Config: cfg3.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckNumTemplateVersions(ctx, client, 2), + resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{ + "active": regexp.MustCompile("false"), + }), + ), + }, + }, + }) + }) + + t.Run("AmbiguousActiveVersionResolvedByCreatingNewVersion", func(t *testing.T) { + cfg1 := testAccTemplateResourceConfig{ + URL: client.URL.String(), + Token: client.SessionToken(), + Name: ptr.Ref("example-template"), + Versions: []testAccTemplateVersionConfig{ + { + // Auto-generated version name + Directory: &exTemplateOne, + Active: ptr.Ref(true), + }, + }, + ACL: testAccTemplateACLConfig{ + null: true, + }, + } + + cfg2 := cfg1 + cfg2.Versions = slices.Clone(cfg2.Versions) + cfg2.Versions[0].Active = ptr.Ref(false) + cfg2.Versions = append(cfg2.Versions, testAccTemplateVersionConfig{ + Directory: &exTemplateTwo, + Active: ptr.Ref(false), + }) + + cfg3 := cfg2 + cfg3.Versions = slices.Clone(cfg3.Versions) + cfg3.Versions[1].Active = ptr.Ref(true) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: cfg1.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckNumTemplateVersions(ctx, client, 1), + ), + }, + // Adding a new version that's not active doesn't help + { + Config: cfg2.String(t), + ExpectError: regexp.MustCompile("Plan could not determine which version should be active."), + }, + // Making that new version active will fix the issue + { + Config: cfg3.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckNumTemplateVersions(ctx, client, 2), + ), + }, + }, + }) + }) + + t.Run("PushNewInactiveVersion", func(t *testing.T) { + cfg1 := testAccTemplateResourceConfig{ + URL: client.URL.String(), + Token: client.SessionToken(), + Name: ptr.Ref("example-template"), + Versions: []testAccTemplateVersionConfig{ + { + // Auto-generated version name + Directory: &exTemplateOne, + Active: ptr.Ref(true), + }, + }, + ACL: testAccTemplateACLConfig{ + null: true, + }, + } + + cfg2 := cfg1 + cfg2.Versions = slices.Clone(cfg2.Versions) + cfg2.Versions[0].Active = ptr.Ref(false) + cfg2.Versions[0].Directory = &exTemplateTwo + + cfg3 := cfg2 + cfg3.Versions = slices.Clone(cfg3.Versions) + cfg3.Versions[0].Active = ptr.Ref(true) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create one active version + { + Config: cfg1.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckNumTemplateVersions(ctx, client, 1), + ), + }, + // Modify an existing version, make it inactive + { + Config: cfg2.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckNumTemplateVersions(ctx, client, 2), + resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{ + "active": regexp.MustCompile("false"), + }), + ), + }, + // Make that modification active + { + Config: cfg3.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckNumTemplateVersions(ctx, client, 2), + resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{ + "active": regexp.MustCompile("true"), + }), + ), + }, + }, + }) + }) } func TestAccTemplateResourceEnterprise(t *testing.T) { @@ -434,6 +651,10 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { }) require.NoError(t, err) + exTemplateOne := t.TempDir() + err = cp.Copy("../../integration/template-test/example-template", exTemplateOne) + require.NoError(t, err) + cfg1 := testAccTemplateResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), @@ -441,7 +662,7 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { Versions: []testAccTemplateVersionConfig{ { // Auto-generated version name - Directory: ptr.Ref("../../integration/template-test/example-template"), + Directory: &exTemplateOne, Active: ptr.Ref(true), }, }, @@ -569,6 +790,10 @@ func TestAccTemplateResourceAGPL(t *testing.T) { firstUser, err := client.User(ctx, codersdk.Me) require.NoError(t, err) + exTemplateOne := t.TempDir() + err = cp.Copy("../../integration/template-test/example-template", exTemplateOne) + require.NoError(t, err) + cfg1 := testAccTemplateResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), @@ -576,7 +801,7 @@ func TestAccTemplateResourceAGPL(t *testing.T) { Versions: []testAccTemplateVersionConfig{ { // Auto-generated version name - Directory: ptr.Ref("../../integration/template-test/example-template/"), + Directory: &exTemplateOne, Active: ptr.Ref(true), }, }, @@ -652,8 +877,8 @@ func TestAccTemplateResourceAGPL(t *testing.T) { func TestAccTemplateResourceVariables(t *testing.T) { cfg := ` provider coderd { - url = "%s" - token = "%s" + url = %q + token = %q } data "coderd_organization" "default" { @@ -677,12 +902,12 @@ resource "coderd_template" "sample" { versions = [ { name = "${var.PRIOR_GIT_COMMIT_SHA}" - directory = "../../integration/template-test/example-template" + directory = %q active = var.ACTIVE }, { name = "${var.CURRENT_GIT_COMMIT_SHA}" - directory = "../../integration/template-test/example-template" + directory = %q active = false } ] @@ -691,7 +916,11 @@ resource "coderd_template" "sample" { ctx := context.Background() client := integration.StartCoder(ctx, t, "template_resource_variables_acc", false) - cfg = fmt.Sprintf(cfg, client.URL.String(), client.SessionToken()) + exTemplateOne := t.TempDir() + err := cp.Copy("../../integration/template-test/example-template", exTemplateOne) + require.NoError(t, err) + + cfg = fmt.Sprintf(cfg, client.URL.String(), client.SessionToken(), exTemplateOne, exTemplateOne) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -911,11 +1140,13 @@ func TestReconcileVersionIDs(t *testing.T) { aUUID := uuid.New() bUUID := uuid.New() cases := []struct { - Name string - planVersions Versions - configVersions Versions - inputState LastVersionsByHash - expectedVersions Versions + Name string + planVersions Versions + configVersions Versions + inputState LastVersionsByHash + expectedVersions Versions + cfgHasActiveVersion bool + expectError bool }{ { Name: "IdenticalDontRename", @@ -1242,13 +1473,46 @@ func TestReconcileVersionIDs(t *testing.T) { }, }, }, + { + Name: "NoPossibleActiveVersion", + planVersions: []TemplateVersion{ + { + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, + Active: types.BoolValue(false), + }, + }, + configVersions: []TemplateVersion{ + { + Name: types.StringValue("foo"), + }, + }, + inputState: map[string][]PreviousTemplateVersion{ + "aaa": { + { + ID: aUUID, + Name: "foo", + TFVars: map[string]string{}, + Active: true, + }, + }, + }, + cfgHasActiveVersion: false, + expectError: true, + }, } for _, c := range cases { c := c t.Run(c.Name, func(t *testing.T) { - c.planVersions.reconcileVersionIDs(c.inputState, c.configVersions) - require.Equal(t, c.expectedVersions, c.planVersions) + diag := c.planVersions.reconcileVersionIDs(c.inputState, c.configVersions, c.cfgHasActiveVersion) + if c.expectError { + require.True(t, diag.HasError()) + } else { + require.Equal(t, c.expectedVersions, c.planVersions) + } }) } From 44190949e130eaaf4094e9c2b6469b975ec6f2c8 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 21 Jan 2025 09:06:17 +0000 Subject: [PATCH 2/2] review --- internal/provider/template_resource.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/provider/template_resource.go b/internal/provider/template_resource.go index d02e12f..8f72b52 100644 --- a/internal/provider/template_resource.go +++ b/internal/provider/template_resource.go @@ -1019,10 +1019,7 @@ func hasOneActiveVersion(data Versions) (hasActiveVersion bool, diags diag.Diagn active = true } } - if !active { - return false, diags - } - return true, diags + return active, diags } func NewVersionsPlanModifier() planmodifier.List { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy