Merge pull request #22523 from hashicorp/f-lang-funcs-filelist
lang/funcs: Add fileset function
This commit is contained in:
commit
cf687a94b5
|
@ -207,6 +207,60 @@ func MakeFileExistsFunc(baseDir string) function.Function {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeFileSetFunc constructs a function that takes a glob pattern
|
||||||
|
// and enumerates a file set from that pattern
|
||||||
|
func MakeFileSetFunc(baseDir string) function.Function {
|
||||||
|
return function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "pattern",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.Set(cty.String)),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
pattern := args[0].AsString()
|
||||||
|
pattern, err := homedir.Expand(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to expand ~: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filepath.IsAbs(pattern) {
|
||||||
|
pattern = filepath.Join(baseDir, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the path is canonical for the host OS
|
||||||
|
pattern = filepath.Clean(pattern)
|
||||||
|
|
||||||
|
matches, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to glob pattern (%s): %s", pattern, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchVals []cty.Value
|
||||||
|
for _, match := range matches {
|
||||||
|
fi, err := os.Stat(match)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to stat (%s): %s", match, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fi.Mode().IsRegular() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matchVals = append(matchVals, cty.StringVal(match))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matchVals) == 0 {
|
||||||
|
return cty.SetValEmpty(cty.String), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.SetVal(matchVals), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 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{
|
||||||
|
@ -316,6 +370,16 @@ func FileExists(baseDir string, path cty.Value) (cty.Value, error) {
|
||||||
return fn.Call([]cty.Value{path})
|
return fn.Call([]cty.Value{path})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FileSet enumerates a set of files given a glob pattern
|
||||||
|
//
|
||||||
|
// 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 FileSet(baseDir string, pattern cty.Value) (cty.Value, error) {
|
||||||
|
fn := MakeFileSetFunc(baseDir)
|
||||||
|
return fn.Call([]cty.Value{pattern})
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
|
@ -224,6 +224,120 @@ func TestFileExists(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileSet(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Pattern cty.Value
|
||||||
|
Want cty.Value
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata/missing"),
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata/missing*"),
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("*/missing"),
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata"),
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata*"),
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata/*.txt"),
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("testdata/hello.txt"),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata/hello.txt"),
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("testdata/hello.txt"),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata/hello.???"),
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("testdata/hello.txt"),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("testdata/hello*"),
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("testdata/hello.tmpl"),
|
||||||
|
cty.StringVal("testdata/hello.txt"),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("*/hello.txt"),
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("testdata/hello.txt"),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("*/*.txt"),
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("testdata/hello.txt"),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("*/hello*"),
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("testdata/hello.tmpl"),
|
||||||
|
cty.StringVal("testdata/hello.txt"),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("["),
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("\\"),
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("FileSet(\".\", %#v)", test.Pattern), func(t *testing.T) {
|
||||||
|
got, err := FileSet(".", test.Pattern)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
@ -56,6 +56,7 @@ func (s *Scope) Functions() map[string]function.Function {
|
||||||
"chunklist": funcs.ChunklistFunc,
|
"chunklist": funcs.ChunklistFunc,
|
||||||
"file": funcs.MakeFileFunc(s.BaseDir, false),
|
"file": funcs.MakeFileFunc(s.BaseDir, false),
|
||||||
"fileexists": funcs.MakeFileExistsFunc(s.BaseDir),
|
"fileexists": funcs.MakeFileExistsFunc(s.BaseDir),
|
||||||
|
"fileset": funcs.MakeFileSetFunc(s.BaseDir),
|
||||||
"filebase64": funcs.MakeFileFunc(s.BaseDir, true),
|
"filebase64": funcs.MakeFileFunc(s.BaseDir, true),
|
||||||
"filebase64sha256": funcs.MakeFileBase64Sha256Func(s.BaseDir),
|
"filebase64sha256": funcs.MakeFileBase64Sha256Func(s.BaseDir),
|
||||||
"filebase64sha512": funcs.MakeFileBase64Sha512Func(s.BaseDir),
|
"filebase64sha512": funcs.MakeFileBase64Sha512Func(s.BaseDir),
|
||||||
|
|
|
@ -279,6 +279,16 @@ func TestFunctions(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"fileset": {
|
||||||
|
{
|
||||||
|
`fileset("hello.*")`,
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("testdata/functions-test/hello.tmpl"),
|
||||||
|
cty.StringVal("testdata/functions-test/hello.txt"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"filebase64": {
|
"filebase64": {
|
||||||
{
|
{
|
||||||
`filebase64("hello.txt")`,
|
`filebase64("hello.txt")`,
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
layout: "functions"
|
||||||
|
page_title: "fileset - Functions - Configuration Language"
|
||||||
|
sidebar_current: "docs-funcs-file-file-set"
|
||||||
|
description: |-
|
||||||
|
The fileset function enumerates a set of regular file names given a pattern.
|
||||||
|
---
|
||||||
|
|
||||||
|
# `fileset` Function
|
||||||
|
|
||||||
|
-> **Note:** This page is about Terraform 0.12 and later. For Terraform 0.11 and
|
||||||
|
earlier, see
|
||||||
|
[0.11 Configuration Language: Interpolation Syntax](../../configuration-0-11/interpolation.html).
|
||||||
|
|
||||||
|
`fileset` enumerates a set of regular file names given a pattern.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
fileset(pattern)
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported pattern matches:
|
||||||
|
|
||||||
|
- `*` - matches any sequence of non-separator characters
|
||||||
|
- `?` - matches any single non-separator character
|
||||||
|
- `[RANGE]` - matches a range of characters
|
||||||
|
- `[^RANGE]` - matches outside the range of characters
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
> fileset("${path.module}/*.txt")
|
||||||
|
[
|
||||||
|
"path/to/module/hello.txt",
|
||||||
|
"path/to/module/world.txt",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
resource "example_thing" "example" {
|
||||||
|
for_each = fileset("${path.module}/files/*")
|
||||||
|
|
||||||
|
# other configuration using each.value
|
||||||
|
}
|
||||||
|
```
|
|
@ -304,6 +304,10 @@
|
||||||
<a href="/docs/configuration/functions/fileexists.html">fileexists</a>
|
<a href="/docs/configuration/functions/fileexists.html">fileexists</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="/docs/configuration/functions/fileset.html">fileset</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<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