@@ -163,8 +163,8 @@ type TemplateVersion struct {
163163 Directory types.String `tfsdk:"directory"`
164164 DirectoryHash types.String `tfsdk:"directory_hash"`
165165 Active types.Bool `tfsdk:"active"`
166- TerraformVariables [] Variable `tfsdk:"tf_vars"`
167- ProvisionerTags [] Variable `tfsdk:"provisioner_tags"`
166+ TerraformVariables types. Set `tfsdk:"tf_vars"`
167+ ProvisionerTags types. Set `tfsdk:"provisioner_tags"`
168168}
169169
170170type Versions []TemplateVersion
@@ -183,6 +183,34 @@ type Variable struct {
183183 Value types.String `tfsdk:"value"`
184184}
185185
186+ // variableAttrTypes returns the attribute type map for Variable objects
187+ // used to construct types.Set values.
188+ func variableAttrTypes () map [string ]attr.Type {
189+ return map [string ]attr.Type {
190+ "name" : types .StringType ,
191+ "value" : types .StringType ,
192+ }
193+ }
194+
195+ // variableObjectType returns the object type used as the element type
196+ // in Set attributes for Variable.
197+ func variableObjectType () types.ObjectType {
198+ return types.ObjectType {
199+ AttrTypes : variableAttrTypes (),
200+ }
201+ }
202+
203+ // varsFromSet converts a types.Set to a slice of Variable structs.
204+ // It returns nil with no error when the set is null or unknown.
205+ func varsFromSet (ctx context.Context , varSet types.Set ) ([]Variable , diag.Diagnostics ) {
206+ if varSet .IsNull () || varSet .IsUnknown () {
207+ return nil , nil
208+ }
209+ var vars []Variable
210+ diags := varSet .ElementsAs (ctx , & vars , false )
211+ return vars , diags
212+ }
213+
186214var variableNestedObject = schema.NestedAttributeObject {
187215 Attributes : map [string ]schema.Attribute {
188216 "name" : schema.StringAttribute {
@@ -1043,7 +1071,7 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif
10431071 }
10441072 }
10451073
1046- diag = planVersions .reconcileVersionIDs (lv , configVersions , hasActiveVersion )
1074+ diag = planVersions .reconcileVersionIDs (ctx , lv , configVersions , hasActiveVersion )
10471075 if diag .HasError () {
10481076 resp .Diagnostics .Append (diag ... )
10491077 return
@@ -1199,14 +1227,22 @@ func newVersion(ctx context.Context, client *codersdk.Client, req newVersionRequ
11991227 tflog .Info (ctx , "discovered and parsed vars files" , map [string ]any {
12001228 "vars" : vars ,
12011229 })
1202- for _ , variable := range req .Version .TerraformVariables {
1230+ tfVarSlice , varDiags := varsFromSet (ctx , req .Version .TerraformVariables )
1231+ if varDiags .HasError () {
1232+ return nil , logs , fmt .Errorf ("failed to convert terraform variables: %s" , varDiags .Errors ())
1233+ }
1234+ for _ , variable := range tfVarSlice {
12031235 vars = append (vars , codersdk.VariableValue {
12041236 Name : variable .Name .ValueString (),
12051237 Value : variable .Value .ValueString (),
12061238 })
12071239 }
1208- provTags := make (map [string ]string , len (req .Version .ProvisionerTags ))
1209- for _ , provisionerTag := range req .Version .ProvisionerTags {
1240+ provTagSlice , tagDiags := varsFromSet (ctx , req .Version .ProvisionerTags )
1241+ if tagDiags .HasError () {
1242+ return nil , logs , fmt .Errorf ("failed to convert provisioner tags: %s" , tagDiags .Errors ())
1243+ }
1244+ provTags := make (map [string ]string , len (provTagSlice ))
1245+ for _ , provisionerTag := range provTagSlice {
12101246 provTags [provisionerTag .Name .ValueString ()] = provisionerTag .Value .ValueString ()
12111247 }
12121248 tmplVerReq := codersdk.CreateTemplateVersionRequest {
@@ -1444,8 +1480,13 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d
14441480 lv := make (LastVersionsByHash )
14451481 for _ , version := range v {
14461482 vbh , ok := lv [version .DirectoryHash .ValueString ()]
1447- tfVars := make (map [string ]string , len (version .TerraformVariables ))
1448- for _ , tfVar := range version .TerraformVariables {
1483+ tfVarSlice , varDiags := varsFromSet (ctx , version .TerraformVariables )
1484+ if varDiags .HasError () {
1485+ diags .Append (varDiags ... )
1486+ return diags
1487+ }
1488+ tfVars := make (map [string ]string , len (tfVarSlice ))
1489+ for _ , tfVar := range tfVarSlice {
14491490 tfVars [tfVar .Name .ValueString ()] = tfVar .Value .ValueString ()
14501491 }
14511492 // Store the IDs and names of all versions with the same directory hash,
@@ -1476,7 +1517,7 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d
14761517 return ps .SetKey (ctx , LastVersionsKey , lvBytes )
14771518}
14781519
1479- func (planVersions Versions ) reconcileVersionIDs (lv LastVersionsByHash , configVersions Versions , hasOneActiveVersion bool ) (diag diag.Diagnostics ) {
1520+ func (planVersions Versions ) reconcileVersionIDs (ctx context. Context , lv LastVersionsByHash , configVersions Versions , hasOneActiveVersion bool ) (diag diag.Diagnostics ) {
14801521 // We remove versions that we've matched from `lv`, so make a copy for
14811522 // resolving tfvar changes at the end.
14821523 fullLv := make (LastVersionsByHash )
@@ -1532,7 +1573,7 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe
15321573 if ! ok {
15331574 continue
15341575 }
1535- if tfVariablesChanged (prevs , & planVersions [i ]) {
1576+ if tfVariablesChanged (ctx , prevs , & planVersions [i ]) {
15361577 planVersions [i ].ID = NewUUIDUnknown ()
15371578 // We could always set the name to unknown here, to generate a
15381579 // random one (this is what the Web UI currently does when
@@ -1581,15 +1622,24 @@ func versionDeactivated(prevs []PreviousTemplateVersion, planned *TemplateVersio
15811622 return false
15821623}
15831624
1584- func tfVariablesChanged (prevs []PreviousTemplateVersion , planned * TemplateVersion ) bool {
1625+ func tfVariablesChanged (ctx context. Context , prevs []PreviousTemplateVersion , planned * TemplateVersion ) bool {
15851626 for _ , prev := range prevs {
15861627 if prev .ID == planned .ID .ValueUUID () {
15871628 // If the previous version has no TFVars, then it was created using
15881629 // an older provider version.
15891630 if prev .TFVars == nil {
15901631 return true
15911632 }
1592- for _ , tfVar := range planned .TerraformVariables {
1633+ plannedVars , diags := varsFromSet (ctx , planned .TerraformVariables )
1634+ if diags .HasError () {
1635+ return true
1636+ }
1637+ // If the set is unknown or null, we cannot compare and
1638+ // must treat it as changed.
1639+ if planned .TerraformVariables .IsUnknown () || planned .TerraformVariables .IsNull () {
1640+ return true
1641+ }
1642+ for _ , tfVar := range plannedVars {
15931643 if prev .TFVars [tfVar .Name .ValueString ()] != tfVar .Value .ValueString () {
15941644 return true
15951645 }
@@ -1598,7 +1648,6 @@ func tfVariablesChanged(prevs []PreviousTemplateVersion, planned *TemplateVersio
15981648 }
15991649 }
16001650 return true
1601-
16021651}
16031652
16041653func formatLogs (err error , logs []codersdk.ProvisionerJobLog ) string {
0 commit comments