github.com/hugorut/terraform@v1.1.3/src/command/views/hook_json_test.go (about)

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