github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/views/hook_json_test.go (about) 1 package views 2 3 import ( 4 "fmt" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/terraform/internal/addrs" 10 "github.com/hashicorp/terraform/internal/plans" 11 "github.com/hashicorp/terraform/internal/states" 12 "github.com/hashicorp/terraform/internal/terminal" 13 "github.com/hashicorp/terraform/internal/terraform" 14 "github.com/zclconf/go-cty/cty" 15 ) 16 17 // Test a sequence of hooks associated with creating a resource 18 func TestJSONHook_create(t *testing.T) { 19 streams, done := terminal.StreamsForTesting(t) 20 hook := newJSONHook(NewJSONView(NewView(streams))) 21 22 var nowMu sync.Mutex 23 now := time.Now() 24 hook.timeNow = func() time.Time { 25 nowMu.Lock() 26 defer nowMu.Unlock() 27 return now 28 } 29 30 after := make(chan time.Time, 1) 31 hook.timeAfter = func(time.Duration) <-chan time.Time { return after } 32 33 addr := addrs.Resource{ 34 Mode: addrs.ManagedResourceMode, 35 Type: "test_instance", 36 Name: "boop", 37 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 38 priorState := cty.NullVal(cty.Object(map[string]cty.Type{ 39 "id": cty.String, 40 "bar": cty.List(cty.String), 41 })) 42 plannedNewState := cty.ObjectVal(map[string]cty.Value{ 43 "id": cty.StringVal("test"), 44 "bar": cty.ListVal([]cty.Value{ 45 cty.StringVal("baz"), 46 }), 47 }) 48 49 action, err := hook.PreApply(addr, states.CurrentGen, plans.Create, priorState, plannedNewState) 50 testHookReturnValues(t, action, err) 51 52 action, err = hook.PreProvisionInstanceStep(addr, "local-exec") 53 testHookReturnValues(t, action, err) 54 55 hook.ProvisionOutput(addr, "local-exec", `Executing: ["/bin/sh" "-c" "touch /etc/motd"]`) 56 57 action, err = hook.PostProvisionInstanceStep(addr, "local-exec", nil) 58 testHookReturnValues(t, action, err) 59 60 // Travel 10s into the future, notify the progress goroutine, and sleep 61 // briefly to allow it to execute 62 nowMu.Lock() 63 now = now.Add(10 * time.Second) 64 after <- now 65 nowMu.Unlock() 66 time.Sleep(1 * time.Millisecond) 67 68 // Travel 10s into the future, notify the progress goroutine, and sleep 69 // briefly to allow it to execute 70 nowMu.Lock() 71 now = now.Add(10 * time.Second) 72 after <- now 73 nowMu.Unlock() 74 time.Sleep(1 * time.Millisecond) 75 76 // Travel 2s into the future. We have arrived! 77 nowMu.Lock() 78 now = now.Add(2 * time.Second) 79 nowMu.Unlock() 80 81 action, err = hook.PostApply(addr, states.CurrentGen, plannedNewState, nil) 82 testHookReturnValues(t, action, err) 83 84 // Shut down the progress goroutine if still active 85 hook.applyingLock.Lock() 86 for key, progress := range hook.applying { 87 close(progress.done) 88 <-progress.heartbeatDone 89 delete(hook.applying, key) 90 } 91 hook.applyingLock.Unlock() 92 93 wantResource := map[string]interface{}{ 94 "addr": string("test_instance.boop"), 95 "implied_provider": string("test"), 96 "module": string(""), 97 "resource": string("test_instance.boop"), 98 "resource_key": nil, 99 "resource_name": string("boop"), 100 "resource_type": string("test_instance"), 101 } 102 want := []map[string]interface{}{ 103 { 104 "@level": "info", 105 "@message": "test_instance.boop: Creating...", 106 "@module": "terraform.ui", 107 "type": "apply_start", 108 "hook": map[string]interface{}{ 109 "action": string("create"), 110 "resource": wantResource, 111 }, 112 }, 113 { 114 "@level": "info", 115 "@message": "test_instance.boop: Provisioning with 'local-exec'...", 116 "@module": "terraform.ui", 117 "type": "provision_start", 118 "hook": map[string]interface{}{ 119 "provisioner": "local-exec", 120 "resource": wantResource, 121 }, 122 }, 123 { 124 "@level": "info", 125 "@message": `test_instance.boop: (local-exec): Executing: ["/bin/sh" "-c" "touch /etc/motd"]`, 126 "@module": "terraform.ui", 127 "type": "provision_progress", 128 "hook": map[string]interface{}{ 129 "output": `Executing: ["/bin/sh" "-c" "touch /etc/motd"]`, 130 "provisioner": "local-exec", 131 "resource": wantResource, 132 }, 133 }, 134 { 135 "@level": "info", 136 "@message": "test_instance.boop: (local-exec) Provisioning complete", 137 "@module": "terraform.ui", 138 "type": "provision_complete", 139 "hook": map[string]interface{}{ 140 "provisioner": "local-exec", 141 "resource": wantResource, 142 }, 143 }, 144 { 145 "@level": "info", 146 "@message": "test_instance.boop: Still creating... [10s elapsed]", 147 "@module": "terraform.ui", 148 "type": "apply_progress", 149 "hook": map[string]interface{}{ 150 "action": string("create"), 151 "elapsed_seconds": float64(10), 152 "resource": wantResource, 153 }, 154 }, 155 { 156 "@level": "info", 157 "@message": "test_instance.boop: Still creating... [20s elapsed]", 158 "@module": "terraform.ui", 159 "type": "apply_progress", 160 "hook": map[string]interface{}{ 161 "action": string("create"), 162 "elapsed_seconds": float64(20), 163 "resource": wantResource, 164 }, 165 }, 166 { 167 "@level": "info", 168 "@message": "test_instance.boop: Creation complete after 22s [id=test]", 169 "@module": "terraform.ui", 170 "type": "apply_complete", 171 "hook": map[string]interface{}{ 172 "action": string("create"), 173 "elapsed_seconds": float64(22), 174 "id_key": "id", 175 "id_value": "test", 176 "resource": wantResource, 177 }, 178 }, 179 } 180 181 testJSONViewOutputEquals(t, done(t).Stdout(), want) 182 } 183 184 func TestJSONHook_errors(t *testing.T) { 185 streams, done := terminal.StreamsForTesting(t) 186 hook := newJSONHook(NewJSONView(NewView(streams))) 187 188 addr := addrs.Resource{ 189 Mode: addrs.ManagedResourceMode, 190 Type: "test_instance", 191 Name: "boop", 192 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 193 priorState := cty.NullVal(cty.Object(map[string]cty.Type{ 194 "id": cty.String, 195 "bar": cty.List(cty.String), 196 })) 197 plannedNewState := cty.ObjectVal(map[string]cty.Value{ 198 "id": cty.StringVal("test"), 199 "bar": cty.ListVal([]cty.Value{ 200 cty.StringVal("baz"), 201 }), 202 }) 203 204 action, err := hook.PreApply(addr, states.CurrentGen, plans.Delete, priorState, plannedNewState) 205 testHookReturnValues(t, action, err) 206 207 provisionError := fmt.Errorf("provisioner didn't want to") 208 action, err = hook.PostProvisionInstanceStep(addr, "local-exec", provisionError) 209 testHookReturnValues(t, action, err) 210 211 applyError := fmt.Errorf("provider was sad") 212 action, err = hook.PostApply(addr, states.CurrentGen, plannedNewState, applyError) 213 testHookReturnValues(t, action, err) 214 215 // Shut down the progress goroutine 216 hook.applyingLock.Lock() 217 for key, progress := range hook.applying { 218 close(progress.done) 219 <-progress.heartbeatDone 220 delete(hook.applying, key) 221 } 222 hook.applyingLock.Unlock() 223 224 wantResource := map[string]interface{}{ 225 "addr": string("test_instance.boop"), 226 "implied_provider": string("test"), 227 "module": string(""), 228 "resource": string("test_instance.boop"), 229 "resource_key": nil, 230 "resource_name": string("boop"), 231 "resource_type": string("test_instance"), 232 } 233 want := []map[string]interface{}{ 234 { 235 "@level": "info", 236 "@message": "test_instance.boop: Destroying...", 237 "@module": "terraform.ui", 238 "type": "apply_start", 239 "hook": map[string]interface{}{ 240 "action": string("delete"), 241 "resource": wantResource, 242 }, 243 }, 244 { 245 "@level": "info", 246 "@message": "test_instance.boop: (local-exec) Provisioning errored", 247 "@module": "terraform.ui", 248 "type": "provision_errored", 249 "hook": map[string]interface{}{ 250 "provisioner": "local-exec", 251 "resource": wantResource, 252 }, 253 }, 254 { 255 "@level": "info", 256 "@message": "test_instance.boop: Destruction errored after 0s", 257 "@module": "terraform.ui", 258 "type": "apply_errored", 259 "hook": map[string]interface{}{ 260 "action": string("delete"), 261 "elapsed_seconds": float64(0), 262 "resource": wantResource, 263 }, 264 }, 265 } 266 267 testJSONViewOutputEquals(t, done(t).Stdout(), want) 268 } 269 270 func TestJSONHook_refresh(t *testing.T) { 271 streams, done := terminal.StreamsForTesting(t) 272 hook := newJSONHook(NewJSONView(NewView(streams))) 273 274 addr := addrs.Resource{ 275 Mode: addrs.DataResourceMode, 276 Type: "test_data_source", 277 Name: "beep", 278 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 279 state := cty.ObjectVal(map[string]cty.Value{ 280 "id": cty.StringVal("honk"), 281 "bar": cty.ListVal([]cty.Value{ 282 cty.StringVal("baz"), 283 }), 284 }) 285 286 action, err := hook.PreRefresh(addr, states.CurrentGen, state) 287 testHookReturnValues(t, action, err) 288 289 action, err = hook.PostRefresh(addr, states.CurrentGen, state, state) 290 testHookReturnValues(t, action, err) 291 292 wantResource := map[string]interface{}{ 293 "addr": string("data.test_data_source.beep"), 294 "implied_provider": string("test"), 295 "module": string(""), 296 "resource": string("data.test_data_source.beep"), 297 "resource_key": nil, 298 "resource_name": string("beep"), 299 "resource_type": string("test_data_source"), 300 } 301 want := []map[string]interface{}{ 302 { 303 "@level": "info", 304 "@message": "data.test_data_source.beep: Refreshing state... [id=honk]", 305 "@module": "terraform.ui", 306 "type": "refresh_start", 307 "hook": map[string]interface{}{ 308 "resource": wantResource, 309 "id_key": "id", 310 "id_value": "honk", 311 }, 312 }, 313 { 314 "@level": "info", 315 "@message": "data.test_data_source.beep: Refresh complete [id=honk]", 316 "@module": "terraform.ui", 317 "type": "refresh_complete", 318 "hook": map[string]interface{}{ 319 "resource": wantResource, 320 "id_key": "id", 321 "id_value": "honk", 322 }, 323 }, 324 } 325 326 testJSONViewOutputEquals(t, done(t).Stdout(), want) 327 } 328 329 func testHookReturnValues(t *testing.T, action terraform.HookAction, err error) { 330 t.Helper() 331 332 if err != nil { 333 t.Fatal(err) 334 } 335 if action != terraform.HookActionContinue { 336 t.Fatalf("Expected hook to continue, given: %#v", action) 337 } 338 }