Skip to content

Commit bf558f5

Browse files
fix: create new template version when tfvars change (#98)
Closes #97.
1 parent 09e0394 commit bf558f5

File tree

3 files changed

+319
-77
lines changed

3 files changed

+319
-77
lines changed

docs/resources/template.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ page_title: "coderd_template Resource - terraform-provider-coderd"
44
subcategory: ""
55
description: |-
66
A Coder template.
7-
Logs from building template versions are streamed from the provisioner when the TF_LOG environment variable is INFO or higher.
7+
Logs from building template versions can be optionally streamed from the provisioner by setting the TF_LOG environment variable to INFO or higher.
88
When importing, the ID supplied can be either a template UUID retrieved via the API or <organization-name>/<template-name>.
99
---
1010

1111
# coderd_template (Resource)
1212

1313
A Coder template.
1414

15-
Logs from building template versions are streamed from the provisioner when the `TF_LOG` environment variable is `INFO` or higher.
15+
Logs from building template versions can be optionally streamed from the provisioner by setting the `TF_LOG` environment variable to `INFO` or higher.
1616

1717
When importing, the ID supplied can be either a template UUID retrieved via the API or `<organization-name>/<template-name>`.
1818

@@ -101,7 +101,7 @@ Optional:
101101

102102
- `active` (Boolean) Whether this version is the active version of the template. Only one version can be active at a time.
103103
- `message` (String) A message describing the changes in this version of the template. Messages longer than 72 characters will be truncated.
104-
- `name` (String) The name of the template version. Automatically generated if not provided. If provided, the name *must* change each time the directory contents are updated.
104+
- `name` (String) The name of the template version. Automatically generated if not provided. If provided, the name *must* change each time the directory contents, or the `tf_vars` attribute are updated.
105105
- `provisioner_tags` (Attributes Set) Provisioner tags for the template version. (see [below for nested schema](#nestedatt--versions--provisioner_tags))
106106
- `tf_vars` (Attributes Set) Terraform variables for the template version. (see [below for nested schema](#nestedatt--versions--tf_vars))
107107

internal/provider/template_resource.go

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"io"
9+
"slices"
910
"strings"
1011

1112
"cdr.dev/slog"
@@ -230,8 +231,8 @@ func (r *TemplateResource) Metadata(ctx context.Context, req resource.MetadataRe
230231

231232
func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
232233
resp.Schema = schema.Schema{
233-
MarkdownDescription: "A Coder template.\n\nLogs from building template versions are streamed from the provisioner " +
234-
"when the `TF_LOG` environment variable is `INFO` or higher.\n\n" +
234+
MarkdownDescription: "A Coder template.\n\nLogs from building template versions can be optionally streamed from the provisioner " +
235+
"by setting the `TF_LOG` environment variable to `INFO` or higher.\n\n" +
235236
"When importing, the ID supplied can be either a template UUID retrieved via the API or `<organization-name>/<template-name>`.",
236237

237238
Attributes: map[string]schema.Attribute{
@@ -395,7 +396,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
395396
Computed: true,
396397
},
397398
"name": schema.StringAttribute{
398-
MarkdownDescription: "The name of the template version. Automatically generated if not provided. If provided, the name *must* change each time the directory contents are updated.",
399+
MarkdownDescription: "The name of the template version. Automatically generated if not provided. If provided, the name *must* change each time the directory contents, or the `tf_vars` attribute are updated.",
399400
Optional: true,
400401
Computed: true,
401402
Validators: []validator.String{
@@ -1058,7 +1059,7 @@ func markActive(ctx context.Context, client *codersdk.Client, templateID uuid.UU
10581059
ID: versionID,
10591060
})
10601061
if err != nil {
1061-
return fmt.Errorf("Failed to update active template version: %s", err)
1062+
return fmt.Errorf("failed to update active template version: %s", err)
10621063
}
10631064
tflog.Info(ctx, "marked template version as active")
10641065
return nil
@@ -1236,8 +1237,9 @@ type LastVersionsByHash = map[string][]PreviousTemplateVersion
12361237
var LastVersionsKey = "last_versions"
12371238

12381239
type PreviousTemplateVersion struct {
1239-
ID uuid.UUID `json:"id"`
1240-
Name string `json:"name"`
1240+
ID uuid.UUID `json:"id"`
1241+
Name string `json:"name"`
1242+
TFVars map[string]string `json:"tf_vars"`
12411243
}
12421244

12431245
type privateState interface {
@@ -1249,18 +1251,24 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d
12491251
lv := make(LastVersionsByHash)
12501252
for _, version := range v {
12511253
vbh, ok := lv[version.DirectoryHash.ValueString()]
1254+
tfVars := make(map[string]string, len(version.TerraformVariables))
1255+
for _, tfVar := range version.TerraformVariables {
1256+
tfVars[tfVar.Name.ValueString()] = tfVar.Value.ValueString()
1257+
}
12521258
// Store the IDs and names of all versions with the same directory hash,
12531259
// in the order they appear
12541260
if ok {
12551261
lv[version.DirectoryHash.ValueString()] = append(vbh, PreviousTemplateVersion{
1256-
ID: version.ID.ValueUUID(),
1257-
Name: version.Name.ValueString(),
1262+
ID: version.ID.ValueUUID(),
1263+
Name: version.Name.ValueString(),
1264+
TFVars: tfVars,
12581265
})
12591266
} else {
12601267
lv[version.DirectoryHash.ValueString()] = []PreviousTemplateVersion{
12611268
{
1262-
ID: version.ID.ValueUUID(),
1263-
Name: version.Name.ValueString(),
1269+
ID: version.ID.ValueUUID(),
1270+
Name: version.Name.ValueString(),
1271+
TFVars: tfVars,
12641272
},
12651273
}
12661274
}
@@ -1274,6 +1282,13 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d
12741282
}
12751283

12761284
func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVersions Versions) {
1285+
// We remove versions that we've matched from `lv`, so make a copy for
1286+
// resolving tfvar changes at the end.
1287+
fullLv := make(LastVersionsByHash)
1288+
for k, v := range lv {
1289+
fullLv[k] = slices.Clone(v)
1290+
}
1291+
12771292
for i := range planVersions {
12781293
prevList, ok := lv[planVersions[i].DirectoryHash.ValueString()]
12791294
// If not in state, mark as known after apply since we'll create a new version.
@@ -1313,6 +1328,49 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe
13131328
lv[planVersions[i].DirectoryHash.ValueString()] = prevList[1:]
13141329
}
13151330
}
1331+
1332+
// If only the Terraform variables have changed,
1333+
// we need to create a new version with the new variables.
1334+
for i := range planVersions {
1335+
if !planVersions[i].ID.IsUnknown() {
1336+
prevs, ok := fullLv[planVersions[i].DirectoryHash.ValueString()]
1337+
if !ok {
1338+
continue
1339+
}
1340+
if tfVariablesChanged(prevs, &planVersions[i]) {
1341+
planVersions[i].ID = NewUUIDUnknown()
1342+
// We could always set the name to unknown here, to generate a
1343+
// random one (this is what the Web UI currently does when
1344+
// only updating tfvars).
1345+
// However, I think it'd be weird if the provider just started
1346+
// ignoring the name you set in the config, we'll instead
1347+
// require that users update the name if they update the tfvars.
1348+
if configVersions[i].Name.IsNull() {
1349+
planVersions[i].Name = types.StringUnknown()
1350+
}
1351+
}
1352+
}
1353+
}
1354+
}
1355+
1356+
func tfVariablesChanged(prevs []PreviousTemplateVersion, planned *TemplateVersion) bool {
1357+
for _, prev := range prevs {
1358+
if prev.ID == planned.ID.ValueUUID() {
1359+
// If the previous version has no TFVars, then it was created using
1360+
// an older provider version.
1361+
if prev.TFVars == nil {
1362+
return true
1363+
}
1364+
for _, tfVar := range planned.TerraformVariables {
1365+
if prev.TFVars[tfVar.Name.ValueString()] != tfVar.Value.ValueString() {
1366+
return true
1367+
}
1368+
}
1369+
return false
1370+
}
1371+
}
1372+
return true
1373+
13161374
}
13171375

13181376
func formatLogs(err error, logs []codersdk.ProvisionerJobLog) string {

0 commit comments

Comments
 (0)
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