package funcs import ( "encoding/base64" "fmt" "io/ioutil" "os" "path/filepath" "unicode/utf8" homedir "github.com/mitchellh/go-homedir" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) // MakeFileFunc constructs a function that takes a file path and returns the // contents of that file, either directly as a string (where valid UTF-8 is // required) or as a string containing base64 bytes. func MakeFileFunc(baseDir string, encBase64 bool) function.Function { return function.New(&function.Spec{ Params: []function.Parameter{ { Name: "path", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { path := args[0].AsString() path, err := homedir.Expand(path) if err != nil { return cty.UnknownVal(cty.String), fmt.Errorf("failed to expand ~: %s", err) } if !filepath.IsAbs(path) { path = filepath.Join(baseDir, path) } // Ensure that the path is canonical for the host OS path = filepath.Clean(path) src, err := ioutil.ReadFile(path) if err != nil { // ReadFile does not return Terraform-user-friendly error // messages, so we'll provide our own. if os.IsNotExist(err) { return cty.UnknownVal(cty.String), fmt.Errorf("no file exists at %s", path) } return cty.UnknownVal(cty.String), fmt.Errorf("failed to read %s", path) } switch { case encBase64: enc := base64.StdEncoding.EncodeToString(src) return cty.StringVal(enc), nil default: if !utf8.Valid(src) { return cty.UnknownVal(cty.String), fmt.Errorf("contents of %s are not valid UTF-8; to read arbitrary bytes, use the filebase64 function instead", path) } return cty.StringVal(string(src)), nil } }, }) } // File reads the contents of the file at the given path. // // The file must contain valid UTF-8 bytes, or this function will return an error. // // The underlying function implementation works relative to a particular base // directory, so this wrapper takes a base directory string and uses it to // construct the underlying function before calling it. func File(baseDir string, path cty.Value) (cty.Value, error) { fn := MakeFileFunc(baseDir, false) return fn.Call([]cty.Value{path}) } // FileBase64 reads the contents of the file at the given path. // // The bytes from the file are encoded as base64 before returning. // // The underlying function implementation works relative to a particular base // directory, so this wrapper takes a base directory string and uses it to // construct the underlying function before calling it. func FileBase64(baseDir string, path cty.Value) (cty.Value, error) { fn := MakeFileFunc(baseDir, true) return fn.Call([]cty.Value{path}) }