2018-05-22 02:39:26 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-10-11 12:40:09 +02:00
|
|
|
// MakeFileExistsFunc constructs a function that takes a path
|
|
|
|
// and determines whether a file exists at that path
|
|
|
|
func MakeFileExistsFunc(baseDir string) function.Function {
|
|
|
|
return function.New(&function.Spec{
|
|
|
|
Params: []function.Parameter{
|
|
|
|
{
|
|
|
|
Name: "path",
|
|
|
|
Type: cty.String,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Type: function.StaticReturnType(cty.Bool),
|
|
|
|
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.Bool), 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)
|
|
|
|
|
|
|
|
fi, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return cty.False, nil
|
|
|
|
}
|
|
|
|
return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to stat %s", path)
|
|
|
|
}
|
|
|
|
|
|
|
|
if fi.Mode().IsRegular() {
|
|
|
|
return cty.True, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return cty.False, fmt.Errorf("%s is not a regular file, but %q",
|
|
|
|
path, fi.Mode().String())
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-05-23 18:38:12 +02:00
|
|
|
// BasenameFunc constructs a function that takes a string containing a filesystem path
|
|
|
|
// and removes all except the last portion from it.
|
2018-05-23 00:04:49 +02:00
|
|
|
var BasenameFunc = 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) {
|
|
|
|
return cty.StringVal(filepath.Base(args[0].AsString())), nil
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2018-05-23 18:38:12 +02:00
|
|
|
// DirnameFunc constructs a function that takes a string containing a filesystem path
|
|
|
|
// and removes the last portion from it.
|
2018-05-23 00:24:50 +02:00
|
|
|
var DirnameFunc = 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) {
|
|
|
|
return cty.StringVal(filepath.Dir(args[0].AsString())), nil
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2018-05-23 00:43:29 +02:00
|
|
|
// PathExpandFunc constructs a function that expands a leading ~ character to the current user's home directory.
|
|
|
|
var PathExpandFunc = 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) {
|
|
|
|
|
|
|
|
homePath, err := homedir.Expand(args[0].AsString())
|
|
|
|
return cty.StringVal(homePath), err
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2018-05-22 02:39:26 +02:00
|
|
|
// 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})
|
2018-10-11 12:40:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// FileExists determines whether a file exists at the given path.
|
|
|
|
//
|
|
|
|
// 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 FileExists(baseDir string, path cty.Value) (cty.Value, error) {
|
|
|
|
fn := MakeFileExistsFunc(baseDir)
|
|
|
|
return fn.Call([]cty.Value{path})
|
2018-05-22 02:39:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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})
|
|
|
|
}
|
2018-05-23 00:04:49 +02:00
|
|
|
|
|
|
|
// Basename takes a string containing a filesystem path and removes all except the last portion from it.
|
|
|
|
//
|
|
|
|
// The underlying function implementation works only with the path string and does not access the filesystem itself.
|
|
|
|
// It is therefore unable to take into account filesystem features such as symlinks.
|
|
|
|
//
|
|
|
|
// If the path is empty then the result is ".", representing the current working directory.
|
|
|
|
func Basename(path cty.Value) (cty.Value, error) {
|
|
|
|
return BasenameFunc.Call([]cty.Value{path})
|
|
|
|
}
|
2018-05-23 00:24:50 +02:00
|
|
|
|
|
|
|
// Dirname takes a string containing a filesystem path and removes the last portion from it.
|
|
|
|
//
|
|
|
|
// The underlying function implementation works only with the path string and does not access the filesystem itself.
|
|
|
|
// It is therefore unable to take into account filesystem features such as symlinks.
|
|
|
|
//
|
|
|
|
// If the path is empty then the result is ".", representing the current working directory.
|
|
|
|
func Dirname(path cty.Value) (cty.Value, error) {
|
|
|
|
return DirnameFunc.Call([]cty.Value{path})
|
|
|
|
}
|
2018-05-23 00:43:29 +02:00
|
|
|
|
|
|
|
// Pathexpand takes a string that might begin with a `~` segment, and if so it replaces that segment with
|
|
|
|
// the current user's home directory path.
|
|
|
|
//
|
|
|
|
// The underlying function implementation works only with the path string and does not access the filesystem itself.
|
|
|
|
// It is therefore unable to take into account filesystem features such as symlinks.
|
|
|
|
//
|
|
|
|
// If the leading segment in the path is not `~` then the given path is returned unmodified.
|
|
|
|
func Pathexpand(path cty.Value) (cty.Value, error) {
|
|
|
|
return PathExpandFunc.Call([]cty.Value{path})
|
|
|
|
}
|