github.com/brandonstevens/terraform@v0.9.6-0.20170512224929-5367f2607e16/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  		// Ignore __-prefixed keys since they're used for magic
   214  		if k[0] == '_' && k[1] == '_' {
   215  			continue
   216  		}
   217  
   218  		if k == "nil" {
   219  			return nil, nil
   220  		}
   221  
   222  		// This key is used for other purposes
   223  		if k == "compute_value" {
   224  			continue
   225  		}
   226  
   227  		if k == "compute" {
   228  			attrDiff := &ResourceAttrDiff{
   229  				Old:         "",
   230  				New:         "",
   231  				NewComputed: true,
   232  			}
   233  
   234  			if cv, ok := c.Config["compute_value"]; ok {
   235  				if cv.(string) == "1" {
   236  					attrDiff.NewComputed = false
   237  					attrDiff.New = fmt.Sprintf("computed_%s", v.(string))
   238  				}
   239  			}
   240  
   241  			diff.Attributes[v.(string)] = attrDiff
   242  			continue
   243  		}
   244  
   245  		// If this key is not computed, then look it up in the
   246  		// cleaned config.
   247  		found := false
   248  		for _, ck := range c.ComputedKeys {
   249  			if ck == k {
   250  				found = true
   251  				break
   252  			}
   253  		}
   254  		if !found {
   255  			v = c.Config[k]
   256  		}
   257  
   258  		for k, attrDiff := range testFlatAttrDiffs(k, v) {
   259  			if k == "require_new" {
   260  				attrDiff.RequiresNew = true
   261  			}
   262  			if _, ok := c.Raw["__"+k+"_requires_new"]; ok {
   263  				attrDiff.RequiresNew = true
   264  			}
   265  
   266  			if attr, ok := s.Attributes[k]; ok {
   267  				attrDiff.Old = attr
   268  			}
   269  
   270  			diff.Attributes[k] = attrDiff
   271  		}
   272  	}
   273  
   274  	for _, k := range c.ComputedKeys {
   275  		diff.Attributes[k] = &ResourceAttrDiff{
   276  			Old:         "",
   277  			NewComputed: true,
   278  		}
   279  	}
   280  
   281  	// If we recreate this resource because it's tainted, we keep all attrs
   282  	if !diff.RequiresNew() {
   283  		for k, v := range diff.Attributes {
   284  			if v.NewComputed {
   285  				continue
   286  			}
   287  
   288  			old, ok := s.Attributes[k]
   289  			if !ok {
   290  				continue
   291  			}
   292  
   293  			if old == v.New {
   294  				delete(diff.Attributes, k)
   295  			}
   296  		}
   297  	}
   298  
   299  	if !diff.Empty() {
   300  		diff.Attributes["type"] = &ResourceAttrDiff{
   301  			Old: "",
   302  			New: info.Type,
   303  		}
   304  	}
   305  
   306  	return diff, nil
   307  }
   308  
   309  // generate ResourceAttrDiffs for nested data structures in tests
   310  func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff {
   311  	diffs := make(map[string]*ResourceAttrDiff)
   312  	// check for strings and empty containers first
   313  	switch t := i.(type) {
   314  	case string:
   315  		diffs[k] = &ResourceAttrDiff{New: t}
   316  		return diffs
   317  	case map[string]interface{}:
   318  		if len(t) == 0 {
   319  			diffs[k] = &ResourceAttrDiff{New: ""}
   320  			return diffs
   321  		}
   322  	case []interface{}:
   323  		if len(t) == 0 {
   324  			diffs[k] = &ResourceAttrDiff{New: ""}
   325  			return diffs
   326  		}
   327  	}
   328  
   329  	flat := flatmap.Flatten(map[string]interface{}{k: i})
   330  
   331  	for k, v := range flat {
   332  		attrDiff := &ResourceAttrDiff{
   333  			Old: "",
   334  			New: v,
   335  		}
   336  		diffs[k] = attrDiff
   337  	}
   338  
   339  	return diffs
   340  }
   341  
   342  func testProvider(prefix string) *MockResourceProvider {
   343  	p := new(MockResourceProvider)
   344  	p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) {
   345  		return s, nil
   346  	}
   347  	p.ResourcesReturn = []ResourceType{
   348  		ResourceType{
   349  			Name: fmt.Sprintf("%s_instance", prefix),
   350  		},
   351  	}
   352  
   353  	return p
   354  }
   355  
   356  func testProvisioner() *MockResourceProvisioner {
   357  	p := new(MockResourceProvisioner)
   358  	return p
   359  }
   360  
   361  func checkStateString(t *testing.T, state *State, expected string) {
   362  	actual := strings.TrimSpace(state.String())
   363  	expected = strings.TrimSpace(expected)
   364  
   365  	if actual != expected {
   366  		t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected)
   367  	}
   368  }
   369  
   370  func resourceState(resourceType, resourceID string) *ResourceState {
   371  	return &ResourceState{
   372  		Type: resourceType,
   373  		Primary: &InstanceState{
   374  			ID: resourceID,
   375  		},
   376  	}
   377  }
   378  
   379  // Test helper that gives a function 3 seconds to finish, assumes deadlock and
   380  // fails test if it does not.
   381  func testCheckDeadlock(t *testing.T, f func()) {
   382  	timeout := make(chan bool, 1)
   383  	done := make(chan bool, 1)
   384  	go func() {
   385  		time.Sleep(3 * time.Second)
   386  		timeout <- true
   387  	}()
   388  	go func(f func(), done chan bool) {
   389  		defer func() { done <- true }()
   390  		f()
   391  	}(f, done)
   392  	select {
   393  	case <-timeout:
   394  		t.Fatalf("timed out! probably deadlock")
   395  	case <-done:
   396  		// ok
   397  	}
   398  }
   399  
   400  const testContextGraph = `
   401  root: root
   402  aws_instance.bar
   403    aws_instance.bar -> provider.aws
   404  aws_instance.foo
   405    aws_instance.foo -> provider.aws
   406  provider.aws
   407  root
   408    root -> aws_instance.bar
   409    root -> aws_instance.foo
   410  `
   411  
   412  const testContextRefreshModuleStr = `
   413  aws_instance.web: (tainted)
   414    ID = bar
   415  
   416  module.child:
   417    aws_instance.web:
   418      ID = new
   419  `
   420  
   421  const testContextRefreshOutputStr = `
   422  aws_instance.web:
   423    ID = foo
   424    foo = bar
   425  
   426  Outputs:
   427  
   428  foo = bar
   429  `
   430  
   431  const testContextRefreshOutputPartialStr = `
   432  <no state>
   433  `
   434  
   435  const testContextRefreshTaintedStr = `
   436  aws_instance.web: (tainted)
   437    ID = foo
   438  `