github.com/rhenning/terraform@v0.8.0-beta2/terraform/context_test.go (about)

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