Skip to content

Commit dca1e82

Browse files
committed
chore: support complex types in workspace tags
1 parent 53a1fe4 commit dca1e82

File tree

3 files changed

+47
-76
lines changed

3 files changed

+47
-76
lines changed

preview_test.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,16 @@ func Test_Extract(t *testing.T) {
5353
name: "sometags",
5454
dir: "sometags",
5555
expTags: map[string]string{
56-
"string": "foo",
57-
"number": "42",
58-
"bool": "true",
59-
"extra": "bar",
60-
},
61-
unknownTags: []string{
62-
"map", "list", "null",
56+
"string": "foo",
57+
"number": "42",
58+
"bool": "true",
59+
"extra": "bar",
60+
"list": `["a", "b", "c"]`,
61+
"map": `{"key1": "value1", "key2": "value2"}`,
62+
"complex": `{"nested": {"key": "value"}, "nested_list": [1, 2, 3]}`,
63+
"null": "null",
6364
},
65+
unknownTags: []string{},
6466
},
6567
{
6668
name: "simple static values",
@@ -522,7 +524,18 @@ func Test_Extract(t *testing.T) {
522524
// Assert tags
523525
validTags := output.WorkspaceTags.Tags()
524526

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

528541
// Assert params

testdata/sometags/main.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ data "coder_workspace_tags" "custom_workspace_tags" {
1717
"key1" = "value1"
1818
"key2" = "value2"
1919
}
20+
"complex" = {
21+
"nested_list" = [1, 2, 3]
22+
"nested" = {
23+
"key" = "value"
24+
}
25+
}
2026
"null" = null
2127
}
2228
}

workspacetags.go

Lines changed: 20 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/aquasecurity/trivy/pkg/iac/terraform"
77
"github.com/hashicorp/hcl/v2"
88
"github.com/zclconf/go-cty/cty"
9+
"github.com/zclconf/go-cty/cty/json"
910

1011
"github.com/coder/preview/types"
1112
)
@@ -47,21 +48,6 @@ func workspaceTags(modules terraform.Modules, files map[string]*hcl.File) (types
4748
continue
4849
}
4950

50-
// tagsObj, ok := tagsAttr.HCLAttribute().Expr.(*hclsyntax.ObjectConsExpr)
51-
// if !ok {
52-
// diags = diags.Append(&hcl.Diagnostic{
53-
// Severity: hcl.DiagError,
54-
// Summary: "Incorrect type for \"tags\" attribute",
55-
// // TODO: better error message for types
56-
// Detail: fmt.Sprintf(`"tags" attribute must be an 'ObjectConsExpr', but got %T`, tagsAttr.HCLAttribute().Expr),
57-
// Subject: &tagsAttr.HCLAttribute().NameRange,
58-
// Context: &tagsAttr.HCLAttribute().Range,
59-
// Expression: tagsAttr.HCLAttribute().Expr,
60-
// EvalContext: block.Context().Inner(),
61-
// })
62-
// continue
63-
//}
64-
6551
var tags []types.Tag
6652
tagsValue.ForEachElement(func(key cty.Value, val cty.Value) (stop bool) {
6753
r := tagsAttr.HCLAttribute().Expr.Range()
@@ -75,15 +61,7 @@ func workspaceTags(modules terraform.Modules, files map[string]*hcl.File) (types
7561

7662
return false
7763
})
78-
// for _, item := range tagsObj.Items {
79-
// tag, tagDiag := newTag(tagsObj, files, item, evCtx)
80-
// if tagDiag != nil {
81-
// diags = diags.Append(tagDiag)
82-
// continue
83-
// }
84-
//
85-
// tags = append(tags, tag)
86-
//}
64+
8765
tagBlocks = append(tagBlocks, types.TagBlock{
8866
Tags: tags,
8967
Block: block,
@@ -96,73 +74,47 @@ func workspaceTags(modules terraform.Modules, files map[string]*hcl.File) (types
9674

9775
// newTag creates a workspace tag from its hcl expression.
9876
func newTag(srcRange *hcl.Range, _ map[string]*hcl.File, key, val cty.Value) (types.Tag, *hcl.Diagnostic) {
99-
// key, kdiags := expr.KeyExpr.Value(evCtx)
100-
// val, vdiags := expr.ValueExpr.Value(evCtx)
101-
102-
// TODO: ???
103-
104-
// if kdiags.HasErrors() {
105-
// key = cty.UnknownVal(cty.String)
106-
//}
107-
// if vdiags.HasErrors() {
108-
// val = cty.UnknownVal(cty.String)
109-
//}
110-
11177
if key.IsKnown() && key.Type() != cty.String {
11278
return types.Tag{}, &hcl.Diagnostic{
11379
Severity: hcl.DiagError,
11480
Summary: "Invalid key type for tags",
11581
Detail: fmt.Sprintf("Key must be a string, but got %s", key.Type().FriendlyName()),
116-
//Subject: &r,
117-
Context: srcRange,
118-
//Expression: expr.KeyExpr,
119-
//EvalContext: evCtx,
82+
Context: srcRange,
12083
}
12184
}
12285

12386
tag := types.Tag{
12487
Key: types.HCLString{
12588
Value: key,
126-
//ValueDiags: kdiags,
127-
//ValueExpr: expr.KeyExpr,
12889
},
12990
Value: types.HCLString{
13091
Value: val,
131-
//ValueDiags: vdiags,
132-
//ValueExpr: expr.ValueExpr,
13392
},
13493
}
13594

136-
// If the value is known, but the type is not a string, bool, or number.
137-
// Then throw an error. Only the supported types can safely be converted to a string.
138-
if !(val.Type() == cty.String || val.Type() == cty.Bool || val.Type() == cty.Number) {
95+
switch val.Type() {
96+
case cty.String, cty.Bool, cty.Number:
97+
// These types are supported and can be safely converted to a string.
98+
default:
13999
fr := "<nil>"
140100
if !val.Type().Equals(cty.NilType) {
141101
fr = val.Type().FriendlyName()
142102
}
143103

144-
tag.Value.ValueDiags = tag.Value.ValueDiags.Append(&hcl.Diagnostic{
145-
Severity: hcl.DiagError,
146-
Summary: fmt.Sprintf("Invalid value type for tag %q", tag.KeyString()),
147-
Detail: fmt.Sprintf("Value must be a string, but got %s", fr),
148-
//Subject: &r,
149-
Context: srcRange,
150-
//Expression: expr.ValueExpr,
151-
//EvalContext: evCtx,
152-
})
104+
// Unsupported types will be converted to a JSON string representation.
105+
jsonData, err := json.Marshal(val, val.Type())
106+
if err != nil {
107+
tag.Value.ValueDiags = tag.Value.ValueDiags.Append(&hcl.Diagnostic{
108+
Severity: hcl.DiagError,
109+
Summary: fmt.Sprintf("Invalid value type for tag %q", tag.KeyString()),
110+
Detail: fmt.Sprintf("Value must be a string, but got %s. Attempt to marshal to json: %s", fr, err.Error()),
111+
Context: srcRange,
112+
})
113+
} else {
114+
// Value successfully marshaled to JSON, we can store it as a string.
115+
tag.Value.Value = cty.StringVal(string(jsonData))
116+
}
153117
}
154118

155-
// ks, err := source(expr.KeyExpr.Range(), files)
156-
// if err == nil {
157-
// src := string(ks)
158-
// tag.Key.Source = &src
159-
//}
160-
//
161-
// vs, err := source(expr.ValueExpr.Range(), files)
162-
// if err == nil {
163-
// src := string(vs)
164-
// tag.Value.Source = &src
165-
//}
166-
167119
return tag, nil
168120
}

0 commit comments

Comments
 (0)