github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/terraform/context_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/flatmap"
    10  )
    11  
    12  func TestNewContextState(t *testing.T) {
    13  	cases := map[string]struct {
    14  		Input *ContextOpts
    15  		Err   bool
    16  	}{
    17  		"empty TFVersion": {
    18  			&ContextOpts{
    19  				State: &State{},
    20  			},
    21  			false,
    22  		},
    23  
    24  		"past TFVersion": {
    25  			&ContextOpts{
    26  				State: &State{TFVersion: "0.1.2"},
    27  			},
    28  			false,
    29  		},
    30  
    31  		"equal TFVersion": {
    32  			&ContextOpts{
    33  				State: &State{TFVersion: Version},
    34  			},
    35  			false,
    36  		},
    37  
    38  		"future TFVersion": {
    39  			&ContextOpts{
    40  				State: &State{TFVersion: "99.99.99"},
    41  			},
    42  			true,
    43  		},
    44  
    45  		"future TFVersion, allowed": {
    46  			&ContextOpts{
    47  				State:              &State{TFVersion: "99.99.99"},
    48  				StateFutureAllowed: true,
    49  			},
    50  			false,
    51  		},
    52  	}
    53  
    54  	for k, tc := range cases {
    55  		ctx, err := NewContext(tc.Input)
    56  		if (err != nil) != tc.Err {
    57  			t.Fatalf("%s: err: %s", k, err)
    58  		}
    59  		if err != nil {
    60  			continue
    61  		}
    62  
    63  		// Version should always be set to our current
    64  		if ctx.state.TFVersion != Version {
    65  			t.Fatalf("%s: state not set to current version", k)
    66  		}
    67  	}
    68  }
    69  
    70  func testContext2(t *testing.T, opts *ContextOpts) *Context {
    71  	// Enable the shadow graph
    72  	opts.Shadow = true
    73  
    74  	ctx, err := NewContext(opts)
    75  	if err != nil {
    76  		t.Fatalf("err: %s", err)
    77  	}
    78  
    79  	return ctx
    80  }
    81  
    82  func testDataApplyFn(
    83  	info *InstanceInfo,
    84  	d *InstanceDiff) (*InstanceState, error) {
    85  	return testApplyFn(info, new(InstanceState), d)
    86  }
    87  
    88  func testDataDiffFn(
    89  	info *InstanceInfo,
    90  	c *ResourceConfig) (*InstanceDiff, error) {
    91  	return testDiffFn(info, new(InstanceState), c)
    92  }
    93  
    94  func testApplyFn(
    95  	info *InstanceInfo,
    96  	s *InstanceState,
    97  	d *InstanceDiff) (*InstanceState, error) {
    98  	if d.Destroy {
    99  		return nil, nil
   100  	}
   101  
   102  	id := "foo"
   103  	if idAttr, ok := d.Attributes["id"]; ok && !idAttr.NewComputed {
   104  		id = idAttr.New
   105  	}
   106  
   107  	result := &InstanceState{
   108  		ID:         id,
   109  		Attributes: make(map[string]string),
   110  	}
   111  
   112  	// Copy all the prior attributes
   113  	for k, v := range s.Attributes {
   114  		result.Attributes[k] = v
   115  	}
   116  
   117  	if d != nil {
   118  		result = result.MergeDiff(d)
   119  	}
   120  	return result, nil
   121  }
   122  
   123  func testDiffFn(
   124  	info *InstanceInfo,
   125  	s *InstanceState,
   126  	c *ResourceConfig) (*InstanceDiff, error) {
   127  	diff := new(InstanceDiff)
   128  	diff.Attributes = make(map[string]*ResourceAttrDiff)
   129  
   130  	if s != nil {
   131  		diff.DestroyTainted = s.Tainted
   132  	}
   133  
   134  	for k, v := range c.Raw {
   135  		if _, ok := v.(string); !ok {
   136  			continue
   137  		}
   138  
   139  		// Ignore __-prefixed keys since they're used for magic
   140  		if k[0] == '_' && k[1] == '_' {
   141  			continue
   142  		}
   143  
   144  		if k == "nil" {
   145  			return nil, nil
   146  		}
   147  
   148  		// This key is used for other purposes
   149  		if k == "compute_value" {
   150  			continue
   151  		}
   152  
   153  		if k == "compute" {
   154  			attrDiff := &ResourceAttrDiff{
   155  				Old:         "",
   156  				New:         "",
   157  				NewComputed: true,
   158  			}
   159  
   160  			if cv, ok := c.Config["compute_value"]; ok {
   161  				if cv.(string) == "1" {
   162  					attrDiff.NewComputed = false
   163  					attrDiff.New = fmt.Sprintf("computed_%s", v.(string))
   164  				}
   165  			}
   166  
   167  			diff.Attributes[v.(string)] = attrDiff
   168  			continue
   169  		}
   170  
   171  		// If this key is not computed, then look it up in the
   172  		// cleaned config.
   173  		found := false
   174  		for _, ck := range c.ComputedKeys {
   175  			if ck == k {
   176  				found = true
   177  				break
   178  			}
   179  		}
   180  		if !found {
   181  			v = c.Config[k]
   182  		}
   183  
   184  		for k, attrDiff := range testFlatAttrDiffs(k, v) {
   185  			if k == "require_new" {
   186  				attrDiff.RequiresNew = true
   187  			}
   188  			if _, ok := c.Raw["__"+k+"_requires_new"]; ok {
   189  				attrDiff.RequiresNew = true
   190  			}
   191  			diff.Attributes[k] = attrDiff
   192  		}
   193  	}
   194  
   195  	for _, k := range c.ComputedKeys {
   196  		diff.Attributes[k] = &ResourceAttrDiff{
   197  			Old:         "",
   198  			NewComputed: true,
   199  		}
   200  	}
   201  
   202  	// If we recreate this resource because it's tainted, we keep all attrs
   203  	if !diff.RequiresNew() {
   204  		for k, v := range diff.Attributes {
   205  			if v.NewComputed {
   206  				continue
   207  			}
   208  
   209  			old, ok := s.Attributes[k]
   210  			if !ok {
   211  				continue
   212  			}
   213  
   214  			if old == v.New {
   215  				delete(diff.Attributes, k)
   216  			}
   217  		}
   218  	}
   219  
   220  	if !diff.Empty() {
   221  		diff.Attributes["type"] = &ResourceAttrDiff{
   222  			Old: "",
   223  			New: info.Type,
   224  		}
   225  	}
   226  
   227  	return diff, nil
   228  }
   229  
   230  // generate ResourceAttrDiffs for nested data structures in tests
   231  func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff {
   232  	diffs := make(map[string]*ResourceAttrDiff)
   233  	// check for strings and empty containers first
   234  	switch t := i.(type) {
   235  	case string:
   236  		diffs[k] = &ResourceAttrDiff{New: t}
   237  		return diffs
   238  	case map[string]interface{}:
   239  		if len(t) == 0 {
   240  			diffs[k] = &ResourceAttrDiff{New: ""}
   241  			return diffs
   242  		}
   243  	case []interface{}:
   244  		if len(t) == 0 {
   245  			diffs[k] = &ResourceAttrDiff{New: ""}
   246  			return diffs
   247  		}
   248  	}
   249  
   250  	flat := flatmap.Flatten(map[string]interface{}{k: i})
   251  
   252  	for k, v := range flat {
   253  		attrDiff := &ResourceAttrDiff{
   254  			Old: "",
   255  			New: v,
   256  		}
   257  		diffs[k] = attrDiff
   258  	}
   259  
   260  	return diffs
   261  }
   262  
   263  func testProvider(prefix string) *MockResourceProvider {
   264  	p := new(MockResourceProvider)
   265  	p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) {
   266  		return s, nil
   267  	}
   268  	p.ResourcesReturn = []ResourceType{
   269  		ResourceType{
   270  			Name: fmt.Sprintf("%s_instance", prefix),
   271  		},
   272  	}
   273  
   274  	return p
   275  }
   276  
   277  func testProvisioner() *MockResourceProvisioner {
   278  	p := new(MockResourceProvisioner)
   279  	return p
   280  }
   281  
   282  func checkStateString(t *testing.T, state *State, expected string) {
   283  	actual := strings.TrimSpace(state.String())
   284  	expected = strings.TrimSpace(expected)
   285  
   286  	if actual != expected {
   287  		t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected)
   288  	}
   289  }
   290  
   291  func resourceState(resourceType, resourceID string) *ResourceState {
   292  	return &ResourceState{
   293  		Type: resourceType,
   294  		Primary: &InstanceState{
   295  			ID: resourceID,
   296  		},
   297  	}
   298  }
   299  
   300  // Test helper that gives a function 3 seconds to finish, assumes deadlock and
   301  // fails test if it does not.
   302  func testCheckDeadlock(t *testing.T, f func()) {
   303  	timeout := make(chan bool, 1)
   304  	done := make(chan bool, 1)
   305  	go func() {
   306  		time.Sleep(3 * time.Second)
   307  		timeout <- true
   308  	}()
   309  	go func(f func(), done chan bool) {
   310  		defer func() { done <- true }()
   311  		f()
   312  	}(f, done)
   313  	select {
   314  	case <-timeout:
   315  		t.Fatalf("timed out! probably deadlock")
   316  	case <-done:
   317  		// ok
   318  	}
   319  }
   320  
   321  const testContextGraph = `
   322  root: root
   323  aws_instance.bar
   324    aws_instance.bar -> provider.aws
   325  aws_instance.foo
   326    aws_instance.foo -> provider.aws
   327  provider.aws
   328  root
   329    root -> aws_instance.bar
   330    root -> aws_instance.foo
   331  `
   332  
   333  const testContextRefreshModuleStr = `
   334  aws_instance.web: (tainted)
   335    ID = bar
   336  
   337  module.child:
   338    aws_instance.web:
   339      ID = new
   340  `
   341  
   342  const testContextRefreshOutputStr = `
   343  aws_instance.web:
   344    ID = foo
   345    foo = bar
   346  
   347  Outputs:
   348  
   349  foo = bar
   350  `
   351  
   352  const testContextRefreshOutputPartialStr = `
   353  <no state>
   354  `
   355  
   356  const testContextRefreshTaintedStr = `
   357  aws_instance.web: (tainted)
   358    ID = foo
   359  `