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