github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/e2etest/providers_tamper_test.go (about) 1 package e2etest 2 3 import ( 4 "io/ioutil" 5 "os" 6 "path/filepath" 7 "strings" 8 "testing" 9 10 "github.com/hashicorp/terraform/internal/e2e" 11 "github.com/hashicorp/terraform/internal/getproviders" 12 ) 13 14 // TestProviderTampering tests various ways that the provider plugins in the 15 // local cache directory might be modified after an initial "terraform init", 16 // which other Terraform commands which use those plugins should catch and 17 // report early. 18 func TestProviderTampering(t *testing.T) { 19 // General setup: we'll do a one-off init of a test directory as our 20 // starting point, and then we'll clone that result for each test so 21 // that we can save the cost of a repeated re-init with the same 22 // provider. 23 t.Parallel() 24 25 // This test reaches out to releases.hashicorp.com to download the 26 // null provider, so it can only run if network access is allowed. 27 skipIfCannotAccessNetwork(t) 28 29 fixturePath := filepath.Join("testdata", "provider-tampering-base") 30 tf := e2e.NewBinary(t, terraformBin, fixturePath) 31 32 stdout, stderr, err := tf.Run("init") 33 if err != nil { 34 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 35 } 36 if !strings.Contains(stdout, "Installing hashicorp/null v") { 37 t.Errorf("null provider download message is missing from init output:\n%s", stdout) 38 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 39 } 40 41 seedDir := tf.WorkDir() 42 const providerVersion = "3.1.0" // must match the version in the fixture config 43 pluginDir := filepath.Join(".terraform", "providers", "registry.terraform.io", "hashicorp", "null", providerVersion, getproviders.CurrentPlatform.String()) 44 pluginExe := filepath.Join(pluginDir, "terraform-provider-null_v"+providerVersion+"_x5") 45 if getproviders.CurrentPlatform.OS == "windows" { 46 pluginExe += ".exe" // ugh 47 } 48 49 // filepath.Join here to make sure we get the right path separator 50 // for whatever OS we're running these tests on. 51 providerCacheDir := filepath.Join(".terraform", "providers") 52 53 t.Run("cache dir totally gone", func(t *testing.T) { 54 tf := e2e.NewBinary(t, terraformBin, seedDir) 55 workDir := tf.WorkDir() 56 57 err := os.RemoveAll(filepath.Join(workDir, ".terraform")) 58 if err != nil { 59 t.Fatal(err) 60 } 61 62 stdout, stderr, err := tf.Run("plan") 63 if err == nil { 64 t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) 65 } 66 if want := `registry.terraform.io/hashicorp/null: there is no package for registry.terraform.io/hashicorp/null 3.1.0 cached in ` + providerCacheDir; !strings.Contains(stderr, want) { 67 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 68 } 69 if want := `terraform init`; !strings.Contains(stderr, want) { 70 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 71 } 72 73 // Running init as suggested resolves the problem 74 _, stderr, err = tf.Run("init") 75 if err != nil { 76 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 77 } 78 _, stderr, err = tf.Run("plan") 79 if err != nil { 80 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 81 } 82 }) 83 t.Run("cache dir totally gone, explicit backend", func(t *testing.T) { 84 tf := e2e.NewBinary(t, terraformBin, seedDir) 85 workDir := tf.WorkDir() 86 87 err := ioutil.WriteFile(filepath.Join(workDir, "backend.tf"), []byte(localBackendConfig), 0600) 88 if err != nil { 89 t.Fatal(err) 90 } 91 92 err = os.RemoveAll(filepath.Join(workDir, ".terraform")) 93 if err != nil { 94 t.Fatal(err) 95 } 96 97 stdout, stderr, err := tf.Run("plan") 98 if err == nil { 99 t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) 100 } 101 if want := `Initial configuration of the requested backend "local"`; !strings.Contains(stderr, want) { 102 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 103 } 104 if want := `terraform init`; !strings.Contains(stderr, want) { 105 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 106 } 107 108 // Running init as suggested resolves the problem 109 _, stderr, err = tf.Run("init") 110 if err != nil { 111 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 112 } 113 _, stderr, err = tf.Run("plan") 114 if err != nil { 115 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 116 } 117 }) 118 t.Run("null plugin package modified before plan", func(t *testing.T) { 119 tf := e2e.NewBinary(t, terraformBin, seedDir) 120 workDir := tf.WorkDir() 121 122 err := ioutil.WriteFile(filepath.Join(workDir, pluginExe), []byte("tamper"), 0600) 123 if err != nil { 124 t.Fatal(err) 125 } 126 127 stdout, stderr, err := tf.Run("plan") 128 if err == nil { 129 t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) 130 } 131 if want := `registry.terraform.io/hashicorp/null: the cached package for registry.terraform.io/hashicorp/null 3.1.0 (in ` + providerCacheDir + `) does not match any of the checksums recorded in the dependency lock file`; !strings.Contains(stderr, want) { 132 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 133 } 134 if want := `terraform init`; !strings.Contains(stderr, want) { 135 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 136 } 137 }) 138 t.Run("version constraint changed in config before plan", func(t *testing.T) { 139 tf := e2e.NewBinary(t, terraformBin, seedDir) 140 workDir := tf.WorkDir() 141 142 err := ioutil.WriteFile(filepath.Join(workDir, "provider-tampering-base.tf"), []byte(` 143 terraform { 144 required_providers { 145 null = { 146 source = "hashicorp/null" 147 version = "1.0.0" 148 } 149 } 150 } 151 `), 0600) 152 if err != nil { 153 t.Fatal(err) 154 } 155 156 stdout, stderr, err := tf.Run("plan") 157 if err == nil { 158 t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) 159 } 160 if want := `provider registry.terraform.io/hashicorp/null: locked version selection 3.1.0 doesn't match the updated version constraints "1.0.0"`; !strings.Contains(stderr, want) { 161 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 162 } 163 if want := `terraform init -upgrade`; !strings.Contains(stderr, want) { 164 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 165 } 166 }) 167 t.Run("lock file modified before plan", func(t *testing.T) { 168 tf := e2e.NewBinary(t, terraformBin, seedDir) 169 workDir := tf.WorkDir() 170 171 // NOTE: We're just emptying out the lock file here because that's 172 // good enough for what we're trying to assert. The leaf codepath 173 // that generates this family of errors has some different variations 174 // of this error message for otehr sorts of inconsistency, but those 175 // are tested more thoroughly over in the "configs" package, which is 176 // ultimately responsible for that logic. 177 err := ioutil.WriteFile(filepath.Join(workDir, ".terraform.lock.hcl"), []byte(``), 0600) 178 if err != nil { 179 t.Fatal(err) 180 } 181 182 stdout, stderr, err := tf.Run("plan") 183 if err == nil { 184 t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) 185 } 186 if want := `provider registry.terraform.io/hashicorp/null: required by this configuration but no version is selected`; !strings.Contains(stderr, want) { 187 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 188 } 189 if want := `terraform init`; !strings.Contains(stderr, want) { 190 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 191 } 192 }) 193 t.Run("lock file modified after plan", func(t *testing.T) { 194 tf := e2e.NewBinary(t, terraformBin, seedDir) 195 workDir := tf.WorkDir() 196 197 _, stderr, err := tf.Run("plan", "-out", "tfplan") 198 if err != nil { 199 t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr) 200 } 201 202 err = os.Remove(filepath.Join(workDir, ".terraform.lock.hcl")) 203 if err != nil { 204 t.Fatal(err) 205 } 206 207 stdout, stderr, err := tf.Run("apply", "tfplan") 208 if err == nil { 209 t.Fatalf("unexpected apply success\nstdout:\n%s", stdout) 210 } 211 if want := `provider registry.terraform.io/hashicorp/null: required by this configuration but no version is selected`; !strings.Contains(stderr, want) { 212 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 213 } 214 if want := `Create a new plan from the updated configuration.`; !strings.Contains(stderr, want) { 215 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 216 } 217 }) 218 t.Run("plugin cache dir entirely removed after plan", func(t *testing.T) { 219 tf := e2e.NewBinary(t, terraformBin, seedDir) 220 workDir := tf.WorkDir() 221 222 _, stderr, err := tf.Run("plan", "-out", "tfplan") 223 if err != nil { 224 t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr) 225 } 226 227 err = os.RemoveAll(filepath.Join(workDir, ".terraform")) 228 if err != nil { 229 t.Fatal(err) 230 } 231 232 stdout, stderr, err := tf.Run("apply", "tfplan") 233 if err == nil { 234 t.Fatalf("unexpected apply success\nstdout:\n%s", stdout) 235 } 236 if want := `registry.terraform.io/hashicorp/null: there is no package for registry.terraform.io/hashicorp/null 3.1.0 cached in ` + providerCacheDir; !strings.Contains(stderr, want) { 237 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 238 } 239 }) 240 t.Run("null plugin package modified after plan", func(t *testing.T) { 241 tf := e2e.NewBinary(t, terraformBin, seedDir) 242 workDir := tf.WorkDir() 243 244 _, stderr, err := tf.Run("plan", "-out", "tfplan") 245 if err != nil { 246 t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr) 247 } 248 249 err = ioutil.WriteFile(filepath.Join(workDir, pluginExe), []byte("tamper"), 0600) 250 if err != nil { 251 t.Fatal(err) 252 } 253 254 stdout, stderr, err := tf.Run("apply", "tfplan") 255 if err == nil { 256 t.Fatalf("unexpected apply success\nstdout:\n%s", stdout) 257 } 258 if want := `registry.terraform.io/hashicorp/null: the cached package for registry.terraform.io/hashicorp/null 3.1.0 (in ` + providerCacheDir + `) does not match any of the checksums recorded in the dependency lock file`; !strings.Contains(stderr, want) { 259 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 260 } 261 }) 262 } 263 264 const localBackendConfig = ` 265 terraform { 266 backend "local" { 267 path = "terraform.tfstate" 268 } 269 } 270 `