From 098225dc0d29c6900495c1a189025aa66203c35b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 29 Sep 2016 17:47:47 -0700 Subject: [PATCH] config/module: use the raw source as part of the key This changes the key for the storage to be the _raw_ source from the module, not the fully expanded source. Example: it'll be a relative path instead of an absolute path. This allows the ".terraform/modules" directory to be portable when moving to other machines. This was a behavior that existed in <= 0.7.2 and was broken with #8398. This amends that and adds a test to verify. --- config/module/tree.go | 2 +- config/module/tree_test.go | 62 +++++++++++++++++++ helper/copy/copy.go | 121 +++++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 helper/copy/copy.go diff --git a/config/module/tree.go b/config/module/tree.go index 3af556e91..a0818bfa4 100644 --- a/config/module/tree.go +++ b/config/module/tree.go @@ -170,7 +170,7 @@ func (t *Tree) Load(s getter.Storage, mode GetMode) error { // Get the directory where this module is so we can load it key := strings.Join(path, ".") - key = fmt.Sprintf("root.%s-%s", key, source) + key = fmt.Sprintf("root.%s-%s", key, m.Source) dir, ok, err := getStorage(s, key, source, mode) if err != nil { return err diff --git a/config/module/tree_test.go b/config/module/tree_test.go index 74757a498..f455c2a02 100644 --- a/config/module/tree_test.go +++ b/config/module/tree_test.go @@ -1,9 +1,14 @@ package module import ( + "os" "reflect" "strings" "testing" + + "github.com/hashicorp/go-getter" + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/helper/copy" ) func TestTreeChild(t *testing.T) { @@ -102,6 +107,63 @@ func TestTreeLoad_duplicate(t *testing.T) { } } +func TestTreeLoad_copyable(t *testing.T) { + dir := tempDir(t) + storage := &getter.FolderStorage{StorageDir: dir} + cfg := testConfig(t, "basic") + tree := NewTree("", cfg) + + // This should get things + if err := tree.Load(storage, GetModeGet); err != nil { + t.Fatalf("err: %s", err) + } + + if !tree.Loaded() { + t.Fatal("should be loaded") + } + + // This should no longer error + if err := tree.Load(storage, GetModeNone); err != nil { + t.Fatalf("err: %s", err) + } + + // Now we copy the directory, this COPIES symlink values, and + // doesn't create symlinks themselves. That is important. + dir2 := tempDir(t) + os.RemoveAll(dir2) + defer os.RemoveAll(dir2) + if err := copy.CopyDir(dir, dir2); err != nil { + t.Fatalf("err: %s", err) + } + + // Now copy the configuration + cfgDir := tempDir(t) + os.RemoveAll(cfgDir) + defer os.RemoveAll(cfgDir) + if err := copy.CopyDir(cfg.Dir, cfgDir); err != nil { + t.Fatalf("err: %s", err) + } + + { + cfg, err := config.LoadDir(cfgDir) + if err != nil { + t.Fatalf("err: %s", err) + } + + tree := NewTree("", cfg) + storage := &getter.FolderStorage{StorageDir: dir2} + + // This should not error since we already got it! + if err := tree.Load(storage, GetModeNone); err != nil { + t.Fatalf("err: %s", err) + } + + if !tree.Loaded() { + t.Fatal("should be loaded") + } + } +} + func TestTreeLoad_parentRef(t *testing.T) { storage := testStorage(t) tree := NewTree("", testConfig(t, "basic-parent")) diff --git a/helper/copy/copy.go b/helper/copy/copy.go new file mode 100644 index 000000000..2944d2acd --- /dev/null +++ b/helper/copy/copy.go @@ -0,0 +1,121 @@ +package copy + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" +) + +// From: https://gist.github.com/m4ng0squ4sh/92462b38df26839a3ca324697c8cba04 + +// CopyFile copies the contents of the file named src to the file named +// by dst. The file will be created if it does not already exist. If the +// destination file exists, all it's contents will be replaced by the contents +// of the source file. The file mode will be copied from the source and +// the copied data is synced/flushed to stable storage. +func CopyFile(src, dst string) (err error) { + in, err := os.Open(src) + if err != nil { + return + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return + } + defer func() { + if e := out.Close(); e != nil { + err = e + } + }() + + _, err = io.Copy(out, in) + if err != nil { + return + } + + err = out.Sync() + if err != nil { + return + } + + si, err := os.Stat(src) + if err != nil { + return + } + err = os.Chmod(dst, si.Mode()) + if err != nil { + return + } + + return +} + +// CopyDir recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory must *not* exist. +// Symlinks are ignored and skipped. +func CopyDir(src string, dst string) (err error) { + src = filepath.Clean(src) + dst = filepath.Clean(dst) + + si, err := os.Stat(src) + if err != nil { + return err + } + if !si.IsDir() { + return fmt.Errorf("source is not a directory") + } + + _, err = os.Stat(dst) + if err != nil && !os.IsNotExist(err) { + return + } + if err == nil { + return fmt.Errorf("destination already exists") + } + + err = os.MkdirAll(dst, si.Mode()) + if err != nil { + return + } + + entries, err := ioutil.ReadDir(src) + if err != nil { + return + } + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + // If the entry is a symlink, we copy the contents + for entry.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(srcPath) + if err != nil { + return err + } + + entry, err = os.Stat(target) + if err != nil { + return err + } + } + + if entry.IsDir() { + err = CopyDir(srcPath, dstPath) + if err != nil { + return + } + } else { + err = CopyFile(srcPath, dstPath) + if err != nil { + return + } + } + } + + return +}