Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: Add fileexists function #19086

Merged
merged 1 commit into from
Oct 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions lang/funcs/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// and removes all except the last portion from it.
var BasenameFunc = function.New(&function.Spec{
Expand Down Expand Up @@ -121,6 +164,16 @@ func File(baseDir string, path cty.Value) (cty.Value, error) {
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.
//
// The bytes from the file are encoded as base64 before returning.
Expand Down
43 changes: 43 additions & 0 deletions lang/funcs/filesystem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
tests := []struct {
Path cty.Value
Expand Down
1 change: 1 addition & 0 deletions lang/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func (s *Scope) Functions() map[string]function.Function {
"element": funcs.ElementFunc,
"chunklist": funcs.ChunklistFunc,
"file": funcs.MakeFileFunc(s.BaseDir, false),
"fileexists": funcs.MakeFileExistsFunc(s.BaseDir),
"filebase64": funcs.MakeFileFunc(s.BaseDir, true),
"flatten": funcs.FlattenFunc,
"floor": funcs.FloorFunc,
Expand Down
2 changes: 2 additions & 0 deletions website/docs/configuration/functions/file.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ Hello World
* [`filebase64`](./filebase64.html) also reads the contents of a given file,
but returns the raw bytes in that file Base64-encoded, rather than
interpreting the contents as UTF-8 text.
* [`fileexists`](./fileexists.html) determines whether a file exists
at a given path.
37 changes: 37 additions & 0 deletions website/docs/configuration/functions/fileexists.html.md
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions website/layouts/functions.erb
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@
<a href="/docs/configuration/functions/file.html">file</a>
</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") %>>
<a href="/docs/configuration/functions/filebase64.html">filebase64</a>
</li>
Expand Down