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