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