Merge pull request #19086 from hashicorp/f-file-exists-func
lang: Add file_exists function
This commit is contained in:
commit
d6f7577955
|
@ -63,6 +63,49 @@ func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// BasenameFunc constructs a function that takes a string containing a filesystem path
|
// BasenameFunc constructs a function that takes a string containing a filesystem path
|
||||||
// and removes all except the last portion from it.
|
// and removes all except the last portion from it.
|
||||||
var BasenameFunc = function.New(&function.Spec{
|
var BasenameFunc = function.New(&function.Spec{
|
||||||
|
@ -121,6 +164,16 @@ func File(baseDir string, path cty.Value) (cty.Value, error) {
|
||||||
return fn.Call([]cty.Value{path})
|
return fn.Call([]cty.Value{path})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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})
|
||||||
|
}
|
||||||
|
|
||||||
// FileBase64 reads the contents of the file at the given path.
|
// FileBase64 reads the contents of the file at the given path.
|
||||||
//
|
//
|
||||||
// The bytes from the file are encoded as base64 before returning.
|
// The bytes from the file are encoded as base64 before returning.
|
||||||
|
|
|
@ -52,6 +52,49 @@ func TestFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileExists(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Path cty.Value
|
||||||
|
Want cty.Value
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata/hello.txt"),
|
||||||
|
cty.BoolVal(true),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal(""), // empty path
|
||||||
|
cty.BoolVal(false),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata/missing"),
|
||||||
|
cty.BoolVal(false),
|
||||||
|
false, // no file exists
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("FileExists(\".\", %#v)", test.Path), func(t *testing.T) {
|
||||||
|
got, err := FileExists(".", test.Path)
|
||||||
|
|
||||||
|
if test.Err {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("succeeded; want error")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !got.RawEquals(test.Want) {
|
||||||
|
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFileBase64(t *testing.T) {
|
func TestFileBase64(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Path cty.Value
|
Path cty.Value
|
||||||
|
|
|
@ -53,6 +53,7 @@ func (s *Scope) Functions() map[string]function.Function {
|
||||||
"element": funcs.ElementFunc,
|
"element": funcs.ElementFunc,
|
||||||
"chunklist": funcs.ChunklistFunc,
|
"chunklist": funcs.ChunklistFunc,
|
||||||
"file": funcs.MakeFileFunc(s.BaseDir, false),
|
"file": funcs.MakeFileFunc(s.BaseDir, false),
|
||||||
|
"fileexists": funcs.MakeFileExistsFunc(s.BaseDir),
|
||||||
"filebase64": funcs.MakeFileFunc(s.BaseDir, true),
|
"filebase64": funcs.MakeFileFunc(s.BaseDir, true),
|
||||||
"flatten": funcs.FlattenFunc,
|
"flatten": funcs.FlattenFunc,
|
||||||
"floor": funcs.FloorFunc,
|
"floor": funcs.FloorFunc,
|
||||||
|
|
|
@ -42,3 +42,5 @@ Hello World
|
||||||
* [`filebase64`](./filebase64.html) also reads the contents of a given file,
|
* [`filebase64`](./filebase64.html) also reads the contents of a given file,
|
||||||
but returns the raw bytes in that file Base64-encoded, rather than
|
but returns the raw bytes in that file Base64-encoded, rather than
|
||||||
interpreting the contents as UTF-8 text.
|
interpreting the contents as UTF-8 text.
|
||||||
|
* [`fileexists`](./fileexists.html) determines whether a file exists
|
||||||
|
at a given path.
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
layout: "functions"
|
||||||
|
page_title: "fileexists function"
|
||||||
|
sidebar_current: "docs-funcs-file-file-exists"
|
||||||
|
description: |-
|
||||||
|
The fileexists function determines whether a file exists at a given path.
|
||||||
|
---
|
||||||
|
|
||||||
|
# `fileexists` Function
|
||||||
|
|
||||||
|
`fileexists` determines whether a file exists at a given path.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
fileexists(path)
|
||||||
|
```
|
||||||
|
|
||||||
|
Functions are evaluated during configuration parsing rather than at apply time,
|
||||||
|
so this function can only be used with files that are already present on disk
|
||||||
|
before Terraform takes any actions.
|
||||||
|
|
||||||
|
This function works only with regular files. If used with a directory, FIFO,
|
||||||
|
or other special mode, it will return an error.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
> fileexists("${path.module}/hello.txt")
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
fileexists("custom-section.sh") ? file("custom-section.sh") : local.default_content
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Functions
|
||||||
|
|
||||||
|
* [`file`](./file.html) reads the contents of a file at a given path
|
|
@ -249,6 +249,10 @@
|
||||||
<a href="/docs/configuration/functions/file.html">file</a>
|
<a href="/docs/configuration/functions/file.html">file</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-funcs-file-file-exists") %>>
|
||||||
|
<a href="/docs/configuration/functions/fileexists.html">fileexists</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-funcs-file-filebase64") %>>
|
<li<%= sidebar_current("docs-funcs-file-filebase64") %>>
|
||||||
<a href="/docs/configuration/functions/filebase64.html">filebase64</a>
|
<a href="/docs/configuration/functions/filebase64.html">filebase64</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue