github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/e2etest/automation_test.go (about) 1 package e2etest 2 3 import ( 4 "path/filepath" 5 "reflect" 6 "sort" 7 "strings" 8 "testing" 9 10 "github.com/hashicorp/terraform/internal/e2e" 11 "github.com/hashicorp/terraform/internal/plans" 12 ) 13 14 // The tests in this file run through different scenarios recommended in our 15 // "Running Terraform in Automation" guide: 16 // https://www.terraform.io/guides/running-terraform-in-automation.html 17 18 // TestPlanApplyInAutomation runs through the "main case" of init, plan, apply 19 // using the specific command line options suggested in the guide. 20 func TestPlanApplyInAutomation(t *testing.T) { 21 t.Parallel() 22 23 // This test reaches out to releases.hashicorp.com to download the 24 // template and null providers, so it can only run if network access is 25 // allowed. 26 skipIfCannotAccessNetwork(t) 27 28 fixturePath := filepath.Join("testdata", "full-workflow-null") 29 tf := e2e.NewBinary(t, terraformBin, fixturePath) 30 31 // We advertise that _any_ non-empty value works, so we'll test something 32 // unconventional here. 33 tf.AddEnv("TF_IN_AUTOMATION=yes-please") 34 35 //// INIT 36 stdout, stderr, err := tf.Run("init", "-input=false") 37 if err != nil { 38 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 39 } 40 41 // Make sure we actually downloaded the plugins, rather than picking up 42 // copies that might be already installed globally on the system. 43 if !strings.Contains(stdout, "Installing hashicorp/template v") { 44 t.Errorf("template provider download message is missing from init output:\n%s", stdout) 45 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 46 } 47 if !strings.Contains(stdout, "Installing hashicorp/null v") { 48 t.Errorf("null provider download message is missing from init output:\n%s", stdout) 49 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 50 } 51 52 //// PLAN 53 stdout, stderr, err = tf.Run("plan", "-out=tfplan", "-input=false") 54 if err != nil { 55 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 56 } 57 58 if !strings.Contains(stdout, "1 to add, 0 to change, 0 to destroy") { 59 t.Errorf("incorrect plan tally; want 1 to add:\n%s", stdout) 60 } 61 62 // Because we're running with TF_IN_AUTOMATION set, we should not see 63 // any mention of the plan file in the output. 64 if strings.Contains(stdout, "tfplan") { 65 t.Errorf("unwanted mention of \"tfplan\" file 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 // stateResources := plan.Changes.Resources 74 diffResources := plan.Changes.Resources 75 if len(diffResources) != 1 { 76 t.Errorf("incorrect number of resources in plan") 77 } 78 79 expected := map[string]plans.Action{ 80 "null_resource.test": plans.Create, 81 } 82 83 for _, r := range diffResources { 84 expectedAction, ok := expected[r.Addr.String()] 85 if !ok { 86 t.Fatalf("unexpected change for %q", r.Addr) 87 } 88 if r.Action != expectedAction { 89 t.Fatalf("unexpected action %q for %q", r.Action, r.Addr) 90 } 91 } 92 93 //// APPLY 94 stdout, stderr, err = tf.Run("apply", "-input=false", "tfplan") 95 if err != nil { 96 t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) 97 } 98 99 if !strings.Contains(stdout, "Resources: 1 added, 0 changed, 0 destroyed") { 100 t.Errorf("incorrect apply tally; want 1 added:\n%s", stdout) 101 } 102 103 state, err := tf.LocalState() 104 if err != nil { 105 t.Fatalf("failed to read state file: %s", err) 106 } 107 108 stateResources := state.RootModule().Resources 109 var gotResources []string 110 for n := range stateResources { 111 gotResources = append(gotResources, n) 112 } 113 sort.Strings(gotResources) 114 115 wantResources := []string{ 116 "data.template_file.test", 117 "null_resource.test", 118 } 119 120 if !reflect.DeepEqual(gotResources, wantResources) { 121 t.Errorf("wrong resources in state\ngot: %#v\nwant: %#v", gotResources, wantResources) 122 } 123 } 124 125 // TestAutoApplyInAutomation tests the scenario where the caller skips creating 126 // an explicit plan and instead forces automatic application of changes. 127 func TestAutoApplyInAutomation(t *testing.T) { 128 t.Parallel() 129 130 // This test reaches out to releases.hashicorp.com to download the 131 // template and null providers, so it can only run if network access is 132 // allowed. 133 skipIfCannotAccessNetwork(t) 134 135 fixturePath := filepath.Join("testdata", "full-workflow-null") 136 tf := e2e.NewBinary(t, terraformBin, fixturePath) 137 138 // We advertise that _any_ non-empty value works, so we'll test something 139 // unconventional here. 140 tf.AddEnv("TF_IN_AUTOMATION=very-much-so") 141 142 //// INIT 143 stdout, stderr, err := tf.Run("init", "-input=false") 144 if err != nil { 145 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 146 } 147 148 // Make sure we actually downloaded the plugins, rather than picking up 149 // copies that might be already installed globally on the system. 150 if !strings.Contains(stdout, "Installing hashicorp/template v") { 151 t.Errorf("template provider download message is missing from init output:\n%s", stdout) 152 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 153 } 154 if !strings.Contains(stdout, "Installing hashicorp/null v") { 155 t.Errorf("null provider download message is missing from init output:\n%s", stdout) 156 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 157 } 158 159 //// APPLY 160 stdout, stderr, err = tf.Run("apply", "-input=false", "-auto-approve") 161 if err != nil { 162 t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) 163 } 164 165 if !strings.Contains(stdout, "Resources: 1 added, 0 changed, 0 destroyed") { 166 t.Errorf("incorrect apply tally; want 1 added:\n%s", stdout) 167 } 168 169 state, err := tf.LocalState() 170 if err != nil { 171 t.Fatalf("failed to read state file: %s", err) 172 } 173 174 stateResources := state.RootModule().Resources 175 var gotResources []string 176 for n := range stateResources { 177 gotResources = append(gotResources, n) 178 } 179 sort.Strings(gotResources) 180 181 wantResources := []string{ 182 "data.template_file.test", 183 "null_resource.test", 184 } 185 186 if !reflect.DeepEqual(gotResources, wantResources) { 187 t.Errorf("wrong resources in state\ngot: %#v\nwant: %#v", gotResources, wantResources) 188 } 189 } 190 191 // TestPlanOnlyInAutomation tests the scenario of creating a "throwaway" plan, 192 // which we recommend as a way to verify a pull request. 193 func TestPlanOnlyInAutomation(t *testing.T) { 194 t.Parallel() 195 196 // This test reaches out to releases.hashicorp.com to download the 197 // template and null providers, so it can only run if network access is 198 // allowed. 199 skipIfCannotAccessNetwork(t) 200 201 fixturePath := filepath.Join("testdata", "full-workflow-null") 202 tf := e2e.NewBinary(t, terraformBin, fixturePath) 203 204 // We advertise that _any_ non-empty value works, so we'll test something 205 // unconventional here. 206 tf.AddEnv("TF_IN_AUTOMATION=verily") 207 208 //// INIT 209 stdout, stderr, err := tf.Run("init", "-input=false") 210 if err != nil { 211 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 212 } 213 214 // Make sure we actually downloaded the plugins, rather than picking up 215 // copies that might be already installed globally on the system. 216 if !strings.Contains(stdout, "Installing hashicorp/template v") { 217 t.Errorf("template provider download message is missing from init output:\n%s", stdout) 218 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 219 } 220 if !strings.Contains(stdout, "Installing hashicorp/null v") { 221 t.Errorf("null provider download message is missing from init output:\n%s", stdout) 222 t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") 223 } 224 225 //// PLAN 226 stdout, stderr, err = tf.Run("plan", "-input=false") 227 if err != nil { 228 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 229 } 230 231 if !strings.Contains(stdout, "1 to add, 0 to change, 0 to destroy") { 232 t.Errorf("incorrect plan tally; want 1 to add:\n%s", stdout) 233 } 234 235 // Because we're running with TF_IN_AUTOMATION set, we should not see 236 // any mention of the the "terraform apply" command in the output. 237 if strings.Contains(stdout, "terraform apply") { 238 t.Errorf("unwanted mention of \"terraform apply\" in plan output\n%s", stdout) 239 } 240 241 if tf.FileExists("tfplan") { 242 t.Error("plan file was created, but was not expected") 243 } 244 }