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