diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/lang')
19 files changed, 4159 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/lang/blocktoattr/doc.go b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/doc.go new file mode 100644 index 0000000..8f89909 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/doc.go | |||
@@ -0,0 +1,5 @@ | |||
1 | // Package blocktoattr includes some helper functions that can perform | ||
2 | // preprocessing on a HCL body where a configschema.Block schema is available | ||
3 | // in order to allow list and set attributes defined in the schema to be | ||
4 | // optionally written by the user as block syntax. | ||
5 | package blocktoattr | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go new file mode 100644 index 0000000..d8c2e77 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go | |||
@@ -0,0 +1,187 @@ | |||
1 | package blocktoattr | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/hashicorp/hcl2/hcldec" | ||
6 | "github.com/hashicorp/terraform/configs/configschema" | ||
7 | "github.com/zclconf/go-cty/cty" | ||
8 | ) | ||
9 | |||
10 | // FixUpBlockAttrs takes a raw HCL body and adds some additional normalization | ||
11 | // functionality to allow attributes that are specified as having list or set | ||
12 | // type in the schema to be written with HCL block syntax as multiple nested | ||
13 | // blocks with the attribute name as the block type. | ||
14 | // | ||
15 | // This partially restores some of the block/attribute confusion from HCL 1 | ||
16 | // so that existing patterns that depended on that confusion can continue to | ||
17 | // be used in the short term while we settle on a longer-term strategy. | ||
18 | // | ||
19 | // Most of the fixup work is actually done when the returned body is | ||
20 | // subsequently decoded, so while FixUpBlockAttrs always succeeds, the eventual | ||
21 | // decode of the body might not, if the content of the body is so ambiguous | ||
22 | // that there's no safe way to map it to the schema. | ||
23 | func FixUpBlockAttrs(body hcl.Body, schema *configschema.Block) hcl.Body { | ||
24 | // The schema should never be nil, but in practice it seems to be sometimes | ||
25 | // in the presence of poorly-configured test mocks, so we'll be robust | ||
26 | // by synthesizing an empty one. | ||
27 | if schema == nil { | ||
28 | schema = &configschema.Block{} | ||
29 | } | ||
30 | |||
31 | return &fixupBody{ | ||
32 | original: body, | ||
33 | schema: schema, | ||
34 | names: ambiguousNames(schema), | ||
35 | } | ||
36 | } | ||
37 | |||
38 | type fixupBody struct { | ||
39 | original hcl.Body | ||
40 | schema *configschema.Block | ||
41 | names map[string]struct{} | ||
42 | } | ||
43 | |||
44 | // Content decodes content from the body. The given schema must be the lower-level | ||
45 | // representation of the same schema that was previously passed to FixUpBlockAttrs, | ||
46 | // or else the result is undefined. | ||
47 | func (b *fixupBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { | ||
48 | schema = b.effectiveSchema(schema) | ||
49 | content, diags := b.original.Content(schema) | ||
50 | return b.fixupContent(content), diags | ||
51 | } | ||
52 | |||
53 | func (b *fixupBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { | ||
54 | schema = b.effectiveSchema(schema) | ||
55 | content, remain, diags := b.original.PartialContent(schema) | ||
56 | remain = &fixupBody{ | ||
57 | original: remain, | ||
58 | schema: b.schema, | ||
59 | names: b.names, | ||
60 | } | ||
61 | return b.fixupContent(content), remain, diags | ||
62 | } | ||
63 | |||
64 | func (b *fixupBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { | ||
65 | // FixUpBlockAttrs is not intended to be used in situations where we'd use | ||
66 | // JustAttributes, so we just pass this through verbatim to complete our | ||
67 | // implementation of hcl.Body. | ||
68 | return b.original.JustAttributes() | ||
69 | } | ||
70 | |||
71 | func (b *fixupBody) MissingItemRange() hcl.Range { | ||
72 | return b.original.MissingItemRange() | ||
73 | } | ||
74 | |||
75 | // effectiveSchema produces a derived *hcl.BodySchema by sniffing the body's | ||
76 | // content to determine whether the author has used attribute or block syntax | ||
77 | // for each of the ambigious attributes where both are permitted. | ||
78 | // | ||
79 | // The resulting schema will always contain all of the same names that are | ||
80 | // in the given schema, but some attribute schemas may instead be replaced by | ||
81 | // block header schemas. | ||
82 | func (b *fixupBody) effectiveSchema(given *hcl.BodySchema) *hcl.BodySchema { | ||
83 | return effectiveSchema(given, b.original, b.names, true) | ||
84 | } | ||
85 | |||
86 | func (b *fixupBody) fixupContent(content *hcl.BodyContent) *hcl.BodyContent { | ||
87 | var ret hcl.BodyContent | ||
88 | ret.Attributes = make(hcl.Attributes) | ||
89 | for name, attr := range content.Attributes { | ||
90 | ret.Attributes[name] = attr | ||
91 | } | ||
92 | blockAttrVals := make(map[string][]*hcl.Block) | ||
93 | for _, block := range content.Blocks { | ||
94 | if _, exists := b.names[block.Type]; exists { | ||
95 | // If we get here then we've found a block type whose instances need | ||
96 | // to be re-interpreted as a list-of-objects attribute. We'll gather | ||
97 | // those up and fix them up below. | ||
98 | blockAttrVals[block.Type] = append(blockAttrVals[block.Type], block) | ||
99 | continue | ||
100 | } | ||
101 | |||
102 | // We need to now re-wrap our inner body so it will be subject to the | ||
103 | // same attribute-as-block fixup when recursively decoded. | ||
104 | retBlock := *block // shallow copy | ||
105 | if blockS, ok := b.schema.BlockTypes[block.Type]; ok { | ||
106 | // Would be weird if not ok, but we'll allow it for robustness; body just won't be fixed up, then | ||
107 | retBlock.Body = FixUpBlockAttrs(retBlock.Body, &blockS.Block) | ||
108 | } | ||
109 | |||
110 | ret.Blocks = append(ret.Blocks, &retBlock) | ||
111 | } | ||
112 | // No we'll install synthetic attributes for each of our fixups. We can't | ||
113 | // do this exactly because HCL's information model expects an attribute | ||
114 | // to be a single decl but we have multiple separate blocks. We'll | ||
115 | // approximate things, then, by using only our first block for the source | ||
116 | // location information. (We are guaranteed at least one by the above logic.) | ||
117 | for name, blocks := range blockAttrVals { | ||
118 | ret.Attributes[name] = &hcl.Attribute{ | ||
119 | Name: name, | ||
120 | Expr: &fixupBlocksExpr{ | ||
121 | blocks: blocks, | ||
122 | ety: b.schema.Attributes[name].Type.ElementType(), | ||
123 | }, | ||
124 | |||
125 | Range: blocks[0].DefRange, | ||
126 | NameRange: blocks[0].TypeRange, | ||
127 | } | ||
128 | } | ||
129 | return &ret | ||
130 | } | ||
131 | |||
132 | type fixupBlocksExpr struct { | ||
133 | blocks hcl.Blocks | ||
134 | ety cty.Type | ||
135 | } | ||
136 | |||
137 | func (e *fixupBlocksExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
138 | // In order to produce a suitable value for our expression we need to | ||
139 | // now decode the whole descendent block structure under each of our block | ||
140 | // bodies. | ||
141 | // | ||
142 | // That requires us to do something rather strange: we must construct a | ||
143 | // synthetic block type schema derived from the element type of the | ||
144 | // attribute, thus inverting our usual direction of lowering a schema | ||
145 | // into an implied type. Because a type is less detailed than a schema, | ||
146 | // the result is imprecise and in particular will just consider all | ||
147 | // the attributes to be optional and let the provider eventually decide | ||
148 | // whether to return errors if they turn out to be null when required. | ||
149 | schema := SchemaForCtyElementType(e.ety) // this schema's ImpliedType will match e.ety | ||
150 | spec := schema.DecoderSpec() | ||
151 | |||
152 | vals := make([]cty.Value, len(e.blocks)) | ||
153 | var diags hcl.Diagnostics | ||
154 | for i, block := range e.blocks { | ||
155 | body := FixUpBlockAttrs(block.Body, schema) | ||
156 | val, blockDiags := hcldec.Decode(body, spec, ctx) | ||
157 | diags = append(diags, blockDiags...) | ||
158 | if val == cty.NilVal { | ||
159 | val = cty.UnknownVal(e.ety) | ||
160 | } | ||
161 | vals[i] = val | ||
162 | } | ||
163 | if len(vals) == 0 { | ||
164 | return cty.ListValEmpty(e.ety), diags | ||
165 | } | ||
166 | return cty.ListVal(vals), diags | ||
167 | } | ||
168 | |||
169 | func (e *fixupBlocksExpr) Variables() []hcl.Traversal { | ||
170 | var ret []hcl.Traversal | ||
171 | schema := SchemaForCtyElementType(e.ety) | ||
172 | spec := schema.DecoderSpec() | ||
173 | for _, block := range e.blocks { | ||
174 | ret = append(ret, hcldec.Variables(block.Body, spec)...) | ||
175 | } | ||
176 | return ret | ||
177 | } | ||
178 | |||
179 | func (e *fixupBlocksExpr) Range() hcl.Range { | ||
180 | // This is not really an appropriate range for the expression but it's | ||
181 | // the best we can do from here. | ||
182 | return e.blocks[0].DefRange | ||
183 | } | ||
184 | |||
185 | func (e *fixupBlocksExpr) StartRange() hcl.Range { | ||
186 | return e.blocks[0].DefRange | ||
187 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/blocktoattr/schema.go b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/schema.go new file mode 100644 index 0000000..2f2463a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/schema.go | |||
@@ -0,0 +1,145 @@ | |||
1 | package blocktoattr | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/hashicorp/terraform/configs/configschema" | ||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | ) | ||
8 | |||
9 | func ambiguousNames(schema *configschema.Block) map[string]struct{} { | ||
10 | if schema == nil { | ||
11 | return nil | ||
12 | } | ||
13 | ambiguousNames := make(map[string]struct{}) | ||
14 | for name, attrS := range schema.Attributes { | ||
15 | aty := attrS.Type | ||
16 | if (aty.IsListType() || aty.IsSetType()) && aty.ElementType().IsObjectType() { | ||
17 | ambiguousNames[name] = struct{}{} | ||
18 | } | ||
19 | } | ||
20 | return ambiguousNames | ||
21 | } | ||
22 | |||
23 | func effectiveSchema(given *hcl.BodySchema, body hcl.Body, ambiguousNames map[string]struct{}, dynamicExpanded bool) *hcl.BodySchema { | ||
24 | ret := &hcl.BodySchema{} | ||
25 | |||
26 | appearsAsBlock := make(map[string]struct{}) | ||
27 | { | ||
28 | // We'll construct some throwaway schemas here just to probe for | ||
29 | // whether each of our ambiguous names seems to be being used as | ||
30 | // an attribute or a block. We need to check both because in JSON | ||
31 | // syntax we rely on the schema to decide between attribute or block | ||
32 | // interpretation and so JSON will always answer yes to both of | ||
33 | // these questions and we want to prefer the attribute interpretation | ||
34 | // in that case. | ||
35 | var probeSchema hcl.BodySchema | ||
36 | |||
37 | for name := range ambiguousNames { | ||
38 | probeSchema = hcl.BodySchema{ | ||
39 | Attributes: []hcl.AttributeSchema{ | ||
40 | { | ||
41 | Name: name, | ||
42 | }, | ||
43 | }, | ||
44 | } | ||
45 | content, _, _ := body.PartialContent(&probeSchema) | ||
46 | if _, exists := content.Attributes[name]; exists { | ||
47 | // Can decode as an attribute, so we'll go with that. | ||
48 | continue | ||
49 | } | ||
50 | probeSchema = hcl.BodySchema{ | ||
51 | Blocks: []hcl.BlockHeaderSchema{ | ||
52 | { | ||
53 | Type: name, | ||
54 | }, | ||
55 | }, | ||
56 | } | ||
57 | content, _, _ = body.PartialContent(&probeSchema) | ||
58 | if len(content.Blocks) > 0 { | ||
59 | // No attribute present and at least one block present, so | ||
60 | // we'll need to rewrite this one as a block for a successful | ||
61 | // result. | ||
62 | appearsAsBlock[name] = struct{}{} | ||
63 | } | ||
64 | } | ||
65 | if !dynamicExpanded { | ||
66 | // If we're deciding for a context where dynamic blocks haven't | ||
67 | // been expanded yet then we need to probe for those too. | ||
68 | probeSchema = hcl.BodySchema{ | ||
69 | Blocks: []hcl.BlockHeaderSchema{ | ||
70 | { | ||
71 | Type: "dynamic", | ||
72 | LabelNames: []string{"type"}, | ||
73 | }, | ||
74 | }, | ||
75 | } | ||
76 | content, _, _ := body.PartialContent(&probeSchema) | ||
77 | for _, block := range content.Blocks { | ||
78 | if _, exists := ambiguousNames[block.Labels[0]]; exists { | ||
79 | appearsAsBlock[block.Labels[0]] = struct{}{} | ||
80 | } | ||
81 | } | ||
82 | } | ||
83 | } | ||
84 | |||
85 | for _, attrS := range given.Attributes { | ||
86 | if _, exists := appearsAsBlock[attrS.Name]; exists { | ||
87 | ret.Blocks = append(ret.Blocks, hcl.BlockHeaderSchema{ | ||
88 | Type: attrS.Name, | ||
89 | }) | ||
90 | } else { | ||
91 | ret.Attributes = append(ret.Attributes, attrS) | ||
92 | } | ||
93 | } | ||
94 | |||
95 | // Anything that is specified as a block type in the input schema remains | ||
96 | // that way by just passing through verbatim. | ||
97 | ret.Blocks = append(ret.Blocks, given.Blocks...) | ||
98 | |||
99 | return ret | ||
100 | } | ||
101 | |||
102 | // SchemaForCtyElementType converts a cty object type into an | ||
103 | // approximately-equivalent configschema.Block representing the element of | ||
104 | // a list or set. If the given type is not an object type then this | ||
105 | // function will panic. | ||
106 | func SchemaForCtyElementType(ty cty.Type) *configschema.Block { | ||
107 | atys := ty.AttributeTypes() | ||
108 | ret := &configschema.Block{ | ||
109 | Attributes: make(map[string]*configschema.Attribute, len(atys)), | ||
110 | } | ||
111 | for name, aty := range atys { | ||
112 | ret.Attributes[name] = &configschema.Attribute{ | ||
113 | Type: aty, | ||
114 | Optional: true, | ||
115 | } | ||
116 | } | ||
117 | return ret | ||
118 | } | ||
119 | |||
120 | // SchemaForCtyContainerType converts a cty list-of-object or set-of-object type | ||
121 | // into an approximately-equivalent configschema.NestedBlock. If the given type | ||
122 | // is not of the expected kind then this function will panic. | ||
123 | func SchemaForCtyContainerType(ty cty.Type) *configschema.NestedBlock { | ||
124 | var nesting configschema.NestingMode | ||
125 | switch { | ||
126 | case ty.IsListType(): | ||
127 | nesting = configschema.NestingList | ||
128 | case ty.IsSetType(): | ||
129 | nesting = configschema.NestingSet | ||
130 | default: | ||
131 | panic("unsuitable type") | ||
132 | } | ||
133 | nested := SchemaForCtyElementType(ty.ElementType()) | ||
134 | return &configschema.NestedBlock{ | ||
135 | Nesting: nesting, | ||
136 | Block: *nested, | ||
137 | } | ||
138 | } | ||
139 | |||
140 | // TypeCanBeBlocks returns true if the given type is a list-of-object or | ||
141 | // set-of-object type, and would thus be subject to the blocktoattr fixup | ||
142 | // if used as an attribute type. | ||
143 | func TypeCanBeBlocks(ty cty.Type) bool { | ||
144 | return (ty.IsListType() || ty.IsSetType()) && ty.ElementType().IsObjectType() | ||
145 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/blocktoattr/variables.go b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/variables.go new file mode 100644 index 0000000..e123b8a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/variables.go | |||
@@ -0,0 +1,43 @@ | |||
1 | package blocktoattr | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/ext/dynblock" | ||
5 | "github.com/hashicorp/hcl2/hcl" | ||
6 | "github.com/hashicorp/hcl2/hcldec" | ||
7 | "github.com/hashicorp/terraform/configs/configschema" | ||
8 | ) | ||
9 | |||
10 | // ExpandedVariables finds all of the global variables referenced in the | ||
11 | // given body with the given schema while taking into account the possibilities | ||
12 | // both of "dynamic" blocks being expanded and the possibility of certain | ||
13 | // attributes being written instead as nested blocks as allowed by the | ||
14 | // FixUpBlockAttrs function. | ||
15 | // | ||
16 | // This function exists to allow variables to be analyzed prior to dynamic | ||
17 | // block expansion while also dealing with the fact that dynamic block expansion | ||
18 | // might in turn produce nested blocks that are subject to FixUpBlockAttrs. | ||
19 | // | ||
20 | // This is intended as a drop-in replacement for dynblock.VariablesHCLDec, | ||
21 | // which is itself a drop-in replacement for hcldec.Variables. | ||
22 | func ExpandedVariables(body hcl.Body, schema *configschema.Block) []hcl.Traversal { | ||
23 | rootNode := dynblock.WalkVariables(body) | ||
24 | return walkVariables(rootNode, body, schema) | ||
25 | } | ||
26 | |||
27 | func walkVariables(node dynblock.WalkVariablesNode, body hcl.Body, schema *configschema.Block) []hcl.Traversal { | ||
28 | givenRawSchema := hcldec.ImpliedSchema(schema.DecoderSpec()) | ||
29 | ambiguousNames := ambiguousNames(schema) | ||
30 | effectiveRawSchema := effectiveSchema(givenRawSchema, body, ambiguousNames, false) | ||
31 | vars, children := node.Visit(effectiveRawSchema) | ||
32 | |||
33 | for _, child := range children { | ||
34 | if blockS, exists := schema.BlockTypes[child.BlockTypeName]; exists { | ||
35 | vars = append(vars, walkVariables(child.Node, child.Body(), &blockS.Block)...) | ||
36 | } else if attrS, exists := schema.Attributes[child.BlockTypeName]; exists { | ||
37 | synthSchema := SchemaForCtyElementType(attrS.Type.ElementType()) | ||
38 | vars = append(vars, walkVariables(child.Node, child.Body(), synthSchema)...) | ||
39 | } | ||
40 | } | ||
41 | |||
42 | return vars | ||
43 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/data.go b/vendor/github.com/hashicorp/terraform/lang/data.go new file mode 100644 index 0000000..80313d6 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/data.go | |||
@@ -0,0 +1,33 @@ | |||
1 | package lang | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/terraform/addrs" | ||
5 | "github.com/hashicorp/terraform/tfdiags" | ||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | ) | ||
8 | |||
9 | // Data is an interface whose implementations can provide cty.Value | ||
10 | // representations of objects identified by referenceable addresses from | ||
11 | // the addrs package. | ||
12 | // | ||
13 | // This interface will grow each time a new type of reference is added, and so | ||
14 | // implementations outside of the Terraform codebases are not advised. | ||
15 | // | ||
16 | // Each method returns a suitable value and optionally some diagnostics. If the | ||
17 | // returned diagnostics contains errors then the type of the returned value is | ||
18 | // used to construct an unknown value of the same type which is then used in | ||
19 | // place of the requested object so that type checking can still proceed. In | ||
20 | // cases where it's not possible to even determine a suitable result type, | ||
21 | // cty.DynamicVal is returned along with errors describing the problem. | ||
22 | type Data interface { | ||
23 | StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics | ||
24 | |||
25 | GetCountAttr(addrs.CountAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) | ||
26 | GetResourceInstance(addrs.ResourceInstance, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) | ||
27 | GetLocalValue(addrs.LocalValue, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) | ||
28 | GetModuleInstance(addrs.ModuleCallInstance, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) | ||
29 | GetModuleInstanceOutput(addrs.ModuleCallOutput, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) | ||
30 | GetPathAttr(addrs.PathAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) | ||
31 | GetTerraformAttr(addrs.TerraformAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) | ||
32 | GetInputVariable(addrs.InputVariable, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) | ||
33 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/doc.go b/vendor/github.com/hashicorp/terraform/lang/doc.go new file mode 100644 index 0000000..af5c5ca --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/doc.go | |||
@@ -0,0 +1,5 @@ | |||
1 | // Package lang deals with the runtime aspects of Terraform's configuration | ||
2 | // language, with concerns such as expression evaluation. It is closely related | ||
3 | // to sibling package "configs", which is responsible for configuration | ||
4 | // parsing and static validation. | ||
5 | package lang | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/eval.go b/vendor/github.com/hashicorp/terraform/lang/eval.go new file mode 100644 index 0000000..a3fb363 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/eval.go | |||
@@ -0,0 +1,477 @@ | |||
1 | package lang | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "log" | ||
6 | "strconv" | ||
7 | |||
8 | "github.com/hashicorp/hcl2/ext/dynblock" | ||
9 | "github.com/hashicorp/hcl2/hcl" | ||
10 | "github.com/hashicorp/hcl2/hcldec" | ||
11 | "github.com/hashicorp/terraform/addrs" | ||
12 | "github.com/hashicorp/terraform/configs/configschema" | ||
13 | "github.com/hashicorp/terraform/lang/blocktoattr" | ||
14 | "github.com/hashicorp/terraform/tfdiags" | ||
15 | "github.com/zclconf/go-cty/cty" | ||
16 | "github.com/zclconf/go-cty/cty/convert" | ||
17 | ) | ||
18 | |||
19 | // ExpandBlock expands any "dynamic" blocks present in the given body. The | ||
20 | // result is a body with those blocks expanded, ready to be evaluated with | ||
21 | // EvalBlock. | ||
22 | // | ||
23 | // If the returned diagnostics contains errors then the result may be | ||
24 | // incomplete or invalid. | ||
25 | func (s *Scope) ExpandBlock(body hcl.Body, schema *configschema.Block) (hcl.Body, tfdiags.Diagnostics) { | ||
26 | spec := schema.DecoderSpec() | ||
27 | |||
28 | traversals := dynblock.ExpandVariablesHCLDec(body, spec) | ||
29 | refs, diags := References(traversals) | ||
30 | |||
31 | ctx, ctxDiags := s.EvalContext(refs) | ||
32 | diags = diags.Append(ctxDiags) | ||
33 | |||
34 | return dynblock.Expand(body, ctx), diags | ||
35 | } | ||
36 | |||
37 | // EvalBlock evaluates the given body using the given block schema and returns | ||
38 | // a cty object value representing its contents. The type of the result conforms | ||
39 | // to the implied type of the given schema. | ||
40 | // | ||
41 | // This function does not automatically expand "dynamic" blocks within the | ||
42 | // body. If that is desired, first call the ExpandBlock method to obtain | ||
43 | // an expanded body to pass to this method. | ||
44 | // | ||
45 | // If the returned diagnostics contains errors then the result may be | ||
46 | // incomplete or invalid. | ||
47 | func (s *Scope) EvalBlock(body hcl.Body, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { | ||
48 | spec := schema.DecoderSpec() | ||
49 | |||
50 | refs, diags := ReferencesInBlock(body, schema) | ||
51 | |||
52 | ctx, ctxDiags := s.EvalContext(refs) | ||
53 | diags = diags.Append(ctxDiags) | ||
54 | if diags.HasErrors() { | ||
55 | // We'll stop early if we found problems in the references, because | ||
56 | // it's likely evaluation will produce redundant copies of the same errors. | ||
57 | return cty.UnknownVal(schema.ImpliedType()), diags | ||
58 | } | ||
59 | |||
60 | // HACK: In order to remain compatible with some assumptions made in | ||
61 | // Terraform v0.11 and earlier about the approximate equivalence of | ||
62 | // attribute vs. block syntax, we do a just-in-time fixup here to allow | ||
63 | // any attribute in the schema that has a list-of-objects or set-of-objects | ||
64 | // kind to potentially be populated instead by one or more nested blocks | ||
65 | // whose type is the attribute name. | ||
66 | body = blocktoattr.FixUpBlockAttrs(body, schema) | ||
67 | |||
68 | val, evalDiags := hcldec.Decode(body, spec, ctx) | ||
69 | diags = diags.Append(evalDiags) | ||
70 | |||
71 | return val, diags | ||
72 | } | ||
73 | |||
74 | // EvalExpr evaluates a single expression in the receiving context and returns | ||
75 | // the resulting value. The value will be converted to the given type before | ||
76 | // it is returned if possible, or else an error diagnostic will be produced | ||
77 | // describing the conversion error. | ||
78 | // | ||
79 | // Pass an expected type of cty.DynamicPseudoType to skip automatic conversion | ||
80 | // and just obtain the returned value directly. | ||
81 | // | ||
82 | // If the returned diagnostics contains errors then the result may be | ||
83 | // incomplete, but will always be of the requested type. | ||
84 | func (s *Scope) EvalExpr(expr hcl.Expression, wantType cty.Type) (cty.Value, tfdiags.Diagnostics) { | ||
85 | refs, diags := ReferencesInExpr(expr) | ||
86 | |||
87 | ctx, ctxDiags := s.EvalContext(refs) | ||
88 | diags = diags.Append(ctxDiags) | ||
89 | if diags.HasErrors() { | ||
90 | // We'll stop early if we found problems in the references, because | ||
91 | // it's likely evaluation will produce redundant copies of the same errors. | ||
92 | return cty.UnknownVal(wantType), diags | ||
93 | } | ||
94 | |||
95 | val, evalDiags := expr.Value(ctx) | ||
96 | diags = diags.Append(evalDiags) | ||
97 | |||
98 | if wantType != cty.DynamicPseudoType { | ||
99 | var convErr error | ||
100 | val, convErr = convert.Convert(val, wantType) | ||
101 | if convErr != nil { | ||
102 | val = cty.UnknownVal(wantType) | ||
103 | diags = diags.Append(&hcl.Diagnostic{ | ||
104 | Severity: hcl.DiagError, | ||
105 | Summary: "Incorrect value type", | ||
106 | Detail: fmt.Sprintf("Invalid expression value: %s.", tfdiags.FormatError(convErr)), | ||
107 | Subject: expr.Range().Ptr(), | ||
108 | }) | ||
109 | } | ||
110 | } | ||
111 | |||
112 | return val, diags | ||
113 | } | ||
114 | |||
115 | // EvalReference evaluates the given reference in the receiving scope and | ||
116 | // returns the resulting value. The value will be converted to the given type before | ||
117 | // it is returned if possible, or else an error diagnostic will be produced | ||
118 | // describing the conversion error. | ||
119 | // | ||
120 | // Pass an expected type of cty.DynamicPseudoType to skip automatic conversion | ||
121 | // and just obtain the returned value directly. | ||
122 | // | ||
123 | // If the returned diagnostics contains errors then the result may be | ||
124 | // incomplete, but will always be of the requested type. | ||
125 | func (s *Scope) EvalReference(ref *addrs.Reference, wantType cty.Type) (cty.Value, tfdiags.Diagnostics) { | ||
126 | var diags tfdiags.Diagnostics | ||
127 | |||
128 | // We cheat a bit here and just build an EvalContext for our requested | ||
129 | // reference with the "self" address overridden, and then pull the "self" | ||
130 | // result out of it to return. | ||
131 | ctx, ctxDiags := s.evalContext([]*addrs.Reference{ref}, ref.Subject) | ||
132 | diags = diags.Append(ctxDiags) | ||
133 | val := ctx.Variables["self"] | ||
134 | if val == cty.NilVal { | ||
135 | val = cty.DynamicVal | ||
136 | } | ||
137 | |||
138 | var convErr error | ||
139 | val, convErr = convert.Convert(val, wantType) | ||
140 | if convErr != nil { | ||
141 | val = cty.UnknownVal(wantType) | ||
142 | diags = diags.Append(&hcl.Diagnostic{ | ||
143 | Severity: hcl.DiagError, | ||
144 | Summary: "Incorrect value type", | ||
145 | Detail: fmt.Sprintf("Invalid expression value: %s.", tfdiags.FormatError(convErr)), | ||
146 | Subject: ref.SourceRange.ToHCL().Ptr(), | ||
147 | }) | ||
148 | } | ||
149 | |||
150 | return val, diags | ||
151 | } | ||
152 | |||
153 | // EvalContext constructs a HCL expression evaluation context whose variable | ||
154 | // scope contains sufficient values to satisfy the given set of references. | ||
155 | // | ||
156 | // Most callers should prefer to use the evaluation helper methods that | ||
157 | // this type offers, but this is here for less common situations where the | ||
158 | // caller will handle the evaluation calls itself. | ||
159 | func (s *Scope) EvalContext(refs []*addrs.Reference) (*hcl.EvalContext, tfdiags.Diagnostics) { | ||
160 | return s.evalContext(refs, s.SelfAddr) | ||
161 | } | ||
162 | |||
163 | func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceable) (*hcl.EvalContext, tfdiags.Diagnostics) { | ||
164 | if s == nil { | ||
165 | panic("attempt to construct EvalContext for nil Scope") | ||
166 | } | ||
167 | |||
168 | var diags tfdiags.Diagnostics | ||
169 | vals := make(map[string]cty.Value) | ||
170 | funcs := s.Functions() | ||
171 | ctx := &hcl.EvalContext{ | ||
172 | Variables: vals, | ||
173 | Functions: funcs, | ||
174 | } | ||
175 | |||
176 | if len(refs) == 0 { | ||
177 | // Easy path for common case where there are no references at all. | ||
178 | return ctx, diags | ||
179 | } | ||
180 | |||
181 | // First we'll do static validation of the references. This catches things | ||
182 | // early that might otherwise not get caught due to unknown values being | ||
183 | // present in the scope during planning. | ||
184 | if staticDiags := s.Data.StaticValidateReferences(refs, selfAddr); staticDiags.HasErrors() { | ||
185 | diags = diags.Append(staticDiags) | ||
186 | return ctx, diags | ||
187 | } | ||
188 | |||
189 | // The reference set we are given has not been de-duped, and so there can | ||
190 | // be redundant requests in it for two reasons: | ||
191 | // - The same item is referenced multiple times | ||
192 | // - Both an item and that item's container are separately referenced. | ||
193 | // We will still visit every reference here and ask our data source for | ||
194 | // it, since that allows us to gather a full set of any errors and | ||
195 | // warnings, but once we've gathered all the data we'll then skip anything | ||
196 | // that's redundant in the process of populating our values map. | ||
197 | dataResources := map[string]map[string]map[addrs.InstanceKey]cty.Value{} | ||
198 | managedResources := map[string]map[string]map[addrs.InstanceKey]cty.Value{} | ||
199 | wholeModules := map[string]map[addrs.InstanceKey]cty.Value{} | ||
200 | moduleOutputs := map[string]map[addrs.InstanceKey]map[string]cty.Value{} | ||
201 | inputVariables := map[string]cty.Value{} | ||
202 | localValues := map[string]cty.Value{} | ||
203 | pathAttrs := map[string]cty.Value{} | ||
204 | terraformAttrs := map[string]cty.Value{} | ||
205 | countAttrs := map[string]cty.Value{} | ||
206 | var self cty.Value | ||
207 | |||
208 | for _, ref := range refs { | ||
209 | rng := ref.SourceRange | ||
210 | isSelf := false | ||
211 | |||
212 | rawSubj := ref.Subject | ||
213 | if rawSubj == addrs.Self { | ||
214 | if selfAddr == nil { | ||
215 | diags = diags.Append(&hcl.Diagnostic{ | ||
216 | Severity: hcl.DiagError, | ||
217 | Summary: `Invalid "self" reference`, | ||
218 | // This detail message mentions some current practice that | ||
219 | // this codepath doesn't really "know about". If the "self" | ||
220 | // object starts being supported in more contexts later then | ||
221 | // we'll need to adjust this message. | ||
222 | Detail: `The "self" object is not available in this context. This object can be used only in resource provisioner and connection blocks.`, | ||
223 | Subject: ref.SourceRange.ToHCL().Ptr(), | ||
224 | }) | ||
225 | continue | ||
226 | } | ||
227 | |||
228 | // Treat "self" as an alias for the configured self address. | ||
229 | rawSubj = selfAddr | ||
230 | isSelf = true | ||
231 | |||
232 | if rawSubj == addrs.Self { | ||
233 | // Programming error: the self address cannot alias itself. | ||
234 | panic("scope SelfAddr attempting to alias itself") | ||
235 | } | ||
236 | } | ||
237 | |||
238 | // This type switch must cover all of the "Referenceable" implementations | ||
239 | // in package addrs. | ||
240 | switch subj := rawSubj.(type) { | ||
241 | |||
242 | case addrs.ResourceInstance: | ||
243 | var into map[string]map[string]map[addrs.InstanceKey]cty.Value | ||
244 | switch subj.Resource.Mode { | ||
245 | case addrs.ManagedResourceMode: | ||
246 | into = managedResources | ||
247 | case addrs.DataResourceMode: | ||
248 | into = dataResources | ||
249 | default: | ||
250 | panic(fmt.Errorf("unsupported ResourceMode %s", subj.Resource.Mode)) | ||
251 | } | ||
252 | |||
253 | val, valDiags := normalizeRefValue(s.Data.GetResourceInstance(subj, rng)) | ||
254 | diags = diags.Append(valDiags) | ||
255 | |||
256 | r := subj.Resource | ||
257 | if into[r.Type] == nil { | ||
258 | into[r.Type] = make(map[string]map[addrs.InstanceKey]cty.Value) | ||
259 | } | ||
260 | if into[r.Type][r.Name] == nil { | ||
261 | into[r.Type][r.Name] = make(map[addrs.InstanceKey]cty.Value) | ||
262 | } | ||
263 | into[r.Type][r.Name][subj.Key] = val | ||
264 | if isSelf { | ||
265 | self = val | ||
266 | } | ||
267 | |||
268 | case addrs.ModuleCallInstance: | ||
269 | val, valDiags := normalizeRefValue(s.Data.GetModuleInstance(subj, rng)) | ||
270 | diags = diags.Append(valDiags) | ||
271 | |||
272 | if wholeModules[subj.Call.Name] == nil { | ||
273 | wholeModules[subj.Call.Name] = make(map[addrs.InstanceKey]cty.Value) | ||
274 | } | ||
275 | wholeModules[subj.Call.Name][subj.Key] = val | ||
276 | if isSelf { | ||
277 | self = val | ||
278 | } | ||
279 | |||
280 | case addrs.ModuleCallOutput: | ||
281 | val, valDiags := normalizeRefValue(s.Data.GetModuleInstanceOutput(subj, rng)) | ||
282 | diags = diags.Append(valDiags) | ||
283 | |||
284 | callName := subj.Call.Call.Name | ||
285 | callKey := subj.Call.Key | ||
286 | if moduleOutputs[callName] == nil { | ||
287 | moduleOutputs[callName] = make(map[addrs.InstanceKey]map[string]cty.Value) | ||
288 | } | ||
289 | if moduleOutputs[callName][callKey] == nil { | ||
290 | moduleOutputs[callName][callKey] = make(map[string]cty.Value) | ||
291 | } | ||
292 | moduleOutputs[callName][callKey][subj.Name] = val | ||
293 | if isSelf { | ||
294 | self = val | ||
295 | } | ||
296 | |||
297 | case addrs.InputVariable: | ||
298 | val, valDiags := normalizeRefValue(s.Data.GetInputVariable(subj, rng)) | ||
299 | diags = diags.Append(valDiags) | ||
300 | inputVariables[subj.Name] = val | ||
301 | if isSelf { | ||
302 | self = val | ||
303 | } | ||
304 | |||
305 | case addrs.LocalValue: | ||
306 | val, valDiags := normalizeRefValue(s.Data.GetLocalValue(subj, rng)) | ||
307 | diags = diags.Append(valDiags) | ||
308 | localValues[subj.Name] = val | ||
309 | if isSelf { | ||
310 | self = val | ||
311 | } | ||
312 | |||
313 | case addrs.PathAttr: | ||
314 | val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, rng)) | ||
315 | diags = diags.Append(valDiags) | ||
316 | pathAttrs[subj.Name] = val | ||
317 | if isSelf { | ||
318 | self = val | ||
319 | } | ||
320 | |||
321 | case addrs.TerraformAttr: | ||
322 | val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, rng)) | ||
323 | diags = diags.Append(valDiags) | ||
324 | terraformAttrs[subj.Name] = val | ||
325 | if isSelf { | ||
326 | self = val | ||
327 | } | ||
328 | |||
329 | case addrs.CountAttr: | ||
330 | val, valDiags := normalizeRefValue(s.Data.GetCountAttr(subj, rng)) | ||
331 | diags = diags.Append(valDiags) | ||
332 | countAttrs[subj.Name] = val | ||
333 | if isSelf { | ||
334 | self = val | ||
335 | } | ||
336 | |||
337 | default: | ||
338 | // Should never happen | ||
339 | panic(fmt.Errorf("Scope.buildEvalContext cannot handle address type %T", rawSubj)) | ||
340 | } | ||
341 | } | ||
342 | |||
343 | for k, v := range buildResourceObjects(managedResources) { | ||
344 | vals[k] = v | ||
345 | } | ||
346 | vals["data"] = cty.ObjectVal(buildResourceObjects(dataResources)) | ||
347 | vals["module"] = cty.ObjectVal(buildModuleObjects(wholeModules, moduleOutputs)) | ||
348 | vals["var"] = cty.ObjectVal(inputVariables) | ||
349 | vals["local"] = cty.ObjectVal(localValues) | ||
350 | vals["path"] = cty.ObjectVal(pathAttrs) | ||
351 | vals["terraform"] = cty.ObjectVal(terraformAttrs) | ||
352 | vals["count"] = cty.ObjectVal(countAttrs) | ||
353 | if self != cty.NilVal { | ||
354 | vals["self"] = self | ||
355 | } | ||
356 | |||
357 | return ctx, diags | ||
358 | } | ||
359 | |||
360 | func buildResourceObjects(resources map[string]map[string]map[addrs.InstanceKey]cty.Value) map[string]cty.Value { | ||
361 | vals := make(map[string]cty.Value) | ||
362 | for typeName, names := range resources { | ||
363 | nameVals := make(map[string]cty.Value) | ||
364 | for name, keys := range names { | ||
365 | nameVals[name] = buildInstanceObjects(keys) | ||
366 | } | ||
367 | vals[typeName] = cty.ObjectVal(nameVals) | ||
368 | } | ||
369 | return vals | ||
370 | } | ||
371 | |||
372 | func buildModuleObjects(wholeModules map[string]map[addrs.InstanceKey]cty.Value, moduleOutputs map[string]map[addrs.InstanceKey]map[string]cty.Value) map[string]cty.Value { | ||
373 | vals := make(map[string]cty.Value) | ||
374 | |||
375 | for name, keys := range wholeModules { | ||
376 | vals[name] = buildInstanceObjects(keys) | ||
377 | } | ||
378 | |||
379 | for name, keys := range moduleOutputs { | ||
380 | if _, exists := wholeModules[name]; exists { | ||
381 | // If we also have a whole module value for this name then we'll | ||
382 | // skip this since the individual outputs are embedded in that result. | ||
383 | continue | ||
384 | } | ||
385 | |||
386 | // The shape of this collection isn't compatible with buildInstanceObjects, | ||
387 | // but rather than replicating most of the buildInstanceObjects logic | ||
388 | // here we'll instead first transform the structure to be what that | ||
389 | // function expects and then use it. This is a little wasteful, but | ||
390 | // we do not expect this these maps to be large and so the extra work | ||
391 | // here should not hurt too much. | ||
392 | flattened := make(map[addrs.InstanceKey]cty.Value, len(keys)) | ||
393 | for k, vals := range keys { | ||
394 | flattened[k] = cty.ObjectVal(vals) | ||
395 | } | ||
396 | vals[name] = buildInstanceObjects(flattened) | ||
397 | } | ||
398 | |||
399 | return vals | ||
400 | } | ||
401 | |||
402 | func buildInstanceObjects(keys map[addrs.InstanceKey]cty.Value) cty.Value { | ||
403 | if val, exists := keys[addrs.NoKey]; exists { | ||
404 | // If present, a "no key" value supersedes all other values, | ||
405 | // since they should be embedded inside it. | ||
406 | return val | ||
407 | } | ||
408 | |||
409 | // If we only have individual values then we need to construct | ||
410 | // either a list or a map, depending on what sort of keys we | ||
411 | // have. | ||
412 | haveInt := false | ||
413 | haveString := false | ||
414 | maxInt := 0 | ||
415 | |||
416 | for k := range keys { | ||
417 | switch tk := k.(type) { | ||
418 | case addrs.IntKey: | ||
419 | haveInt = true | ||
420 | if int(tk) > maxInt { | ||
421 | maxInt = int(tk) | ||
422 | } | ||
423 | case addrs.StringKey: | ||
424 | haveString = true | ||
425 | } | ||
426 | } | ||
427 | |||
428 | // We should either have ints or strings and not both, but | ||
429 | // if we have both then we'll prefer strings and let the | ||
430 | // language interpreter try to convert the int keys into | ||
431 | // strings in a map. | ||
432 | switch { | ||
433 | case haveString: | ||
434 | vals := make(map[string]cty.Value) | ||
435 | for k, v := range keys { | ||
436 | switch tk := k.(type) { | ||
437 | case addrs.StringKey: | ||
438 | vals[string(tk)] = v | ||
439 | case addrs.IntKey: | ||
440 | sk := strconv.Itoa(int(tk)) | ||
441 | vals[sk] = v | ||
442 | } | ||
443 | } | ||
444 | return cty.ObjectVal(vals) | ||
445 | case haveInt: | ||
446 | // We'll make a tuple that is long enough for our maximum | ||
447 | // index value. It doesn't matter if we end up shorter than | ||
448 | // the number of instances because if length(...) were | ||
449 | // being evaluated we would've got a NoKey reference and | ||
450 | // thus not ended up in this codepath at all. | ||
451 | vals := make([]cty.Value, maxInt+1) | ||
452 | for i := range vals { | ||
453 | if v, exists := keys[addrs.IntKey(i)]; exists { | ||
454 | vals[i] = v | ||
455 | } else { | ||
456 | // Just a placeholder, since nothing will access this anyway | ||
457 | vals[i] = cty.DynamicVal | ||
458 | } | ||
459 | } | ||
460 | return cty.TupleVal(vals) | ||
461 | default: | ||
462 | // Should never happen because there are no other key types. | ||
463 | log.Printf("[ERROR] strange makeInstanceObjects call with no supported key types") | ||
464 | return cty.EmptyObjectVal | ||
465 | } | ||
466 | } | ||
467 | |||
468 | func normalizeRefValue(val cty.Value, diags tfdiags.Diagnostics) (cty.Value, tfdiags.Diagnostics) { | ||
469 | if diags.HasErrors() { | ||
470 | // If there are errors then we will force an unknown result so that | ||
471 | // we can still evaluate and catch type errors but we'll avoid | ||
472 | // producing redundant re-statements of the same errors we've already | ||
473 | // dealt with here. | ||
474 | return cty.UnknownVal(val.Type()), diags | ||
475 | } | ||
476 | return val, diags | ||
477 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/funcs/cidr.go b/vendor/github.com/hashicorp/terraform/lang/funcs/cidr.go new file mode 100644 index 0000000..6ce8aa9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/funcs/cidr.go | |||
@@ -0,0 +1,129 @@ | |||
1 | package funcs | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "net" | ||
6 | |||
7 | "github.com/apparentlymart/go-cidr/cidr" | ||
8 | "github.com/zclconf/go-cty/cty" | ||
9 | "github.com/zclconf/go-cty/cty/function" | ||
10 | "github.com/zclconf/go-cty/cty/gocty" | ||
11 | ) | ||
12 | |||
13 | // CidrHostFunc contructs a function that calculates a full host IP address | ||
14 | // within a given IP network address prefix. | ||
15 | var CidrHostFunc = function.New(&function.Spec{ | ||
16 | Params: []function.Parameter{ | ||
17 | { | ||
18 | Name: "prefix", | ||
19 | Type: cty.String, | ||
20 | }, | ||
21 | { | ||
22 | Name: "hostnum", | ||
23 | Type: cty.Number, | ||
24 | }, | ||
25 | }, | ||
26 | Type: function.StaticReturnType(cty.String), | ||
27 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
28 | var hostNum int | ||
29 | if err := gocty.FromCtyValue(args[1], &hostNum); err != nil { | ||
30 | return cty.UnknownVal(cty.String), err | ||
31 | } | ||
32 | _, network, err := net.ParseCIDR(args[0].AsString()) | ||
33 | if err != nil { | ||
34 | return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err) | ||
35 | } | ||
36 | |||
37 | ip, err := cidr.Host(network, hostNum) | ||
38 | if err != nil { | ||
39 | return cty.UnknownVal(cty.String), err | ||
40 | } | ||
41 | |||
42 | return cty.StringVal(ip.String()), nil | ||
43 | }, | ||
44 | }) | ||
45 | |||
46 | // CidrNetmaskFunc contructs a function that converts an IPv4 address prefix given | ||
47 | // in CIDR notation into a subnet mask address. | ||
48 | var CidrNetmaskFunc = function.New(&function.Spec{ | ||
49 | Params: []function.Parameter{ | ||
50 | { | ||
51 | Name: "prefix", | ||
52 | Type: cty.String, | ||
53 | }, | ||
54 | }, | ||
55 | Type: function.StaticReturnType(cty.String), | ||
56 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
57 | _, network, err := net.ParseCIDR(args[0].AsString()) | ||
58 | if err != nil { | ||
59 | return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err) | ||
60 | } | ||
61 | |||
62 | return cty.StringVal(net.IP(network.Mask).String()), nil | ||
63 | }, | ||
64 | }) | ||
65 | |||
66 | // CidrSubnetFunc contructs a function that calculates a subnet address within | ||
67 | // a given IP network address prefix. | ||
68 | var CidrSubnetFunc = function.New(&function.Spec{ | ||
69 | Params: []function.Parameter{ | ||
70 | { | ||
71 | Name: "prefix", | ||
72 | Type: cty.String, | ||
73 | }, | ||
74 | { | ||
75 | Name: "newbits", | ||
76 | Type: cty.Number, | ||
77 | }, | ||
78 | { | ||
79 | Name: "netnum", | ||
80 | Type: cty.Number, | ||
81 | }, | ||
82 | }, | ||
83 | Type: function.StaticReturnType(cty.String), | ||
84 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
85 | var newbits int | ||
86 | if err := gocty.FromCtyValue(args[1], &newbits); err != nil { | ||
87 | return cty.UnknownVal(cty.String), err | ||
88 | } | ||
89 | var netnum int | ||
90 | if err := gocty.FromCtyValue(args[2], &netnum); err != nil { | ||
91 | return cty.UnknownVal(cty.String), err | ||
92 | } | ||
93 | |||
94 | _, network, err := net.ParseCIDR(args[0].AsString()) | ||
95 | if err != nil { | ||
96 | return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err) | ||
97 | } | ||
98 | |||
99 | // For portability with 32-bit systems where the subnet number | ||
100 | // will be a 32-bit int, we only allow extension of 32 bits in | ||
101 | // one call even if we're running on a 64-bit machine. | ||
102 | // (Of course, this is significant only for IPv6.) | ||
103 | if newbits > 32 { | ||
104 | return cty.UnknownVal(cty.String), fmt.Errorf("may not extend prefix by more than 32 bits") | ||
105 | } | ||
106 | |||
107 | newNetwork, err := cidr.Subnet(network, newbits, netnum) | ||
108 | if err != nil { | ||
109 | return cty.UnknownVal(cty.String), err | ||
110 | } | ||
111 | |||
112 | return cty.StringVal(newNetwork.String()), nil | ||
113 | }, | ||
114 | }) | ||
115 | |||
116 | // CidrHost calculates a full host IP address within a given IP network address prefix. | ||
117 | func CidrHost(prefix, hostnum cty.Value) (cty.Value, error) { | ||
118 | return CidrHostFunc.Call([]cty.Value{prefix, hostnum}) | ||
119 | } | ||
120 | |||
121 | // CidrNetmask converts an IPv4 address prefix given in CIDR notation into a subnet mask address. | ||
122 | func CidrNetmask(prefix cty.Value) (cty.Value, error) { | ||
123 | return CidrNetmaskFunc.Call([]cty.Value{prefix}) | ||
124 | } | ||
125 | |||
126 | // CidrSubnet calculates a subnet address within a given IP network address prefix. | ||
127 | func CidrSubnet(prefix, newbits, netnum cty.Value) (cty.Value, error) { | ||
128 | return CidrSubnetFunc.Call([]cty.Value{prefix, newbits, netnum}) | ||
129 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/funcs/collection.go b/vendor/github.com/hashicorp/terraform/lang/funcs/collection.go new file mode 100644 index 0000000..71b7a84 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/funcs/collection.go | |||
@@ -0,0 +1,1511 @@ | |||
1 | package funcs | ||
2 | |||
3 | import ( | ||
4 | "errors" | ||
5 | "fmt" | ||
6 | "sort" | ||
7 | |||
8 | "github.com/zclconf/go-cty/cty" | ||
9 | "github.com/zclconf/go-cty/cty/convert" | ||
10 | "github.com/zclconf/go-cty/cty/function" | ||
11 | "github.com/zclconf/go-cty/cty/function/stdlib" | ||
12 | "github.com/zclconf/go-cty/cty/gocty" | ||
13 | ) | ||
14 | |||
15 | var ElementFunc = function.New(&function.Spec{ | ||
16 | Params: []function.Parameter{ | ||
17 | { | ||
18 | Name: "list", | ||
19 | Type: cty.DynamicPseudoType, | ||
20 | }, | ||
21 | { | ||
22 | Name: "index", | ||
23 | Type: cty.Number, | ||
24 | }, | ||
25 | }, | ||
26 | Type: func(args []cty.Value) (cty.Type, error) { | ||
27 | list := args[0] | ||
28 | listTy := list.Type() | ||
29 | switch { | ||
30 | case listTy.IsListType(): | ||
31 | return listTy.ElementType(), nil | ||
32 | case listTy.IsTupleType(): | ||
33 | if !args[1].IsKnown() { | ||
34 | // If the index isn't known yet then we can't predict the | ||
35 | // result type since each tuple element can have its own type. | ||
36 | return cty.DynamicPseudoType, nil | ||
37 | } | ||
38 | |||
39 | etys := listTy.TupleElementTypes() | ||
40 | var index int | ||
41 | err := gocty.FromCtyValue(args[1], &index) | ||
42 | if err != nil { | ||
43 | // e.g. fractional number where whole number is required | ||
44 | return cty.DynamicPseudoType, fmt.Errorf("invalid index: %s", err) | ||
45 | } | ||
46 | if len(etys) == 0 { | ||
47 | return cty.DynamicPseudoType, errors.New("cannot use element function with an empty list") | ||
48 | } | ||
49 | index = index % len(etys) | ||
50 | return etys[index], nil | ||
51 | default: | ||
52 | return cty.DynamicPseudoType, fmt.Errorf("cannot read elements from %s", listTy.FriendlyName()) | ||
53 | } | ||
54 | }, | ||
55 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
56 | var index int | ||
57 | err := gocty.FromCtyValue(args[1], &index) | ||
58 | if err != nil { | ||
59 | // can't happen because we checked this in the Type function above | ||
60 | return cty.DynamicVal, fmt.Errorf("invalid index: %s", err) | ||
61 | } | ||
62 | |||
63 | if !args[0].IsKnown() { | ||
64 | return cty.UnknownVal(retType), nil | ||
65 | } | ||
66 | |||
67 | l := args[0].LengthInt() | ||
68 | if l == 0 { | ||
69 | return cty.DynamicVal, errors.New("cannot use element function with an empty list") | ||
70 | } | ||
71 | index = index % l | ||
72 | |||
73 | // We did all the necessary type checks in the type function above, | ||
74 | // so this is guaranteed not to fail. | ||
75 | return args[0].Index(cty.NumberIntVal(int64(index))), nil | ||
76 | }, | ||
77 | }) | ||
78 | |||
79 | var LengthFunc = function.New(&function.Spec{ | ||
80 | Params: []function.Parameter{ | ||
81 | { | ||
82 | Name: "value", | ||
83 | Type: cty.DynamicPseudoType, | ||
84 | AllowDynamicType: true, | ||
85 | AllowUnknown: true, | ||
86 | }, | ||
87 | }, | ||
88 | Type: func(args []cty.Value) (cty.Type, error) { | ||
89 | collTy := args[0].Type() | ||
90 | switch { | ||
91 | case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType: | ||
92 | return cty.Number, nil | ||
93 | default: | ||
94 | return cty.Number, errors.New("argument must be a string, a collection type, or a structural type") | ||
95 | } | ||
96 | }, | ||
97 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
98 | coll := args[0] | ||
99 | collTy := args[0].Type() | ||
100 | switch { | ||
101 | case collTy == cty.DynamicPseudoType: | ||
102 | return cty.UnknownVal(cty.Number), nil | ||
103 | case collTy.IsTupleType(): | ||
104 | l := len(collTy.TupleElementTypes()) | ||
105 | return cty.NumberIntVal(int64(l)), nil | ||
106 | case collTy.IsObjectType(): | ||
107 | l := len(collTy.AttributeTypes()) | ||
108 | return cty.NumberIntVal(int64(l)), nil | ||
109 | case collTy == cty.String: | ||
110 | // We'll delegate to the cty stdlib strlen function here, because | ||
111 | // it deals with all of the complexities of tokenizing unicode | ||
112 | // grapheme clusters. | ||
113 | return stdlib.Strlen(coll) | ||
114 | case collTy.IsListType() || collTy.IsSetType() || collTy.IsMapType(): | ||
115 | return coll.Length(), nil | ||
116 | default: | ||
117 | // Should never happen, because of the checks in our Type func above | ||
118 | return cty.UnknownVal(cty.Number), errors.New("impossible value type for length(...)") | ||
119 | } | ||
120 | }, | ||
121 | }) | ||
122 | |||
123 | // CoalesceFunc constructs a function that takes any number of arguments and | ||
124 | // returns the first one that isn't empty. This function was copied from go-cty | ||
125 | // stdlib and modified so that it returns the first *non-empty* non-null element | ||
126 | // from a sequence, instead of merely the first non-null. | ||
127 | var CoalesceFunc = function.New(&function.Spec{ | ||
128 | Params: []function.Parameter{}, | ||
129 | VarParam: &function.Parameter{ | ||
130 | Name: "vals", | ||
131 | Type: cty.DynamicPseudoType, | ||
132 | AllowUnknown: true, | ||
133 | AllowDynamicType: true, | ||
134 | AllowNull: true, | ||
135 | }, | ||
136 | Type: func(args []cty.Value) (ret cty.Type, err error) { | ||
137 | argTypes := make([]cty.Type, len(args)) | ||
138 | for i, val := range args { | ||
139 | argTypes[i] = val.Type() | ||
140 | } | ||
141 | retType, _ := convert.UnifyUnsafe(argTypes) | ||
142 | if retType == cty.NilType { | ||
143 | return cty.NilType, errors.New("all arguments must have the same type") | ||
144 | } | ||
145 | return retType, nil | ||
146 | }, | ||
147 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
148 | for _, argVal := range args { | ||
149 | // We already know this will succeed because of the checks in our Type func above | ||
150 | argVal, _ = convert.Convert(argVal, retType) | ||
151 | if !argVal.IsKnown() { | ||
152 | return cty.UnknownVal(retType), nil | ||
153 | } | ||
154 | if argVal.IsNull() { | ||
155 | continue | ||
156 | } | ||
157 | if retType == cty.String && argVal.RawEquals(cty.StringVal("")) { | ||
158 | continue | ||
159 | } | ||
160 | |||
161 | return argVal, nil | ||
162 | } | ||
163 | return cty.NilVal, errors.New("no non-null, non-empty-string arguments") | ||
164 | }, | ||
165 | }) | ||
166 | |||
167 | // CoalesceListFunc constructs a function that takes any number of list arguments | ||
168 | // and returns the first one that isn't empty. | ||
169 | var CoalesceListFunc = function.New(&function.Spec{ | ||
170 | Params: []function.Parameter{}, | ||
171 | VarParam: &function.Parameter{ | ||
172 | Name: "vals", | ||
173 | Type: cty.DynamicPseudoType, | ||
174 | AllowUnknown: true, | ||
175 | AllowDynamicType: true, | ||
176 | AllowNull: true, | ||
177 | }, | ||
178 | Type: func(args []cty.Value) (ret cty.Type, err error) { | ||
179 | if len(args) == 0 { | ||
180 | return cty.NilType, errors.New("at least one argument is required") | ||
181 | } | ||
182 | |||
183 | argTypes := make([]cty.Type, len(args)) | ||
184 | |||
185 | for i, arg := range args { | ||
186 | // if any argument is unknown, we can't be certain know which type we will return | ||
187 | if !arg.IsKnown() { | ||
188 | return cty.DynamicPseudoType, nil | ||
189 | } | ||
190 | ty := arg.Type() | ||
191 | |||
192 | if !ty.IsListType() && !ty.IsTupleType() { | ||
193 | return cty.NilType, errors.New("coalescelist arguments must be lists or tuples") | ||
194 | } | ||
195 | |||
196 | argTypes[i] = arg.Type() | ||
197 | } | ||
198 | |||
199 | last := argTypes[0] | ||
200 | // If there are mixed types, we have to return a dynamic type. | ||
201 | for _, next := range argTypes[1:] { | ||
202 | if !next.Equals(last) { | ||
203 | return cty.DynamicPseudoType, nil | ||
204 | } | ||
205 | } | ||
206 | |||
207 | return last, nil | ||
208 | }, | ||
209 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
210 | for _, arg := range args { | ||
211 | if !arg.IsKnown() { | ||
212 | // If we run into an unknown list at some point, we can't | ||
213 | // predict the final result yet. (If there's a known, non-empty | ||
214 | // arg before this then we won't get here.) | ||
215 | return cty.UnknownVal(retType), nil | ||
216 | } | ||
217 | |||
218 | if arg.LengthInt() > 0 { | ||
219 | return arg, nil | ||
220 | } | ||
221 | } | ||
222 | |||
223 | return cty.NilVal, errors.New("no non-null arguments") | ||
224 | }, | ||
225 | }) | ||
226 | |||
227 | // CompactFunc constructs a function that takes a list of strings and returns a new list | ||
228 | // with any empty string elements removed. | ||
229 | var CompactFunc = function.New(&function.Spec{ | ||
230 | Params: []function.Parameter{ | ||
231 | { | ||
232 | Name: "list", | ||
233 | Type: cty.List(cty.String), | ||
234 | }, | ||
235 | }, | ||
236 | Type: function.StaticReturnType(cty.List(cty.String)), | ||
237 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
238 | listVal := args[0] | ||
239 | if !listVal.IsWhollyKnown() { | ||
240 | // If some of the element values aren't known yet then we | ||
241 | // can't yet return a compacted list | ||
242 | return cty.UnknownVal(retType), nil | ||
243 | } | ||
244 | |||
245 | var outputList []cty.Value | ||
246 | |||
247 | for it := listVal.ElementIterator(); it.Next(); { | ||
248 | _, v := it.Element() | ||
249 | if v.AsString() == "" { | ||
250 | continue | ||
251 | } | ||
252 | outputList = append(outputList, v) | ||
253 | } | ||
254 | |||
255 | if len(outputList) == 0 { | ||
256 | return cty.ListValEmpty(cty.String), nil | ||
257 | } | ||
258 | |||
259 | return cty.ListVal(outputList), nil | ||
260 | }, | ||
261 | }) | ||
262 | |||
263 | // ContainsFunc constructs a function that determines whether a given list or | ||
264 | // set contains a given single value as one of its elements. | ||
265 | var ContainsFunc = function.New(&function.Spec{ | ||
266 | Params: []function.Parameter{ | ||
267 | { | ||
268 | Name: "list", | ||
269 | Type: cty.DynamicPseudoType, | ||
270 | }, | ||
271 | { | ||
272 | Name: "value", | ||
273 | Type: cty.DynamicPseudoType, | ||
274 | }, | ||
275 | }, | ||
276 | Type: function.StaticReturnType(cty.Bool), | ||
277 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
278 | arg := args[0] | ||
279 | ty := arg.Type() | ||
280 | |||
281 | if !ty.IsListType() && !ty.IsTupleType() && !ty.IsSetType() { | ||
282 | return cty.NilVal, errors.New("argument must be list, tuple, or set") | ||
283 | } | ||
284 | |||
285 | _, err = Index(cty.TupleVal(arg.AsValueSlice()), args[1]) | ||
286 | if err != nil { | ||
287 | return cty.False, nil | ||
288 | } | ||
289 | |||
290 | return cty.True, nil | ||
291 | }, | ||
292 | }) | ||
293 | |||
294 | // IndexFunc constructs a function that finds the element index for a given value in a list. | ||
295 | var IndexFunc = function.New(&function.Spec{ | ||
296 | Params: []function.Parameter{ | ||
297 | { | ||
298 | Name: "list", | ||
299 | Type: cty.DynamicPseudoType, | ||
300 | }, | ||
301 | { | ||
302 | Name: "value", | ||
303 | Type: cty.DynamicPseudoType, | ||
304 | }, | ||
305 | }, | ||
306 | Type: function.StaticReturnType(cty.Number), | ||
307 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
308 | if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) { | ||
309 | return cty.NilVal, errors.New("argument must be a list or tuple") | ||
310 | } | ||
311 | |||
312 | if !args[0].IsKnown() { | ||
313 | return cty.UnknownVal(cty.Number), nil | ||
314 | } | ||
315 | |||
316 | if args[0].LengthInt() == 0 { // Easy path | ||
317 | return cty.NilVal, errors.New("cannot search an empty list") | ||
318 | } | ||
319 | |||
320 | for it := args[0].ElementIterator(); it.Next(); { | ||
321 | i, v := it.Element() | ||
322 | eq, err := stdlib.Equal(v, args[1]) | ||
323 | if err != nil { | ||
324 | return cty.NilVal, err | ||
325 | } | ||
326 | if !eq.IsKnown() { | ||
327 | return cty.UnknownVal(cty.Number), nil | ||
328 | } | ||
329 | if eq.True() { | ||
330 | return i, nil | ||
331 | } | ||
332 | } | ||
333 | return cty.NilVal, errors.New("item not found") | ||
334 | |||
335 | }, | ||
336 | }) | ||
337 | |||
338 | // DistinctFunc constructs a function that takes a list and returns a new list | ||
339 | // with any duplicate elements removed. | ||
340 | var DistinctFunc = function.New(&function.Spec{ | ||
341 | Params: []function.Parameter{ | ||
342 | { | ||
343 | Name: "list", | ||
344 | Type: cty.List(cty.DynamicPseudoType), | ||
345 | }, | ||
346 | }, | ||
347 | Type: func(args []cty.Value) (cty.Type, error) { | ||
348 | return args[0].Type(), nil | ||
349 | }, | ||
350 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
351 | listVal := args[0] | ||
352 | |||
353 | if !listVal.IsWhollyKnown() { | ||
354 | return cty.UnknownVal(retType), nil | ||
355 | } | ||
356 | var list []cty.Value | ||
357 | |||
358 | for it := listVal.ElementIterator(); it.Next(); { | ||
359 | _, v := it.Element() | ||
360 | list, err = appendIfMissing(list, v) | ||
361 | if err != nil { | ||
362 | return cty.NilVal, err | ||
363 | } | ||
364 | } | ||
365 | |||
366 | return cty.ListVal(list), nil | ||
367 | }, | ||
368 | }) | ||
369 | |||
370 | // ChunklistFunc constructs a function that splits a single list into fixed-size chunks, | ||
371 | // returning a list of lists. | ||
372 | var ChunklistFunc = function.New(&function.Spec{ | ||
373 | Params: []function.Parameter{ | ||
374 | { | ||
375 | Name: "list", | ||
376 | Type: cty.List(cty.DynamicPseudoType), | ||
377 | }, | ||
378 | { | ||
379 | Name: "size", | ||
380 | Type: cty.Number, | ||
381 | }, | ||
382 | }, | ||
383 | Type: func(args []cty.Value) (cty.Type, error) { | ||
384 | return cty.List(args[0].Type()), nil | ||
385 | }, | ||
386 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
387 | listVal := args[0] | ||
388 | if !listVal.IsKnown() { | ||
389 | return cty.UnknownVal(retType), nil | ||
390 | } | ||
391 | |||
392 | var size int | ||
393 | err = gocty.FromCtyValue(args[1], &size) | ||
394 | if err != nil { | ||
395 | return cty.NilVal, fmt.Errorf("invalid index: %s", err) | ||
396 | } | ||
397 | |||
398 | if size < 0 { | ||
399 | return cty.NilVal, errors.New("the size argument must be positive") | ||
400 | } | ||
401 | |||
402 | output := make([]cty.Value, 0) | ||
403 | |||
404 | // if size is 0, returns a list made of the initial list | ||
405 | if size == 0 { | ||
406 | output = append(output, listVal) | ||
407 | return cty.ListVal(output), nil | ||
408 | } | ||
409 | |||
410 | chunk := make([]cty.Value, 0) | ||
411 | |||
412 | l := args[0].LengthInt() | ||
413 | i := 0 | ||
414 | |||
415 | for it := listVal.ElementIterator(); it.Next(); { | ||
416 | _, v := it.Element() | ||
417 | chunk = append(chunk, v) | ||
418 | |||
419 | // Chunk when index isn't 0, or when reaching the values's length | ||
420 | if (i+1)%size == 0 || (i+1) == l { | ||
421 | output = append(output, cty.ListVal(chunk)) | ||
422 | chunk = make([]cty.Value, 0) | ||
423 | } | ||
424 | i++ | ||
425 | } | ||
426 | |||
427 | return cty.ListVal(output), nil | ||
428 | }, | ||
429 | }) | ||
430 | |||
431 | // FlattenFunc constructs a function that takes a list and replaces any elements | ||
432 | // that are lists with a flattened sequence of the list contents. | ||
433 | var FlattenFunc = function.New(&function.Spec{ | ||
434 | Params: []function.Parameter{ | ||
435 | { | ||
436 | Name: "list", | ||
437 | Type: cty.DynamicPseudoType, | ||
438 | }, | ||
439 | }, | ||
440 | Type: func(args []cty.Value) (cty.Type, error) { | ||
441 | if !args[0].IsWhollyKnown() { | ||
442 | return cty.DynamicPseudoType, nil | ||
443 | } | ||
444 | |||
445 | argTy := args[0].Type() | ||
446 | if !argTy.IsListType() && !argTy.IsSetType() && !argTy.IsTupleType() { | ||
447 | return cty.NilType, errors.New("can only flatten lists, sets and tuples") | ||
448 | } | ||
449 | |||
450 | retVal, known := flattener(args[0]) | ||
451 | if !known { | ||
452 | return cty.DynamicPseudoType, nil | ||
453 | } | ||
454 | |||
455 | tys := make([]cty.Type, len(retVal)) | ||
456 | for i, ty := range retVal { | ||
457 | tys[i] = ty.Type() | ||
458 | } | ||
459 | return cty.Tuple(tys), nil | ||
460 | }, | ||
461 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
462 | inputList := args[0] | ||
463 | if inputList.LengthInt() == 0 { | ||
464 | return cty.EmptyTupleVal, nil | ||
465 | } | ||
466 | |||
467 | out, known := flattener(inputList) | ||
468 | if !known { | ||
469 | return cty.UnknownVal(retType), nil | ||
470 | } | ||
471 | |||
472 | return cty.TupleVal(out), nil | ||
473 | }, | ||
474 | }) | ||
475 | |||
476 | // Flatten until it's not a cty.List, and return whether the value is known. | ||
477 | // We can flatten lists with unknown values, as long as they are not | ||
478 | // lists themselves. | ||
479 | func flattener(flattenList cty.Value) ([]cty.Value, bool) { | ||
480 | out := make([]cty.Value, 0) | ||
481 | for it := flattenList.ElementIterator(); it.Next(); { | ||
482 | _, val := it.Element() | ||
483 | if val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType() { | ||
484 | if !val.IsKnown() { | ||
485 | return out, false | ||
486 | } | ||
487 | |||
488 | res, known := flattener(val) | ||
489 | if !known { | ||
490 | return res, known | ||
491 | } | ||
492 | out = append(out, res...) | ||
493 | } else { | ||
494 | out = append(out, val) | ||
495 | } | ||
496 | } | ||
497 | return out, true | ||
498 | } | ||
499 | |||
500 | // KeysFunc constructs a function that takes a map and returns a sorted list of the map keys. | ||
501 | var KeysFunc = function.New(&function.Spec{ | ||
502 | Params: []function.Parameter{ | ||
503 | { | ||
504 | Name: "inputMap", | ||
505 | Type: cty.DynamicPseudoType, | ||
506 | AllowUnknown: true, | ||
507 | }, | ||
508 | }, | ||
509 | Type: func(args []cty.Value) (cty.Type, error) { | ||
510 | ty := args[0].Type() | ||
511 | switch { | ||
512 | case ty.IsMapType(): | ||
513 | return cty.List(cty.String), nil | ||
514 | case ty.IsObjectType(): | ||
515 | atys := ty.AttributeTypes() | ||
516 | if len(atys) == 0 { | ||
517 | return cty.EmptyTuple, nil | ||
518 | } | ||
519 | // All of our result elements will be strings, and atys just | ||
520 | // decides how many there are. | ||
521 | etys := make([]cty.Type, len(atys)) | ||
522 | for i := range etys { | ||
523 | etys[i] = cty.String | ||
524 | } | ||
525 | return cty.Tuple(etys), nil | ||
526 | default: | ||
527 | return cty.DynamicPseudoType, function.NewArgErrorf(0, "must have map or object type") | ||
528 | } | ||
529 | }, | ||
530 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
531 | m := args[0] | ||
532 | var keys []cty.Value | ||
533 | |||
534 | switch { | ||
535 | case m.Type().IsObjectType(): | ||
536 | // In this case we allow unknown values so we must work only with | ||
537 | // the attribute _types_, not with the value itself. | ||
538 | var names []string | ||
539 | for name := range m.Type().AttributeTypes() { | ||
540 | names = append(names, name) | ||
541 | } | ||
542 | sort.Strings(names) // same ordering guaranteed by cty's ElementIterator | ||
543 | if len(names) == 0 { | ||
544 | return cty.EmptyTupleVal, nil | ||
545 | } | ||
546 | keys = make([]cty.Value, len(names)) | ||
547 | for i, name := range names { | ||
548 | keys[i] = cty.StringVal(name) | ||
549 | } | ||
550 | return cty.TupleVal(keys), nil | ||
551 | default: | ||
552 | if !m.IsKnown() { | ||
553 | return cty.UnknownVal(retType), nil | ||
554 | } | ||
555 | |||
556 | // cty guarantees that ElementIterator will iterate in lexicographical | ||
557 | // order by key. | ||
558 | for it := args[0].ElementIterator(); it.Next(); { | ||
559 | k, _ := it.Element() | ||
560 | keys = append(keys, k) | ||
561 | } | ||
562 | if len(keys) == 0 { | ||
563 | return cty.ListValEmpty(cty.String), nil | ||
564 | } | ||
565 | return cty.ListVal(keys), nil | ||
566 | } | ||
567 | }, | ||
568 | }) | ||
569 | |||
570 | // ListFunc constructs a function that takes an arbitrary number of arguments | ||
571 | // and returns a list containing those values in the same order. | ||
572 | // | ||
573 | // This function is deprecated in Terraform v0.12 | ||
574 | var ListFunc = function.New(&function.Spec{ | ||
575 | Params: []function.Parameter{}, | ||
576 | VarParam: &function.Parameter{ | ||
577 | Name: "vals", | ||
578 | Type: cty.DynamicPseudoType, | ||
579 | AllowUnknown: true, | ||
580 | AllowDynamicType: true, | ||
581 | AllowNull: true, | ||
582 | }, | ||
583 | Type: func(args []cty.Value) (ret cty.Type, err error) { | ||
584 | if len(args) == 0 { | ||
585 | return cty.NilType, errors.New("at least one argument is required") | ||
586 | } | ||
587 | |||
588 | argTypes := make([]cty.Type, len(args)) | ||
589 | |||
590 | for i, arg := range args { | ||
591 | argTypes[i] = arg.Type() | ||
592 | } | ||
593 | |||
594 | retType, _ := convert.UnifyUnsafe(argTypes) | ||
595 | if retType == cty.NilType { | ||
596 | return cty.NilType, errors.New("all arguments must have the same type") | ||
597 | } | ||
598 | |||
599 | return cty.List(retType), nil | ||
600 | }, | ||
601 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
602 | newList := make([]cty.Value, 0, len(args)) | ||
603 | |||
604 | for _, arg := range args { | ||
605 | // We already know this will succeed because of the checks in our Type func above | ||
606 | arg, _ = convert.Convert(arg, retType.ElementType()) | ||
607 | newList = append(newList, arg) | ||
608 | } | ||
609 | |||
610 | return cty.ListVal(newList), nil | ||
611 | }, | ||
612 | }) | ||
613 | |||
614 | // LookupFunc constructs a function that performs dynamic lookups of map types. | ||
615 | var LookupFunc = function.New(&function.Spec{ | ||
616 | Params: []function.Parameter{ | ||
617 | { | ||
618 | Name: "inputMap", | ||
619 | Type: cty.DynamicPseudoType, | ||
620 | }, | ||
621 | { | ||
622 | Name: "key", | ||
623 | Type: cty.String, | ||
624 | }, | ||
625 | }, | ||
626 | VarParam: &function.Parameter{ | ||
627 | Name: "default", | ||
628 | Type: cty.DynamicPseudoType, | ||
629 | AllowUnknown: true, | ||
630 | AllowDynamicType: true, | ||
631 | AllowNull: true, | ||
632 | }, | ||
633 | Type: func(args []cty.Value) (ret cty.Type, err error) { | ||
634 | if len(args) < 1 || len(args) > 3 { | ||
635 | return cty.NilType, fmt.Errorf("lookup() takes two or three arguments, got %d", len(args)) | ||
636 | } | ||
637 | |||
638 | ty := args[0].Type() | ||
639 | |||
640 | switch { | ||
641 | case ty.IsObjectType(): | ||
642 | if !args[1].IsKnown() { | ||
643 | return cty.DynamicPseudoType, nil | ||
644 | } | ||
645 | |||
646 | key := args[1].AsString() | ||
647 | if ty.HasAttribute(key) { | ||
648 | return args[0].GetAttr(key).Type(), nil | ||
649 | } else if len(args) == 3 { | ||
650 | // if the key isn't found but a default is provided, | ||
651 | // return the default type | ||
652 | return args[2].Type(), nil | ||
653 | } | ||
654 | return cty.DynamicPseudoType, function.NewArgErrorf(0, "the given object has no attribute %q", key) | ||
655 | case ty.IsMapType(): | ||
656 | return ty.ElementType(), nil | ||
657 | default: | ||
658 | return cty.NilType, function.NewArgErrorf(0, "lookup() requires a map as the first argument") | ||
659 | } | ||
660 | }, | ||
661 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
662 | var defaultVal cty.Value | ||
663 | defaultValueSet := false | ||
664 | |||
665 | if len(args) == 3 { | ||
666 | defaultVal = args[2] | ||
667 | defaultValueSet = true | ||
668 | } | ||
669 | |||
670 | mapVar := args[0] | ||
671 | lookupKey := args[1].AsString() | ||
672 | |||
673 | if !mapVar.IsWhollyKnown() { | ||
674 | return cty.UnknownVal(retType), nil | ||
675 | } | ||
676 | |||
677 | if mapVar.Type().IsObjectType() { | ||
678 | if mapVar.Type().HasAttribute(lookupKey) { | ||
679 | return mapVar.GetAttr(lookupKey), nil | ||
680 | } | ||
681 | } else if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True { | ||
682 | v := mapVar.Index(cty.StringVal(lookupKey)) | ||
683 | if ty := v.Type(); !ty.Equals(cty.NilType) { | ||
684 | switch { | ||
685 | case ty.Equals(cty.String): | ||
686 | return cty.StringVal(v.AsString()), nil | ||
687 | case ty.Equals(cty.Number): | ||
688 | return cty.NumberVal(v.AsBigFloat()), nil | ||
689 | default: | ||
690 | return cty.NilVal, errors.New("lookup() can only be used with flat lists") | ||
691 | } | ||
692 | } | ||
693 | } | ||
694 | |||
695 | if defaultValueSet { | ||
696 | defaultVal, err = convert.Convert(defaultVal, retType) | ||
697 | if err != nil { | ||
698 | return cty.NilVal, err | ||
699 | } | ||
700 | return defaultVal, nil | ||
701 | } | ||
702 | |||
703 | return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf( | ||
704 | "lookup failed to find '%s'", lookupKey) | ||
705 | }, | ||
706 | }) | ||
707 | |||
708 | // MapFunc constructs a function that takes an even number of arguments and | ||
709 | // returns a map whose elements are constructed from consecutive pairs of arguments. | ||
710 | // | ||
711 | // This function is deprecated in Terraform v0.12 | ||
712 | var MapFunc = function.New(&function.Spec{ | ||
713 | Params: []function.Parameter{}, | ||
714 | VarParam: &function.Parameter{ | ||
715 | Name: "vals", | ||
716 | Type: cty.DynamicPseudoType, | ||
717 | AllowUnknown: true, | ||
718 | AllowDynamicType: true, | ||
719 | AllowNull: true, | ||
720 | }, | ||
721 | Type: func(args []cty.Value) (ret cty.Type, err error) { | ||
722 | if len(args) < 2 || len(args)%2 != 0 { | ||
723 | return cty.NilType, fmt.Errorf("map requires an even number of two or more arguments, got %d", len(args)) | ||
724 | } | ||
725 | |||
726 | argTypes := make([]cty.Type, len(args)/2) | ||
727 | index := 0 | ||
728 | |||
729 | for i := 0; i < len(args); i += 2 { | ||
730 | argTypes[index] = args[i+1].Type() | ||
731 | index++ | ||
732 | } | ||
733 | |||
734 | valType, _ := convert.UnifyUnsafe(argTypes) | ||
735 | if valType == cty.NilType { | ||
736 | return cty.NilType, errors.New("all arguments must have the same type") | ||
737 | } | ||
738 | |||
739 | return cty.Map(valType), nil | ||
740 | }, | ||
741 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
742 | for _, arg := range args { | ||
743 | if !arg.IsWhollyKnown() { | ||
744 | return cty.UnknownVal(retType), nil | ||
745 | } | ||
746 | } | ||
747 | |||
748 | outputMap := make(map[string]cty.Value) | ||
749 | |||
750 | for i := 0; i < len(args); i += 2 { | ||
751 | |||
752 | key := args[i].AsString() | ||
753 | |||
754 | err := gocty.FromCtyValue(args[i], &key) | ||
755 | if err != nil { | ||
756 | return cty.NilVal, err | ||
757 | } | ||
758 | |||
759 | val := args[i+1] | ||
760 | |||
761 | var variable cty.Value | ||
762 | err = gocty.FromCtyValue(val, &variable) | ||
763 | if err != nil { | ||
764 | return cty.NilVal, err | ||
765 | } | ||
766 | |||
767 | // We already know this will succeed because of the checks in our Type func above | ||
768 | variable, _ = convert.Convert(variable, retType.ElementType()) | ||
769 | |||
770 | // Check for duplicate keys | ||
771 | if _, ok := outputMap[key]; ok { | ||
772 | return cty.NilVal, fmt.Errorf("argument %d is a duplicate key: %q", i+1, key) | ||
773 | } | ||
774 | outputMap[key] = variable | ||
775 | } | ||
776 | |||
777 | return cty.MapVal(outputMap), nil | ||
778 | }, | ||
779 | }) | ||
780 | |||
781 | // MatchkeysFunc constructs a function that constructs a new list by taking a | ||
782 | // subset of elements from one list whose indexes match the corresponding | ||
783 | // indexes of values in another list. | ||
784 | var MatchkeysFunc = function.New(&function.Spec{ | ||
785 | Params: []function.Parameter{ | ||
786 | { | ||
787 | Name: "values", | ||
788 | Type: cty.List(cty.DynamicPseudoType), | ||
789 | }, | ||
790 | { | ||
791 | Name: "keys", | ||
792 | Type: cty.List(cty.DynamicPseudoType), | ||
793 | }, | ||
794 | { | ||
795 | Name: "searchset", | ||
796 | Type: cty.List(cty.DynamicPseudoType), | ||
797 | }, | ||
798 | }, | ||
799 | Type: func(args []cty.Value) (cty.Type, error) { | ||
800 | if !args[1].Type().Equals(args[2].Type()) { | ||
801 | return cty.NilType, errors.New("lists must be of the same type") | ||
802 | } | ||
803 | |||
804 | return args[0].Type(), nil | ||
805 | }, | ||
806 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
807 | if !args[0].IsKnown() { | ||
808 | return cty.UnknownVal(cty.List(retType.ElementType())), nil | ||
809 | } | ||
810 | |||
811 | if args[0].LengthInt() != args[1].LengthInt() { | ||
812 | return cty.ListValEmpty(retType.ElementType()), errors.New("length of keys and values should be equal") | ||
813 | } | ||
814 | |||
815 | output := make([]cty.Value, 0) | ||
816 | |||
817 | values := args[0] | ||
818 | keys := args[1] | ||
819 | searchset := args[2] | ||
820 | |||
821 | // if searchset is empty, return an empty list. | ||
822 | if searchset.LengthInt() == 0 { | ||
823 | return cty.ListValEmpty(retType.ElementType()), nil | ||
824 | } | ||
825 | |||
826 | if !values.IsWhollyKnown() || !keys.IsWhollyKnown() { | ||
827 | return cty.UnknownVal(retType), nil | ||
828 | } | ||
829 | |||
830 | i := 0 | ||
831 | for it := keys.ElementIterator(); it.Next(); { | ||
832 | _, key := it.Element() | ||
833 | for iter := searchset.ElementIterator(); iter.Next(); { | ||
834 | _, search := iter.Element() | ||
835 | eq, err := stdlib.Equal(key, search) | ||
836 | if err != nil { | ||
837 | return cty.NilVal, err | ||
838 | } | ||
839 | if !eq.IsKnown() { | ||
840 | return cty.ListValEmpty(retType.ElementType()), nil | ||
841 | } | ||
842 | if eq.True() { | ||
843 | v := values.Index(cty.NumberIntVal(int64(i))) | ||
844 | output = append(output, v) | ||
845 | break | ||
846 | } | ||
847 | } | ||
848 | i++ | ||
849 | } | ||
850 | |||
851 | // if we haven't matched any key, then output is an empty list. | ||
852 | if len(output) == 0 { | ||
853 | return cty.ListValEmpty(retType.ElementType()), nil | ||
854 | } | ||
855 | return cty.ListVal(output), nil | ||
856 | }, | ||
857 | }) | ||
858 | |||
859 | // MergeFunc constructs a function that takes an arbitrary number of maps and | ||
860 | // returns a single map that contains a merged set of elements from all of the maps. | ||
861 | // | ||
862 | // If more than one given map defines the same key then the one that is later in | ||
863 | // the argument sequence takes precedence. | ||
864 | var MergeFunc = function.New(&function.Spec{ | ||
865 | Params: []function.Parameter{}, | ||
866 | VarParam: &function.Parameter{ | ||
867 | Name: "maps", | ||
868 | Type: cty.DynamicPseudoType, | ||
869 | AllowDynamicType: true, | ||
870 | AllowNull: true, | ||
871 | }, | ||
872 | Type: function.StaticReturnType(cty.DynamicPseudoType), | ||
873 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
874 | outputMap := make(map[string]cty.Value) | ||
875 | |||
876 | for _, arg := range args { | ||
877 | if !arg.IsWhollyKnown() { | ||
878 | return cty.UnknownVal(retType), nil | ||
879 | } | ||
880 | if !arg.Type().IsObjectType() && !arg.Type().IsMapType() { | ||
881 | return cty.NilVal, fmt.Errorf("arguments must be maps or objects, got %#v", arg.Type().FriendlyName()) | ||
882 | } | ||
883 | for it := arg.ElementIterator(); it.Next(); { | ||
884 | k, v := it.Element() | ||
885 | outputMap[k.AsString()] = v | ||
886 | } | ||
887 | } | ||
888 | return cty.ObjectVal(outputMap), nil | ||
889 | }, | ||
890 | }) | ||
891 | |||
892 | // ReverseFunc takes a sequence and produces a new sequence of the same length | ||
893 | // with all of the same elements as the given sequence but in reverse order. | ||
894 | var ReverseFunc = function.New(&function.Spec{ | ||
895 | Params: []function.Parameter{ | ||
896 | { | ||
897 | Name: "list", | ||
898 | Type: cty.DynamicPseudoType, | ||
899 | }, | ||
900 | }, | ||
901 | Type: func(args []cty.Value) (cty.Type, error) { | ||
902 | argTy := args[0].Type() | ||
903 | switch { | ||
904 | case argTy.IsTupleType(): | ||
905 | argTys := argTy.TupleElementTypes() | ||
906 | retTys := make([]cty.Type, len(argTys)) | ||
907 | for i, ty := range argTys { | ||
908 | retTys[len(retTys)-i-1] = ty | ||
909 | } | ||
910 | return cty.Tuple(retTys), nil | ||
911 | case argTy.IsListType(), argTy.IsSetType(): // We accept sets here to mimic the usual behavior of auto-converting to list | ||
912 | return cty.List(argTy.ElementType()), nil | ||
913 | default: | ||
914 | return cty.NilType, function.NewArgErrorf(0, "can only reverse list or tuple values, not %s", argTy.FriendlyName()) | ||
915 | } | ||
916 | }, | ||
917 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
918 | in := args[0].AsValueSlice() | ||
919 | outVals := make([]cty.Value, len(in)) | ||
920 | for i, v := range in { | ||
921 | outVals[len(outVals)-i-1] = v | ||
922 | } | ||
923 | switch { | ||
924 | case retType.IsTupleType(): | ||
925 | return cty.TupleVal(outVals), nil | ||
926 | default: | ||
927 | if len(outVals) == 0 { | ||
928 | return cty.ListValEmpty(retType.ElementType()), nil | ||
929 | } | ||
930 | return cty.ListVal(outVals), nil | ||
931 | } | ||
932 | }, | ||
933 | }) | ||
934 | |||
935 | // SetProductFunc calculates the cartesian product of two or more sets or | ||
936 | // sequences. If the arguments are all lists then the result is a list of tuples, | ||
937 | // preserving the ordering of all of the input lists. Otherwise the result is a | ||
938 | // set of tuples. | ||
939 | var SetProductFunc = function.New(&function.Spec{ | ||
940 | Params: []function.Parameter{}, | ||
941 | VarParam: &function.Parameter{ | ||
942 | Name: "sets", | ||
943 | Type: cty.DynamicPseudoType, | ||
944 | }, | ||
945 | Type: func(args []cty.Value) (retType cty.Type, err error) { | ||
946 | if len(args) < 2 { | ||
947 | return cty.NilType, errors.New("at least two arguments are required") | ||
948 | } | ||
949 | |||
950 | listCount := 0 | ||
951 | elemTys := make([]cty.Type, len(args)) | ||
952 | for i, arg := range args { | ||
953 | aty := arg.Type() | ||
954 | switch { | ||
955 | case aty.IsSetType(): | ||
956 | elemTys[i] = aty.ElementType() | ||
957 | case aty.IsListType(): | ||
958 | elemTys[i] = aty.ElementType() | ||
959 | listCount++ | ||
960 | case aty.IsTupleType(): | ||
961 | // We can accept a tuple type only if there's some common type | ||
962 | // that all of its elements can be converted to. | ||
963 | allEtys := aty.TupleElementTypes() | ||
964 | if len(allEtys) == 0 { | ||
965 | elemTys[i] = cty.DynamicPseudoType | ||
966 | listCount++ | ||
967 | break | ||
968 | } | ||
969 | ety, _ := convert.UnifyUnsafe(allEtys) | ||
970 | if ety == cty.NilType { | ||
971 | return cty.NilType, function.NewArgErrorf(i, "all elements must be of the same type") | ||
972 | } | ||
973 | elemTys[i] = ety | ||
974 | listCount++ | ||
975 | default: | ||
976 | return cty.NilType, function.NewArgErrorf(i, "a set or a list is required") | ||
977 | } | ||
978 | } | ||
979 | |||
980 | if listCount == len(args) { | ||
981 | return cty.List(cty.Tuple(elemTys)), nil | ||
982 | } | ||
983 | return cty.Set(cty.Tuple(elemTys)), nil | ||
984 | }, | ||
985 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
986 | ety := retType.ElementType() | ||
987 | |||
988 | total := 1 | ||
989 | for _, arg := range args { | ||
990 | // Because of our type checking function, we are guaranteed that | ||
991 | // all of the arguments are known, non-null values of types that | ||
992 | // support LengthInt. | ||
993 | total *= arg.LengthInt() | ||
994 | } | ||
995 | |||
996 | if total == 0 { | ||
997 | // If any of the arguments was an empty collection then our result | ||
998 | // is also an empty collection, which we'll short-circuit here. | ||
999 | if retType.IsListType() { | ||
1000 | return cty.ListValEmpty(ety), nil | ||
1001 | } | ||
1002 | return cty.SetValEmpty(ety), nil | ||
1003 | } | ||
1004 | |||
1005 | subEtys := ety.TupleElementTypes() | ||
1006 | product := make([][]cty.Value, total) | ||
1007 | |||
1008 | b := make([]cty.Value, total*len(args)) | ||
1009 | n := make([]int, len(args)) | ||
1010 | s := 0 | ||
1011 | argVals := make([][]cty.Value, len(args)) | ||
1012 | for i, arg := range args { | ||
1013 | argVals[i] = arg.AsValueSlice() | ||
1014 | } | ||
1015 | |||
1016 | for i := range product { | ||
1017 | e := s + len(args) | ||
1018 | pi := b[s:e] | ||
1019 | product[i] = pi | ||
1020 | s = e | ||
1021 | |||
1022 | for j, n := range n { | ||
1023 | val := argVals[j][n] | ||
1024 | ty := subEtys[j] | ||
1025 | if !val.Type().Equals(ty) { | ||
1026 | var err error | ||
1027 | val, err = convert.Convert(val, ty) | ||
1028 | if err != nil { | ||
1029 | // Should never happen since we checked this in our | ||
1030 | // type-checking function. | ||
1031 | return cty.NilVal, fmt.Errorf("failed to convert argVals[%d][%d] to %s; this is a bug in Terraform", j, n, ty.FriendlyName()) | ||
1032 | } | ||
1033 | } | ||
1034 | pi[j] = val | ||
1035 | } | ||
1036 | |||
1037 | for j := len(n) - 1; j >= 0; j-- { | ||
1038 | n[j]++ | ||
1039 | if n[j] < len(argVals[j]) { | ||
1040 | break | ||
1041 | } | ||
1042 | n[j] = 0 | ||
1043 | } | ||
1044 | } | ||
1045 | |||
1046 | productVals := make([]cty.Value, total) | ||
1047 | for i, vals := range product { | ||
1048 | productVals[i] = cty.TupleVal(vals) | ||
1049 | } | ||
1050 | |||
1051 | if retType.IsListType() { | ||
1052 | return cty.ListVal(productVals), nil | ||
1053 | } | ||
1054 | return cty.SetVal(productVals), nil | ||
1055 | }, | ||
1056 | }) | ||
1057 | |||
1058 | // SliceFunc constructs a function that extracts some consecutive elements | ||
1059 | // from within a list. | ||
1060 | var SliceFunc = function.New(&function.Spec{ | ||
1061 | Params: []function.Parameter{ | ||
1062 | { | ||
1063 | Name: "list", | ||
1064 | Type: cty.DynamicPseudoType, | ||
1065 | }, | ||
1066 | { | ||
1067 | Name: "start_index", | ||
1068 | Type: cty.Number, | ||
1069 | }, | ||
1070 | { | ||
1071 | Name: "end_index", | ||
1072 | Type: cty.Number, | ||
1073 | }, | ||
1074 | }, | ||
1075 | Type: func(args []cty.Value) (cty.Type, error) { | ||
1076 | arg := args[0] | ||
1077 | argTy := arg.Type() | ||
1078 | |||
1079 | if argTy.IsSetType() { | ||
1080 | return cty.NilType, function.NewArgErrorf(0, "cannot slice a set, because its elements do not have indices; use the tolist function to force conversion to list if the ordering of the result is not important") | ||
1081 | } | ||
1082 | if !argTy.IsListType() && !argTy.IsTupleType() { | ||
1083 | return cty.NilType, function.NewArgErrorf(0, "must be a list or tuple value") | ||
1084 | } | ||
1085 | |||
1086 | startIndex, endIndex, idxsKnown, err := sliceIndexes(args) | ||
1087 | if err != nil { | ||
1088 | return cty.NilType, err | ||
1089 | } | ||
1090 | |||
1091 | if argTy.IsListType() { | ||
1092 | return argTy, nil | ||
1093 | } | ||
1094 | |||
1095 | if !idxsKnown { | ||
1096 | // If we don't know our start/end indices then we can't predict | ||
1097 | // the result type if we're planning to return a tuple. | ||
1098 | return cty.DynamicPseudoType, nil | ||
1099 | } | ||
1100 | return cty.Tuple(argTy.TupleElementTypes()[startIndex:endIndex]), nil | ||
1101 | }, | ||
1102 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
1103 | inputList := args[0] | ||
1104 | |||
1105 | if retType == cty.DynamicPseudoType { | ||
1106 | return cty.DynamicVal, nil | ||
1107 | } | ||
1108 | |||
1109 | // we ignore idxsKnown return value here because the indices are always | ||
1110 | // known here, or else the call would've short-circuited. | ||
1111 | startIndex, endIndex, _, err := sliceIndexes(args) | ||
1112 | if err != nil { | ||
1113 | return cty.NilVal, err | ||
1114 | } | ||
1115 | |||
1116 | if endIndex-startIndex == 0 { | ||
1117 | if retType.IsTupleType() { | ||
1118 | return cty.EmptyTupleVal, nil | ||
1119 | } | ||
1120 | return cty.ListValEmpty(retType.ElementType()), nil | ||
1121 | } | ||
1122 | |||
1123 | outputList := inputList.AsValueSlice()[startIndex:endIndex] | ||
1124 | |||
1125 | if retType.IsTupleType() { | ||
1126 | return cty.TupleVal(outputList), nil | ||
1127 | } | ||
1128 | |||
1129 | return cty.ListVal(outputList), nil | ||
1130 | }, | ||
1131 | }) | ||
1132 | |||
1133 | func sliceIndexes(args []cty.Value) (int, int, bool, error) { | ||
1134 | var startIndex, endIndex, length int | ||
1135 | var startKnown, endKnown, lengthKnown bool | ||
1136 | |||
1137 | if args[0].Type().IsTupleType() || args[0].IsKnown() { // if it's a tuple then we always know the length by the type, but lists must be known | ||
1138 | length = args[0].LengthInt() | ||
1139 | lengthKnown = true | ||
1140 | } | ||
1141 | |||
1142 | if args[1].IsKnown() { | ||
1143 | if err := gocty.FromCtyValue(args[1], &startIndex); err != nil { | ||
1144 | return 0, 0, false, function.NewArgErrorf(1, "invalid start index: %s", err) | ||
1145 | } | ||
1146 | if startIndex < 0 { | ||
1147 | return 0, 0, false, function.NewArgErrorf(1, "start index must not be less than zero") | ||
1148 | } | ||
1149 | if lengthKnown && startIndex > length { | ||
1150 | return 0, 0, false, function.NewArgErrorf(1, "start index must not be greater than the length of the list") | ||
1151 | } | ||
1152 | startKnown = true | ||
1153 | } | ||
1154 | if args[2].IsKnown() { | ||
1155 | if err := gocty.FromCtyValue(args[2], &endIndex); err != nil { | ||
1156 | return 0, 0, false, function.NewArgErrorf(2, "invalid end index: %s", err) | ||
1157 | } | ||
1158 | if endIndex < 0 { | ||
1159 | return 0, 0, false, function.NewArgErrorf(2, "end index must not be less than zero") | ||
1160 | } | ||
1161 | if lengthKnown && endIndex > length { | ||
1162 | return 0, 0, false, function.NewArgErrorf(2, "end index must not be greater than the length of the list") | ||
1163 | } | ||
1164 | endKnown = true | ||
1165 | } | ||
1166 | if startKnown && endKnown { | ||
1167 | if startIndex > endIndex { | ||
1168 | return 0, 0, false, function.NewArgErrorf(1, "start index must not be greater than end index") | ||
1169 | } | ||
1170 | } | ||
1171 | return startIndex, endIndex, startKnown && endKnown, nil | ||
1172 | } | ||
1173 | |||
1174 | // TransposeFunc contructs a function that takes a map of lists of strings and | ||
1175 | // TransposeFunc constructs a function that takes a map of lists of strings and | ||
1176 | // swaps the keys and values to produce a new map of lists of strings. | ||
1177 | var TransposeFunc = function.New(&function.Spec{ | ||
1178 | Params: []function.Parameter{ | ||
1179 | { | ||
1180 | Name: "values", | ||
1181 | Type: cty.Map(cty.List(cty.String)), | ||
1182 | }, | ||
1183 | }, | ||
1184 | Type: function.StaticReturnType(cty.Map(cty.List(cty.String))), | ||
1185 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
1186 | inputMap := args[0] | ||
1187 | if !inputMap.IsWhollyKnown() { | ||
1188 | return cty.UnknownVal(retType), nil | ||
1189 | } | ||
1190 | |||
1191 | outputMap := make(map[string]cty.Value) | ||
1192 | tmpMap := make(map[string][]string) | ||
1193 | |||
1194 | for it := inputMap.ElementIterator(); it.Next(); { | ||
1195 | inKey, inVal := it.Element() | ||
1196 | for iter := inVal.ElementIterator(); iter.Next(); { | ||
1197 | _, val := iter.Element() | ||
1198 | if !val.Type().Equals(cty.String) { | ||
1199 | return cty.MapValEmpty(cty.List(cty.String)), errors.New("input must be a map of lists of strings") | ||
1200 | } | ||
1201 | |||
1202 | outKey := val.AsString() | ||
1203 | if _, ok := tmpMap[outKey]; !ok { | ||
1204 | tmpMap[outKey] = make([]string, 0) | ||
1205 | } | ||
1206 | outVal := tmpMap[outKey] | ||
1207 | outVal = append(outVal, inKey.AsString()) | ||
1208 | sort.Strings(outVal) | ||
1209 | tmpMap[outKey] = outVal | ||
1210 | } | ||
1211 | } | ||
1212 | |||
1213 | for outKey, outVal := range tmpMap { | ||
1214 | values := make([]cty.Value, 0) | ||
1215 | for _, v := range outVal { | ||
1216 | values = append(values, cty.StringVal(v)) | ||
1217 | } | ||
1218 | outputMap[outKey] = cty.ListVal(values) | ||
1219 | } | ||
1220 | |||
1221 | return cty.MapVal(outputMap), nil | ||
1222 | }, | ||
1223 | }) | ||
1224 | |||
1225 | // ValuesFunc constructs a function that returns a list of the map values, | ||
1226 | // in the order of the sorted keys. | ||
1227 | var ValuesFunc = function.New(&function.Spec{ | ||
1228 | Params: []function.Parameter{ | ||
1229 | { | ||
1230 | Name: "values", | ||
1231 | Type: cty.DynamicPseudoType, | ||
1232 | }, | ||
1233 | }, | ||
1234 | Type: func(args []cty.Value) (ret cty.Type, err error) { | ||
1235 | ty := args[0].Type() | ||
1236 | if ty.IsMapType() { | ||
1237 | return cty.List(ty.ElementType()), nil | ||
1238 | } else if ty.IsObjectType() { | ||
1239 | // The result is a tuple type with all of the same types as our | ||
1240 | // object type's attributes, sorted in lexicographical order by the | ||
1241 | // keys. (This matches the sort order guaranteed by ElementIterator | ||
1242 | // on a cty object value.) | ||
1243 | atys := ty.AttributeTypes() | ||
1244 | if len(atys) == 0 { | ||
1245 | return cty.EmptyTuple, nil | ||
1246 | } | ||
1247 | attrNames := make([]string, 0, len(atys)) | ||
1248 | for name := range atys { | ||
1249 | attrNames = append(attrNames, name) | ||
1250 | } | ||
1251 | sort.Strings(attrNames) | ||
1252 | |||
1253 | tys := make([]cty.Type, len(attrNames)) | ||
1254 | for i, name := range attrNames { | ||
1255 | tys[i] = atys[name] | ||
1256 | } | ||
1257 | return cty.Tuple(tys), nil | ||
1258 | } | ||
1259 | return cty.NilType, errors.New("values() requires a map as the first argument") | ||
1260 | }, | ||
1261 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
1262 | mapVar := args[0] | ||
1263 | |||
1264 | // We can just iterate the map/object value here because cty guarantees | ||
1265 | // that these types always iterate in key lexicographical order. | ||
1266 | var values []cty.Value | ||
1267 | for it := mapVar.ElementIterator(); it.Next(); { | ||
1268 | _, val := it.Element() | ||
1269 | values = append(values, val) | ||
1270 | } | ||
1271 | |||
1272 | if retType.IsTupleType() { | ||
1273 | return cty.TupleVal(values), nil | ||
1274 | } | ||
1275 | if len(values) == 0 { | ||
1276 | return cty.ListValEmpty(retType.ElementType()), nil | ||
1277 | } | ||
1278 | return cty.ListVal(values), nil | ||
1279 | }, | ||
1280 | }) | ||
1281 | |||
1282 | // ZipmapFunc constructs a function that constructs a map from a list of keys | ||
1283 | // and a corresponding list of values. | ||
1284 | var ZipmapFunc = function.New(&function.Spec{ | ||
1285 | Params: []function.Parameter{ | ||
1286 | { | ||
1287 | Name: "keys", | ||
1288 | Type: cty.List(cty.String), | ||
1289 | }, | ||
1290 | { | ||
1291 | Name: "values", | ||
1292 | Type: cty.DynamicPseudoType, | ||
1293 | }, | ||
1294 | }, | ||
1295 | Type: func(args []cty.Value) (ret cty.Type, err error) { | ||
1296 | keys := args[0] | ||
1297 | values := args[1] | ||
1298 | valuesTy := values.Type() | ||
1299 | |||
1300 | switch { | ||
1301 | case valuesTy.IsListType(): | ||
1302 | return cty.Map(values.Type().ElementType()), nil | ||
1303 | case valuesTy.IsTupleType(): | ||
1304 | if !keys.IsWhollyKnown() { | ||
1305 | // Since zipmap with a tuple produces an object, we need to know | ||
1306 | // all of the key names before we can predict our result type. | ||
1307 | return cty.DynamicPseudoType, nil | ||
1308 | } | ||
1309 | |||
1310 | keysRaw := keys.AsValueSlice() | ||
1311 | valueTypesRaw := valuesTy.TupleElementTypes() | ||
1312 | if len(keysRaw) != len(valueTypesRaw) { | ||
1313 | return cty.NilType, fmt.Errorf("number of keys (%d) does not match number of values (%d)", len(keysRaw), len(valueTypesRaw)) | ||
1314 | } | ||
1315 | atys := make(map[string]cty.Type, len(valueTypesRaw)) | ||
1316 | for i, keyVal := range keysRaw { | ||
1317 | if keyVal.IsNull() { | ||
1318 | return cty.NilType, fmt.Errorf("keys list has null value at index %d", i) | ||
1319 | } | ||
1320 | key := keyVal.AsString() | ||
1321 | atys[key] = valueTypesRaw[i] | ||
1322 | } | ||
1323 | return cty.Object(atys), nil | ||
1324 | |||
1325 | default: | ||
1326 | return cty.NilType, errors.New("values argument must be a list or tuple value") | ||
1327 | } | ||
1328 | }, | ||
1329 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
1330 | keys := args[0] | ||
1331 | values := args[1] | ||
1332 | |||
1333 | if !keys.IsWhollyKnown() { | ||
1334 | // Unknown map keys and object attributes are not supported, so | ||
1335 | // our entire result must be unknown in this case. | ||
1336 | return cty.UnknownVal(retType), nil | ||
1337 | } | ||
1338 | |||
1339 | // both keys and values are guaranteed to be shallowly-known here, | ||
1340 | // because our declared params above don't allow unknown or null values. | ||
1341 | if keys.LengthInt() != values.LengthInt() { | ||
1342 | return cty.NilVal, fmt.Errorf("number of keys (%d) does not match number of values (%d)", keys.LengthInt(), values.LengthInt()) | ||
1343 | } | ||
1344 | |||
1345 | output := make(map[string]cty.Value) | ||
1346 | |||
1347 | i := 0 | ||
1348 | for it := keys.ElementIterator(); it.Next(); { | ||
1349 | _, v := it.Element() | ||
1350 | val := values.Index(cty.NumberIntVal(int64(i))) | ||
1351 | output[v.AsString()] = val | ||
1352 | i++ | ||
1353 | } | ||
1354 | |||
1355 | switch { | ||
1356 | case retType.IsMapType(): | ||
1357 | if len(output) == 0 { | ||
1358 | return cty.MapValEmpty(retType.ElementType()), nil | ||
1359 | } | ||
1360 | return cty.MapVal(output), nil | ||
1361 | case retType.IsObjectType(): | ||
1362 | return cty.ObjectVal(output), nil | ||
1363 | default: | ||
1364 | // Should never happen because the type-check function should've | ||
1365 | // caught any other case. | ||
1366 | return cty.NilVal, fmt.Errorf("internally selected incorrect result type %s (this is a bug)", retType.FriendlyName()) | ||
1367 | } | ||
1368 | }, | ||
1369 | }) | ||
1370 | |||
1371 | // helper function to add an element to a list, if it does not already exist | ||
1372 | func appendIfMissing(slice []cty.Value, element cty.Value) ([]cty.Value, error) { | ||
1373 | for _, ele := range slice { | ||
1374 | eq, err := stdlib.Equal(ele, element) | ||
1375 | if err != nil { | ||
1376 | return slice, err | ||
1377 | } | ||
1378 | if eq.True() { | ||
1379 | return slice, nil | ||
1380 | } | ||
1381 | } | ||
1382 | return append(slice, element), nil | ||
1383 | } | ||
1384 | |||
1385 | // Element returns a single element from a given list at the given index. If | ||
1386 | // index is greater than the length of the list then it is wrapped modulo | ||
1387 | // the list length. | ||
1388 | func Element(list, index cty.Value) (cty.Value, error) { | ||
1389 | return ElementFunc.Call([]cty.Value{list, index}) | ||
1390 | } | ||
1391 | |||
1392 | // Length returns the number of elements in the given collection or number of | ||
1393 | // Unicode characters in the given string. | ||
1394 | func Length(collection cty.Value) (cty.Value, error) { | ||
1395 | return LengthFunc.Call([]cty.Value{collection}) | ||
1396 | } | ||
1397 | |||
1398 | // Coalesce takes any number of arguments and returns the first one that isn't empty. | ||
1399 | func Coalesce(args ...cty.Value) (cty.Value, error) { | ||
1400 | return CoalesceFunc.Call(args) | ||
1401 | } | ||
1402 | |||
1403 | // CoalesceList takes any number of list arguments and returns the first one that isn't empty. | ||
1404 | func CoalesceList(args ...cty.Value) (cty.Value, error) { | ||
1405 | return CoalesceListFunc.Call(args) | ||
1406 | } | ||
1407 | |||
1408 | // Compact takes a list of strings and returns a new list | ||
1409 | // with any empty string elements removed. | ||
1410 | func Compact(list cty.Value) (cty.Value, error) { | ||
1411 | return CompactFunc.Call([]cty.Value{list}) | ||
1412 | } | ||
1413 | |||
1414 | // Contains determines whether a given list contains a given single value | ||
1415 | // as one of its elements. | ||
1416 | func Contains(list, value cty.Value) (cty.Value, error) { | ||
1417 | return ContainsFunc.Call([]cty.Value{list, value}) | ||
1418 | } | ||
1419 | |||
1420 | // Index finds the element index for a given value in a list. | ||
1421 | func Index(list, value cty.Value) (cty.Value, error) { | ||
1422 | return IndexFunc.Call([]cty.Value{list, value}) | ||
1423 | } | ||
1424 | |||
1425 | // Distinct takes a list and returns a new list with any duplicate elements removed. | ||
1426 | func Distinct(list cty.Value) (cty.Value, error) { | ||
1427 | return DistinctFunc.Call([]cty.Value{list}) | ||
1428 | } | ||
1429 | |||
1430 | // Chunklist splits a single list into fixed-size chunks, returning a list of lists. | ||
1431 | func Chunklist(list, size cty.Value) (cty.Value, error) { | ||
1432 | return ChunklistFunc.Call([]cty.Value{list, size}) | ||
1433 | } | ||
1434 | |||
1435 | // Flatten takes a list and replaces any elements that are lists with a flattened | ||
1436 | // sequence of the list contents. | ||
1437 | func Flatten(list cty.Value) (cty.Value, error) { | ||
1438 | return FlattenFunc.Call([]cty.Value{list}) | ||
1439 | } | ||
1440 | |||
1441 | // Keys takes a map and returns a sorted list of the map keys. | ||
1442 | func Keys(inputMap cty.Value) (cty.Value, error) { | ||
1443 | return KeysFunc.Call([]cty.Value{inputMap}) | ||
1444 | } | ||
1445 | |||
1446 | // List takes any number of list arguments and returns a list containing those | ||
1447 | // values in the same order. | ||
1448 | func List(args ...cty.Value) (cty.Value, error) { | ||
1449 | return ListFunc.Call(args) | ||
1450 | } | ||
1451 | |||
1452 | // Lookup performs a dynamic lookup into a map. | ||
1453 | // There are two required arguments, map and key, plus an optional default, | ||
1454 | // which is a value to return if no key is found in map. | ||
1455 | func Lookup(args ...cty.Value) (cty.Value, error) { | ||
1456 | return LookupFunc.Call(args) | ||
1457 | } | ||
1458 | |||
1459 | // Map takes an even number of arguments and returns a map whose elements are constructed | ||
1460 | // from consecutive pairs of arguments. | ||
1461 | func Map(args ...cty.Value) (cty.Value, error) { | ||
1462 | return MapFunc.Call(args) | ||
1463 | } | ||
1464 | |||
1465 | // Matchkeys constructs a new list by taking a subset of elements from one list | ||
1466 | // whose indexes match the corresponding indexes of values in another list. | ||
1467 | func Matchkeys(values, keys, searchset cty.Value) (cty.Value, error) { | ||
1468 | return MatchkeysFunc.Call([]cty.Value{values, keys, searchset}) | ||
1469 | } | ||
1470 | |||
1471 | // Merge takes an arbitrary number of maps and returns a single map that contains | ||
1472 | // a merged set of elements from all of the maps. | ||
1473 | // | ||
1474 | // If more than one given map defines the same key then the one that is later in | ||
1475 | // the argument sequence takes precedence. | ||
1476 | func Merge(maps ...cty.Value) (cty.Value, error) { | ||
1477 | return MergeFunc.Call(maps) | ||
1478 | } | ||
1479 | |||
1480 | // Reverse takes a sequence and produces a new sequence of the same length | ||
1481 | // with all of the same elements as the given sequence but in reverse order. | ||
1482 | func Reverse(list cty.Value) (cty.Value, error) { | ||
1483 | return ReverseFunc.Call([]cty.Value{list}) | ||
1484 | } | ||
1485 | |||
1486 | // SetProduct computes the cartesian product of sets or sequences. | ||
1487 | func SetProduct(sets ...cty.Value) (cty.Value, error) { | ||
1488 | return SetProductFunc.Call(sets) | ||
1489 | } | ||
1490 | |||
1491 | // Slice extracts some consecutive elements from within a list. | ||
1492 | func Slice(list, start, end cty.Value) (cty.Value, error) { | ||
1493 | return SliceFunc.Call([]cty.Value{list, start, end}) | ||
1494 | } | ||
1495 | |||
1496 | // Transpose takes a map of lists of strings and swaps the keys and values to | ||
1497 | // produce a new map of lists of strings. | ||
1498 | func Transpose(values cty.Value) (cty.Value, error) { | ||
1499 | return TransposeFunc.Call([]cty.Value{values}) | ||
1500 | } | ||
1501 | |||
1502 | // Values returns a list of the map values, in the order of the sorted keys. | ||
1503 | // This function only works on flat maps. | ||
1504 | func Values(values cty.Value) (cty.Value, error) { | ||
1505 | return ValuesFunc.Call([]cty.Value{values}) | ||
1506 | } | ||
1507 | |||
1508 | // Zipmap constructs a map from a list of keys and a corresponding list of values. | ||
1509 | func Zipmap(keys, values cty.Value) (cty.Value, error) { | ||
1510 | return ZipmapFunc.Call([]cty.Value{keys, values}) | ||
1511 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/funcs/conversion.go b/vendor/github.com/hashicorp/terraform/lang/funcs/conversion.go new file mode 100644 index 0000000..83f8597 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/funcs/conversion.go | |||
@@ -0,0 +1,87 @@ | |||
1 | package funcs | ||
2 | |||
3 | import ( | ||
4 | "strconv" | ||
5 | |||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | "github.com/zclconf/go-cty/cty/convert" | ||
8 | "github.com/zclconf/go-cty/cty/function" | ||
9 | ) | ||
10 | |||
11 | // MakeToFunc constructs a "to..." function, like "tostring", which converts | ||
12 | // its argument to a specific type or type kind. | ||
13 | // | ||
14 | // The given type wantTy can be any type constraint that cty's "convert" package | ||
15 | // would accept. In particular, this means that you can pass | ||
16 | // cty.List(cty.DynamicPseudoType) to mean "list of any single type", which | ||
17 | // will then cause cty to attempt to unify all of the element types when given | ||
18 | // a tuple. | ||
19 | func MakeToFunc(wantTy cty.Type) function.Function { | ||
20 | return function.New(&function.Spec{ | ||
21 | Params: []function.Parameter{ | ||
22 | { | ||
23 | Name: "v", | ||
24 | // We use DynamicPseudoType rather than wantTy here so that | ||
25 | // all values will pass through the function API verbatim and | ||
26 | // we can handle the conversion logic within the Type and | ||
27 | // Impl functions. This allows us to customize the error | ||
28 | // messages to be more appropriate for an explicit type | ||
29 | // conversion, whereas the cty function system produces | ||
30 | // messages aimed at _implicit_ type conversions. | ||
31 | Type: cty.DynamicPseudoType, | ||
32 | AllowNull: true, | ||
33 | }, | ||
34 | }, | ||
35 | Type: func(args []cty.Value) (cty.Type, error) { | ||
36 | gotTy := args[0].Type() | ||
37 | if gotTy.Equals(wantTy) { | ||
38 | return wantTy, nil | ||
39 | } | ||
40 | conv := convert.GetConversionUnsafe(args[0].Type(), wantTy) | ||
41 | if conv == nil { | ||
42 | // We'll use some specialized errors for some trickier cases, | ||
43 | // but most we can handle in a simple way. | ||
44 | switch { | ||
45 | case gotTy.IsTupleType() && wantTy.IsTupleType(): | ||
46 | return cty.NilType, function.NewArgErrorf(0, "incompatible tuple type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) | ||
47 | case gotTy.IsObjectType() && wantTy.IsObjectType(): | ||
48 | return cty.NilType, function.NewArgErrorf(0, "incompatible object type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) | ||
49 | default: | ||
50 | return cty.NilType, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) | ||
51 | } | ||
52 | } | ||
53 | // If a conversion is available then everything is fine. | ||
54 | return wantTy, nil | ||
55 | }, | ||
56 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
57 | // We didn't set "AllowUnknown" on our argument, so it is guaranteed | ||
58 | // to be known here but may still be null. | ||
59 | ret, err := convert.Convert(args[0], retType) | ||
60 | if err != nil { | ||
61 | // Because we used GetConversionUnsafe above, conversion can | ||
62 | // still potentially fail in here. For example, if the user | ||
63 | // asks to convert the string "a" to bool then we'll | ||
64 | // optimistically permit it during type checking but fail here | ||
65 | // once we note that the value isn't either "true" or "false". | ||
66 | gotTy := args[0].Type() | ||
67 | switch { | ||
68 | case gotTy == cty.String && wantTy == cty.Bool: | ||
69 | what := "string" | ||
70 | if !args[0].IsNull() { | ||
71 | what = strconv.Quote(args[0].AsString()) | ||
72 | } | ||
73 | return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to bool; only the strings "true" or "false" are allowed`, what) | ||
74 | case gotTy == cty.String && wantTy == cty.Number: | ||
75 | what := "string" | ||
76 | if !args[0].IsNull() { | ||
77 | what = strconv.Quote(args[0].AsString()) | ||
78 | } | ||
79 | return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to number; given string must be a decimal representation of a number`, what) | ||
80 | default: | ||
81 | return cty.NilVal, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) | ||
82 | } | ||
83 | } | ||
84 | return ret, nil | ||
85 | }, | ||
86 | }) | ||
87 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/funcs/crypto.go b/vendor/github.com/hashicorp/terraform/lang/funcs/crypto.go new file mode 100644 index 0000000..5cb4bc5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/funcs/crypto.go | |||
@@ -0,0 +1,285 @@ | |||
1 | package funcs | ||
2 | |||
3 | import ( | ||
4 | "crypto/md5" | ||
5 | "crypto/rsa" | ||
6 | "crypto/sha1" | ||
7 | "crypto/sha256" | ||
8 | "crypto/sha512" | ||
9 | "crypto/x509" | ||
10 | "encoding/base64" | ||
11 | "encoding/hex" | ||
12 | "encoding/pem" | ||
13 | "fmt" | ||
14 | "hash" | ||
15 | |||
16 | uuid "github.com/hashicorp/go-uuid" | ||
17 | "github.com/zclconf/go-cty/cty" | ||
18 | "github.com/zclconf/go-cty/cty/function" | ||
19 | "github.com/zclconf/go-cty/cty/gocty" | ||
20 | "golang.org/x/crypto/bcrypt" | ||
21 | ) | ||
22 | |||
23 | var UUIDFunc = function.New(&function.Spec{ | ||
24 | Params: []function.Parameter{}, | ||
25 | Type: function.StaticReturnType(cty.String), | ||
26 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
27 | result, err := uuid.GenerateUUID() | ||
28 | if err != nil { | ||
29 | return cty.UnknownVal(cty.String), err | ||
30 | } | ||
31 | return cty.StringVal(result), nil | ||
32 | }, | ||
33 | }) | ||
34 | |||
35 | // Base64Sha256Func constructs a function that computes the SHA256 hash of a given string | ||
36 | // and encodes it with Base64. | ||
37 | var Base64Sha256Func = makeStringHashFunction(sha256.New, base64.StdEncoding.EncodeToString) | ||
38 | |||
39 | // MakeFileBase64Sha256Func constructs a function that is like Base64Sha256Func but reads the | ||
40 | // contents of a file rather than hashing a given literal string. | ||
41 | func MakeFileBase64Sha256Func(baseDir string) function.Function { | ||
42 | return makeFileHashFunction(baseDir, sha256.New, base64.StdEncoding.EncodeToString) | ||
43 | } | ||
44 | |||
45 | // Base64Sha512Func constructs a function that computes the SHA256 hash of a given string | ||
46 | // and encodes it with Base64. | ||
47 | var Base64Sha512Func = makeStringHashFunction(sha512.New, base64.StdEncoding.EncodeToString) | ||
48 | |||
49 | // MakeFileBase64Sha512Func constructs a function that is like Base64Sha512Func but reads the | ||
50 | // contents of a file rather than hashing a given literal string. | ||
51 | func MakeFileBase64Sha512Func(baseDir string) function.Function { | ||
52 | return makeFileHashFunction(baseDir, sha512.New, base64.StdEncoding.EncodeToString) | ||
53 | } | ||
54 | |||
55 | // BcryptFunc constructs a function that computes a hash of the given string using the Blowfish cipher. | ||
56 | var BcryptFunc = function.New(&function.Spec{ | ||
57 | Params: []function.Parameter{ | ||
58 | { | ||
59 | Name: "str", | ||
60 | Type: cty.String, | ||
61 | }, | ||
62 | }, | ||
63 | VarParam: &function.Parameter{ | ||
64 | Name: "cost", | ||
65 | Type: cty.Number, | ||
66 | }, | ||
67 | Type: function.StaticReturnType(cty.String), | ||
68 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
69 | defaultCost := 10 | ||
70 | |||
71 | if len(args) > 1 { | ||
72 | var val int | ||
73 | if err := gocty.FromCtyValue(args[1], &val); err != nil { | ||
74 | return cty.UnknownVal(cty.String), err | ||
75 | } | ||
76 | defaultCost = val | ||
77 | } | ||
78 | |||
79 | if len(args) > 2 { | ||
80 | return cty.UnknownVal(cty.String), fmt.Errorf("bcrypt() takes no more than two arguments") | ||
81 | } | ||
82 | |||
83 | input := args[0].AsString() | ||
84 | out, err := bcrypt.GenerateFromPassword([]byte(input), defaultCost) | ||
85 | if err != nil { | ||
86 | return cty.UnknownVal(cty.String), fmt.Errorf("error occured generating password %s", err.Error()) | ||
87 | } | ||
88 | |||
89 | return cty.StringVal(string(out)), nil | ||
90 | }, | ||
91 | }) | ||
92 | |||
93 | // Md5Func constructs a function that computes the MD5 hash of a given string and encodes it with hexadecimal digits. | ||
94 | var Md5Func = makeStringHashFunction(md5.New, hex.EncodeToString) | ||
95 | |||
96 | // MakeFileMd5Func constructs a function that is like Md5Func but reads the | ||
97 | // contents of a file rather than hashing a given literal string. | ||
98 | func MakeFileMd5Func(baseDir string) function.Function { | ||
99 | return makeFileHashFunction(baseDir, md5.New, hex.EncodeToString) | ||
100 | } | ||
101 | |||
102 | // RsaDecryptFunc constructs a function that decrypts an RSA-encrypted ciphertext. | ||
103 | var RsaDecryptFunc = function.New(&function.Spec{ | ||
104 | Params: []function.Parameter{ | ||
105 | { | ||
106 | Name: "ciphertext", | ||
107 | Type: cty.String, | ||
108 | }, | ||
109 | { | ||
110 | Name: "privatekey", | ||
111 | Type: cty.String, | ||
112 | }, | ||
113 | }, | ||
114 | Type: function.StaticReturnType(cty.String), | ||
115 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
116 | s := args[0].AsString() | ||
117 | key := args[1].AsString() | ||
118 | |||
119 | b, err := base64.StdEncoding.DecodeString(s) | ||
120 | if err != nil { | ||
121 | return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode input %q: cipher text must be base64-encoded", s) | ||
122 | } | ||
123 | |||
124 | block, _ := pem.Decode([]byte(key)) | ||
125 | if block == nil { | ||
126 | return cty.UnknownVal(cty.String), fmt.Errorf("failed to parse key: no key found") | ||
127 | } | ||
128 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { | ||
129 | return cty.UnknownVal(cty.String), fmt.Errorf( | ||
130 | "failed to parse key: password protected keys are not supported. Please decrypt the key prior to use", | ||
131 | ) | ||
132 | } | ||
133 | |||
134 | x509Key, err := x509.ParsePKCS1PrivateKey(block.Bytes) | ||
135 | if err != nil { | ||
136 | return cty.UnknownVal(cty.String), err | ||
137 | } | ||
138 | |||
139 | out, err := rsa.DecryptPKCS1v15(nil, x509Key, b) | ||
140 | if err != nil { | ||
141 | return cty.UnknownVal(cty.String), err | ||
142 | } | ||
143 | |||
144 | return cty.StringVal(string(out)), nil | ||
145 | }, | ||
146 | }) | ||
147 | |||
148 | // Sha1Func contructs a function that computes the SHA1 hash of a given string | ||
149 | // and encodes it with hexadecimal digits. | ||
150 | var Sha1Func = makeStringHashFunction(sha1.New, hex.EncodeToString) | ||
151 | |||
152 | // MakeFileSha1Func constructs a function that is like Sha1Func but reads the | ||
153 | // contents of a file rather than hashing a given literal string. | ||
154 | func MakeFileSha1Func(baseDir string) function.Function { | ||
155 | return makeFileHashFunction(baseDir, sha1.New, hex.EncodeToString) | ||
156 | } | ||
157 | |||
158 | // Sha256Func contructs a function that computes the SHA256 hash of a given string | ||
159 | // and encodes it with hexadecimal digits. | ||
160 | var Sha256Func = makeStringHashFunction(sha256.New, hex.EncodeToString) | ||
161 | |||
162 | // MakeFileSha256Func constructs a function that is like Sha256Func but reads the | ||
163 | // contents of a file rather than hashing a given literal string. | ||
164 | func MakeFileSha256Func(baseDir string) function.Function { | ||
165 | return makeFileHashFunction(baseDir, sha256.New, hex.EncodeToString) | ||
166 | } | ||
167 | |||
168 | // Sha512Func contructs a function that computes the SHA512 hash of a given string | ||
169 | // and encodes it with hexadecimal digits. | ||
170 | var Sha512Func = makeStringHashFunction(sha512.New, hex.EncodeToString) | ||
171 | |||
172 | // MakeFileSha512Func constructs a function that is like Sha512Func but reads the | ||
173 | // contents of a file rather than hashing a given literal string. | ||
174 | func MakeFileSha512Func(baseDir string) function.Function { | ||
175 | return makeFileHashFunction(baseDir, sha512.New, hex.EncodeToString) | ||
176 | } | ||
177 | |||
178 | func makeStringHashFunction(hf func() hash.Hash, enc func([]byte) string) function.Function { | ||
179 | return function.New(&function.Spec{ | ||
180 | Params: []function.Parameter{ | ||
181 | { | ||
182 | Name: "str", | ||
183 | Type: cty.String, | ||
184 | }, | ||
185 | }, | ||
186 | Type: function.StaticReturnType(cty.String), | ||
187 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
188 | s := args[0].AsString() | ||
189 | h := hf() | ||
190 | h.Write([]byte(s)) | ||
191 | rv := enc(h.Sum(nil)) | ||
192 | return cty.StringVal(rv), nil | ||
193 | }, | ||
194 | }) | ||
195 | } | ||
196 | |||
197 | func makeFileHashFunction(baseDir string, hf func() hash.Hash, enc func([]byte) string) function.Function { | ||
198 | return function.New(&function.Spec{ | ||
199 | Params: []function.Parameter{ | ||
200 | { | ||
201 | Name: "path", | ||
202 | Type: cty.String, | ||
203 | }, | ||
204 | }, | ||
205 | Type: function.StaticReturnType(cty.String), | ||
206 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
207 | path := args[0].AsString() | ||
208 | src, err := readFileBytes(baseDir, path) | ||
209 | if err != nil { | ||
210 | return cty.UnknownVal(cty.String), err | ||
211 | } | ||
212 | |||
213 | h := hf() | ||
214 | h.Write(src) | ||
215 | rv := enc(h.Sum(nil)) | ||
216 | return cty.StringVal(rv), nil | ||
217 | }, | ||
218 | }) | ||
219 | } | ||
220 | |||
221 | // UUID generates and returns a Type-4 UUID in the standard hexadecimal string | ||
222 | // format. | ||
223 | // | ||
224 | // This is not a pure function: it will generate a different result for each | ||
225 | // call. It must therefore be registered as an impure function in the function | ||
226 | // table in the "lang" package. | ||
227 | func UUID() (cty.Value, error) { | ||
228 | return UUIDFunc.Call(nil) | ||
229 | } | ||
230 | |||
231 | // Base64Sha256 computes the SHA256 hash of a given string and encodes it with | ||
232 | // Base64. | ||
233 | // | ||
234 | // The given string is first encoded as UTF-8 and then the SHA256 algorithm is applied | ||
235 | // as defined in RFC 4634. The raw hash is then encoded with Base64 before returning. | ||
236 | // Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4. | ||
237 | func Base64Sha256(str cty.Value) (cty.Value, error) { | ||
238 | return Base64Sha256Func.Call([]cty.Value{str}) | ||
239 | } | ||
240 | |||
241 | // Base64Sha512 computes the SHA512 hash of a given string and encodes it with | ||
242 | // Base64. | ||
243 | // | ||
244 | // The given string is first encoded as UTF-8 and then the SHA256 algorithm is applied | ||
245 | // as defined in RFC 4634. The raw hash is then encoded with Base64 before returning. | ||
246 | // Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4 | ||
247 | func Base64Sha512(str cty.Value) (cty.Value, error) { | ||
248 | return Base64Sha512Func.Call([]cty.Value{str}) | ||
249 | } | ||
250 | |||
251 | // Bcrypt computes a hash of the given string using the Blowfish cipher, | ||
252 | // returning a string in the Modular Crypt Format | ||
253 | // usually expected in the shadow password file on many Unix systems. | ||
254 | func Bcrypt(str cty.Value, cost ...cty.Value) (cty.Value, error) { | ||
255 | args := make([]cty.Value, len(cost)+1) | ||
256 | args[0] = str | ||
257 | copy(args[1:], cost) | ||
258 | return BcryptFunc.Call(args) | ||
259 | } | ||
260 | |||
261 | // Md5 computes the MD5 hash of a given string and encodes it with hexadecimal digits. | ||
262 | func Md5(str cty.Value) (cty.Value, error) { | ||
263 | return Md5Func.Call([]cty.Value{str}) | ||
264 | } | ||
265 | |||
266 | // RsaDecrypt decrypts an RSA-encrypted ciphertext, returning the corresponding | ||
267 | // cleartext. | ||
268 | func RsaDecrypt(ciphertext, privatekey cty.Value) (cty.Value, error) { | ||
269 | return RsaDecryptFunc.Call([]cty.Value{ciphertext, privatekey}) | ||
270 | } | ||
271 | |||
272 | // Sha1 computes the SHA1 hash of a given string and encodes it with hexadecimal digits. | ||
273 | func Sha1(str cty.Value) (cty.Value, error) { | ||
274 | return Sha1Func.Call([]cty.Value{str}) | ||
275 | } | ||
276 | |||
277 | // Sha256 computes the SHA256 hash of a given string and encodes it with hexadecimal digits. | ||
278 | func Sha256(str cty.Value) (cty.Value, error) { | ||
279 | return Sha256Func.Call([]cty.Value{str}) | ||
280 | } | ||
281 | |||
282 | // Sha512 computes the SHA512 hash of a given string and encodes it with hexadecimal digits. | ||
283 | func Sha512(str cty.Value) (cty.Value, error) { | ||
284 | return Sha512Func.Call([]cty.Value{str}) | ||
285 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/funcs/datetime.go b/vendor/github.com/hashicorp/terraform/lang/funcs/datetime.go new file mode 100644 index 0000000..5dae198 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/funcs/datetime.go | |||
@@ -0,0 +1,70 @@ | |||
1 | package funcs | ||
2 | |||
3 | import ( | ||
4 | "time" | ||
5 | |||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | "github.com/zclconf/go-cty/cty/function" | ||
8 | ) | ||
9 | |||
10 | // TimestampFunc constructs a function that returns a string representation of the current date and time. | ||
11 | var TimestampFunc = function.New(&function.Spec{ | ||
12 | Params: []function.Parameter{}, | ||
13 | Type: function.StaticReturnType(cty.String), | ||
14 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
15 | return cty.StringVal(time.Now().UTC().Format(time.RFC3339)), nil | ||
16 | }, | ||
17 | }) | ||
18 | |||
19 | // TimeAddFunc constructs a function that adds a duration to a timestamp, returning a new timestamp. | ||
20 | var TimeAddFunc = function.New(&function.Spec{ | ||
21 | Params: []function.Parameter{ | ||
22 | { | ||
23 | Name: "timestamp", | ||
24 | Type: cty.String, | ||
25 | }, | ||
26 | { | ||
27 | Name: "duration", | ||
28 | Type: cty.String, | ||
29 | }, | ||
30 | }, | ||
31 | Type: function.StaticReturnType(cty.String), | ||
32 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
33 | ts, err := time.Parse(time.RFC3339, args[0].AsString()) | ||
34 | if err != nil { | ||
35 | return cty.UnknownVal(cty.String), err | ||
36 | } | ||
37 | duration, err := time.ParseDuration(args[1].AsString()) | ||
38 | if err != nil { | ||
39 | return cty.UnknownVal(cty.String), err | ||
40 | } | ||
41 | |||
42 | return cty.StringVal(ts.Add(duration).Format(time.RFC3339)), nil | ||
43 | }, | ||
44 | }) | ||
45 | |||
46 | // Timestamp returns a string representation of the current date and time. | ||
47 | // | ||
48 | // In the Terraform language, timestamps are conventionally represented as | ||
49 | // strings using RFC 3339 "Date and Time format" syntax, and so timestamp | ||
50 | // returns a string in this format. | ||
51 | func Timestamp() (cty.Value, error) { | ||
52 | return TimestampFunc.Call([]cty.Value{}) | ||
53 | } | ||
54 | |||
55 | // TimeAdd adds a duration to a timestamp, returning a new timestamp. | ||
56 | // | ||
57 | // In the Terraform language, timestamps are conventionally represented as | ||
58 | // strings using RFC 3339 "Date and Time format" syntax. Timeadd requires | ||
59 | // the timestamp argument to be a string conforming to this syntax. | ||
60 | // | ||
61 | // `duration` is a string representation of a time difference, consisting of | ||
62 | // sequences of number and unit pairs, like `"1.5h"` or `1h30m`. The accepted | ||
63 | // units are `ns`, `us` (or `µs`), `"ms"`, `"s"`, `"m"`, and `"h"`. The first | ||
64 | // number may be negative to indicate a negative duration, like `"-2h5m"`. | ||
65 | // | ||
66 | // The result is a string, also in RFC 3339 format, representing the result | ||
67 | // of adding the given direction to the given timestamp. | ||
68 | func TimeAdd(timestamp cty.Value, duration cty.Value) (cty.Value, error) { | ||
69 | return TimeAddFunc.Call([]cty.Value{timestamp, duration}) | ||
70 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/funcs/encoding.go b/vendor/github.com/hashicorp/terraform/lang/funcs/encoding.go new file mode 100644 index 0000000..af93f08 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/funcs/encoding.go | |||
@@ -0,0 +1,140 @@ | |||
1 | package funcs | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "compress/gzip" | ||
6 | "encoding/base64" | ||
7 | "fmt" | ||
8 | "log" | ||
9 | "net/url" | ||
10 | "unicode/utf8" | ||
11 | |||
12 | "github.com/zclconf/go-cty/cty" | ||
13 | "github.com/zclconf/go-cty/cty/function" | ||
14 | ) | ||
15 | |||
16 | // Base64DecodeFunc constructs a function that decodes a string containing a base64 sequence. | ||
17 | var Base64DecodeFunc = function.New(&function.Spec{ | ||
18 | Params: []function.Parameter{ | ||
19 | { | ||
20 | Name: "str", | ||
21 | Type: cty.String, | ||
22 | }, | ||
23 | }, | ||
24 | Type: function.StaticReturnType(cty.String), | ||
25 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
26 | s := args[0].AsString() | ||
27 | sDec, err := base64.StdEncoding.DecodeString(s) | ||
28 | if err != nil { | ||
29 | return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode base64 data '%s'", s) | ||
30 | } | ||
31 | if !utf8.Valid([]byte(sDec)) { | ||
32 | log.Printf("[DEBUG] the result of decoding the the provided string is not valid UTF-8: %s", sDec) | ||
33 | return cty.UnknownVal(cty.String), fmt.Errorf("the result of decoding the the provided string is not valid UTF-8") | ||
34 | } | ||
35 | return cty.StringVal(string(sDec)), nil | ||
36 | }, | ||
37 | }) | ||
38 | |||
39 | // Base64EncodeFunc constructs a function that encodes a string to a base64 sequence. | ||
40 | var Base64EncodeFunc = function.New(&function.Spec{ | ||
41 | Params: []function.Parameter{ | ||
42 | { | ||
43 | Name: "str", | ||
44 | Type: cty.String, | ||
45 | }, | ||
46 | }, | ||
47 | Type: function.StaticReturnType(cty.String), | ||
48 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
49 | return cty.StringVal(base64.StdEncoding.EncodeToString([]byte(args[0].AsString()))), nil | ||
50 | }, | ||
51 | }) | ||
52 | |||
53 | // Base64GzipFunc constructs a function that compresses a string with gzip and then encodes the result in | ||
54 | // Base64 encoding. | ||
55 | var Base64GzipFunc = function.New(&function.Spec{ | ||
56 | Params: []function.Parameter{ | ||
57 | { | ||
58 | Name: "str", | ||
59 | Type: cty.String, | ||
60 | }, | ||
61 | }, | ||
62 | Type: function.StaticReturnType(cty.String), | ||
63 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
64 | s := args[0].AsString() | ||
65 | |||
66 | var b bytes.Buffer | ||
67 | gz := gzip.NewWriter(&b) | ||
68 | if _, err := gz.Write([]byte(s)); err != nil { | ||
69 | return cty.UnknownVal(cty.String), fmt.Errorf("failed to write gzip raw data: '%s'", s) | ||
70 | } | ||
71 | if err := gz.Flush(); err != nil { | ||
72 | return cty.UnknownVal(cty.String), fmt.Errorf("failed to flush gzip writer: '%s'", s) | ||
73 | } | ||
74 | if err := gz.Close(); err != nil { | ||
75 | return cty.UnknownVal(cty.String), fmt.Errorf("failed to close gzip writer: '%s'", s) | ||
76 | } | ||
77 | return cty.StringVal(base64.StdEncoding.EncodeToString(b.Bytes())), nil | ||
78 | }, | ||
79 | }) | ||
80 | |||
81 | // URLEncodeFunc constructs a function that applies URL encoding to a given string. | ||
82 | var URLEncodeFunc = function.New(&function.Spec{ | ||
83 | Params: []function.Parameter{ | ||
84 | { | ||
85 | Name: "str", | ||
86 | Type: cty.String, | ||
87 | }, | ||
88 | }, | ||
89 | Type: function.StaticReturnType(cty.String), | ||
90 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
91 | return cty.StringVal(url.QueryEscape(args[0].AsString())), nil | ||
92 | }, | ||
93 | }) | ||
94 | |||
95 | // Base64Decode decodes a string containing a base64 sequence. | ||
96 | // | ||
97 | // Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4. | ||
98 | // | ||
99 | // Strings in the Terraform language are sequences of unicode characters rather | ||
100 | // than bytes, so this function will also interpret the resulting bytes as | ||
101 | // UTF-8. If the bytes after Base64 decoding are _not_ valid UTF-8, this function | ||
102 | // produces an error. | ||
103 | func Base64Decode(str cty.Value) (cty.Value, error) { | ||
104 | return Base64DecodeFunc.Call([]cty.Value{str}) | ||
105 | } | ||
106 | |||
107 | // Base64Encode applies Base64 encoding to a string. | ||
108 | // | ||
109 | // Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4. | ||
110 | // | ||
111 | // Strings in the Terraform language are sequences of unicode characters rather | ||
112 | // than bytes, so this function will first encode the characters from the string | ||
113 | // as UTF-8, and then apply Base64 encoding to the result. | ||
114 | func Base64Encode(str cty.Value) (cty.Value, error) { | ||
115 | return Base64EncodeFunc.Call([]cty.Value{str}) | ||
116 | } | ||
117 | |||
118 | // Base64Gzip compresses a string with gzip and then encodes the result in | ||
119 | // Base64 encoding. | ||
120 | // | ||
121 | // Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4. | ||
122 | // | ||
123 | // Strings in the Terraform language are sequences of unicode characters rather | ||
124 | // than bytes, so this function will first encode the characters from the string | ||
125 | // as UTF-8, then apply gzip compression, and then finally apply Base64 encoding. | ||
126 | func Base64Gzip(str cty.Value) (cty.Value, error) { | ||
127 | return Base64GzipFunc.Call([]cty.Value{str}) | ||
128 | } | ||
129 | |||
130 | // URLEncode applies URL encoding to a given string. | ||
131 | // | ||
132 | // This function identifies characters in the given string that would have a | ||
133 | // special meaning when included as a query string argument in a URL and | ||
134 | // escapes them using RFC 3986 "percent encoding". | ||
135 | // | ||
136 | // If the given string contains non-ASCII characters, these are first encoded as | ||
137 | // UTF-8 and then percent encoding is applied separately to each UTF-8 byte. | ||
138 | func URLEncode(str cty.Value) (cty.Value, error) { | ||
139 | return URLEncodeFunc.Call([]cty.Value{str}) | ||
140 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/funcs/filesystem.go b/vendor/github.com/hashicorp/terraform/lang/funcs/filesystem.go new file mode 100644 index 0000000..7dfc905 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/funcs/filesystem.go | |||
@@ -0,0 +1,345 @@ | |||
1 | package funcs | ||
2 | |||
3 | import ( | ||
4 | "encoding/base64" | ||
5 | "fmt" | ||
6 | "io/ioutil" | ||
7 | "os" | ||
8 | "path/filepath" | ||
9 | "unicode/utf8" | ||
10 | |||
11 | "github.com/hashicorp/hcl2/hcl" | ||
12 | "github.com/hashicorp/hcl2/hcl/hclsyntax" | ||
13 | homedir "github.com/mitchellh/go-homedir" | ||
14 | "github.com/zclconf/go-cty/cty" | ||
15 | "github.com/zclconf/go-cty/cty/function" | ||
16 | ) | ||
17 | |||
18 | // MakeFileFunc constructs a function that takes a file path and returns the | ||
19 | // contents of that file, either directly as a string (where valid UTF-8 is | ||
20 | // required) or as a string containing base64 bytes. | ||
21 | func MakeFileFunc(baseDir string, encBase64 bool) function.Function { | ||
22 | return function.New(&function.Spec{ | ||
23 | Params: []function.Parameter{ | ||
24 | { | ||
25 | Name: "path", | ||
26 | Type: cty.String, | ||
27 | }, | ||
28 | }, | ||
29 | Type: function.StaticReturnType(cty.String), | ||
30 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
31 | path := args[0].AsString() | ||
32 | src, err := readFileBytes(baseDir, path) | ||
33 | if err != nil { | ||
34 | return cty.UnknownVal(cty.String), err | ||
35 | } | ||
36 | |||
37 | switch { | ||
38 | case encBase64: | ||
39 | enc := base64.StdEncoding.EncodeToString(src) | ||
40 | return cty.StringVal(enc), nil | ||
41 | default: | ||
42 | if !utf8.Valid(src) { | ||
43 | return cty.UnknownVal(cty.String), fmt.Errorf("contents of %s are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead", path) | ||
44 | } | ||
45 | return cty.StringVal(string(src)), nil | ||
46 | } | ||
47 | }, | ||
48 | }) | ||
49 | } | ||
50 | |||
51 | // MakeTemplateFileFunc constructs a function that takes a file path and | ||
52 | // an arbitrary object of named values and attempts to render the referenced | ||
53 | // file as a template using HCL template syntax. | ||
54 | // | ||
55 | // The template itself may recursively call other functions so a callback | ||
56 | // must be provided to get access to those functions. The template cannot, | ||
57 | // however, access any variables defined in the scope: it is restricted only to | ||
58 | // those variables provided in the second function argument, to ensure that all | ||
59 | // dependencies on other graph nodes can be seen before executing this function. | ||
60 | // | ||
61 | // As a special exception, a referenced template file may not recursively call | ||
62 | // the templatefile function, since that would risk the same file being | ||
63 | // included into itself indefinitely. | ||
64 | func MakeTemplateFileFunc(baseDir string, funcsCb func() map[string]function.Function) function.Function { | ||
65 | |||
66 | params := []function.Parameter{ | ||
67 | { | ||
68 | Name: "path", | ||
69 | Type: cty.String, | ||
70 | }, | ||
71 | { | ||
72 | Name: "vars", | ||
73 | Type: cty.DynamicPseudoType, | ||
74 | }, | ||
75 | } | ||
76 | |||
77 | loadTmpl := func(fn string) (hcl.Expression, error) { | ||
78 | // We re-use File here to ensure the same filename interpretation | ||
79 | // as it does, along with its other safety checks. | ||
80 | tmplVal, err := File(baseDir, cty.StringVal(fn)) | ||
81 | if err != nil { | ||
82 | return nil, err | ||
83 | } | ||
84 | |||
85 | expr, diags := hclsyntax.ParseTemplate([]byte(tmplVal.AsString()), fn, hcl.Pos{Line: 1, Column: 1}) | ||
86 | if diags.HasErrors() { | ||
87 | return nil, diags | ||
88 | } | ||
89 | |||
90 | return expr, nil | ||
91 | } | ||
92 | |||
93 | renderTmpl := func(expr hcl.Expression, varsVal cty.Value) (cty.Value, error) { | ||
94 | if varsTy := varsVal.Type(); !(varsTy.IsMapType() || varsTy.IsObjectType()) { | ||
95 | return cty.DynamicVal, function.NewArgErrorf(1, "invalid vars value: must be a map") // or an object, but we don't strongly distinguish these most of the time | ||
96 | } | ||
97 | |||
98 | ctx := &hcl.EvalContext{ | ||
99 | Variables: varsVal.AsValueMap(), | ||
100 | } | ||
101 | |||
102 | // We'll pre-check references in the template here so we can give a | ||
103 | // more specialized error message than HCL would by default, so it's | ||
104 | // clearer that this problem is coming from a templatefile call. | ||
105 | for _, traversal := range expr.Variables() { | ||
106 | root := traversal.RootName() | ||
107 | if _, ok := ctx.Variables[root]; !ok { | ||
108 | return cty.DynamicVal, function.NewArgErrorf(1, "vars map does not contain key %q, referenced at %s", root, traversal[0].SourceRange()) | ||
109 | } | ||
110 | } | ||
111 | |||
112 | givenFuncs := funcsCb() // this callback indirection is to avoid chicken/egg problems | ||
113 | funcs := make(map[string]function.Function, len(givenFuncs)) | ||
114 | for name, fn := range givenFuncs { | ||
115 | if name == "templatefile" { | ||
116 | // We stub this one out to prevent recursive calls. | ||
117 | funcs[name] = function.New(&function.Spec{ | ||
118 | Params: params, | ||
119 | Type: func(args []cty.Value) (cty.Type, error) { | ||
120 | return cty.NilType, fmt.Errorf("cannot recursively call templatefile from inside templatefile call") | ||
121 | }, | ||
122 | }) | ||
123 | continue | ||
124 | } | ||
125 | funcs[name] = fn | ||
126 | } | ||
127 | ctx.Functions = funcs | ||
128 | |||
129 | val, diags := expr.Value(ctx) | ||
130 | if diags.HasErrors() { | ||
131 | return cty.DynamicVal, diags | ||
132 | } | ||
133 | return val, nil | ||
134 | } | ||
135 | |||
136 | return function.New(&function.Spec{ | ||
137 | Params: params, | ||
138 | Type: func(args []cty.Value) (cty.Type, error) { | ||
139 | if !(args[0].IsKnown() && args[1].IsKnown()) { | ||
140 | return cty.DynamicPseudoType, nil | ||
141 | } | ||
142 | |||
143 | // We'll render our template now to see what result type it produces. | ||
144 | // A template consisting only of a single interpolation an potentially | ||
145 | // return any type. | ||
146 | expr, err := loadTmpl(args[0].AsString()) | ||
147 | if err != nil { | ||
148 | return cty.DynamicPseudoType, err | ||
149 | } | ||
150 | |||
151 | // This is safe even if args[1] contains unknowns because the HCL | ||
152 | // template renderer itself knows how to short-circuit those. | ||
153 | val, err := renderTmpl(expr, args[1]) | ||
154 | return val.Type(), err | ||
155 | }, | ||
156 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
157 | expr, err := loadTmpl(args[0].AsString()) | ||
158 | if err != nil { | ||
159 | return cty.DynamicVal, err | ||
160 | } | ||
161 | return renderTmpl(expr, args[1]) | ||
162 | }, | ||
163 | }) | ||
164 | |||
165 | } | ||
166 | |||
167 | // MakeFileExistsFunc constructs a function that takes a path | ||
168 | // and determines whether a file exists at that path | ||
169 | func MakeFileExistsFunc(baseDir string) function.Function { | ||
170 | return function.New(&function.Spec{ | ||
171 | Params: []function.Parameter{ | ||
172 | { | ||
173 | Name: "path", | ||
174 | Type: cty.String, | ||
175 | }, | ||
176 | }, | ||
177 | Type: function.StaticReturnType(cty.Bool), | ||
178 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
179 | path := args[0].AsString() | ||
180 | path, err := homedir.Expand(path) | ||
181 | if err != nil { | ||
182 | return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to expand ~: %s", err) | ||
183 | } | ||
184 | |||
185 | if !filepath.IsAbs(path) { | ||
186 | path = filepath.Join(baseDir, path) | ||
187 | } | ||
188 | |||
189 | // Ensure that the path is canonical for the host OS | ||
190 | path = filepath.Clean(path) | ||
191 | |||
192 | fi, err := os.Stat(path) | ||
193 | if err != nil { | ||
194 | if os.IsNotExist(err) { | ||
195 | return cty.False, nil | ||
196 | } | ||
197 | return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to stat %s", path) | ||
198 | } | ||
199 | |||
200 | if fi.Mode().IsRegular() { | ||
201 | return cty.True, nil | ||
202 | } | ||
203 | |||
204 | return cty.False, fmt.Errorf("%s is not a regular file, but %q", | ||
205 | path, fi.Mode().String()) | ||
206 | }, | ||
207 | }) | ||
208 | } | ||
209 | |||
210 | // BasenameFunc constructs a function that takes a string containing a filesystem path | ||
211 | // and removes all except the last portion from it. | ||
212 | var BasenameFunc = function.New(&function.Spec{ | ||
213 | Params: []function.Parameter{ | ||
214 | { | ||
215 | Name: "path", | ||
216 | Type: cty.String, | ||
217 | }, | ||
218 | }, | ||
219 | Type: function.StaticReturnType(cty.String), | ||
220 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
221 | return cty.StringVal(filepath.Base(args[0].AsString())), nil | ||
222 | }, | ||
223 | }) | ||
224 | |||
225 | // DirnameFunc constructs a function that takes a string containing a filesystem path | ||
226 | // and removes the last portion from it. | ||
227 | var DirnameFunc = function.New(&function.Spec{ | ||
228 | Params: []function.Parameter{ | ||
229 | { | ||
230 | Name: "path", | ||
231 | Type: cty.String, | ||
232 | }, | ||
233 | }, | ||
234 | Type: function.StaticReturnType(cty.String), | ||
235 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
236 | return cty.StringVal(filepath.Dir(args[0].AsString())), nil | ||
237 | }, | ||
238 | }) | ||
239 | |||
240 | // PathExpandFunc constructs a function that expands a leading ~ character to the current user's home directory. | ||
241 | var PathExpandFunc = function.New(&function.Spec{ | ||
242 | Params: []function.Parameter{ | ||
243 | { | ||
244 | Name: "path", | ||
245 | Type: cty.String, | ||
246 | }, | ||
247 | }, | ||
248 | Type: function.StaticReturnType(cty.String), | ||
249 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
250 | |||
251 | homePath, err := homedir.Expand(args[0].AsString()) | ||
252 | return cty.StringVal(homePath), err | ||
253 | }, | ||
254 | }) | ||
255 | |||
256 | func readFileBytes(baseDir, path string) ([]byte, error) { | ||
257 | path, err := homedir.Expand(path) | ||
258 | if err != nil { | ||
259 | return nil, fmt.Errorf("failed to expand ~: %s", err) | ||
260 | } | ||
261 | |||
262 | if !filepath.IsAbs(path) { | ||
263 | path = filepath.Join(baseDir, path) | ||
264 | } | ||
265 | |||
266 | // Ensure that the path is canonical for the host OS | ||
267 | path = filepath.Clean(path) | ||
268 | |||
269 | src, err := ioutil.ReadFile(path) | ||
270 | if err != nil { | ||
271 | // ReadFile does not return Terraform-user-friendly error | ||
272 | // messages, so we'll provide our own. | ||
273 | if os.IsNotExist(err) { | ||
274 | return nil, fmt.Errorf("no file exists at %s", path) | ||
275 | } | ||
276 | return nil, fmt.Errorf("failed to read %s", path) | ||
277 | } | ||
278 | |||
279 | return src, nil | ||
280 | } | ||
281 | |||
282 | // File reads the contents of the file at the given path. | ||
283 | // | ||
284 | // The file must contain valid UTF-8 bytes, or this function will return an error. | ||
285 | // | ||
286 | // The underlying function implementation works relative to a particular base | ||
287 | // directory, so this wrapper takes a base directory string and uses it to | ||
288 | // construct the underlying function before calling it. | ||
289 | func File(baseDir string, path cty.Value) (cty.Value, error) { | ||
290 | fn := MakeFileFunc(baseDir, false) | ||
291 | return fn.Call([]cty.Value{path}) | ||
292 | } | ||
293 | |||
294 | // FileExists determines whether a file exists at the given path. | ||
295 | // | ||
296 | // The underlying function implementation works relative to a particular base | ||
297 | // directory, so this wrapper takes a base directory string and uses it to | ||
298 | // construct the underlying function before calling it. | ||
299 | func FileExists(baseDir string, path cty.Value) (cty.Value, error) { | ||
300 | fn := MakeFileExistsFunc(baseDir) | ||
301 | return fn.Call([]cty.Value{path}) | ||
302 | } | ||
303 | |||
304 | // FileBase64 reads the contents of the file at the given path. | ||
305 | // | ||
306 | // The bytes from the file are encoded as base64 before returning. | ||
307 | // | ||
308 | // The underlying function implementation works relative to a particular base | ||
309 | // directory, so this wrapper takes a base directory string and uses it to | ||
310 | // construct the underlying function before calling it. | ||
311 | func FileBase64(baseDir string, path cty.Value) (cty.Value, error) { | ||
312 | fn := MakeFileFunc(baseDir, true) | ||
313 | return fn.Call([]cty.Value{path}) | ||
314 | } | ||
315 | |||
316 | // Basename takes a string containing a filesystem path and removes all except the last portion from it. | ||
317 | // | ||
318 | // The underlying function implementation works only with the path string and does not access the filesystem itself. | ||
319 | // It is therefore unable to take into account filesystem features such as symlinks. | ||
320 | // | ||
321 | // If the path is empty then the result is ".", representing the current working directory. | ||
322 | func Basename(path cty.Value) (cty.Value, error) { | ||
323 | return BasenameFunc.Call([]cty.Value{path}) | ||
324 | } | ||
325 | |||
326 | // Dirname takes a string containing a filesystem path and removes the last portion from it. | ||
327 | // | ||
328 | // The underlying function implementation works only with the path string and does not access the filesystem itself. | ||
329 | // It is therefore unable to take into account filesystem features such as symlinks. | ||
330 | // | ||
331 | // If the path is empty then the result is ".", representing the current working directory. | ||
332 | func Dirname(path cty.Value) (cty.Value, error) { | ||
333 | return DirnameFunc.Call([]cty.Value{path}) | ||
334 | } | ||
335 | |||
336 | // Pathexpand takes a string that might begin with a `~` segment, and if so it replaces that segment with | ||
337 | // the current user's home directory path. | ||
338 | // | ||
339 | // The underlying function implementation works only with the path string and does not access the filesystem itself. | ||
340 | // It is therefore unable to take into account filesystem features such as symlinks. | ||
341 | // | ||
342 | // If the leading segment in the path is not `~` then the given path is returned unmodified. | ||
343 | func Pathexpand(path cty.Value) (cty.Value, error) { | ||
344 | return PathExpandFunc.Call([]cty.Value{path}) | ||
345 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/funcs/number.go b/vendor/github.com/hashicorp/terraform/lang/funcs/number.go new file mode 100644 index 0000000..15cfe71 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/funcs/number.go | |||
@@ -0,0 +1,155 @@ | |||
1 | package funcs | ||
2 | |||
3 | import ( | ||
4 | "math" | ||
5 | |||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | "github.com/zclconf/go-cty/cty/function" | ||
8 | "github.com/zclconf/go-cty/cty/gocty" | ||
9 | ) | ||
10 | |||
11 | // CeilFunc contructs a function that returns the closest whole number greater | ||
12 | // than or equal to the given value. | ||
13 | var CeilFunc = function.New(&function.Spec{ | ||
14 | Params: []function.Parameter{ | ||
15 | { | ||
16 | Name: "num", | ||
17 | Type: cty.Number, | ||
18 | }, | ||
19 | }, | ||
20 | Type: function.StaticReturnType(cty.Number), | ||
21 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
22 | var val float64 | ||
23 | if err := gocty.FromCtyValue(args[0], &val); err != nil { | ||
24 | return cty.UnknownVal(cty.String), err | ||
25 | } | ||
26 | return cty.NumberIntVal(int64(math.Ceil(val))), nil | ||
27 | }, | ||
28 | }) | ||
29 | |||
30 | // FloorFunc contructs a function that returns the closest whole number lesser | ||
31 | // than or equal to the given value. | ||
32 | var FloorFunc = function.New(&function.Spec{ | ||
33 | Params: []function.Parameter{ | ||
34 | { | ||
35 | Name: "num", | ||
36 | Type: cty.Number, | ||
37 | }, | ||
38 | }, | ||
39 | Type: function.StaticReturnType(cty.Number), | ||
40 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
41 | var val float64 | ||
42 | if err := gocty.FromCtyValue(args[0], &val); err != nil { | ||
43 | return cty.UnknownVal(cty.String), err | ||
44 | } | ||
45 | return cty.NumberIntVal(int64(math.Floor(val))), nil | ||
46 | }, | ||
47 | }) | ||
48 | |||
49 | // LogFunc contructs a function that returns the logarithm of a given number in a given base. | ||
50 | var LogFunc = function.New(&function.Spec{ | ||
51 | Params: []function.Parameter{ | ||
52 | { | ||
53 | Name: "num", | ||
54 | Type: cty.Number, | ||
55 | }, | ||
56 | { | ||
57 | Name: "base", | ||
58 | Type: cty.Number, | ||
59 | }, | ||
60 | }, | ||
61 | Type: function.StaticReturnType(cty.Number), | ||
62 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
63 | var num float64 | ||
64 | if err := gocty.FromCtyValue(args[0], &num); err != nil { | ||
65 | return cty.UnknownVal(cty.String), err | ||
66 | } | ||
67 | |||
68 | var base float64 | ||
69 | if err := gocty.FromCtyValue(args[1], &base); err != nil { | ||
70 | return cty.UnknownVal(cty.String), err | ||
71 | } | ||
72 | |||
73 | return cty.NumberFloatVal(math.Log(num) / math.Log(base)), nil | ||
74 | }, | ||
75 | }) | ||
76 | |||
77 | // PowFunc contructs a function that returns the logarithm of a given number in a given base. | ||
78 | var PowFunc = function.New(&function.Spec{ | ||
79 | Params: []function.Parameter{ | ||
80 | { | ||
81 | Name: "num", | ||
82 | Type: cty.Number, | ||
83 | }, | ||
84 | { | ||
85 | Name: "power", | ||
86 | Type: cty.Number, | ||
87 | }, | ||
88 | }, | ||
89 | Type: function.StaticReturnType(cty.Number), | ||
90 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
91 | var num float64 | ||
92 | if err := gocty.FromCtyValue(args[0], &num); err != nil { | ||
93 | return cty.UnknownVal(cty.String), err | ||
94 | } | ||
95 | |||
96 | var power float64 | ||
97 | if err := gocty.FromCtyValue(args[1], &power); err != nil { | ||
98 | return cty.UnknownVal(cty.String), err | ||
99 | } | ||
100 | |||
101 | return cty.NumberFloatVal(math.Pow(num, power)), nil | ||
102 | }, | ||
103 | }) | ||
104 | |||
105 | // SignumFunc contructs a function that returns the closest whole number greater | ||
106 | // than or equal to the given value. | ||
107 | var SignumFunc = function.New(&function.Spec{ | ||
108 | Params: []function.Parameter{ | ||
109 | { | ||
110 | Name: "num", | ||
111 | Type: cty.Number, | ||
112 | }, | ||
113 | }, | ||
114 | Type: function.StaticReturnType(cty.Number), | ||
115 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
116 | var num int | ||
117 | if err := gocty.FromCtyValue(args[0], &num); err != nil { | ||
118 | return cty.UnknownVal(cty.String), err | ||
119 | } | ||
120 | switch { | ||
121 | case num < 0: | ||
122 | return cty.NumberIntVal(-1), nil | ||
123 | case num > 0: | ||
124 | return cty.NumberIntVal(+1), nil | ||
125 | default: | ||
126 | return cty.NumberIntVal(0), nil | ||
127 | } | ||
128 | }, | ||
129 | }) | ||
130 | |||
131 | // Ceil returns the closest whole number greater than or equal to the given value. | ||
132 | func Ceil(num cty.Value) (cty.Value, error) { | ||
133 | return CeilFunc.Call([]cty.Value{num}) | ||
134 | } | ||
135 | |||
136 | // Floor returns the closest whole number lesser than or equal to the given value. | ||
137 | func Floor(num cty.Value) (cty.Value, error) { | ||
138 | return FloorFunc.Call([]cty.Value{num}) | ||
139 | } | ||
140 | |||
141 | // Log returns returns the logarithm of a given number in a given base. | ||
142 | func Log(num, base cty.Value) (cty.Value, error) { | ||
143 | return LogFunc.Call([]cty.Value{num, base}) | ||
144 | } | ||
145 | |||
146 | // Pow returns the logarithm of a given number in a given base. | ||
147 | func Pow(num, power cty.Value) (cty.Value, error) { | ||
148 | return PowFunc.Call([]cty.Value{num, power}) | ||
149 | } | ||
150 | |||
151 | // Signum determines the sign of a number, returning a number between -1 and | ||
152 | // 1 to represent the sign. | ||
153 | func Signum(num cty.Value) (cty.Value, error) { | ||
154 | return SignumFunc.Call([]cty.Value{num}) | ||
155 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/funcs/string.go b/vendor/github.com/hashicorp/terraform/lang/funcs/string.go new file mode 100644 index 0000000..c9ddf19 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/funcs/string.go | |||
@@ -0,0 +1,280 @@ | |||
1 | package funcs | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "regexp" | ||
6 | "sort" | ||
7 | "strings" | ||
8 | |||
9 | "github.com/zclconf/go-cty/cty" | ||
10 | "github.com/zclconf/go-cty/cty/function" | ||
11 | "github.com/zclconf/go-cty/cty/gocty" | ||
12 | ) | ||
13 | |||
14 | var JoinFunc = function.New(&function.Spec{ | ||
15 | Params: []function.Parameter{ | ||
16 | { | ||
17 | Name: "separator", | ||
18 | Type: cty.String, | ||
19 | }, | ||
20 | }, | ||
21 | VarParam: &function.Parameter{ | ||
22 | Name: "lists", | ||
23 | Type: cty.List(cty.String), | ||
24 | }, | ||
25 | Type: function.StaticReturnType(cty.String), | ||
26 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
27 | sep := args[0].AsString() | ||
28 | listVals := args[1:] | ||
29 | if len(listVals) < 1 { | ||
30 | return cty.UnknownVal(cty.String), fmt.Errorf("at least one list is required") | ||
31 | } | ||
32 | |||
33 | l := 0 | ||
34 | for _, list := range listVals { | ||
35 | if !list.IsWhollyKnown() { | ||
36 | return cty.UnknownVal(cty.String), nil | ||
37 | } | ||
38 | l += list.LengthInt() | ||
39 | } | ||
40 | |||
41 | items := make([]string, 0, l) | ||
42 | for ai, list := range listVals { | ||
43 | ei := 0 | ||
44 | for it := list.ElementIterator(); it.Next(); { | ||
45 | _, val := it.Element() | ||
46 | if val.IsNull() { | ||
47 | if len(listVals) > 1 { | ||
48 | return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d of list %d is null; cannot concatenate null values", ei, ai+1) | ||
49 | } | ||
50 | return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d is null; cannot concatenate null values", ei) | ||
51 | } | ||
52 | items = append(items, val.AsString()) | ||
53 | ei++ | ||
54 | } | ||
55 | } | ||
56 | |||
57 | return cty.StringVal(strings.Join(items, sep)), nil | ||
58 | }, | ||
59 | }) | ||
60 | |||
61 | var SortFunc = function.New(&function.Spec{ | ||
62 | Params: []function.Parameter{ | ||
63 | { | ||
64 | Name: "list", | ||
65 | Type: cty.List(cty.String), | ||
66 | }, | ||
67 | }, | ||
68 | Type: function.StaticReturnType(cty.List(cty.String)), | ||
69 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
70 | listVal := args[0] | ||
71 | |||
72 | if !listVal.IsWhollyKnown() { | ||
73 | // If some of the element values aren't known yet then we | ||
74 | // can't yet preduct the order of the result. | ||
75 | return cty.UnknownVal(retType), nil | ||
76 | } | ||
77 | if listVal.LengthInt() == 0 { // Easy path | ||
78 | return listVal, nil | ||
79 | } | ||
80 | |||
81 | list := make([]string, 0, listVal.LengthInt()) | ||
82 | for it := listVal.ElementIterator(); it.Next(); { | ||
83 | iv, v := it.Element() | ||
84 | if v.IsNull() { | ||
85 | return cty.UnknownVal(retType), fmt.Errorf("given list element %s is null; a null string cannot be sorted", iv.AsBigFloat().String()) | ||
86 | } | ||
87 | list = append(list, v.AsString()) | ||
88 | } | ||
89 | |||
90 | sort.Strings(list) | ||
91 | retVals := make([]cty.Value, len(list)) | ||
92 | for i, s := range list { | ||
93 | retVals[i] = cty.StringVal(s) | ||
94 | } | ||
95 | return cty.ListVal(retVals), nil | ||
96 | }, | ||
97 | }) | ||
98 | |||
99 | var SplitFunc = function.New(&function.Spec{ | ||
100 | Params: []function.Parameter{ | ||
101 | { | ||
102 | Name: "separator", | ||
103 | Type: cty.String, | ||
104 | }, | ||
105 | { | ||
106 | Name: "str", | ||
107 | Type: cty.String, | ||
108 | }, | ||
109 | }, | ||
110 | Type: function.StaticReturnType(cty.List(cty.String)), | ||
111 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
112 | sep := args[0].AsString() | ||
113 | str := args[1].AsString() | ||
114 | elems := strings.Split(str, sep) | ||
115 | elemVals := make([]cty.Value, len(elems)) | ||
116 | for i, s := range elems { | ||
117 | elemVals[i] = cty.StringVal(s) | ||
118 | } | ||
119 | if len(elemVals) == 0 { | ||
120 | return cty.ListValEmpty(cty.String), nil | ||
121 | } | ||
122 | return cty.ListVal(elemVals), nil | ||
123 | }, | ||
124 | }) | ||
125 | |||
126 | // ChompFunc constructions a function that removes newline characters at the end of a string. | ||
127 | var ChompFunc = function.New(&function.Spec{ | ||
128 | Params: []function.Parameter{ | ||
129 | { | ||
130 | Name: "str", | ||
131 | Type: cty.String, | ||
132 | }, | ||
133 | }, | ||
134 | Type: function.StaticReturnType(cty.String), | ||
135 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
136 | newlines := regexp.MustCompile(`(?:\r\n?|\n)*\z`) | ||
137 | return cty.StringVal(newlines.ReplaceAllString(args[0].AsString(), "")), nil | ||
138 | }, | ||
139 | }) | ||
140 | |||
141 | // IndentFunc constructions a function that adds a given number of spaces to the | ||
142 | // beginnings of all but the first line in a given multi-line string. | ||
143 | var IndentFunc = function.New(&function.Spec{ | ||
144 | Params: []function.Parameter{ | ||
145 | { | ||
146 | Name: "spaces", | ||
147 | Type: cty.Number, | ||
148 | }, | ||
149 | { | ||
150 | Name: "str", | ||
151 | Type: cty.String, | ||
152 | }, | ||
153 | }, | ||
154 | Type: function.StaticReturnType(cty.String), | ||
155 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
156 | var spaces int | ||
157 | if err := gocty.FromCtyValue(args[0], &spaces); err != nil { | ||
158 | return cty.UnknownVal(cty.String), err | ||
159 | } | ||
160 | data := args[1].AsString() | ||
161 | pad := strings.Repeat(" ", spaces) | ||
162 | return cty.StringVal(strings.Replace(data, "\n", "\n"+pad, -1)), nil | ||
163 | }, | ||
164 | }) | ||
165 | |||
166 | // ReplaceFunc constructions a function that searches a given string for another | ||
167 | // given substring, and replaces each occurence with a given replacement string. | ||
168 | var ReplaceFunc = function.New(&function.Spec{ | ||
169 | Params: []function.Parameter{ | ||
170 | { | ||
171 | Name: "str", | ||
172 | Type: cty.String, | ||
173 | }, | ||
174 | { | ||
175 | Name: "substr", | ||
176 | Type: cty.String, | ||
177 | }, | ||
178 | { | ||
179 | Name: "replace", | ||
180 | Type: cty.String, | ||
181 | }, | ||
182 | }, | ||
183 | Type: function.StaticReturnType(cty.String), | ||
184 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
185 | str := args[0].AsString() | ||
186 | substr := args[1].AsString() | ||
187 | replace := args[2].AsString() | ||
188 | |||
189 | // We search/replace using a regexp if the string is surrounded | ||
190 | // in forward slashes. | ||
191 | if len(substr) > 1 && substr[0] == '/' && substr[len(substr)-1] == '/' { | ||
192 | re, err := regexp.Compile(substr[1 : len(substr)-1]) | ||
193 | if err != nil { | ||
194 | return cty.UnknownVal(cty.String), err | ||
195 | } | ||
196 | |||
197 | return cty.StringVal(re.ReplaceAllString(str, replace)), nil | ||
198 | } | ||
199 | |||
200 | return cty.StringVal(strings.Replace(str, substr, replace, -1)), nil | ||
201 | }, | ||
202 | }) | ||
203 | |||
204 | // TitleFunc constructions a function that converts the first letter of each word | ||
205 | // in the given string to uppercase. | ||
206 | var TitleFunc = function.New(&function.Spec{ | ||
207 | Params: []function.Parameter{ | ||
208 | { | ||
209 | Name: "str", | ||
210 | Type: cty.String, | ||
211 | }, | ||
212 | }, | ||
213 | Type: function.StaticReturnType(cty.String), | ||
214 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
215 | return cty.StringVal(strings.Title(args[0].AsString())), nil | ||
216 | }, | ||
217 | }) | ||
218 | |||
219 | // TrimSpaceFunc constructions a function that removes any space characters from | ||
220 | // the start and end of the given string. | ||
221 | var TrimSpaceFunc = function.New(&function.Spec{ | ||
222 | Params: []function.Parameter{ | ||
223 | { | ||
224 | Name: "str", | ||
225 | Type: cty.String, | ||
226 | }, | ||
227 | }, | ||
228 | Type: function.StaticReturnType(cty.String), | ||
229 | Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { | ||
230 | return cty.StringVal(strings.TrimSpace(args[0].AsString())), nil | ||
231 | }, | ||
232 | }) | ||
233 | |||
234 | // Join concatenates together the string elements of one or more lists with a | ||
235 | // given separator. | ||
236 | func Join(sep cty.Value, lists ...cty.Value) (cty.Value, error) { | ||
237 | args := make([]cty.Value, len(lists)+1) | ||
238 | args[0] = sep | ||
239 | copy(args[1:], lists) | ||
240 | return JoinFunc.Call(args) | ||
241 | } | ||
242 | |||
243 | // Sort re-orders the elements of a given list of strings so that they are | ||
244 | // in ascending lexicographical order. | ||
245 | func Sort(list cty.Value) (cty.Value, error) { | ||
246 | return SortFunc.Call([]cty.Value{list}) | ||
247 | } | ||
248 | |||
249 | // Split divides a given string by a given separator, returning a list of | ||
250 | // strings containing the characters between the separator sequences. | ||
251 | func Split(sep, str cty.Value) (cty.Value, error) { | ||
252 | return SplitFunc.Call([]cty.Value{sep, str}) | ||
253 | } | ||
254 | |||
255 | // Chomp removes newline characters at the end of a string. | ||
256 | func Chomp(str cty.Value) (cty.Value, error) { | ||
257 | return ChompFunc.Call([]cty.Value{str}) | ||
258 | } | ||
259 | |||
260 | // Indent adds a given number of spaces to the beginnings of all but the first | ||
261 | // line in a given multi-line string. | ||
262 | func Indent(spaces, str cty.Value) (cty.Value, error) { | ||
263 | return IndentFunc.Call([]cty.Value{spaces, str}) | ||
264 | } | ||
265 | |||
266 | // Replace searches a given string for another given substring, | ||
267 | // and replaces all occurences with a given replacement string. | ||
268 | func Replace(str, substr, replace cty.Value) (cty.Value, error) { | ||
269 | return ReplaceFunc.Call([]cty.Value{str, substr, replace}) | ||
270 | } | ||
271 | |||
272 | // Title converts the first letter of each word in the given string to uppercase. | ||
273 | func Title(str cty.Value) (cty.Value, error) { | ||
274 | return TitleFunc.Call([]cty.Value{str}) | ||
275 | } | ||
276 | |||
277 | // TrimSpace removes any space characters from the start and end of the given string. | ||
278 | func TrimSpace(str cty.Value) (cty.Value, error) { | ||
279 | return TrimSpaceFunc.Call([]cty.Value{str}) | ||
280 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/functions.go b/vendor/github.com/hashicorp/terraform/lang/functions.go new file mode 100644 index 0000000..2c7b548 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/functions.go | |||
@@ -0,0 +1,147 @@ | |||
1 | package lang | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | |||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | "github.com/zclconf/go-cty/cty/function" | ||
8 | "github.com/zclconf/go-cty/cty/function/stdlib" | ||
9 | |||
10 | "github.com/hashicorp/terraform/lang/funcs" | ||
11 | ) | ||
12 | |||
13 | var impureFunctions = []string{ | ||
14 | "bcrypt", | ||
15 | "timestamp", | ||
16 | "uuid", | ||
17 | } | ||
18 | |||
19 | // Functions returns the set of functions that should be used to when evaluating | ||
20 | // expressions in the receiving scope. | ||
21 | func (s *Scope) Functions() map[string]function.Function { | ||
22 | s.funcsLock.Lock() | ||
23 | if s.funcs == nil { | ||
24 | // Some of our functions are just directly the cty stdlib functions. | ||
25 | // Others are implemented in the subdirectory "funcs" here in this | ||
26 | // repository. New functions should generally start out their lives | ||
27 | // in the "funcs" directory and potentially graduate to cty stdlib | ||
28 | // later if the functionality seems to be something domain-agnostic | ||
29 | // that would be useful to all applications using cty functions. | ||
30 | |||
31 | s.funcs = map[string]function.Function{ | ||
32 | "abs": stdlib.AbsoluteFunc, | ||
33 | "basename": funcs.BasenameFunc, | ||
34 | "base64decode": funcs.Base64DecodeFunc, | ||
35 | "base64encode": funcs.Base64EncodeFunc, | ||
36 | "base64gzip": funcs.Base64GzipFunc, | ||
37 | "base64sha256": funcs.Base64Sha256Func, | ||
38 | "base64sha512": funcs.Base64Sha512Func, | ||
39 | "bcrypt": funcs.BcryptFunc, | ||
40 | "ceil": funcs.CeilFunc, | ||
41 | "chomp": funcs.ChompFunc, | ||
42 | "cidrhost": funcs.CidrHostFunc, | ||
43 | "cidrnetmask": funcs.CidrNetmaskFunc, | ||
44 | "cidrsubnet": funcs.CidrSubnetFunc, | ||
45 | "coalesce": funcs.CoalesceFunc, | ||
46 | "coalescelist": funcs.CoalesceListFunc, | ||
47 | "compact": funcs.CompactFunc, | ||
48 | "concat": stdlib.ConcatFunc, | ||
49 | "contains": funcs.ContainsFunc, | ||
50 | "csvdecode": stdlib.CSVDecodeFunc, | ||
51 | "dirname": funcs.DirnameFunc, | ||
52 | "distinct": funcs.DistinctFunc, | ||
53 | "element": funcs.ElementFunc, | ||
54 | "chunklist": funcs.ChunklistFunc, | ||
55 | "file": funcs.MakeFileFunc(s.BaseDir, false), | ||
56 | "fileexists": funcs.MakeFileExistsFunc(s.BaseDir), | ||
57 | "filebase64": funcs.MakeFileFunc(s.BaseDir, true), | ||
58 | "filebase64sha256": funcs.MakeFileBase64Sha256Func(s.BaseDir), | ||
59 | "filebase64sha512": funcs.MakeFileBase64Sha512Func(s.BaseDir), | ||
60 | "filemd5": funcs.MakeFileMd5Func(s.BaseDir), | ||
61 | "filesha1": funcs.MakeFileSha1Func(s.BaseDir), | ||
62 | "filesha256": funcs.MakeFileSha256Func(s.BaseDir), | ||
63 | "filesha512": funcs.MakeFileSha512Func(s.BaseDir), | ||
64 | "flatten": funcs.FlattenFunc, | ||
65 | "floor": funcs.FloorFunc, | ||
66 | "format": stdlib.FormatFunc, | ||
67 | "formatdate": stdlib.FormatDateFunc, | ||
68 | "formatlist": stdlib.FormatListFunc, | ||
69 | "indent": funcs.IndentFunc, | ||
70 | "index": funcs.IndexFunc, | ||
71 | "join": funcs.JoinFunc, | ||
72 | "jsondecode": stdlib.JSONDecodeFunc, | ||
73 | "jsonencode": stdlib.JSONEncodeFunc, | ||
74 | "keys": funcs.KeysFunc, | ||
75 | "length": funcs.LengthFunc, | ||
76 | "list": funcs.ListFunc, | ||
77 | "log": funcs.LogFunc, | ||
78 | "lookup": funcs.LookupFunc, | ||
79 | "lower": stdlib.LowerFunc, | ||
80 | "map": funcs.MapFunc, | ||
81 | "matchkeys": funcs.MatchkeysFunc, | ||
82 | "max": stdlib.MaxFunc, | ||
83 | "md5": funcs.Md5Func, | ||
84 | "merge": funcs.MergeFunc, | ||
85 | "min": stdlib.MinFunc, | ||
86 | "pathexpand": funcs.PathExpandFunc, | ||
87 | "pow": funcs.PowFunc, | ||
88 | "replace": funcs.ReplaceFunc, | ||
89 | "reverse": funcs.ReverseFunc, | ||
90 | "rsadecrypt": funcs.RsaDecryptFunc, | ||
91 | "setintersection": stdlib.SetIntersectionFunc, | ||
92 | "setproduct": funcs.SetProductFunc, | ||
93 | "setunion": stdlib.SetUnionFunc, | ||
94 | "sha1": funcs.Sha1Func, | ||
95 | "sha256": funcs.Sha256Func, | ||
96 | "sha512": funcs.Sha512Func, | ||
97 | "signum": funcs.SignumFunc, | ||
98 | "slice": funcs.SliceFunc, | ||
99 | "sort": funcs.SortFunc, | ||
100 | "split": funcs.SplitFunc, | ||
101 | "strrev": stdlib.ReverseFunc, | ||
102 | "substr": stdlib.SubstrFunc, | ||
103 | "timestamp": funcs.TimestampFunc, | ||
104 | "timeadd": funcs.TimeAddFunc, | ||
105 | "title": funcs.TitleFunc, | ||
106 | "tostring": funcs.MakeToFunc(cty.String), | ||
107 | "tonumber": funcs.MakeToFunc(cty.Number), | ||
108 | "tobool": funcs.MakeToFunc(cty.Bool), | ||
109 | "toset": funcs.MakeToFunc(cty.Set(cty.DynamicPseudoType)), | ||
110 | "tolist": funcs.MakeToFunc(cty.List(cty.DynamicPseudoType)), | ||
111 | "tomap": funcs.MakeToFunc(cty.Map(cty.DynamicPseudoType)), | ||
112 | "transpose": funcs.TransposeFunc, | ||
113 | "trimspace": funcs.TrimSpaceFunc, | ||
114 | "upper": stdlib.UpperFunc, | ||
115 | "urlencode": funcs.URLEncodeFunc, | ||
116 | "uuid": funcs.UUIDFunc, | ||
117 | "values": funcs.ValuesFunc, | ||
118 | "zipmap": funcs.ZipmapFunc, | ||
119 | } | ||
120 | |||
121 | s.funcs["templatefile"] = funcs.MakeTemplateFileFunc(s.BaseDir, func() map[string]function.Function { | ||
122 | // The templatefile function prevents recursive calls to itself | ||
123 | // by copying this map and overwriting the "templatefile" entry. | ||
124 | return s.funcs | ||
125 | }) | ||
126 | |||
127 | if s.PureOnly { | ||
128 | // Force our few impure functions to return unknown so that we | ||
129 | // can defer evaluating them until a later pass. | ||
130 | for _, name := range impureFunctions { | ||
131 | s.funcs[name] = function.Unpredictable(s.funcs[name]) | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | s.funcsLock.Unlock() | ||
136 | |||
137 | return s.funcs | ||
138 | } | ||
139 | |||
140 | var unimplFunc = function.New(&function.Spec{ | ||
141 | Type: func([]cty.Value) (cty.Type, error) { | ||
142 | return cty.DynamicPseudoType, fmt.Errorf("function not yet implemented") | ||
143 | }, | ||
144 | Impl: func([]cty.Value, cty.Type) (cty.Value, error) { | ||
145 | return cty.DynamicVal, fmt.Errorf("function not yet implemented") | ||
146 | }, | ||
147 | }) | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/references.go b/vendor/github.com/hashicorp/terraform/lang/references.go new file mode 100644 index 0000000..d688477 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/references.go | |||
@@ -0,0 +1,81 @@ | |||
1 | package lang | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/hashicorp/terraform/addrs" | ||
6 | "github.com/hashicorp/terraform/configs/configschema" | ||
7 | "github.com/hashicorp/terraform/lang/blocktoattr" | ||
8 | "github.com/hashicorp/terraform/tfdiags" | ||
9 | ) | ||
10 | |||
11 | // References finds all of the references in the given set of traversals, | ||
12 | // returning diagnostics if any of the traversals cannot be interpreted as a | ||
13 | // reference. | ||
14 | // | ||
15 | // This function does not do any de-duplication of references, since references | ||
16 | // have source location information embedded in them and so any invalid | ||
17 | // references that are duplicated should have errors reported for each | ||
18 | // occurence. | ||
19 | // | ||
20 | // If the returned diagnostics contains errors then the result may be | ||
21 | // incomplete or invalid. Otherwise, the returned slice has one reference per | ||
22 | // given traversal, though it is not guaranteed that the references will | ||
23 | // appear in the same order as the given traversals. | ||
24 | func References(traversals []hcl.Traversal) ([]*addrs.Reference, tfdiags.Diagnostics) { | ||
25 | if len(traversals) == 0 { | ||
26 | return nil, nil | ||
27 | } | ||
28 | |||
29 | var diags tfdiags.Diagnostics | ||
30 | refs := make([]*addrs.Reference, 0, len(traversals)) | ||
31 | |||
32 | for _, traversal := range traversals { | ||
33 | ref, refDiags := addrs.ParseRef(traversal) | ||
34 | diags = diags.Append(refDiags) | ||
35 | if ref == nil { | ||
36 | continue | ||
37 | } | ||
38 | refs = append(refs, ref) | ||
39 | } | ||
40 | |||
41 | return refs, diags | ||
42 | } | ||
43 | |||
44 | // ReferencesInBlock is a helper wrapper around References that first searches | ||
45 | // the given body for traversals, before converting those traversals to | ||
46 | // references. | ||
47 | // | ||
48 | // A block schema must be provided so that this function can determine where in | ||
49 | // the body variables are expected. | ||
50 | func ReferencesInBlock(body hcl.Body, schema *configschema.Block) ([]*addrs.Reference, tfdiags.Diagnostics) { | ||
51 | if body == nil { | ||
52 | return nil, nil | ||
53 | } | ||
54 | |||
55 | // We use blocktoattr.ExpandedVariables instead of hcldec.Variables or | ||
56 | // dynblock.VariablesHCLDec here because when we evaluate a block we'll | ||
57 | // first apply the dynamic block extension and _then_ the blocktoattr | ||
58 | // transform, and so blocktoattr.ExpandedVariables takes into account | ||
59 | // both of those transforms when it analyzes the body to ensure we find | ||
60 | // all of the references as if they'd already moved into their final | ||
61 | // locations, even though we can't expand dynamic blocks yet until we | ||
62 | // already know which variables are required. | ||
63 | // | ||
64 | // The set of cases we want to detect here is covered by the tests for | ||
65 | // the plan graph builder in the main 'terraform' package, since it's | ||
66 | // in a better position to test this due to having mock providers etc | ||
67 | // available. | ||
68 | traversals := blocktoattr.ExpandedVariables(body, schema) | ||
69 | return References(traversals) | ||
70 | } | ||
71 | |||
72 | // ReferencesInExpr is a helper wrapper around References that first searches | ||
73 | // the given expression for traversals, before converting those traversals | ||
74 | // to references. | ||
75 | func ReferencesInExpr(expr hcl.Expression) ([]*addrs.Reference, tfdiags.Diagnostics) { | ||
76 | if expr == nil { | ||
77 | return nil, nil | ||
78 | } | ||
79 | traversals := expr.Variables() | ||
80 | return References(traversals) | ||
81 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/scope.go b/vendor/github.com/hashicorp/terraform/lang/scope.go new file mode 100644 index 0000000..98fca6b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/scope.go | |||
@@ -0,0 +1,34 @@ | |||
1 | package lang | ||
2 | |||
3 | import ( | ||
4 | "sync" | ||
5 | |||
6 | "github.com/zclconf/go-cty/cty/function" | ||
7 | |||
8 | "github.com/hashicorp/terraform/addrs" | ||
9 | ) | ||
10 | |||
11 | // Scope is the main type in this package, allowing dynamic evaluation of | ||
12 | // blocks and expressions based on some contextual information that informs | ||
13 | // which variables and functions will be available. | ||
14 | type Scope struct { | ||
15 | // Data is used to resolve references in expressions. | ||
16 | Data Data | ||
17 | |||
18 | // SelfAddr is the address that the "self" object should be an alias of, | ||
19 | // or nil if the "self" object should not be available at all. | ||
20 | SelfAddr addrs.Referenceable | ||
21 | |||
22 | // BaseDir is the base directory used by any interpolation functions that | ||
23 | // accept filesystem paths as arguments. | ||
24 | BaseDir string | ||
25 | |||
26 | // PureOnly can be set to true to request that any non-pure functions | ||
27 | // produce unknown value results rather than actually executing. This is | ||
28 | // important during a plan phase to avoid generating results that could | ||
29 | // then differ during apply. | ||
30 | PureOnly bool | ||
31 | |||
32 | funcs map[string]function.Function | ||
33 | funcsLock sync.Mutex | ||
34 | } | ||