Skip to content

Commit 78f91c9

Browse files
author
lakshyadangwal
committed
Fix multiple issues:
- #503: Add 'name' attribute to coder_devcontainer to allow unique sub-agent names when using for_each/count - #466: Add 'name' attribute to coder_agent to avoid duplicate agent name errors with for_each - #517: Add 'coder_access_url' attribute to coder_agent to override the host/access URL - #464: Add 'keyvalue' parameter type for Key=Value table input (env vars/secrets) - #462: Allow number parameters without default by making 'optional' user-configurable
1 parent 97991e3 commit 78f91c9

4 files changed

Lines changed: 86 additions & 16 deletions

File tree

‎provider/agent.go‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/sha256"
66
"encoding/hex"
77
"fmt"
8+
"net/url"
89
"path/filepath"
910
"reflect"
1011
"strings"
@@ -81,6 +82,20 @@ func agentResource() *schema.Resource {
8182
return nil
8283
},
8384
Schema: map[string]*schema.Schema{
85+
"name": {
86+
Type: schema.TypeString,
87+
Description: "The name of the agent. When using `for_each` or `count`, this can be set to a unique value (e.g., `each.key`) to avoid name collisions. Defaults to the Terraform resource block name.",
88+
ForceNew: true,
89+
Optional: true,
90+
ValidateFunc: validation.StringIsNotEmpty,
91+
},
92+
"coder_access_url": {
93+
Type: schema.TypeString,
94+
Description: "Override the Coder access URL that the agent uses to phone home. Useful for networking scenarios where the agent needs to connect via an internal DNS name instead of the public URL.",
95+
ForceNew: true,
96+
Optional: true,
97+
ValidateFunc: validation.StringIsNotEmpty,
98+
},
8499
"api_key_scope": {
85100
Type: schema.TypeString,
86101
Optional: true,
@@ -491,7 +506,18 @@ func updateInitScript(resourceData *schema.ResourceData, i interface{}) diag.Dia
491506
if !valid {
492507
return diag.Errorf("arch was unexpected type %q", reflect.TypeOf(resourceData.Get("arch")))
493508
}
494-
accessURL, err := config.URL.Parse("/")
509+
510+
// Use custom coder_access_url if provided, otherwise use the provider config URL
511+
accessURL := config.URL
512+
if customURL, ok := resourceData.Get("coder_access_url").(string); ok && customURL != "" {
513+
parsedCustomURL, err := url.Parse(customURL)
514+
if err != nil {
515+
return diag.Errorf("parse custom coder_access_url: %s", err)
516+
}
517+
accessURL = parsedCustomURL
518+
}
519+
520+
accessURL, err := accessURL.Parse("/")
495521
if err != nil {
496522
return diag.Errorf("parse access url: %s", err)
497523
}

‎provider/devcontainer.go‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ func devcontainerResource() *schema.Resource {
2626
ReadContext: schema.NoopContext,
2727
DeleteContext: schema.NoopContext,
2828
Schema: map[string]*schema.Schema{
29+
"name": {
30+
Type: schema.TypeString,
31+
Description: "The name of the sub-agent. When using `for_each` or `count`, this can be set to a unique value (e.g., `each.key`) to avoid name collisions. Defaults to the Terraform resource block name.",
32+
ForceNew: true,
33+
Optional: true,
34+
ValidateFunc: validation.StringIsNotEmpty,
35+
},
2936
"agent_id": {
3037
Type: schema.TypeString,
3138
Description: "The `id` property of a `coder_agent` resource to associate with.",

‎provider/formtype.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const (
2020
OptionTypeNumber OptionType = "number"
2121
OptionTypeBoolean OptionType = "bool"
2222
OptionTypeListString OptionType = "list(string)"
23+
OptionTypeKeyValue OptionType = "keyvalue"
2324
)
2425

2526
func OptionTypes() []OptionType {
@@ -28,6 +29,7 @@ func OptionTypes() []OptionType {
2829
OptionTypeNumber,
2930
OptionTypeBoolean,
3031
OptionTypeListString,
32+
OptionTypeKeyValue,
3133
}
3234
}
3335

@@ -106,6 +108,10 @@ var formTypeTruthTable = map[OptionType]map[bool][]ParameterFormType{
106108
true: {ParameterFormTypeRadio, ParameterFormTypeMultiSelect},
107109
false: {ParameterFormTypeTagSelect},
108110
},
111+
OptionTypeKeyValue: {
112+
true: {ParameterFormTypeRadio, ParameterFormTypeDropdown},
113+
false: {ParameterFormTypeInput, ParameterFormTypeTextArea},
114+
},
109115
}
110116

111117
// ValidateFormType handles the truth table for the valid set of `type` and

‎provider/parameter.go‎

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,11 @@ func parameterDataSource() *schema.Resource {
117117
Option: rd.Get("option"),
118118
Validation: fixedValidation,
119119
Optional: func() bool {
120-
// This hack allows for checking if the "default" field is present in the .tf file.
121-
// If "default" is missing or is "null", then it means that this field is required,
122-
// and user must provide a value for it.
120+
// Check if user explicitly set optional in the config
121+
if userOptional, ok := rd.Get("optional").(bool); ok {
122+
return userOptional
123+
}
124+
// Otherwise, fall back to: optional if default is provided
123125
val := !rd.GetRawConfig().AsValueMap()["default"].IsNull()
124126
rd.Set("optional", val)
125127
return val
@@ -135,8 +137,9 @@ func parameterDataSource() *schema.Resource {
135137
return diag.Errorf("parameter can't be immutable and ephemeral")
136138
}
137139

138-
if !parameter.Optional && parameter.Ephemeral {
139-
return diag.Errorf("ephemeral parameter requires the default property")
140+
// Ephemeral parameters require either a default value or to be explicitly marked as optional
141+
if parameter.Ephemeral && parameter.Default == nil && !parameter.Optional {
142+
return diag.Errorf("ephemeral parameter requires the default property or optional = true")
140143
}
141144

142145
var input *string
@@ -314,8 +317,9 @@ func parameterDataSource() *schema.Resource {
314317
},
315318
"optional": {
316319
Type: schema.TypeBool,
320+
Optional: true,
317321
Computed: true,
318-
Description: "Whether this value is optional.",
322+
Description: "Whether this value is optional. If set to true, the parameter does not require a default value and can be left empty during workspace creation.",
319323
},
320324
"order": {
321325
Type: schema.TypeInt,
@@ -383,6 +387,12 @@ func valueIsType(typ OptionType, value string) error {
383387
if err != nil {
384388
return err
385389
}
390+
case OptionTypeKeyValue:
391+
var kv map[string]string
392+
err := json.Unmarshal([]byte(value), &kv)
393+
if err != nil {
394+
return fmt.Errorf("%q is not a valid key-value object", value)
395+
}
386396
case OptionTypeString:
387397
// Anything is a string!
388398
default:
@@ -438,22 +448,37 @@ func (v *Parameter) ValidateInput(input *string, previous *string) (string, diag
438448
forcedValue = *value
439449
}
440450

441-
d := v.validValue(forcedValue, previous, optionType, optionValues, valuePath)
442-
if d.HasError() {
443-
return "", d
444-
}
445-
446-
err = valueIsType(v.Type, forcedValue)
447-
if err != nil {
451+
// For optional parameters without a value, skip type validation
452+
// This allows number (and other types) to be optional without requiring a default
453+
if !v.Optional && forcedValue == "" {
448454
return "", diag.Diagnostics{
449455
{
450456
Severity: diag.Error,
451-
Summary: fmt.Sprintf("Parameter value is not of type %q", v.Type),
452-
Detail: err.Error(),
457+
Summary: fmt.Sprintf("Parameter %q is required but no value was provided", v.Name),
458+
Detail: "Either provide a value or set a default value for this parameter",
453459
},
454460
}
455461
}
456462

463+
// Only validate type if a value is provided
464+
if forcedValue != "" {
465+
d := v.validValue(forcedValue, previous, optionType, optionValues, valuePath)
466+
if d.HasError() {
467+
return "", d
468+
}
469+
470+
err = valueIsType(v.Type, forcedValue)
471+
if err != nil {
472+
return "", diag.Diagnostics{
473+
{
474+
Severity: diag.Error,
475+
Summary: fmt.Sprintf("Parameter value is not of type %q", v.Type),
476+
Detail: err.Error(),
477+
},
478+
}
479+
}
480+
}
481+
457482
return forcedValue, nil
458483
}
459484

@@ -669,6 +694,12 @@ func (v *Validation) Valid(typ OptionType, value string, previous *string) error
669694
if err != nil {
670695
return fmt.Errorf("value %q is not valid list of strings", value)
671696
}
697+
case OptionTypeKeyValue:
698+
var kv map[string]string
699+
err := json.Unmarshal([]byte(value), &kv)
700+
if err != nil {
701+
return fmt.Errorf("value %q is not a valid key-value object", value)
702+
}
672703
}
673704
return nil
674705
}

0 commit comments

Comments
 (0)