Skip to content

Commit

Permalink
[VAULT-28010] fix(funcs): fix relative paths in file()
Browse files Browse the repository at this point in the history
The recently added `file()` function supports relative paths but they’ll
always be relative to the working dir that exec’s enos. This is usually
fine unless enos is invoked with `--chdir`, in which case we need funcs
that support relative paths to be relative to the `--chdir` working dir.

* Make `file()` support the `--chdir` base path when given a base path
* Bump the version
* Add tests for funcs that support relative paths
* Remove test data that wasn't being used

Signed-off-by: Ryan Cragun <[email protected]>
  • Loading branch information
ryancragun committed Jun 10, 2024
1 parent 57167cd commit 03d6df6
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 44 deletions.
2 changes: 1 addition & 1 deletion internal/flightplan/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func (d *Decoder) baseEvalContext() *hcl.EvalContext {
"element": stdlib.ElementFunc,
"equal": stdlib.EqualFunc,
"endswith": funcs.EndsWithFunc,
"file": funcs.FileFunc,
"file": funcs.FileFunc(d.dir),
"flatten": stdlib.FlattenFunc,
"floor": stdlib.FloorFunc,
"format": stdlib.FormatFunc,
Expand Down
71 changes: 44 additions & 27 deletions internal/flightplan/funcs/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
"github.com/zclconf/go-cty/cty/function"
)

// AbsPathFunc constructs a function that converts a filesystem path to an
// absolute path. It takes basePath that is equal to the decoders working
// directory, that way relative paths are relative to the working dir.
// AbsPathFunc constructs a function that converts a filesystem path to an absolute path. It takes
// basePath that is equal to the decoders working directory, that way relative paths are relative
// to the working dir.
func AbsPathFunc(basePath string) function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
Expand All @@ -25,16 +25,8 @@ func AbsPathFunc(basePath string) function.Function {
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
path := args[0].AsString()
if filepath.IsAbs(path) {
return cty.StringVal(filepath.ToSlash(path)), nil
}

if basePath != "" {
path = filepath.Join(basePath, path)
}
absPath, err := filepath.Abs(path)

return cty.StringVal(filepath.ToSlash(absPath)), err
str, err := absolutePathRelativeToBase(basePath, path)
return cty.StringVal(str), err

Check failure on line 29 in internal/flightplan/funcs/filesystem.go

View workflow job for this annotation

GitHub Actions / Validate Artifact / Format

return with no blank line before (nlreturn)
},
})
}
Expand All @@ -59,19 +51,44 @@ var JoinPathFunc = function.New(&function.Spec{
},
})

// FileFunc reads the contents of the file at the path given. The file must
// be valid UTF-8.
var FileFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "path",
Type: cty.String,
// FileFunc constructs a function that FileFunc reads the contents of the file at the path given.
// The file must be valid UTF-8. It takes basePath that is equal to the decoders working directory,
// that way relative paths are relative to the working dir.
func FileFunc(basePath string) 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) {
f, err := os.ReadFile(args[0].AsString())
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
path := args[0].AsString()
abs, err := absolutePathRelativeToBase(basePath, path)
if err != nil {
return cty.StringVal(""), err
}

return cty.StringVal(string(f)), err
},
})
f, err := os.ReadFile(abs)

return cty.StringVal(string(f)), err
},
})
}

// Take a basePath and another path and return the absolute path. If the path is absolute we'll
// return it unchanged. If it's relative and a basePath is not blank we'll make the path absolute
// realitve to the basePath, otherwise it will be absolute relative to the current directory.
func absolutePathRelativeToBase(base, path string) (string, error) {
if filepath.IsAbs(path) {
return filepath.ToSlash(path), nil
}

if base != "" {
path = filepath.Join(base, path)
}
absPath, err := filepath.Abs(path)

return filepath.ToSlash(absPath), err
}
115 changes: 115 additions & 0 deletions internal/flightplan/funcs/filesystem_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package funcs

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)

func TestAbsPathFunc(t *testing.T) {
tmpDir := t.TempDir()
t.Parallel()

for name, test := range map[string]struct {
basePath func() string
path string
absPath func() string
}{
"absolute_with_irrelevant_base": {
basePath: func() string { return "/my/current/working/dir" },
path: "/some/absolute/path/file.txt",
absPath: func() string { return "/some/absolute/path/file.txt" },
},
"absolute_no_base": {
basePath: func() string { return "" },
path: "/some/absolute/path/file.txt",
absPath: func() string { return "/some/absolute/path/file.txt" },
},
"relative_no_base": {
basePath: func() string {
wd, err := os.Getwd()
require.NoError(t, err)
return wd

Check failure on line 35 in internal/flightplan/funcs/filesystem_test.go

View workflow job for this annotation

GitHub Actions / Validate Artifact / Format

return with no blank line before (nlreturn)
},
path: "./some/relative",
absPath: func() string {
wd, err := os.Getwd()
require.NoError(t, err)
abs, err := filepath.Abs(filepath.Join(wd, "./some/relative"))
require.NoError(t, err)
return abs

Check failure on line 43 in internal/flightplan/funcs/filesystem_test.go

View workflow job for this annotation

GitHub Actions / Validate Artifact / Format

return with no blank line before (nlreturn)
},
},
"relative_base": {
basePath: func() string { return tmpDir },
path: "./some/relative",
absPath: func() string {
abs, err := filepath.Abs(filepath.Join(tmpDir, "./some/relative"))
require.NoError(t, err)
return abs
},
},
} {
t.Run(name, func(t *testing.T) {
t.Parallel()
basePath := test.basePath()
absPath, err := AbsPathFunc(basePath).Call([]cty.Value{cty.StringVal(test.path)})
require.NoError(t, err)
require.Equal(t, test.absPath(), absPath.AsString())
})
}
}

func TestFileFunc(t *testing.T) {
tmpDir := t.TempDir()
t.Parallel()

for name, test := range map[string]struct {
basePath func() string
path func() string
contents string
}{
"absolute_with_irrelevant_base": {
basePath: func() string { return "/my/current/working/dir" },
path: func() string {
p, err := filepath.Abs("./testdata/test_file_func.txt")
require.NoError(t, err)
return p
},
contents: "static\n",
},
"absolute_no_base": {
basePath: func() string { return "" },
path: func() string {
p, err := filepath.Abs("./testdata/test_file_func.txt")
require.NoError(t, err)
return p
},
contents: "static\n",
},
"relative": {
basePath: func() string {
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "test_file_func.txt"), []byte("dynamic"), 0o755))
return tmpDir
},
path: func() string { return "./test_file_func.txt" },
contents: "dynamic",
},
"relative_no_base": {
basePath: func() string { return "" },
path: func() string { return "./testdata/test_file_func.txt" },
contents: "static\n",
},
} {
t.Run(name, func(t *testing.T) {
t.Parallel()
basePath := test.basePath()
contents, err := FileFunc(basePath).Call([]cty.Value{cty.StringVal(test.path())})
require.NoError(t, err)
require.Equal(t, test.contents, contents.AsString())
})
}
}
1 change: 1 addition & 0 deletions internal/flightplan/funcs/testdata/test_file_func.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static
15 changes: 0 additions & 15 deletions internal/flightplan/tests/simple_module/main.tf

This file was deleted.

2 changes: 1 addition & 1 deletion version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var (
//
// Version must conform to the format expected by github.com/hashicorp/go-version
// for tests to work.
Version = "0.0.30"
Version = "0.0.31"

// VersionPrerelease is a pre-release marker for the version. If this is ""
// (empty string) then it means that it is a final release. Otherwise, this
Expand Down

0 comments on commit 03d6df6

Please sign in to comment.