@@ -15,7 +15,8 @@ import (
15
15
type parameterValueSource int
16
16
17
17
const (
18
- sourcePrevious parameterValueSource = iota
18
+ sourceDefault parameterValueSource = iota
19
+ sourcePrevious
19
20
sourceBuild
20
21
sourcePreset
21
22
)
@@ -36,24 +37,26 @@ func ResolveParameters(
36
37
presetValues []database.TemplateVersionPresetParameter ,
37
38
) (map [string ]string , hcl.Diagnostics ) {
38
39
previousValuesMap := slice .ToMap (previousValues ,func (p database.WorkspaceBuildParameter ) (string ,string ) {
39
- return p .Value ,p .Value
40
+ return p .Name ,p .Value
40
41
})
41
42
42
43
// Start with previous
43
44
values := parameterValueMap (slice .ToMap (previousValues ,func (p database.WorkspaceBuildParameter ) (string ,parameterValue ) {
44
45
return p .Name ,parameterValue {Source :sourcePrevious ,Value :p .Value }
45
46
}))
46
47
47
- // Add build values
48
+ // Add build values (overwrite previous values if they exist)
48
49
for _ ,buildValue := range buildValues {
49
50
values [buildValue .Name ]= parameterValue {Source :sourceBuild ,Value :buildValue .Value }
50
51
}
51
52
52
- // Add preset values
53
+ // Add preset values (overwrite previous and build values if they exist)
53
54
for _ ,preset := range presetValues {
54
55
values [preset .Name ]= parameterValue {Source :sourcePreset ,Value :preset .Value }
55
56
}
56
57
58
+ // originalValues is going to be used to detect if a user tried to change
59
+ // an immutable parameter after the first build.
57
60
originalValues := make (map [string ]parameterValue ,len (values ))
58
61
for name ,value := range values {
59
62
// Store the original values for later use.
@@ -63,7 +66,7 @@ func ResolveParameters(
63
66
// Render the parameters using the values that were supplied to the previous build.
64
67
//
65
68
// This is how the form should look to the user on their workspace settings page.
66
- // This is the original form truth that our validations should be based on going forward .
69
+ // This is the original form truth that our validations shouldinitially be based on.
67
70
output ,diags := renderer .Render (ctx ,ownerID ,values .ValuesMap ())
68
71
if diags .HasErrors () {
69
72
// Top level diagnostics should break the build. Previous values (and new) should
@@ -81,7 +84,8 @@ func ResolveParameters(
81
84
// The output parameters
82
85
for _ ,parameter := range output .Parameters {
83
86
// Ephemeral parameters should not be taken from the previous build.
84
- // Remove their values from the input if they are sourced from the previous build.
87
+ // They must always be explicitly set in every build.
88
+ // So remove their values if they are sourced from the previous build.
85
89
if parameter .Ephemeral {
86
90
v := values [parameter .Name ]
87
91
if v .Source == sourcePrevious {
@@ -92,6 +96,8 @@ func ResolveParameters(
92
96
// Immutable parameters should also not be allowed to be changed from
93
97
// the previous build. Remove any values taken from the preset or
94
98
// new build params. This forces the value to be the same as it was before.
99
+ //
100
+ // We do this so the next form render uses the original immutable value.
95
101
if ! firstBuild && ! parameter .Mutable {
96
102
delete (values ,parameter .Name )
97
103
prev ,ok := previousValuesMap [parameter .Name ]
@@ -111,8 +117,15 @@ func ResolveParameters(
111
117
return nil ,diags
112
118
}
113
119
120
+ // parameterNames is going to be used to remove any excess values that were left
121
+ // around without a parameter.
122
+ parameterNames := make (map [string ]struct {},len (output .Parameters ))
114
123
for _ ,parameter := range output .Parameters {
124
+ parameterNames [parameter .Name ]= struct {}{}
125
+
115
126
if ! firstBuild && ! parameter .Mutable {
127
+ // Immutable parameters should not be changed after the first build.
128
+ // They can match the original value though!
116
129
if parameter .Value .AsString ()!= originalValues [parameter .Name ].Value {
117
130
var src * hcl.Range
118
131
if parameter .Source != nil {
@@ -135,12 +148,34 @@ func ResolveParameters(
135
148
// All validation errors are raised here.
136
149
diags = diags .Extend (hcl .Diagnostics (parameter .Diagnostics ))
137
150
}
151
+
152
+ // If the parameter has a value, but it was not set explicitly by the user at any
153
+ // build, then save the default value. An example where this is important is if a
154
+ // template has a default value of 'region = us-west-2', but the user never sets
155
+ // it. If the default value changes to 'region = us-east-1', we want to preserve
156
+ // the original value of 'us-west-2' for the existing workspaces.
157
+ //
158
+ // parameter.Value will be populated from the default at this point. So grab it
159
+ // from there.
160
+ if _ ,ok := values [parameter .Name ];! ok && parameter .Value .IsKnown ()&& parameter .Value .Valid () {
161
+ values [parameter .Name ]= parameterValue {
162
+ Value :parameter .Value .AsString (),
163
+ Source :sourceDefault ,
164
+ }
165
+ }
166
+ }
167
+
168
+ // Delete any values that do not belong to a parameter. This is to not save
169
+ // parameter values that have no effect. These leaky parameter values can cause
170
+ // problems in the future, as it makes it challenging to remove values from the
171
+ // database
172
+ for k := range values {
173
+ if _ ,ok := parameterNames [k ];! ok {
174
+ delete (values ,k )
175
+ }
138
176
}
139
177
140
178
// Return the values to be saved for the build.
141
- // TODO: The previous code always returned parameter names and values, even if they were not set
142
- // by the user. So this should loop over the parameters and return all of them.
143
- // This catches things like if a default value changes, we keep the old value.
144
179
return values .ValuesMap (),diags
145
180
}
146
181