github.com/hugorut/terraform@v1.1.3/src/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/hugorut/terraform/src/e2e" 11 "github.com/hugorut/terraform/src/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(terraformBin, fixturePath) 31 defer tf.Close() 32 33 stdout, stderr, err := tf.Run("init") 34 if err != nil { 35 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 36 } 37 if !strings.Contains(stdout, "Installing hashicorp/null v") { 38 t.Errorf("null provider download message is missing from init output:\n%s", stdout) 39 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 40 } 41 42 seedDir := tf.WorkDir() 43 const providerVersion = "3.1.0" // must match the version in the fixture config 44 pluginDir := ".terraform/providers/registry.terraform.io/hashicorp/null/" + providerVersion + "/" + getproviders.CurrentPlatform.String() 45 pluginExe := pluginDir + "/terraform-provider-null_v" + providerVersion + "_x5" 46 if getproviders.CurrentPlatform.OS == "windows" { 47 pluginExe += ".exe" // ugh 48 } 49 50 t.Run("cache dir totally gone", func(t *testing.T) { 51 tf := e2e.NewBinary(terraformBin, seedDir) 52 defer tf.Close() 53 workDir := tf.WorkDir() 54 55 err := os.RemoveAll(filepath.Join(workDir, ".terraform")) 56 if err != nil { 57 t.Fatal(err) 58 } 59 60 stdout, stderr, err := tf.Run("plan") 61 if err == nil { 62 t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) 63 } 64 if want := `registry.terraform.io/hashicorp/null: there is no package for registry.terraform.io/hashicorp/null 3.1.0 cached in .terraform/providers`; !strings.Contains(stderr, want) { 65 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 66 } 67 if want := `terraform init`; !strings.Contains(stderr, want) { 68 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 69 } 70 71 // Running init as suggested resolves the problem 72 _, stderr, err = tf.Run("init") 73 if err != nil { 74 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 75 } 76 _, stderr, err = tf.Run("plan") 77 if err != nil { 78 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 79 } 80 }) 81 t.Run("cache dir totally gone, explicit backend", func(t *testing.T) { 82 tf := e2e.NewBinary(terraformBin, seedDir) 83 defer tf.Close() 84 workDir := tf.WorkDir() 85 86 err := ioutil.WriteFile(filepath.Join(workDir, "backend.tf"), []byte(localBackendConfig), 0600) 87 if err != nil { 88 t.Fatal(err) 89 } 90 91 err = os.RemoveAll(filepath.Join(workDir, ".terraform")) 92 if err != nil { 93 t.Fatal(err) 94 } 95 96 stdout, stderr, err := tf.Run("plan") 97 if err == nil { 98 t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) 99 } 100 if want := `Initial configuration of the requested backend "local"`; !strings.Contains(stderr, want) { 101 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 102 } 103 if want := `terraform init`; !strings.Contains(stderr, want) { 104 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 105 } 106 107 // Running init as suggested resolves the problem 108 _, stderr, err = tf.Run("init") 109 if err != nil { 110 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 111 } 112 _, stderr, err = tf.Run("plan") 113 if err != nil { 114 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 115 } 116 }) 117 t.Run("null plugin package modified before plan", func(t *testing.T) { 118 tf := e2e.NewBinary(terraformBin, seedDir) 119 defer tf.Close() 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 .terraform/providers) 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(terraformBin, seedDir) 140 defer tf.Close() 141 workDir := tf.WorkDir() 142 143 err := ioutil.WriteFile(filepath.Join(workDir, "provider-tampering-base.tf"), []byte(` 144 terraform { 145 required_providers { 146 null = { 147 source = "hashicorp/null" 148 version = "1.0.0" 149 } 150 } 151 } 152 `), 0600) 153 if err != nil { 154 t.Fatal(err) 155 } 156 157 stdout, stderr, err := tf.Run("plan") 158 if err == nil { 159 t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) 160 } 161 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) { 162 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 163 } 164 if want := `terraform init -upgrade`; !strings.Contains(stderr, want) { 165 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 166 } 167 }) 168 t.Run("lock file modified before plan", func(t *testing.T) { 169 tf := e2e.NewBinary(terraformBin, seedDir) 170 defer tf.Close() 171 workDir := tf.WorkDir() 172 173 // NOTE: We're just emptying out the lock file here because that's 174 // good enough for what we're trying to assert. The leaf codepath 175 // that generates this family of errors has some different variations 176 // of this error message for otehr sorts of inconsistency, but those 177 // are tested more thoroughly over in the "configs" package, which is 178 // ultimately responsible for that logic. 179 err := ioutil.WriteFile(filepath.Join(workDir, ".terraform.lock.hcl"), []byte(``), 0600) 180 if err != nil { 181 t.Fatal(err) 182 } 183 184 stdout, stderr, err := tf.Run("plan") 185 if err == nil { 186 t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) 187 } 188 if want := `provider registry.terraform.io/hashicorp/null: required by this configuration but no version is selected`; !strings.Contains(stderr, want) { 189 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 190 } 191 if want := `terraform init`; !strings.Contains(stderr, want) { 192 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 193 } 194 }) 195 t.Run("lock file modified after plan", func(t *testing.T) { 196 tf := e2e.NewBinary(terraformBin, seedDir) 197 defer tf.Close() 198 workDir := tf.WorkDir() 199 200 _, stderr, err := tf.Run("plan", "-out", "tfplan") 201 if err != nil { 202 t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr) 203 } 204 205 err = os.Remove(filepath.Join(workDir, ".terraform.lock.hcl")) 206 if err != nil { 207 t.Fatal(err) 208 } 209 210 stdout, stderr, err := tf.Run("apply", "tfplan") 211 if err == nil { 212 t.Fatalf("unexpected apply success\nstdout:\n%s", stdout) 213 } 214 if want := `provider registry.terraform.io/hashicorp/null: required by this configuration but no version is selected`; !strings.Contains(stderr, want) { 215 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 216 } 217 if want := `Create a new plan from the updated configuration.`; !strings.Contains(stderr, want) { 218 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 219 } 220 }) 221 t.Run("plugin cache dir entirely removed after plan", func(t *testing.T) { 222 tf := e2e.NewBinary(terraformBin, seedDir) 223 defer tf.Close() 224 workDir := tf.WorkDir() 225 226 _, stderr, err := tf.Run("plan", "-out", "tfplan") 227 if err != nil { 228 t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr) 229 } 230 231 err = os.RemoveAll(filepath.Join(workDir, ".terraform")) 232 if err != nil { 233 t.Fatal(err) 234 } 235 236 stdout, stderr, err := tf.Run("apply", "tfplan") 237 if err == nil { 238 t.Fatalf("unexpected apply success\nstdout:\n%s", stdout) 239 } 240 if want := `registry.terraform.io/hashicorp/null: there is no package for registry.terraform.io/hashicorp/null 3.1.0 cached in .terraform/providers`; !strings.Contains(stderr, want) { 241 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 242 } 243 }) 244 t.Run("null plugin package modified after plan", func(t *testing.T) { 245 tf := e2e.NewBinary(terraformBin, seedDir) 246 defer tf.Close() 247 workDir := tf.WorkDir() 248 249 _, stderr, err := tf.Run("plan", "-out", "tfplan") 250 if err != nil { 251 t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr) 252 } 253 254 err = ioutil.WriteFile(filepath.Join(workDir, pluginExe), []byte("tamper"), 0600) 255 if err != nil { 256 t.Fatal(err) 257 } 258 259 stdout, stderr, err := tf.Run("apply", "tfplan") 260 if err == nil { 261 t.Fatalf("unexpected apply success\nstdout:\n%s", stdout) 262 } 263 if want := `registry.terraform.io/hashicorp/null: the cached package for registry.terraform.io/hashicorp/null 3.1.0 (in .terraform/providers) does not match any of the checksums recorded in the dependency lock file`; !strings.Contains(stderr, want) { 264 t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) 265 } 266 }) 267 } 268 269 const localBackendConfig = ` 270 terraform { 271 backend "local" { 272 path = "terraform.tfstate" 273 } 274 } 275 `