github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/e2etest/primary_test.go (about) 1 package e2etest 2 3 import ( 4 "path/filepath" 5 "reflect" 6 "sort" 7 "strings" 8 "testing" 9 10 "github.com/davecgh/go-spew/spew" 11 "github.com/hashicorp/terraform/internal/e2e" 12 "github.com/hashicorp/terraform/internal/plans" 13 "github.com/zclconf/go-cty/cty" 14 ) 15 16 // The tests in this file are for the "primary workflow", which includes 17 // variants of the following sequence, with different details: 18 // terraform init 19 // terraform plan 20 // terraform apply 21 // terraform destroy 22 23 func TestPrimarySeparatePlan(t *testing.T) { 24 t.Parallel() 25 26 // This test reaches out to releases.hashicorp.com to download the 27 // template and null providers, so it can only run if network access is 28 // allowed. 29 skipIfCannotAccessNetwork(t) 30 31 fixturePath := filepath.Join("testdata", "full-workflow-null") 32 tf := e2e.NewBinary(t, terraformBin, fixturePath) 33 34 //// INIT 35 stdout, stderr, err := tf.Run("init") 36 if err != nil { 37 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 38 } 39 40 // Make sure we actually downloaded the plugins, rather than picking up 41 // copies that might be already installed globally on the system. 42 if !strings.Contains(stdout, "Installing hashicorp/template v") { 43 t.Errorf("template provider download message is missing from init output:\n%s", stdout) 44 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 45 } 46 if !strings.Contains(stdout, "Installing hashicorp/null v") { 47 t.Errorf("null provider download message is missing from init output:\n%s", stdout) 48 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 49 } 50 51 //// PLAN 52 stdout, stderr, err = tf.Run("plan", "-out=tfplan") 53 if err != nil { 54 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 55 } 56 57 if !strings.Contains(stdout, "1 to add, 0 to change, 0 to destroy") { 58 t.Errorf("incorrect plan tally; want 1 to add:\n%s", stdout) 59 } 60 61 if !strings.Contains(stdout, "Saved the plan to: tfplan") { 62 t.Errorf("missing \"Saved the plan to...\" message in plan output\n%s", stdout) 63 } 64 if !strings.Contains(stdout, "terraform apply \"tfplan\"") { 65 t.Errorf("missing next-step instruction in plan output\n%s", stdout) 66 } 67 68 plan, err := tf.Plan("tfplan") 69 if err != nil { 70 t.Fatalf("failed to read plan file: %s", err) 71 } 72 73 diffResources := plan.Changes.Resources 74 if len(diffResources) != 1 { 75 t.Errorf("incorrect number of resources in plan") 76 } 77 78 expected := map[string]plans.Action{ 79 "null_resource.test": plans.Create, 80 } 81 82 for _, r := range diffResources { 83 expectedAction, ok := expected[r.Addr.String()] 84 if !ok { 85 t.Fatalf("unexpected change for %q", r.Addr) 86 } 87 if r.Action != expectedAction { 88 t.Fatalf("unexpected action %q for %q", r.Action, r.Addr) 89 } 90 } 91 92 //// APPLY 93 stdout, stderr, err = tf.Run("apply", "tfplan") 94 if err != nil { 95 t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) 96 } 97 98 if !strings.Contains(stdout, "Resources: 1 added, 0 changed, 0 destroyed") { 99 t.Errorf("incorrect apply tally; want 1 added:\n%s", stdout) 100 } 101 102 state, err := tf.LocalState() 103 if err != nil { 104 t.Fatalf("failed to read state file: %s", err) 105 } 106 107 stateResources := state.RootModule().Resources 108 var gotResources []string 109 for n := range stateResources { 110 gotResources = append(gotResources, n) 111 } 112 sort.Strings(gotResources) 113 114 wantResources := []string{ 115 "data.template_file.test", 116 "null_resource.test", 117 } 118 119 if !reflect.DeepEqual(gotResources, wantResources) { 120 t.Errorf("wrong resources in state\ngot: %#v\nwant: %#v", gotResources, wantResources) 121 } 122 123 //// DESTROY 124 stdout, stderr, err = tf.Run("destroy", "-auto-approve") 125 if err != nil { 126 t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr) 127 } 128 129 if !strings.Contains(stdout, "Resources: 1 destroyed") { 130 t.Errorf("incorrect destroy tally; want 1 destroyed:\n%s", stdout) 131 } 132 133 state, err = tf.LocalState() 134 if err != nil { 135 t.Fatalf("failed to read state file after destroy: %s", err) 136 } 137 138 stateResources = state.RootModule().Resources 139 if len(stateResources) != 0 { 140 t.Errorf("wrong resources in state after destroy; want none, but still have:%s", spew.Sdump(stateResources)) 141 } 142 143 } 144 145 func TestPrimaryChdirOption(t *testing.T) { 146 t.Parallel() 147 148 // This test case does not include any provider dependencies, so it's 149 // safe to run it even when network access is disallowed. 150 151 fixturePath := filepath.Join("testdata", "chdir-option") 152 tf := e2e.NewBinary(t, terraformBin, fixturePath) 153 154 //// INIT 155 _, stderr, err := tf.Run("-chdir=subdir", "init") 156 if err != nil { 157 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 158 } 159 160 //// PLAN 161 stdout, stderr, err := tf.Run("-chdir=subdir", "plan", "-out=tfplan") 162 if err != nil { 163 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 164 } 165 166 if want := "You can apply this plan to save these new output values"; !strings.Contains(stdout, want) { 167 t.Errorf("missing expected message for an outputs-only plan\ngot:\n%s\n\nwant substring: %s", stdout, want) 168 } 169 170 if !strings.Contains(stdout, "Saved the plan to: tfplan") { 171 t.Errorf("missing \"Saved the plan to...\" message in plan output\n%s", stdout) 172 } 173 if !strings.Contains(stdout, "terraform apply \"tfplan\"") { 174 t.Errorf("missing next-step instruction in plan output\n%s", stdout) 175 } 176 177 // The saved plan is in the subdirectory because -chdir switched there 178 plan, err := tf.Plan("subdir/tfplan") 179 if err != nil { 180 t.Fatalf("failed to read plan file: %s", err) 181 } 182 183 diffResources := plan.Changes.Resources 184 if len(diffResources) != 0 { 185 t.Errorf("incorrect diff in plan; want no resource changes, but have:\n%s", spew.Sdump(diffResources)) 186 } 187 188 //// APPLY 189 stdout, stderr, err = tf.Run("-chdir=subdir", "apply", "tfplan") 190 if err != nil { 191 t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) 192 } 193 194 if !strings.Contains(stdout, "Resources: 0 added, 0 changed, 0 destroyed") { 195 t.Errorf("incorrect apply tally; want 0 added:\n%s", stdout) 196 } 197 198 // The state file is in subdir because -chdir changed the current working directory. 199 state, err := tf.StateFromFile("subdir/terraform.tfstate") 200 if err != nil { 201 t.Fatalf("failed to read state file: %s", err) 202 } 203 204 gotOutput := state.RootModule().OutputValues["cwd"] 205 wantOutputValue := cty.StringVal(filepath.ToSlash(tf.Path())) // path.cwd returns the original path, because path.root is how we get the overridden path 206 if gotOutput == nil || !wantOutputValue.RawEquals(gotOutput.Value) { 207 t.Errorf("incorrect value for cwd output\ngot: %#v\nwant Value: %#v", gotOutput, wantOutputValue) 208 } 209 210 gotOutput = state.RootModule().OutputValues["root"] 211 wantOutputValue = cty.StringVal(filepath.ToSlash(tf.Path("subdir"))) // path.root is a relative path, but the text fixture uses abspath on it. 212 if gotOutput == nil || !wantOutputValue.RawEquals(gotOutput.Value) { 213 t.Errorf("incorrect value for root output\ngot: %#v\nwant Value: %#v", gotOutput, wantOutputValue) 214 } 215 216 if len(state.RootModule().Resources) != 0 { 217 t.Errorf("unexpected resources in state") 218 } 219 220 //// DESTROY 221 stdout, stderr, err = tf.Run("-chdir=subdir", "destroy", "-auto-approve") 222 if err != nil { 223 t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr) 224 } 225 226 if !strings.Contains(stdout, "Resources: 0 destroyed") { 227 t.Errorf("incorrect destroy tally; want 0 destroyed:\n%s", stdout) 228 } 229 }