Skip to content

Commit 6b516cf

Browse files
committed
Add parameter
1 parent 9a12972 commit 6b516cf

File tree

4 files changed

+508
-3
lines changed

4 files changed

+508
-3
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
data "coder_parameter" "example" {
2+
display_name = "Region"
3+
description = "Specify a region to place your workspace."
4+
immutable = true
5+
type = "string"
6+
option {
7+
value = "us-central1-a"
8+
label = "US Central"
9+
icon = "/icon/usa.svg"
10+
}
11+
option {
12+
value = "asia-central1-a"
13+
label = "Asia"
14+
icon = "/icon/asia.svg"
15+
}
16+
}
17+
18+
data "coder_parameter" "ami" {
19+
display_name = "Machine Image"
20+
option {
21+
value = "ami-xxxxxxxx"
22+
label = "Ubuntu"
23+
icon = "/icon/ubuntu.svg"
24+
}
25+
}
26+
27+
data "coder_parameter" "image" {
28+
display_name = "Docker Image"
29+
icon = "/icon/docker.svg"
30+
type = "bool"
31+
}
32+
33+
data "coder_parameter" "cores" {
34+
display_name = "CPU Cores"
35+
icon = "/icon/"
36+
}
37+
38+
data "coder_parameter" "disk_size" {
39+
display_name = "Disk Size"
40+
type = "number"
41+
increase_only = true
42+
validation {
43+
# This can apply to number and string types.
44+
min = 0
45+
max = 10
46+
}
47+
}

internal/provider/provider.go

Lines changed: 215 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,198 @@ func New() *schema.Provider {
178178
},
179179
},
180180
},
181+
"coder_parameter": {
182+
Description: "Use this data source to configure editable options for workspaces.",
183+
ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
184+
rd.SetId(uuid.NewString())
185+
186+
name := rd.Get("name").(string)
187+
typ := rd.Get("type").(string)
188+
var value string
189+
rawDefaultValue, ok := rd.GetOk("default")
190+
if ok {
191+
defaultValue := rawDefaultValue.(string)
192+
err := valueIsType(typ, defaultValue)
193+
if err != nil {
194+
return err
195+
}
196+
value = defaultValue
197+
}
198+
envValue, ok := os.LookupEnv(fmt.Sprintf("CODER_PARAMETER_%s", name))
199+
if ok {
200+
value = envValue
201+
}
202+
rd.Set("value", value)
203+
204+
rawOptions, exists := rd.GetOk("option")
205+
if exists {
206+
rawArrayOptions, valid := rawOptions.([]interface{})
207+
if !valid {
208+
return diag.Errorf("options is of wrong type %T", rawArrayOptions)
209+
}
210+
optionDisplayNames := map[string]interface{}{}
211+
optionValues := map[string]interface{}{}
212+
for _, rawOption := range rawArrayOptions {
213+
option, valid := rawOption.(map[string]interface{})
214+
if !valid {
215+
return diag.Errorf("option is of wrong type %T", rawOption)
216+
}
217+
rawName, ok := option["name"]
218+
if !ok {
219+
return diag.Errorf("no name for %+v", option)
220+
}
221+
displayName, ok := rawName.(string)
222+
if !ok {
223+
return diag.Errorf("display name is of wrong type %T", displayName)
224+
}
225+
_, exists := optionDisplayNames[displayName]
226+
if exists {
227+
return diag.Errorf("multiple options cannot have the same display name %q", displayName)
228+
}
229+
230+
rawValue, ok := option["value"]
231+
if !ok {
232+
return diag.Errorf("no value for %+v\n", option)
233+
}
234+
value, ok := rawValue.(string)
235+
if !ok {
236+
return diag.Errorf("")
237+
}
238+
_, exists = optionValues[value]
239+
if exists {
240+
return diag.Errorf("multiple options cannot have the same value %q", value)
241+
}
242+
err := valueIsType(typ, value)
243+
if err != nil {
244+
return err
245+
}
246+
247+
optionValues[value] = nil
248+
optionDisplayNames[displayName] = nil
249+
}
250+
}
251+
252+
return nil
253+
},
254+
Schema: map[string]*schema.Schema{
255+
"value": {
256+
Type: schema.TypeString,
257+
Computed: true,
258+
Description: "The output value of a parameter.",
259+
},
260+
"name": {
261+
Type: schema.TypeString,
262+
Required: true,
263+
Description: "The name of the parameter as it appears in the interface. If this is changed, the parameter will need to be re-updated by developers.",
264+
},
265+
"description": {
266+
Type: schema.TypeString,
267+
Optional: true,
268+
Description: "Explain what the parameter does.",
269+
},
270+
"type": {
271+
Type: schema.TypeString,
272+
Default: "string",
273+
Optional: true,
274+
ValidateFunc: validation.StringInSlice([]string{"number", "string", "bool"}, false),
275+
},
276+
"immutable": {
277+
Type: schema.TypeBool,
278+
Optional: true,
279+
Description: "Whether the value can be changed after it's set initially.",
280+
},
281+
"default": {
282+
Type: schema.TypeString,
283+
Optional: true,
284+
Description: "A default value for the parameter.",
285+
ExactlyOneOf: []string{"option"},
286+
},
287+
"icon": {
288+
Type: schema.TypeString,
289+
Description: "A URL to an icon that will display in the dashboard. View built-in " +
290+
"icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a " +
291+
"built-in icon with `data.coder_workspace.me.access_url + \"/icon/<path>\"`.",
292+
ForceNew: true,
293+
Optional: true,
294+
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
295+
_, err := url.Parse(s)
296+
if err != nil {
297+
return nil, []error{err}
298+
}
299+
return nil, nil
300+
},
301+
},
302+
"option": {
303+
Type: schema.TypeList,
304+
Description: "Each \"option\" block defines a single displayable value for a user to select.",
305+
ForceNew: true,
306+
Optional: true,
307+
MaxItems: 64,
308+
Elem: &schema.Resource{
309+
Schema: map[string]*schema.Schema{
310+
"name": {
311+
Type: schema.TypeString,
312+
Description: "The display name of this value in the UI.",
313+
ForceNew: true,
314+
Required: true,
315+
},
316+
"description": {
317+
Type: schema.TypeString,
318+
Description: "Add a description to select this item.",
319+
ForceNew: true,
320+
Optional: true,
321+
},
322+
"value": {
323+
Type: schema.TypeString,
324+
Description: "The value of this option.",
325+
ForceNew: true,
326+
Required: true,
327+
},
328+
"icon": {
329+
Type: schema.TypeString,
330+
Description: "A URL to an icon that will display in the dashboard. View built-in " +
331+
"icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a " +
332+
"built-in icon with `data.coder_workspace.me.access_url + \"/icon/<path>\"`.",
333+
ForceNew: true,
334+
Optional: true,
335+
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
336+
_, err := url.Parse(s)
337+
if err != nil {
338+
return nil, []error{err}
339+
}
340+
return nil, nil
341+
},
342+
},
343+
},
344+
},
345+
},
346+
"validation": {
347+
Type: schema.TypeSet,
348+
MaxItems: 1,
349+
Optional: true,
350+
Elem: &schema.Resource{
351+
Schema: map[string]*schema.Schema{
352+
"min": {
353+
Type: schema.TypeInt,
354+
Optional: true,
355+
Default: 0,
356+
Description: "The minimum for a number to be.",
357+
},
358+
"max": {
359+
Type: schema.TypeInt,
360+
Optional: true,
361+
Description: "The maximum for a number to be.",
362+
},
363+
"regex": {
364+
Type: schema.TypeString,
365+
ExactlyOneOf: []string{"min", "max"},
366+
Optional: true,
367+
},
368+
},
369+
},
370+
},
371+
},
372+
},
181373
"coder_provisioner": {
182374
Description: "Use this data source to get information about the Coder provisioner.",
183375
ReadContext: func(c context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
@@ -339,8 +531,8 @@ func New() *schema.Provider {
339531
"icon": {
340532
Type: schema.TypeString,
341533
Description: "A URL to an icon that will display in the dashboard. View built-in " +
342-
"icons here: https://github.com/coder/coder/tree/main/site/static/icons. Use a " +
343-
"built-in icon with `data.coder_workspace.me.access_url + \"/icons/<path>\"`.",
534+
"icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a " +
535+
"built-in icon with `data.coder_workspace.me.access_url + \"/icon/<path>\"`.",
344536
ForceNew: true,
345537
Optional: true,
346538
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
@@ -445,7 +637,7 @@ func New() *schema.Provider {
445637
Type: schema.TypeString,
446638
Description: "A URL to an icon that will display in the dashboard. View built-in " +
447639
"icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a " +
448-
"built-in icon with `data.coder_workspace.me.access_url + \"/icons/<path>\"`.",
640+
"built-in icon with `data.coder_workspace.me.access_url + \"/icon/<path>\"`.",
449641
ForceNew: true,
450642
Optional: true,
451643
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
@@ -499,6 +691,26 @@ func New() *schema.Provider {
499691
}
500692
}
501693

694+
func valueIsType(typ, value string) diag.Diagnostics {
695+
switch typ {
696+
case "number":
697+
_, err := strconv.ParseFloat(value, 64)
698+
if err != nil {
699+
return diag.Errorf("%q is not a number", value)
700+
}
701+
case "bool":
702+
_, err := strconv.ParseBool(value)
703+
if err != nil {
704+
return diag.Errorf("%q is not a bool", value)
705+
}
706+
case "string":
707+
// Anything is a string!
708+
default:
709+
return diag.Errorf("invalid type %q", typ)
710+
}
711+
return nil
712+
}
713+
502714
// updateInitScript fetches parameters from a "coder_agent" to produce the
503715
// agent script from environment variables.
504716
func updateInitScript(resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {

0 commit comments

Comments
 (0)