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 }