]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/lang/funcs/filesystem.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / lang / funcs / filesystem.go
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 }