github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/context_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/davecgh/go-spew/spew"
    18  	"github.com/google/go-cmp/cmp"
    19  	"github.com/google/go-cmp/cmp/cmpopts"
    20  	"github.com/hashicorp/go-version"
    21  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs"
    22  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configload"
    23  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    24  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
    25  	"github.com/hashicorp/terraform-plugin-sdk/internal/flatmap"
    26  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
    27  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans/planfile"
    28  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
    29  	"github.com/hashicorp/terraform-plugin-sdk/internal/provisioners"
    30  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    31  	"github.com/hashicorp/terraform-plugin-sdk/internal/states/statefile"
    32  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    33  	tfversion "github.com/hashicorp/terraform-plugin-sdk/internal/version"
    34  	"github.com/zclconf/go-cty/cty"
    35  )
    36  
    37  var (
    38  	equateEmpty   = cmpopts.EquateEmpty()
    39  	typeComparer  = cmp.Comparer(cty.Type.Equals)
    40  	valueComparer = cmp.Comparer(cty.Value.RawEquals)
    41  	valueTrans    = cmp.Transformer("hcl2shim", hcl2shim.ConfigValueFromHCL2)
    42  )
    43  
    44  func TestNewContextRequiredVersion(t *testing.T) {
    45  	cases := []struct {
    46  		Name    string
    47  		Module  string
    48  		Version string
    49  		Value   string
    50  		Err     bool
    51  	}{
    52  		{
    53  			"no requirement",
    54  			"",
    55  			"0.1.0",
    56  			"",
    57  			false,
    58  		},
    59  
    60  		{
    61  			"doesn't match",
    62  			"",
    63  			"0.1.0",
    64  			"> 0.6.0",
    65  			true,
    66  		},
    67  
    68  		{
    69  			"matches",
    70  			"",
    71  			"0.7.0",
    72  			"> 0.6.0",
    73  			false,
    74  		},
    75  
    76  		{
    77  			"module matches",
    78  			"context-required-version-module",
    79  			"0.5.0",
    80  			"",
    81  			false,
    82  		},
    83  
    84  		{
    85  			"module doesn't match",
    86  			"context-required-version-module",
    87  			"0.4.0",
    88  			"",
    89  			true,
    90  		},
    91  	}
    92  
    93  	for i, tc := range cases {
    94  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
    95  			// Reset the version for the tests
    96  			old := tfversion.SemVer
    97  			tfversion.SemVer = version.Must(version.NewVersion(tc.Version))
    98  			defer func() { tfversion.SemVer = old }()
    99  
   100  			name := "context-required-version"
   101  			if tc.Module != "" {
   102  				name = tc.Module
   103  			}
   104  			mod := testModule(t, name)
   105  			if tc.Value != "" {
   106  				constraint, err := version.NewConstraint(tc.Value)
   107  				if err != nil {
   108  					t.Fatalf("can't parse %q as version constraint", tc.Value)
   109  				}
   110  				mod.Module.CoreVersionConstraints = append(mod.Module.CoreVersionConstraints, configs.VersionConstraint{
   111  					Required: constraint,
   112  				})
   113  			}
   114  			_, diags := NewContext(&ContextOpts{
   115  				Config: mod,
   116  			})
   117  			if diags.HasErrors() != tc.Err {
   118  				t.Fatalf("err: %s", diags.Err())
   119  			}
   120  		})
   121  	}
   122  }
   123  
   124  func testContext2(t *testing.T, opts *ContextOpts) *Context {
   125  	t.Helper()
   126  
   127  	ctx, diags := NewContext(opts)
   128  	if diags.HasErrors() {
   129  		t.Fatalf("failed to create test context\n\n%s\n", diags.Err())
   130  	}
   131  
   132  	return ctx
   133  }
   134  
   135  func testApplyFn(
   136  	info *InstanceInfo,
   137  	s *InstanceState,
   138  	d *InstanceDiff) (*InstanceState, error) {
   139  	if d.Destroy {
   140  		return nil, nil
   141  	}
   142  
   143  	// find the OLD id, which is probably in the ID field for now, but eventually
   144  	// ID should only be in one place.
   145  	id := s.ID
   146  	if id == "" {
   147  		id = s.Attributes["id"]
   148  	}
   149  	if idAttr, ok := d.Attributes["id"]; ok && !idAttr.NewComputed {
   150  		id = idAttr.New
   151  	}
   152  
   153  	if id == "" || id == hcl2shim.UnknownVariableValue {
   154  		id = "foo"
   155  	}
   156  
   157  	result := &InstanceState{
   158  		ID:         id,
   159  		Attributes: make(map[string]string),
   160  	}
   161  
   162  	// Copy all the prior attributes
   163  	for k, v := range s.Attributes {
   164  		result.Attributes[k] = v
   165  	}
   166  
   167  	if d != nil {
   168  		result = result.MergeDiff(d)
   169  	}
   170  
   171  	// The id attribute always matches ID for the sake of this mock
   172  	// implementation, since it's following the pre-0.12 assumptions where
   173  	// these two were treated as synonyms.
   174  	result.Attributes["id"] = result.ID
   175  	return result, nil
   176  }
   177  
   178  func testDiffFn(
   179  	info *InstanceInfo,
   180  	s *InstanceState,
   181  	c *ResourceConfig) (*InstanceDiff, error) {
   182  	diff := new(InstanceDiff)
   183  	diff.Attributes = make(map[string]*ResourceAttrDiff)
   184  
   185  	defer func() {
   186  		log.Printf("[TRACE] testDiffFn: generated diff is:\n%s", spew.Sdump(diff))
   187  	}()
   188  
   189  	if s != nil {
   190  		diff.DestroyTainted = s.Tainted
   191  	}
   192  
   193  	for k, v := range c.Raw {
   194  		// Ignore __-prefixed keys since they're used for magic
   195  		if k[0] == '_' && k[1] == '_' {
   196  			// ...though we do still need to include them in the diff, to
   197  			// simulate normal provider behaviors.
   198  			old := s.Attributes[k]
   199  			var new string
   200  			switch tv := v.(type) {
   201  			case string:
   202  				new = tv
   203  			default:
   204  				new = fmt.Sprintf("%#v", v)
   205  			}
   206  			if new == hcl2shim.UnknownVariableValue {
   207  				diff.Attributes[k] = &ResourceAttrDiff{
   208  					Old:         old,
   209  					New:         "",
   210  					NewComputed: true,
   211  				}
   212  			} else {
   213  				diff.Attributes[k] = &ResourceAttrDiff{
   214  					Old: old,
   215  					New: new,
   216  				}
   217  			}
   218  			continue
   219  		}
   220  
   221  		if k == "nil" {
   222  			return nil, nil
   223  		}
   224  
   225  		// This key is used for other purposes
   226  		if k == "compute_value" {
   227  			if old, ok := s.Attributes["compute_value"]; !ok || old != v.(string) {
   228  				diff.Attributes["compute_value"] = &ResourceAttrDiff{
   229  					Old: old,
   230  					New: v.(string),
   231  				}
   232  			}
   233  			continue
   234  		}
   235  
   236  		if k == "compute" {
   237  			// The "compute" value itself must be included in the diff if it
   238  			// has changed since prior.
   239  			if old, ok := s.Attributes["compute"]; !ok || old != v.(string) {
   240  				diff.Attributes["compute"] = &ResourceAttrDiff{
   241  					Old: old,
   242  					New: v.(string),
   243  				}
   244  			}
   245  
   246  			if v == hcl2shim.UnknownVariableValue || v == "unknown" {
   247  				// compute wasn't set in the config, so don't use these
   248  				// computed values from the schema.
   249  				delete(c.Raw, k)
   250  				delete(c.Raw, "compute_value")
   251  
   252  				// we need to remove this from the list of ComputedKeys too,
   253  				// since it would get re-added to the diff further down
   254  				newComputed := make([]string, 0, len(c.ComputedKeys))
   255  				for _, ck := range c.ComputedKeys {
   256  					if ck == "compute" || ck == "compute_value" {
   257  						continue
   258  					}
   259  					newComputed = append(newComputed, ck)
   260  				}
   261  				c.ComputedKeys = newComputed
   262  
   263  				if v == "unknown" {
   264  					diff.Attributes["unknown"] = &ResourceAttrDiff{
   265  						Old:         "",
   266  						New:         "",
   267  						NewComputed: true,
   268  					}
   269  
   270  					c.ComputedKeys = append(c.ComputedKeys, "unknown")
   271  				}
   272  
   273  				continue
   274  			}
   275  
   276  			attrDiff := &ResourceAttrDiff{
   277  				Old:         "",
   278  				New:         "",
   279  				NewComputed: true,
   280  			}
   281  
   282  			if cv, ok := c.Config["compute_value"]; ok {
   283  				if cv.(string) == "1" {
   284  					attrDiff.NewComputed = false
   285  					attrDiff.New = fmt.Sprintf("computed_%s", v.(string))
   286  				}
   287  			}
   288  
   289  			diff.Attributes[v.(string)] = attrDiff
   290  			continue
   291  		}
   292  
   293  		// If this key is not computed, then look it up in the
   294  		// cleaned config.
   295  		found := false
   296  		for _, ck := range c.ComputedKeys {
   297  			if ck == k {
   298  				found = true
   299  				break
   300  			}
   301  		}
   302  		if !found {
   303  			v = c.Config[k]
   304  		}
   305  
   306  		for k, attrDiff := range testFlatAttrDiffs(k, v) {
   307  			// we need to ignore 'id' for now, since it's always inferred to be
   308  			// computed.
   309  			if k == "id" {
   310  				continue
   311  			}
   312  
   313  			if k == "require_new" {
   314  				attrDiff.RequiresNew = true
   315  			}
   316  			if _, ok := c.Raw["__"+k+"_requires_new"]; ok {
   317  				attrDiff.RequiresNew = true
   318  			}
   319  
   320  			if attr, ok := s.Attributes[k]; ok {
   321  				attrDiff.Old = attr
   322  			}
   323  
   324  			diff.Attributes[k] = attrDiff
   325  		}
   326  	}
   327  
   328  	for _, k := range c.ComputedKeys {
   329  		if k == "id" {
   330  			continue
   331  		}
   332  		old := ""
   333  		if s != nil {
   334  			old = s.Attributes[k]
   335  		}
   336  		diff.Attributes[k] = &ResourceAttrDiff{
   337  			Old:         old,
   338  			NewComputed: true,
   339  		}
   340  	}
   341  
   342  	// If we recreate this resource because it's tainted, we keep all attrs
   343  	if !diff.RequiresNew() {
   344  		for k, v := range diff.Attributes {
   345  			if v.NewComputed {
   346  				continue
   347  			}
   348  
   349  			old, ok := s.Attributes[k]
   350  			if !ok {
   351  				continue
   352  			}
   353  
   354  			if old == v.New {
   355  				delete(diff.Attributes, k)
   356  			}
   357  		}
   358  	}
   359  
   360  	if !diff.Empty() {
   361  		diff.Attributes["type"] = &ResourceAttrDiff{
   362  			Old: "",
   363  			New: info.Type,
   364  		}
   365  		if s != nil && s.Attributes != nil {
   366  			diff.Attributes["type"].Old = s.Attributes["type"]
   367  		}
   368  	}
   369  
   370  	return diff, nil
   371  }
   372  
   373  // generate ResourceAttrDiffs for nested data structures in tests
   374  func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff {
   375  	diffs := make(map[string]*ResourceAttrDiff)
   376  	// check for strings and empty containers first
   377  	switch t := i.(type) {
   378  	case string:
   379  		diffs[k] = &ResourceAttrDiff{New: t}
   380  		return diffs
   381  	case map[string]interface{}:
   382  		if len(t) == 0 {
   383  			diffs[k] = &ResourceAttrDiff{New: ""}
   384  			return diffs
   385  		}
   386  	case []interface{}:
   387  		if len(t) == 0 {
   388  			diffs[k] = &ResourceAttrDiff{New: ""}
   389  			return diffs
   390  		}
   391  	}
   392  
   393  	flat := flatmap.Flatten(map[string]interface{}{k: i})
   394  
   395  	for k, v := range flat {
   396  		attrDiff := &ResourceAttrDiff{
   397  			Old: "",
   398  			New: v,
   399  		}
   400  		diffs[k] = attrDiff
   401  	}
   402  
   403  	// The legacy flatmap-based diff producing done by helper/schema would
   404  	// additionally insert a k+".%" key here recording the length of the map,
   405  	// which is for some reason not also done by flatmap.Flatten. To make our
   406  	// mock shims helper/schema-compatible, we'll just fake that up here.
   407  	switch t := i.(type) {
   408  	case map[string]interface{}:
   409  		attrDiff := &ResourceAttrDiff{
   410  			Old: "",
   411  			New: strconv.Itoa(len(t)),
   412  		}
   413  		diffs[k+".%"] = attrDiff
   414  	}
   415  
   416  	return diffs
   417  }
   418  
   419  func testProvider(prefix string) *MockProvider {
   420  	p := new(MockProvider)
   421  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
   422  		return providers.ReadResourceResponse{NewState: req.PriorState}
   423  	}
   424  
   425  	p.GetSchemaReturn = testProviderSchema(prefix)
   426  
   427  	return p
   428  }
   429  
   430  func testProvisioner() *MockProvisioner {
   431  	p := new(MockProvisioner)
   432  	p.GetSchemaResponse = provisioners.GetSchemaResponse{
   433  		Provisioner: &configschema.Block{
   434  			Attributes: map[string]*configschema.Attribute{
   435  				"command": {
   436  					Type:     cty.String,
   437  					Optional: true,
   438  				},
   439  				"order": {
   440  					Type:     cty.String,
   441  					Optional: true,
   442  				},
   443  				"when": {
   444  					Type:     cty.String,
   445  					Optional: true,
   446  				},
   447  			},
   448  		},
   449  	}
   450  	return p
   451  }
   452  
   453  func checkStateString(t *testing.T, state *states.State, expected string) {
   454  	t.Helper()
   455  	actual := strings.TrimSpace(state.String())
   456  	expected = strings.TrimSpace(expected)
   457  
   458  	if actual != expected {
   459  		t.Fatalf("incorrect state\ngot:\n%s\n\nwant:\n%s", actual, expected)
   460  	}
   461  }
   462  
   463  func resourceState(resourceType, resourceID string) *ResourceState {
   464  	providerResource := strings.Split(resourceType, "_")
   465  	return &ResourceState{
   466  		Type: resourceType,
   467  		Primary: &InstanceState{
   468  			ID: resourceID,
   469  			Attributes: map[string]string{
   470  				"id": resourceID,
   471  			},
   472  		},
   473  		Provider: "provider." + providerResource[0],
   474  	}
   475  }
   476  
   477  // Test helper that gives a function 3 seconds to finish, assumes deadlock and
   478  // fails test if it does not.
   479  func testCheckDeadlock(t *testing.T, f func()) {
   480  	t.Helper()
   481  	timeout := make(chan bool, 1)
   482  	done := make(chan bool, 1)
   483  	go func() {
   484  		time.Sleep(3 * time.Second)
   485  		timeout <- true
   486  	}()
   487  	go func(f func(), done chan bool) {
   488  		defer func() { done <- true }()
   489  		f()
   490  	}(f, done)
   491  	select {
   492  	case <-timeout:
   493  		t.Fatalf("timed out! probably deadlock")
   494  	case <-done:
   495  		// ok
   496  	}
   497  }
   498  
   499  func testProviderSchema(name string) *ProviderSchema {
   500  	return &ProviderSchema{
   501  		Provider: &configschema.Block{
   502  			Attributes: map[string]*configschema.Attribute{
   503  				"region": {
   504  					Type:     cty.String,
   505  					Optional: true,
   506  				},
   507  				"foo": {
   508  					Type:     cty.String,
   509  					Optional: true,
   510  				},
   511  				"value": {
   512  					Type:     cty.String,
   513  					Optional: true,
   514  				},
   515  				"root": {
   516  					Type:     cty.Number,
   517  					Optional: true,
   518  				},
   519  			},
   520  		},
   521  		ResourceTypes: map[string]*configschema.Block{
   522  			name + "_instance": {
   523  				Attributes: map[string]*configschema.Attribute{
   524  					"id": {
   525  						Type:     cty.String,
   526  						Computed: true,
   527  					},
   528  					"ami": {
   529  						Type:     cty.String,
   530  						Optional: true,
   531  					},
   532  					"dep": {
   533  						Type:     cty.String,
   534  						Optional: true,
   535  					},
   536  					"num": {
   537  						Type:     cty.Number,
   538  						Optional: true,
   539  					},
   540  					"require_new": {
   541  						Type:     cty.String,
   542  						Optional: true,
   543  					},
   544  					"var": {
   545  						Type:     cty.String,
   546  						Optional: true,
   547  					},
   548  					"foo": {
   549  						Type:     cty.String,
   550  						Optional: true,
   551  						Computed: true,
   552  					},
   553  					"bar": {
   554  						Type:     cty.String,
   555  						Optional: true,
   556  					},
   557  					"compute": {
   558  						Type:     cty.String,
   559  						Optional: true,
   560  						Computed: false,
   561  					},
   562  					"compute_value": {
   563  						Type:     cty.String,
   564  						Optional: true,
   565  						Computed: true,
   566  					},
   567  					"value": {
   568  						Type:     cty.String,
   569  						Optional: true,
   570  						Computed: true,
   571  					},
   572  					"output": {
   573  						Type:     cty.String,
   574  						Optional: true,
   575  					},
   576  					"write": {
   577  						Type:     cty.String,
   578  						Optional: true,
   579  					},
   580  					"instance": {
   581  						Type:     cty.String,
   582  						Optional: true,
   583  					},
   584  					"vpc_id": {
   585  						Type:     cty.String,
   586  						Optional: true,
   587  					},
   588  					"type": {
   589  						Type:     cty.String,
   590  						Computed: true,
   591  					},
   592  
   593  					// Generated by testDiffFn if compute = "unknown" is set in the test config
   594  					"unknown": {
   595  						Type:     cty.String,
   596  						Computed: true,
   597  					},
   598  				},
   599  			},
   600  			name + "_eip": {
   601  				Attributes: map[string]*configschema.Attribute{
   602  					"id": {
   603  						Type:     cty.String,
   604  						Computed: true,
   605  					},
   606  					"instance": {
   607  						Type:     cty.String,
   608  						Optional: true,
   609  					},
   610  				},
   611  			},
   612  			name + "_resource": {
   613  				Attributes: map[string]*configschema.Attribute{
   614  					"id": {
   615  						Type:     cty.String,
   616  						Computed: true,
   617  					},
   618  					"value": {
   619  						Type:     cty.String,
   620  						Optional: true,
   621  					},
   622  					"random": {
   623  						Type:     cty.String,
   624  						Optional: true,
   625  					},
   626  				},
   627  			},
   628  			name + "_ami_list": {
   629  				Attributes: map[string]*configschema.Attribute{
   630  					"id": {
   631  						Type:     cty.String,
   632  						Optional: true,
   633  						Computed: true,
   634  					},
   635  					"ids": {
   636  						Type:     cty.List(cty.String),
   637  						Optional: true,
   638  						Computed: true,
   639  					},
   640  				},
   641  			},
   642  			name + "_remote_state": {
   643  				Attributes: map[string]*configschema.Attribute{
   644  					"id": {
   645  						Type:     cty.String,
   646  						Optional: true,
   647  					},
   648  					"foo": {
   649  						Type:     cty.String,
   650  						Optional: true,
   651  					},
   652  					"output": {
   653  						Type:     cty.Map(cty.String),
   654  						Computed: true,
   655  					},
   656  				},
   657  			},
   658  			name + "_file": {
   659  				Attributes: map[string]*configschema.Attribute{
   660  					"id": {
   661  						Type:     cty.String,
   662  						Optional: true,
   663  					},
   664  					"template": {
   665  						Type:     cty.String,
   666  						Optional: true,
   667  					},
   668  					"rendered": {
   669  						Type:     cty.String,
   670  						Computed: true,
   671  					},
   672  					"__template_requires_new": {
   673  						Type:     cty.String,
   674  						Optional: true,
   675  					},
   676  				},
   677  			},
   678  		},
   679  		DataSources: map[string]*configschema.Block{
   680  			name + "_data_source": {
   681  				Attributes: map[string]*configschema.Attribute{
   682  					"id": {
   683  						Type:     cty.String,
   684  						Optional: true,
   685  					},
   686  					"foo": {
   687  						Type:     cty.String,
   688  						Optional: true,
   689  					},
   690  				},
   691  			},
   692  			name + "_remote_state": {
   693  				Attributes: map[string]*configschema.Attribute{
   694  					"id": {
   695  						Type:     cty.String,
   696  						Optional: true,
   697  					},
   698  					"foo": {
   699  						Type:     cty.String,
   700  						Optional: true,
   701  					},
   702  					"output": {
   703  						Type:     cty.Map(cty.String),
   704  						Optional: true,
   705  					},
   706  				},
   707  			},
   708  			name + "_file": {
   709  				Attributes: map[string]*configschema.Attribute{
   710  					"id": {
   711  						Type:     cty.String,
   712  						Optional: true,
   713  					},
   714  					"template": {
   715  						Type:     cty.String,
   716  						Optional: true,
   717  					},
   718  					"rendered": {
   719  						Type:     cty.String,
   720  						Computed: true,
   721  					},
   722  				},
   723  			},
   724  		},
   725  	}
   726  
   727  }
   728  
   729  // contextForPlanViaFile is a helper that creates a temporary plan file, then
   730  // reads it back in again and produces a ContextOpts object containing the
   731  // planned changes, prior state and config from the plan file.
   732  //
   733  // This is intended for testing the separated plan/apply workflow in a more
   734  // convenient way than spelling out all of these steps every time. Normally
   735  // only the command and backend packages need to deal with such things, but
   736  // our context tests try to exercise lots of stuff at once and so having them
   737  // round-trip things through on-disk files is often an important part of
   738  // fully representing an old bug in a regression test.
   739  func contextOptsForPlanViaFile(configSnap *configload.Snapshot, state *states.State, plan *plans.Plan) (*ContextOpts, error) {
   740  	dir, err := ioutil.TempDir("", "terraform-contextForPlanViaFile")
   741  	if err != nil {
   742  		return nil, err
   743  	}
   744  	defer os.RemoveAll(dir)
   745  
   746  	// We'll just create a dummy statefile.File here because we're not going
   747  	// to run through any of the codepaths that care about Lineage/Serial/etc
   748  	// here anyway.
   749  	stateFile := &statefile.File{
   750  		State: state,
   751  	}
   752  
   753  	// To make life a little easier for test authors, we'll populate a simple
   754  	// backend configuration if they didn't set one, since the backend is
   755  	// usually dealt with in a calling package and so tests in this package
   756  	// don't really care about it.
   757  	if plan.Backend.Config == nil {
   758  		cfg, err := plans.NewDynamicValue(cty.EmptyObjectVal, cty.EmptyObject)
   759  		if err != nil {
   760  			panic(fmt.Sprintf("NewDynamicValue failed: %s", err)) // shouldn't happen because we control the inputs
   761  		}
   762  		plan.Backend.Type = "local"
   763  		plan.Backend.Config = cfg
   764  		plan.Backend.Workspace = "default"
   765  	}
   766  
   767  	filename := filepath.Join(dir, "tfplan")
   768  	err = planfile.Create(filename, configSnap, stateFile, plan)
   769  	if err != nil {
   770  		return nil, err
   771  	}
   772  
   773  	pr, err := planfile.Open(filename)
   774  	if err != nil {
   775  		return nil, err
   776  	}
   777  
   778  	config, diags := pr.ReadConfig()
   779  	if diags.HasErrors() {
   780  		return nil, diags.Err()
   781  	}
   782  
   783  	stateFile, err = pr.ReadStateFile()
   784  	if err != nil {
   785  		return nil, err
   786  	}
   787  
   788  	plan, err = pr.ReadPlan()
   789  	if err != nil {
   790  		return nil, err
   791  	}
   792  
   793  	vars := make(InputValues)
   794  	for name, vv := range plan.VariableValues {
   795  		val, err := vv.Decode(cty.DynamicPseudoType)
   796  		if err != nil {
   797  			return nil, fmt.Errorf("can't decode value for variable %q: %s", name, err)
   798  		}
   799  		vars[name] = &InputValue{
   800  			Value:      val,
   801  			SourceType: ValueFromPlan,
   802  		}
   803  	}
   804  
   805  	return &ContextOpts{
   806  		Config:          config,
   807  		State:           stateFile.State,
   808  		Changes:         plan.Changes,
   809  		Variables:       vars,
   810  		Targets:         plan.TargetAddrs,
   811  		ProviderSHA256s: plan.ProviderSHA256s,
   812  	}, nil
   813  }
   814  
   815  // legacyPlanComparisonString produces a string representation of the changes
   816  // from a plan and a given state togther, as was formerly produced by the
   817  // String method of terraform.Plan.
   818  //
   819  // This is here only for compatibility with existing tests that predate our
   820  // new plan and state types, and should not be used in new tests. Instead, use
   821  // a library like "cmp" to do a deep equality check and diff on the two
   822  // data structures.
   823  func legacyPlanComparisonString(state *states.State, changes *plans.Changes) string {
   824  	return fmt.Sprintf(
   825  		"DIFF:\n\n%s\n\nSTATE:\n\n%s",
   826  		legacyDiffComparisonString(changes),
   827  		state.String(),
   828  	)
   829  }
   830  
   831  // legacyDiffComparisonString produces a string representation of the changes
   832  // from a planned changes object, as was formerly produced by the String method
   833  // of terraform.Diff.
   834  //
   835  // This is here only for compatibility with existing tests that predate our
   836  // new plan types, and should not be used in new tests. Instead, use a library
   837  // like "cmp" to do a deep equality check and diff on the two data structures.
   838  func legacyDiffComparisonString(changes *plans.Changes) string {
   839  	// The old string representation of a plan was grouped by module, but
   840  	// our new plan structure is not grouped in that way and so we'll need
   841  	// to preprocess it in order to produce that grouping.
   842  	type ResourceChanges struct {
   843  		Current *plans.ResourceInstanceChangeSrc
   844  		Deposed map[states.DeposedKey]*plans.ResourceInstanceChangeSrc
   845  	}
   846  	byModule := map[string]map[string]*ResourceChanges{}
   847  	resourceKeys := map[string][]string{}
   848  	var moduleKeys []string
   849  	for _, rc := range changes.Resources {
   850  		if rc.Action == plans.NoOp {
   851  			// We won't mention no-op changes here at all, since the old plan
   852  			// model we are emulating here didn't have such a concept.
   853  			continue
   854  		}
   855  		moduleKey := rc.Addr.Module.String()
   856  		if _, exists := byModule[moduleKey]; !exists {
   857  			moduleKeys = append(moduleKeys, moduleKey)
   858  			byModule[moduleKey] = make(map[string]*ResourceChanges)
   859  		}
   860  		resourceKey := rc.Addr.Resource.String()
   861  		if _, exists := byModule[moduleKey][resourceKey]; !exists {
   862  			resourceKeys[moduleKey] = append(resourceKeys[moduleKey], resourceKey)
   863  			byModule[moduleKey][resourceKey] = &ResourceChanges{
   864  				Deposed: make(map[states.DeposedKey]*plans.ResourceInstanceChangeSrc),
   865  			}
   866  		}
   867  
   868  		if rc.DeposedKey == states.NotDeposed {
   869  			byModule[moduleKey][resourceKey].Current = rc
   870  		} else {
   871  			byModule[moduleKey][resourceKey].Deposed[rc.DeposedKey] = rc
   872  		}
   873  	}
   874  	sort.Strings(moduleKeys)
   875  	for _, ks := range resourceKeys {
   876  		sort.Strings(ks)
   877  	}
   878  
   879  	var buf bytes.Buffer
   880  
   881  	for _, moduleKey := range moduleKeys {
   882  		rcs := byModule[moduleKey]
   883  		var mBuf bytes.Buffer
   884  
   885  		for _, resourceKey := range resourceKeys[moduleKey] {
   886  			rc := rcs[resourceKey]
   887  
   888  			crud := "UPDATE"
   889  			if rc.Current != nil {
   890  				switch rc.Current.Action {
   891  				case plans.DeleteThenCreate:
   892  					crud = "DESTROY/CREATE"
   893  				case plans.CreateThenDelete:
   894  					crud = "CREATE/DESTROY"
   895  				case plans.Delete:
   896  					crud = "DESTROY"
   897  				case plans.Create:
   898  					crud = "CREATE"
   899  				}
   900  			} else {
   901  				// We must be working on a deposed object then, in which
   902  				// case destroying is the only possible action.
   903  				crud = "DESTROY"
   904  			}
   905  
   906  			extra := ""
   907  			if rc.Current == nil && len(rc.Deposed) > 0 {
   908  				extra = " (deposed only)"
   909  			}
   910  
   911  			fmt.Fprintf(
   912  				&mBuf, "%s: %s%s\n",
   913  				crud, resourceKey, extra,
   914  			)
   915  
   916  			attrNames := map[string]bool{}
   917  			var oldAttrs map[string]string
   918  			var newAttrs map[string]string
   919  			if rc.Current != nil {
   920  				if before := rc.Current.Before; before != nil {
   921  					ty, err := before.ImpliedType()
   922  					if err == nil {
   923  						val, err := before.Decode(ty)
   924  						if err == nil {
   925  							oldAttrs = hcl2shim.FlatmapValueFromHCL2(val)
   926  							for k := range oldAttrs {
   927  								attrNames[k] = true
   928  							}
   929  						}
   930  					}
   931  				}
   932  				if after := rc.Current.After; after != nil {
   933  					ty, err := after.ImpliedType()
   934  					if err == nil {
   935  						val, err := after.Decode(ty)
   936  						if err == nil {
   937  							newAttrs = hcl2shim.FlatmapValueFromHCL2(val)
   938  							for k := range newAttrs {
   939  								attrNames[k] = true
   940  							}
   941  						}
   942  					}
   943  				}
   944  			}
   945  			if oldAttrs == nil {
   946  				oldAttrs = make(map[string]string)
   947  			}
   948  			if newAttrs == nil {
   949  				newAttrs = make(map[string]string)
   950  			}
   951  
   952  			attrNamesOrder := make([]string, 0, len(attrNames))
   953  			keyLen := 0
   954  			for n := range attrNames {
   955  				attrNamesOrder = append(attrNamesOrder, n)
   956  				if len(n) > keyLen {
   957  					keyLen = len(n)
   958  				}
   959  			}
   960  			sort.Strings(attrNamesOrder)
   961  
   962  			for _, attrK := range attrNamesOrder {
   963  				v := newAttrs[attrK]
   964  				u := oldAttrs[attrK]
   965  
   966  				if v == hcl2shim.UnknownVariableValue {
   967  					v = "<computed>"
   968  				}
   969  				// NOTE: we don't support <sensitive> here because we would
   970  				// need schema to do that. Excluding sensitive values
   971  				// is now done at the UI layer, and so should not be tested
   972  				// at the core layer.
   973  
   974  				updateMsg := ""
   975  				// TODO: Mark " (forces new resource)" in updateMsg when appropriate.
   976  
   977  				fmt.Fprintf(
   978  					&mBuf, "  %s:%s %#v => %#v%s\n",
   979  					attrK,
   980  					strings.Repeat(" ", keyLen-len(attrK)),
   981  					u, v,
   982  					updateMsg,
   983  				)
   984  			}
   985  		}
   986  
   987  		if moduleKey == "" { // root module
   988  			buf.Write(mBuf.Bytes())
   989  			buf.WriteByte('\n')
   990  			continue
   991  		}
   992  
   993  		fmt.Fprintf(&buf, "%s:\n", moduleKey)
   994  		s := bufio.NewScanner(&mBuf)
   995  		for s.Scan() {
   996  			buf.WriteString(fmt.Sprintf("  %s\n", s.Text()))
   997  		}
   998  	}
   999  
  1000  	return buf.String()
  1001  }
  1002  
  1003  // assertNoDiagnostics fails the test in progress (using t.Fatal) if the given
  1004  // diagnostics is non-empty.
  1005  func assertNoDiagnostics(t *testing.T, diags tfdiags.Diagnostics) {
  1006  	t.Helper()
  1007  	if len(diags) == 0 {
  1008  		return
  1009  	}
  1010  	logDiagnostics(t, diags)
  1011  	t.FailNow()
  1012  }
  1013  
  1014  // assertNoDiagnostics fails the test in progress (using t.Fatal) if the given
  1015  // diagnostics has any errors.
  1016  func assertNoErrors(t *testing.T, diags tfdiags.Diagnostics) {
  1017  	t.Helper()
  1018  	if !diags.HasErrors() {
  1019  		return
  1020  	}
  1021  	logDiagnostics(t, diags)
  1022  	t.FailNow()
  1023  }
  1024  
  1025  // logDiagnostics is a test helper that logs the given diagnostics to to the
  1026  // given testing.T using t.Log, in a way that is hopefully useful in debugging
  1027  // a test. It does not generate any errors or fail the test. See
  1028  // assertNoDiagnostics and assertNoErrors for more specific helpers that can
  1029  // also fail the test.
  1030  func logDiagnostics(t *testing.T, diags tfdiags.Diagnostics) {
  1031  	t.Helper()
  1032  	for _, diag := range diags {
  1033  		desc := diag.Description()
  1034  		rng := diag.Source()
  1035  
  1036  		var severity string
  1037  		switch diag.Severity() {
  1038  		case tfdiags.Error:
  1039  			severity = "ERROR"
  1040  		case tfdiags.Warning:
  1041  			severity = "WARN"
  1042  		default:
  1043  			severity = "???" // should never happen
  1044  		}
  1045  
  1046  		if subj := rng.Subject; subj != nil {
  1047  			if desc.Detail == "" {
  1048  				t.Logf("[%s@%s] %s", severity, subj.StartString(), desc.Summary)
  1049  			} else {
  1050  				t.Logf("[%s@%s] %s: %s", severity, subj.StartString(), desc.Summary, desc.Detail)
  1051  			}
  1052  		} else {
  1053  			if desc.Detail == "" {
  1054  				t.Logf("[%s] %s", severity, desc.Summary)
  1055  			} else {
  1056  				t.Logf("[%s] %s: %s", severity, desc.Summary, desc.Detail)
  1057  			}
  1058  		}
  1059  	}
  1060  }
  1061  
  1062  const testContextRefreshModuleStr = `
  1063  aws_instance.web: (tainted)
  1064    ID = bar
  1065    provider = provider.aws
  1066  
  1067  module.child:
  1068    aws_instance.web:
  1069      ID = new
  1070      provider = provider.aws
  1071  `
  1072  
  1073  const testContextRefreshOutputStr = `
  1074  aws_instance.web:
  1075    ID = foo
  1076    provider = provider.aws
  1077    foo = bar
  1078  
  1079  Outputs:
  1080  
  1081  foo = bar
  1082  `
  1083  
  1084  const testContextRefreshOutputPartialStr = `
  1085  <no state>
  1086  `
  1087  
  1088  const testContextRefreshTaintedStr = `
  1089  aws_instance.web: (tainted)
  1090    ID = foo
  1091    provider = provider.aws
  1092  `