-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparamhook.go
203 lines (173 loc) · 4.95 KB
/
paramhook.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package preview
import (
"github.com/aquasecurity/trivy/pkg/iac/terraform"
tfcontext "github.com/aquasecurity/trivy/pkg/iac/terraform/context"
"github.com/zclconf/go-cty/cty"
"github.com/coder/preview/hclext"
)
// ParameterContextsEvalHook is called in a loop, so if parameters affect
// other parameters, this can solve the problem 1 "evaluation" at a time.
//
// Omitting to set a default value is OK, as long as at least 1 parameter
// is resolvable. The resolvable parameter will be accessible on the next
// iteration.
func ParameterContextsEvalHook(input Input) func(ctx *tfcontext.Context, blocks terraform.Blocks, inputVars map[string]cty.Value) {
return func(ctx *tfcontext.Context, blocks terraform.Blocks, inputVars map[string]cty.Value) {
data := blocks.OfType("data")
for _, block := range data {
if block.TypeLabel() != "coder_parameter" {
continue
}
if !block.GetAttribute("value").IsNil() {
continue // Wow a value exists?!. This feels like a bug.
}
nameAttr := block.GetAttribute("name")
nameVal := nameAttr.Value()
if !nameVal.Type().Equals(cty.String) {
continue // Ignore the errors at this point
}
name := nameVal.AsString()
var value cty.Value
pv, ok := input.RichParameterValue(name)
if ok {
// TODO: Handle non-string types
value = cty.StringVal(pv)
} else {
// get the default value
// TODO: Log any diags
value, ok = evaluateCoderParameterDefault(block)
if !ok {
// the default value cannot be resolved, so do not
// set anything.
continue
}
}
path := []string{
"data",
"coder_parameter",
block.Reference().NameLabel(),
}
existing := ctx.Get(path...)
obj, ok := mergeParamInstanceValues(block, existing, value)
if !ok {
continue
}
ctx.Set(obj, path...)
}
}
}
func mergeParamInstanceValues(b *terraform.Block, existing cty.Value, value cty.Value) (cty.Value, bool) {
if existing.IsNull() {
return existing, false
}
ref := b.Reference()
key := ref.RawKey()
switch {
case key.Type().Equals(cty.Number) && b.GetAttribute("count") != nil:
if !existing.Type().IsTupleType() {
return existing, false
}
idx, _ := key.AsBigFloat().Int64()
elem := existing.Index(key)
if elem.IsNull() || !elem.IsKnown() {
return existing, false
}
obj, ok := setObjectField(elem, "value", value)
if !ok {
return existing, false
}
return hclext.InsertTupleElement(existing, int(idx), obj), true
case isForEachKey(key) && b.GetAttribute("for_each") != nil:
keyStr := ref.Key()
if !existing.Type().IsObjectType() {
return existing, false
}
if !existing.CanIterateElements() {
return existing, false
}
instances := existing.AsValueMap()
if instances == nil {
return existing, false
}
instance, ok := instances[keyStr]
if !ok {
return existing, false
}
instance, ok = setObjectField(instance, "value", value)
if !ok {
return existing, false
}
instances[keyStr] = instance
return cty.ObjectVal(instances), true
default:
obj, ok := setObjectField(existing, "value", value)
if !ok {
return existing, false
}
return obj, true
}
}
func setObjectField(object cty.Value, field string, value cty.Value) (cty.Value, bool) {
if object.IsNull() {
return object, false
}
if !object.Type().IsObjectType() {
return object, false
}
if !object.CanIterateElements() {
return object, false
}
instances := object.AsValueMap()
if instances == nil {
return object, false
}
instances[field] = value
return cty.ObjectVal(instances), true
}
func isForEachKey(key cty.Value) bool {
return key.Type().Equals(cty.Number) || key.Type().Equals(cty.String)
}
func evaluateCoderParameterDefault(b *terraform.Block) (cty.Value, bool) {
attributes := b.Attributes()
//typeAttr, exists := attributes["type"]
//valueType := cty.String // TODO: Default to string?
//if exists {
// typeVal := typeAttr.Value()
// if !typeVal.Type().Equals(cty.String) || !typeVal.IsWhollyKnown() {
// // TODO: Mark this value somehow
// return cty.NilVal, nil
// }
//
// var err error
// valueType, err = extract.ParameterCtyType(typeVal.AsString())
// if err != nil {
// // TODO: Mark this value somehow
// return cty.NilVal, nil
// }
//}
//
////return cty.NilVal, hcl.Diagnostics{
//// {
//// Severity: hcl.DiagError,
//// Summary: fmt.Sprintf("Decoding parameter type for %q", b.FullName()),
//// Detail: err.Error(),
//// Subject: &typeAttr.HCLAttribute().Range,
//// Context: &b.HCLBlock().DefRange,
//// Expression: typeAttr.HCLAttribute().Expr,
//// EvalContext: b.Context().Inner(),
//// },
////}
//
//// TODO: We should support different tf types, but at present the tf
//// schema is static. So only string is allowed
//var val cty.Value
def, exists := attributes["default"]
if !exists {
return cty.NilVal, false
}
v, diags := def.HCLAttribute().Expr.Value(b.Context().Inner())
if diags.HasErrors() {
return cty.NilVal, false
}
return v, true
}