mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: load terraform modules when using dynamic parameters (#17714)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// overlayFS allows you to "join" together the template files tar file fs.FS
|
||||
// with the Terraform modules tar file fs.FS. We could potentially turn this
|
||||
// into something more parameterized/configurable, but the requirements here are
|
||||
// a _bit_ odd, because every file in the modulesFS includes the
|
||||
// .terraform/modules/ folder at the beginning of it's path.
|
||||
type overlayFS struct {
|
||||
baseFS fs.FS
|
||||
overlays []Overlay
|
||||
}
|
||||
|
||||
type Overlay struct {
|
||||
Path string
|
||||
fs.FS
|
||||
}
|
||||
|
||||
func NewOverlayFS(baseFS fs.FS, overlays []Overlay) (fs.FS, error) {
|
||||
if err := valid(baseFS); err != nil {
|
||||
return nil, xerrors.Errorf("baseFS: %w", err)
|
||||
}
|
||||
|
||||
for _, overlay := range overlays {
|
||||
if err := valid(overlay.FS); err != nil {
|
||||
return nil, xerrors.Errorf("overlayFS: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return overlayFS{
|
||||
baseFS: baseFS,
|
||||
overlays: overlays,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f overlayFS) Open(p string) (fs.File, error) {
|
||||
for _, overlay := range f.overlays {
|
||||
if strings.HasPrefix(path.Clean(p), overlay.Path) {
|
||||
return overlay.FS.Open(p)
|
||||
}
|
||||
}
|
||||
return f.baseFS.Open(p)
|
||||
}
|
||||
|
||||
func (f overlayFS) ReadDir(p string) ([]fs.DirEntry, error) {
|
||||
for _, overlay := range f.overlays {
|
||||
if strings.HasPrefix(path.Clean(p), overlay.Path) {
|
||||
//nolint:forcetypeassert
|
||||
return overlay.FS.(fs.ReadDirFS).ReadDir(p)
|
||||
}
|
||||
}
|
||||
//nolint:forcetypeassert
|
||||
return f.baseFS.(fs.ReadDirFS).ReadDir(p)
|
||||
}
|
||||
|
||||
func (f overlayFS) ReadFile(p string) ([]byte, error) {
|
||||
for _, overlay := range f.overlays {
|
||||
if strings.HasPrefix(path.Clean(p), overlay.Path) {
|
||||
//nolint:forcetypeassert
|
||||
return overlay.FS.(fs.ReadFileFS).ReadFile(p)
|
||||
}
|
||||
}
|
||||
//nolint:forcetypeassert
|
||||
return f.baseFS.(fs.ReadFileFS).ReadFile(p)
|
||||
}
|
||||
|
||||
// valid checks that the fs.FS implements the required interfaces.
|
||||
// The fs.FS interface is not sufficient.
|
||||
func valid(fsys fs.FS) error {
|
||||
_, ok := fsys.(fs.ReadDirFS)
|
||||
if !ok {
|
||||
return xerrors.New("overlayFS does not implement ReadDirFS")
|
||||
}
|
||||
_, ok = fsys.(fs.ReadFileFS)
|
||||
if !ok {
|
||||
return xerrors.New("overlayFS does not implement ReadFileFS")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package files_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/files"
|
||||
)
|
||||
|
||||
func TestOverlayFS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := afero.NewMemMapFs()
|
||||
afero.WriteFile(a, "main.tf", []byte("terraform {}"), 0o644)
|
||||
afero.WriteFile(a, ".terraform/modules/example_module/main.tf", []byte("inaccessible"), 0o644)
|
||||
afero.WriteFile(a, ".terraform/modules/other_module/main.tf", []byte("inaccessible"), 0o644)
|
||||
b := afero.NewMemMapFs()
|
||||
afero.WriteFile(b, ".terraform/modules/modules.json", []byte("{}"), 0o644)
|
||||
afero.WriteFile(b, ".terraform/modules/example_module/main.tf", []byte("terraform {}"), 0o644)
|
||||
|
||||
it, err := files.NewOverlayFS(afero.NewIOFS(a), []files.Overlay{{
|
||||
Path: ".terraform/modules",
|
||||
FS: afero.NewIOFS(b),
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
|
||||
content, err := fs.ReadFile(it, "main.tf")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "terraform {}", string(content))
|
||||
|
||||
_, err = fs.ReadFile(it, ".terraform/modules/other_module/main.tf")
|
||||
require.Error(t, err)
|
||||
|
||||
content, err = fs.ReadFile(it, ".terraform/modules/modules.json")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "{}", string(content))
|
||||
|
||||
content, err = fs.ReadFile(it, ".terraform/modules/example_module/main.tf")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "terraform {}", string(content))
|
||||
}
|
||||
Reference in New Issue
Block a user