github.com/kevinklinger/open_terraform@v1.3.6/noninternal/initwd/from_module_test.go (about)

     1  package initwd
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	version "github.com/hashicorp/go-version"
    12  	"github.com/kevinklinger/open_terraform/noninternal/configs"
    13  	"github.com/kevinklinger/open_terraform/noninternal/configs/configload"
    14  	"github.com/kevinklinger/open_terraform/noninternal/copy"
    15  	"github.com/kevinklinger/open_terraform/noninternal/registry"
    16  	"github.com/kevinklinger/open_terraform/noninternal/tfdiags"
    17  )
    18  
    19  func TestDirFromModule_registry(t *testing.T) {
    20  	if os.Getenv("TF_ACC") == "" {
    21  		t.Skip("this test accesses registry.terraform.io and github.com; set TF_ACC=1 to run it")
    22  	}
    23  
    24  	fixtureDir := filepath.Clean("testdata/empty")
    25  	tmpDir, done := tempChdir(t, fixtureDir)
    26  	defer done()
    27  
    28  	// the module installer runs filepath.EvalSymlinks() on the destination
    29  	// directory before copying files, and the resultant directory is what is
    30  	// returned by the install hooks. Without this, tests could fail on machines
    31  	// where the default temp dir was a symlink.
    32  	dir, err := filepath.EvalSymlinks(tmpDir)
    33  	if err != nil {
    34  		t.Error(err)
    35  	}
    36  	modsDir := filepath.Join(dir, ".terraform/modules")
    37  
    38  	hooks := &testInstallHooks{}
    39  
    40  	reg := registry.NewClient(nil, nil)
    41  	diags := DirFromModule(context.Background(), dir, modsDir, "hashicorp/module-installer-acctest/aws//examples/main", reg, hooks)
    42  	assertNoDiagnostics(t, diags)
    43  
    44  	v := version.Must(version.NewVersion("0.0.2"))
    45  
    46  	wantCalls := []testInstallHookCall{
    47  		// The module specified to populate the root directory is not mentioned
    48  		// here, because the hook mechanism is defined to talk about descendent
    49  		// modules only and so a caller to InitDirFromModule is expected to
    50  		// produce its own user-facing announcement about the root module being
    51  		// installed.
    52  
    53  		// Note that "root" in the following examples is, confusingly, the
    54  		// label on the module block in the example we've installed here:
    55  		//     module "root" {
    56  
    57  		{
    58  			Name:        "Download",
    59  			ModuleAddr:  "root",
    60  			PackageAddr: "registry.terraform.io/hashicorp/module-installer-acctest/aws",
    61  			Version:     v,
    62  		},
    63  		{
    64  			Name:       "Install",
    65  			ModuleAddr: "root",
    66  			Version:    v,
    67  			// NOTE: This local path and the other paths derived from it below
    68  			// can vary depending on how the registry is implemented. At the
    69  			// time of writing this test, registry.terraform.io returns
    70  			// git repository source addresses and so this path refers to the
    71  			// root of the git clone, but historically the registry referred
    72  			// to GitHub-provided tar archives which meant that there was an
    73  			// extra level of subdirectory here for the typical directory
    74  			// nesting in tar archives, which would've been reflected as
    75  			// an extra segment on this path. If this test fails due to an
    76  			// additional path segment in future, then a change to the upstream
    77  			// registry might be the root cause.
    78  			LocalPath: filepath.Join(dir, ".terraform/modules/root"),
    79  		},
    80  		{
    81  			Name:       "Install",
    82  			ModuleAddr: "root.child_a",
    83  			LocalPath:  filepath.Join(dir, ".terraform/modules/root/modules/child_a"),
    84  		},
    85  		{
    86  			Name:       "Install",
    87  			ModuleAddr: "root.child_a.child_b",
    88  			LocalPath:  filepath.Join(dir, ".terraform/modules/root/modules/child_b"),
    89  		},
    90  	}
    91  
    92  	if diff := cmp.Diff(wantCalls, hooks.Calls); diff != "" {
    93  		t.Fatalf("wrong installer calls\n%s", diff)
    94  	}
    95  
    96  	loader, err := configload.NewLoader(&configload.Config{
    97  		ModulesDir: modsDir,
    98  	})
    99  	if err != nil {
   100  		t.Fatal(err)
   101  	}
   102  
   103  	// Make sure the configuration is loadable now.
   104  	// (This ensures that correct information is recorded in the manifest.)
   105  	config, loadDiags := loader.LoadConfig(".")
   106  	if assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags)) {
   107  		return
   108  	}
   109  
   110  	wantTraces := map[string]string{
   111  		"":                     "in example",
   112  		"root":                 "in root module",
   113  		"root.child_a":         "in child_a module",
   114  		"root.child_a.child_b": "in child_b module",
   115  	}
   116  	gotTraces := map[string]string{}
   117  	config.DeepEach(func(c *configs.Config) {
   118  		path := strings.Join(c.Path, ".")
   119  		if c.Module.Variables["v"] == nil {
   120  			gotTraces[path] = "<missing>"
   121  			return
   122  		}
   123  		varDesc := c.Module.Variables["v"].Description
   124  		gotTraces[path] = varDesc
   125  	})
   126  	assertResultDeepEqual(t, gotTraces, wantTraces)
   127  }
   128  
   129  func TestDirFromModule_submodules(t *testing.T) {
   130  	fixtureDir := filepath.Clean("testdata/empty")
   131  	fromModuleDir, err := filepath.Abs("./testdata/local-modules")
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  
   136  	// DirFromModule will expand ("canonicalize") the pathnames, so we must do
   137  	// the same for our "wantCalls" comparison values. Otherwise this test
   138  	// will fail when building in a source tree with symlinks in $PWD.
   139  	//
   140  	// See also: https://github.com/hashicorp/terraform/issues/26014
   141  	//
   142  	fromModuleDirRealpath, err := filepath.EvalSymlinks(fromModuleDir)
   143  	if err != nil {
   144  		t.Error(err)
   145  	}
   146  
   147  	tmpDir, done := tempChdir(t, fixtureDir)
   148  	defer done()
   149  
   150  	hooks := &testInstallHooks{}
   151  	dir, err := filepath.EvalSymlinks(tmpDir)
   152  	if err != nil {
   153  		t.Error(err)
   154  	}
   155  	modInstallDir := filepath.Join(dir, ".terraform/modules")
   156  
   157  	diags := DirFromModule(context.Background(), dir, modInstallDir, fromModuleDir, nil, hooks)
   158  	assertNoDiagnostics(t, diags)
   159  	wantCalls := []testInstallHookCall{
   160  		{
   161  			Name:       "Install",
   162  			ModuleAddr: "child_a",
   163  			LocalPath:  filepath.Join(fromModuleDirRealpath, "child_a"),
   164  		},
   165  		{
   166  			Name:       "Install",
   167  			ModuleAddr: "child_a.child_b",
   168  			LocalPath:  filepath.Join(fromModuleDirRealpath, "child_a/child_b"),
   169  		},
   170  	}
   171  
   172  	if assertResultDeepEqual(t, hooks.Calls, wantCalls) {
   173  		return
   174  	}
   175  
   176  	loader, err := configload.NewLoader(&configload.Config{
   177  		ModulesDir: modInstallDir,
   178  	})
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  
   183  	// Make sure the configuration is loadable now.
   184  	// (This ensures that correct information is recorded in the manifest.)
   185  	config, loadDiags := loader.LoadConfig(".")
   186  	if assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags)) {
   187  		return
   188  	}
   189  	wantTraces := map[string]string{
   190  		"":                "in root module",
   191  		"child_a":         "in child_a module",
   192  		"child_a.child_b": "in child_b module",
   193  	}
   194  	gotTraces := map[string]string{}
   195  
   196  	config.DeepEach(func(c *configs.Config) {
   197  		path := strings.Join(c.Path, ".")
   198  		if c.Module.Variables["v"] == nil {
   199  			gotTraces[path] = "<missing>"
   200  			return
   201  		}
   202  		varDesc := c.Module.Variables["v"].Description
   203  		gotTraces[path] = varDesc
   204  	})
   205  	assertResultDeepEqual(t, gotTraces, wantTraces)
   206  }
   207  
   208  // TestDirFromModule_rel_submodules is similar to the test above, but the
   209  // from-module is relative to the install dir ("../"):
   210  // https://github.com/hashicorp/terraform/issues/23010
   211  func TestDirFromModule_rel_submodules(t *testing.T) {
   212  	// This test creates a tmpdir with the following directory structure:
   213  	// - tmpdir/local-modules (with contents of testdata/local-modules)
   214  	// - tmpdir/empty: the workDir we CD into for the test
   215  	// - tmpdir/empty/target (target, the destination for init -from-module)
   216  	tmpDir := t.TempDir()
   217  	fromModuleDir := filepath.Join(tmpDir, "local-modules")
   218  	workDir := filepath.Join(tmpDir, "empty")
   219  	if err := os.Mkdir(fromModuleDir, os.ModePerm); err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	if err := copy.CopyDir(fromModuleDir, "testdata/local-modules"); err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	if err := os.Mkdir(workDir, os.ModePerm); err != nil {
   226  		t.Fatal(err)
   227  	}
   228  
   229  	targetDir := filepath.Join(tmpDir, "target")
   230  	if err := os.Mkdir(targetDir, os.ModePerm); err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	oldDir, err := os.Getwd()
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	err = os.Chdir(targetDir)
   238  	if err != nil {
   239  		t.Fatalf("failed to switch to temp dir %s: %s", tmpDir, err)
   240  	}
   241  	t.Cleanup(func() {
   242  		os.Chdir(oldDir)
   243  	})
   244  
   245  	hooks := &testInstallHooks{}
   246  
   247  	modInstallDir := ".terraform/modules"
   248  	sourceDir := "../local-modules"
   249  	diags := DirFromModule(context.Background(), ".", modInstallDir, sourceDir, nil, hooks)
   250  	assertNoDiagnostics(t, diags)
   251  	wantCalls := []testInstallHookCall{
   252  		{
   253  			Name:       "Install",
   254  			ModuleAddr: "child_a",
   255  			LocalPath:  filepath.Join(sourceDir, "child_a"),
   256  		},
   257  		{
   258  			Name:       "Install",
   259  			ModuleAddr: "child_a.child_b",
   260  			LocalPath:  filepath.Join(sourceDir, "child_a/child_b"),
   261  		},
   262  	}
   263  
   264  	if assertResultDeepEqual(t, hooks.Calls, wantCalls) {
   265  		return
   266  	}
   267  
   268  	loader, err := configload.NewLoader(&configload.Config{
   269  		ModulesDir: modInstallDir,
   270  	})
   271  	if err != nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	// Make sure the configuration is loadable now.
   276  	// (This ensures that correct information is recorded in the manifest.)
   277  	config, loadDiags := loader.LoadConfig(".")
   278  	if assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags)) {
   279  		return
   280  	}
   281  	wantTraces := map[string]string{
   282  		"":                "in root module",
   283  		"child_a":         "in child_a module",
   284  		"child_a.child_b": "in child_b module",
   285  	}
   286  	gotTraces := map[string]string{}
   287  
   288  	config.DeepEach(func(c *configs.Config) {
   289  		path := strings.Join(c.Path, ".")
   290  		if c.Module.Variables["v"] == nil {
   291  			gotTraces[path] = "<missing>"
   292  			return
   293  		}
   294  		varDesc := c.Module.Variables["v"].Description
   295  		gotTraces[path] = varDesc
   296  	})
   297  	assertResultDeepEqual(t, gotTraces, wantTraces)
   298  }