Skip to content

Commit 25471dd

Browse files
authored
feat: allow bool + number tag values (#160)
Tag values accept bool & number in coder/coder. Maps, lists, and objects are rejected as invalid tags. `null` values are omitted from the tags, matching terraform apply/plan
1 parent 80d1ef4 commit 25471dd

File tree

5 files changed

+81
-74
lines changed

5 files changed

+81
-74
lines changed

preview_test.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,25 @@ func Test_Extract(t *testing.T) {
4949
dir: "badparam",
5050
failPreview: true,
5151
},
52+
{
53+
name: "sometags",
54+
dir: "sometags",
55+
expTags: map[string]string{
56+
"string": "foo",
57+
"number": "42",
58+
"bool": "true",
59+
// null tags are omitted
60+
},
61+
unknownTags: []string{
62+
"complex", "map", "list",
63+
},
64+
},
5265
{
5366
name: "simple static values",
5467
dir: "static",
5568
expTags: map[string]string{
5669
"zone": "developers",
5770
},
58-
unknownTags: []string{},
5971
params: map[string]assertParam{
6072
"region": ap().value("us").
6173
def("us").
@@ -510,7 +522,18 @@ func Test_Extract(t *testing.T) {
510522
// Assert tags
511523
validTags := output.WorkspaceTags.Tags()
512524

513-
assert.Equal(t, tc.expTags, validTags)
525+
for k, expected := range tc.expTags {
526+
tag, ok := validTags[k]
527+
if !ok {
528+
t.Errorf("expected tag %q to be present in output, but it was not", k)
529+
continue
530+
}
531+
if tag != expected {
532+
assert.JSONEqf(t, expected, tag, "tag %q does not match expected, nor is it a json equivalent", k)
533+
}
534+
}
535+
assert.Equal(t, len(tc.expTags), len(output.WorkspaceTags.Tags()), "unexpected number of tags in output")
536+
514537
assert.ElementsMatch(t, tc.unknownTags, output.WorkspaceTags.UnusableTags().SafeNames())
515538

516539
// Assert params

testdata/sometags/main.tf

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
version = "2.4.0-pre0"
6+
}
7+
}
8+
}
9+
10+
data "coder_workspace_tags" "custom_workspace_tags" {
11+
tags = {
12+
"string" = "foo"
13+
"number" = 42
14+
"bool" = true
15+
"list" = ["a", "b", "c"]
16+
"map" = {
17+
"key1" = "value1"
18+
"key2" = "value2"
19+
}
20+
"complex" = {
21+
"nested_list" = [1, 2, 3]
22+
"nested" = {
23+
"key" = "value"
24+
}
25+
}
26+
"null" = null
27+
}
28+
}
29+

testdata/sometags/skipe2e

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Complex types are not supported by coder provider

testdata/static/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ terraform {
1515
data "coder_workspace_tags" "custom_workspace_tags" {
1616
tags = {
1717
"zone" = "developers"
18+
"null" = null
1819
}
1920
}
2021

workspacetags.go

Lines changed: 25 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,14 @@ func workspaceTags(modules terraform.Modules, files map[string]*hcl.File) (types
3838
continue
3939
}
4040

41-
// tagsObj, ok := tagsAttr.HCLAttribute().Expr.(*hclsyntax.ObjectConsExpr)
42-
// if !ok {
43-
// diags = diags.Append(&hcl.Diagnostic{
44-
// Severity: hcl.DiagError,
45-
// Summary: "Incorrect type for \"tags\" attribute",
46-
// // TODO: better error message for types
47-
// Detail: fmt.Sprintf(`"tags" attribute must be an 'ObjectConsExpr', but got %T`, tagsAttr.HCLAttribute().Expr),
48-
// Subject: &tagsAttr.HCLAttribute().NameRange,
49-
// Context: &tagsAttr.HCLAttribute().Range,
50-
// Expression: tagsAttr.HCLAttribute().Expr,
51-
// EvalContext: block.Context().Inner(),
52-
// })
53-
// continue
54-
//}
55-
5641
var tags []types.Tag
5742
tagsValue.ForEachElement(func(key cty.Value, val cty.Value) (stop bool) {
43+
if val.IsNull() {
44+
// null tags with null values are omitted
45+
// This matches the behavior of `terraform apply``
46+
return false
47+
}
48+
5849
r := tagsAttr.HCLAttribute().Expr.Range()
5950
tag, tagDiag := newTag(&r, files, key, val)
6051
if tagDiag != nil {
@@ -66,15 +57,7 @@ func workspaceTags(modules terraform.Modules, files map[string]*hcl.File) (types
6657

6758
return false
6859
})
69-
// for _, item := range tagsObj.Items {
70-
// tag, tagDiag := newTag(tagsObj, files, item, evCtx)
71-
// if tagDiag != nil {
72-
// diags = diags.Append(tagDiag)
73-
// continue
74-
// }
75-
//
76-
// tags = append(tags, tag)
77-
//}
60+
7861
tagBlocks = append(tagBlocks, types.TagBlock{
7962
Tags: tags,
8063
Block: block,
@@ -87,71 +70,41 @@ func workspaceTags(modules terraform.Modules, files map[string]*hcl.File) (types
8770

8871
// newTag creates a workspace tag from its hcl expression.
8972
func newTag(srcRange *hcl.Range, _ map[string]*hcl.File, key, val cty.Value) (types.Tag, *hcl.Diagnostic) {
90-
// key, kdiags := expr.KeyExpr.Value(evCtx)
91-
// val, vdiags := expr.ValueExpr.Value(evCtx)
92-
93-
// TODO: ???
94-
95-
// if kdiags.HasErrors() {
96-
// key = cty.UnknownVal(cty.String)
97-
//}
98-
// if vdiags.HasErrors() {
99-
// val = cty.UnknownVal(cty.String)
100-
//}
101-
10273
if key.IsKnown() && key.Type() != cty.String {
10374
return types.Tag{}, &hcl.Diagnostic{
10475
Severity: hcl.DiagError,
10576
Summary: "Invalid key type for tags",
10677
Detail: fmt.Sprintf("Key must be a string, but got %s", key.Type().FriendlyName()),
107-
//Subject: &r,
108-
Context: srcRange,
109-
//Expression: expr.KeyExpr,
110-
//EvalContext: evCtx,
111-
}
112-
}
113-
114-
if val.IsKnown() && val.Type() != cty.String {
115-
fr := "<nil>"
116-
if !val.Type().Equals(cty.NilType) {
117-
fr = val.Type().FriendlyName()
118-
}
119-
// r := expr.ValueExpr.Range()
120-
return types.Tag{}, &hcl.Diagnostic{
121-
Severity: hcl.DiagError,
122-
Summary: "Invalid value type for tag",
123-
Detail: fmt.Sprintf("Value must be a string, but got %s", fr),
124-
//Subject: &r,
125-
Context: srcRange,
126-
//Expression: expr.ValueExpr,
127-
//EvalContext: evCtx,
78+
Context: srcRange,
12879
}
12980
}
13081

13182
tag := types.Tag{
13283
Key: types.HCLString{
13384
Value: key,
134-
//ValueDiags: kdiags,
135-
//ValueExpr: expr.KeyExpr,
13685
},
13786
Value: types.HCLString{
13887
Value: val,
139-
//ValueDiags: vdiags,
140-
//ValueExpr: expr.ValueExpr,
14188
},
14289
}
14390

144-
// ks, err := source(expr.KeyExpr.Range(), files)
145-
// if err == nil {
146-
// src := string(ks)
147-
// tag.Key.Source = &src
148-
//}
149-
//
150-
// vs, err := source(expr.ValueExpr.Range(), files)
151-
// if err == nil {
152-
// src := string(vs)
153-
// tag.Value.Source = &src
154-
//}
91+
switch val.Type() {
92+
case cty.String, cty.Bool, cty.Number:
93+
// These types are supported and can be safely converted to a string.
94+
default:
95+
fr := "<nil>"
96+
if !val.Type().Equals(cty.NilType) {
97+
fr = val.Type().FriendlyName()
98+
}
99+
100+
// Unsupported types will be treated as errors.
101+
tag.Value.ValueDiags = tag.Value.ValueDiags.Append(&hcl.Diagnostic{
102+
Severity: hcl.DiagError,
103+
Summary: fmt.Sprintf("Invalid value type for tag %q", tag.KeyString()),
104+
Detail: fmt.Sprintf("Value must be a string, but got %s.", fr),
105+
Context: srcRange,
106+
})
107+
}
155108

156109
return tag, nil
157110
}

0 commit comments

Comments
 (0)