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

     1  package terraform
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/zclconf/go-cty/cty"
     9  	ctyjson "github.com/zclconf/go-cty/cty/json"
    10  
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
    13  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    14  )
    15  
    16  var _ providers.Interface = (*MockProvider)(nil)
    17  
    18  // MockProvider implements providers.Interface but mocks out all the
    19  // calls for testing purposes.
    20  type MockProvider struct {
    21  	sync.Mutex
    22  
    23  	// Anything you want, in case you need to store extra data with the mock.
    24  	Meta interface{}
    25  
    26  	GetSchemaCalled bool
    27  	GetSchemaReturn *ProviderSchema // This is using ProviderSchema directly rather than providers.GetSchemaResponse for compatibility with old tests
    28  
    29  	PrepareProviderConfigCalled   bool
    30  	PrepareProviderConfigResponse providers.PrepareProviderConfigResponse
    31  	PrepareProviderConfigRequest  providers.PrepareProviderConfigRequest
    32  	PrepareProviderConfigFn       func(providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse
    33  
    34  	ValidateResourceTypeConfigCalled   bool
    35  	ValidateResourceTypeConfigTypeName string
    36  	ValidateResourceTypeConfigResponse providers.ValidateResourceTypeConfigResponse
    37  	ValidateResourceTypeConfigRequest  providers.ValidateResourceTypeConfigRequest
    38  	ValidateResourceTypeConfigFn       func(providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse
    39  
    40  	ValidateDataSourceConfigCalled   bool
    41  	ValidateDataSourceConfigTypeName string
    42  	ValidateDataSourceConfigResponse providers.ValidateDataSourceConfigResponse
    43  	ValidateDataSourceConfigRequest  providers.ValidateDataSourceConfigRequest
    44  	ValidateDataSourceConfigFn       func(providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse
    45  
    46  	UpgradeResourceStateCalled   bool
    47  	UpgradeResourceStateTypeName string
    48  	UpgradeResourceStateResponse providers.UpgradeResourceStateResponse
    49  	UpgradeResourceStateRequest  providers.UpgradeResourceStateRequest
    50  	UpgradeResourceStateFn       func(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse
    51  
    52  	ConfigureCalled   bool
    53  	ConfigureResponse providers.ConfigureResponse
    54  	ConfigureRequest  providers.ConfigureRequest
    55  	ConfigureNewFn    func(providers.ConfigureRequest) providers.ConfigureResponse // Named ConfigureNewFn so we can still have the legacy ConfigureFn declared below
    56  
    57  	StopCalled   bool
    58  	StopFn       func() error
    59  	StopResponse error
    60  
    61  	ReadResourceCalled   bool
    62  	ReadResourceResponse providers.ReadResourceResponse
    63  	ReadResourceRequest  providers.ReadResourceRequest
    64  	ReadResourceFn       func(providers.ReadResourceRequest) providers.ReadResourceResponse
    65  
    66  	PlanResourceChangeCalled   bool
    67  	PlanResourceChangeResponse providers.PlanResourceChangeResponse
    68  	PlanResourceChangeRequest  providers.PlanResourceChangeRequest
    69  	PlanResourceChangeFn       func(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse
    70  
    71  	ApplyResourceChangeCalled   bool
    72  	ApplyResourceChangeResponse providers.ApplyResourceChangeResponse
    73  	ApplyResourceChangeRequest  providers.ApplyResourceChangeRequest
    74  	ApplyResourceChangeFn       func(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse
    75  
    76  	ImportResourceStateCalled   bool
    77  	ImportResourceStateResponse providers.ImportResourceStateResponse
    78  	ImportResourceStateRequest  providers.ImportResourceStateRequest
    79  	ImportResourceStateFn       func(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse
    80  	// Legacy return type for existing tests, which will be shimmed into an
    81  	// ImportResourceStateResponse if set
    82  	ImportStateReturn []*InstanceState
    83  
    84  	ReadDataSourceCalled   bool
    85  	ReadDataSourceResponse providers.ReadDataSourceResponse
    86  	ReadDataSourceRequest  providers.ReadDataSourceRequest
    87  	ReadDataSourceFn       func(providers.ReadDataSourceRequest) providers.ReadDataSourceResponse
    88  
    89  	CloseCalled bool
    90  	CloseError  error
    91  
    92  	// Legacy callbacks: if these are set, we will shim incoming calls for
    93  	// new-style methods to these old-fashioned terraform.ResourceProvider
    94  	// mock callbacks, for the benefit of older tests that were written against
    95  	// the old mock API.
    96  	ValidateFn  func(c *ResourceConfig) (ws []string, es []error)
    97  	ConfigureFn func(c *ResourceConfig) error
    98  	DiffFn      func(info *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error)
    99  	ApplyFn     func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error)
   100  }
   101  
   102  func (p *MockProvider) GetSchema() providers.GetSchemaResponse {
   103  	p.Lock()
   104  	defer p.Unlock()
   105  	p.GetSchemaCalled = true
   106  	return p.getSchema()
   107  }
   108  
   109  func (p *MockProvider) getSchema() providers.GetSchemaResponse {
   110  	// This version of getSchema doesn't do any locking, so it's suitable to
   111  	// call from other methods of this mock as long as they are already
   112  	// holding the lock.
   113  
   114  	ret := providers.GetSchemaResponse{
   115  		Provider:      providers.Schema{},
   116  		DataSources:   map[string]providers.Schema{},
   117  		ResourceTypes: map[string]providers.Schema{},
   118  	}
   119  	if p.GetSchemaReturn != nil {
   120  		ret.Provider.Block = p.GetSchemaReturn.Provider
   121  		for n, s := range p.GetSchemaReturn.DataSources {
   122  			ret.DataSources[n] = providers.Schema{
   123  				Block: s,
   124  			}
   125  		}
   126  		for n, s := range p.GetSchemaReturn.ResourceTypes {
   127  			ret.ResourceTypes[n] = providers.Schema{
   128  				Version: int64(p.GetSchemaReturn.ResourceTypeSchemaVersions[n]),
   129  				Block:   s,
   130  			}
   131  		}
   132  	}
   133  
   134  	return ret
   135  }
   136  
   137  func (p *MockProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse {
   138  	p.Lock()
   139  	defer p.Unlock()
   140  
   141  	p.PrepareProviderConfigCalled = true
   142  	p.PrepareProviderConfigRequest = r
   143  	if p.PrepareProviderConfigFn != nil {
   144  		return p.PrepareProviderConfigFn(r)
   145  	}
   146  	return p.PrepareProviderConfigResponse
   147  }
   148  
   149  func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
   150  	p.Lock()
   151  	defer p.Unlock()
   152  
   153  	p.ValidateResourceTypeConfigCalled = true
   154  	p.ValidateResourceTypeConfigRequest = r
   155  
   156  	if p.ValidateFn != nil {
   157  		resp := p.getSchema()
   158  		schema := resp.Provider.Block
   159  		rc := NewResourceConfigShimmed(r.Config, schema)
   160  		warns, errs := p.ValidateFn(rc)
   161  		ret := providers.ValidateResourceTypeConfigResponse{}
   162  		for _, warn := range warns {
   163  			ret.Diagnostics = ret.Diagnostics.Append(tfdiags.SimpleWarning(warn))
   164  		}
   165  		for _, err := range errs {
   166  			ret.Diagnostics = ret.Diagnostics.Append(err)
   167  		}
   168  	}
   169  	if p.ValidateResourceTypeConfigFn != nil {
   170  		return p.ValidateResourceTypeConfigFn(r)
   171  	}
   172  
   173  	return p.ValidateResourceTypeConfigResponse
   174  }
   175  
   176  func (p *MockProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse {
   177  	p.Lock()
   178  	defer p.Unlock()
   179  
   180  	p.ValidateDataSourceConfigCalled = true
   181  	p.ValidateDataSourceConfigRequest = r
   182  
   183  	if p.ValidateDataSourceConfigFn != nil {
   184  		return p.ValidateDataSourceConfigFn(r)
   185  	}
   186  
   187  	return p.ValidateDataSourceConfigResponse
   188  }
   189  
   190  func (p *MockProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
   191  	p.Lock()
   192  	defer p.Unlock()
   193  
   194  	schemas := p.getSchema()
   195  	schema := schemas.ResourceTypes[r.TypeName]
   196  	schemaType := schema.Block.ImpliedType()
   197  
   198  	p.UpgradeResourceStateCalled = true
   199  	p.UpgradeResourceStateRequest = r
   200  
   201  	if p.UpgradeResourceStateFn != nil {
   202  		return p.UpgradeResourceStateFn(r)
   203  	}
   204  
   205  	resp := p.UpgradeResourceStateResponse
   206  
   207  	if resp.UpgradedState == cty.NilVal {
   208  		switch {
   209  		case r.RawStateFlatmap != nil:
   210  			v, err := hcl2shim.HCL2ValueFromFlatmap(r.RawStateFlatmap, schemaType)
   211  			if err != nil {
   212  				resp.Diagnostics = resp.Diagnostics.Append(err)
   213  				return resp
   214  			}
   215  			resp.UpgradedState = v
   216  		case len(r.RawStateJSON) > 0:
   217  			v, err := ctyjson.Unmarshal(r.RawStateJSON, schemaType)
   218  
   219  			if err != nil {
   220  				resp.Diagnostics = resp.Diagnostics.Append(err)
   221  				return resp
   222  			}
   223  			resp.UpgradedState = v
   224  		}
   225  	}
   226  	return resp
   227  }
   228  
   229  func (p *MockProvider) Configure(r providers.ConfigureRequest) providers.ConfigureResponse {
   230  	p.Lock()
   231  	defer p.Unlock()
   232  
   233  	p.ConfigureCalled = true
   234  	p.ConfigureRequest = r
   235  
   236  	if p.ConfigureFn != nil {
   237  		resp := p.getSchema()
   238  		schema := resp.Provider.Block
   239  		rc := NewResourceConfigShimmed(r.Config, schema)
   240  		ret := providers.ConfigureResponse{}
   241  
   242  		err := p.ConfigureFn(rc)
   243  		if err != nil {
   244  			ret.Diagnostics = ret.Diagnostics.Append(err)
   245  		}
   246  		return ret
   247  	}
   248  	if p.ConfigureNewFn != nil {
   249  		return p.ConfigureNewFn(r)
   250  	}
   251  
   252  	return p.ConfigureResponse
   253  }
   254  
   255  func (p *MockProvider) Stop() error {
   256  	// We intentionally don't lock in this one because the whole point of this
   257  	// method is to be called concurrently with another operation that can
   258  	// be cancelled.  The provider itself is responsible for handling
   259  	// any concurrency concerns in this case.
   260  
   261  	p.StopCalled = true
   262  	if p.StopFn != nil {
   263  		return p.StopFn()
   264  	}
   265  
   266  	return p.StopResponse
   267  }
   268  
   269  func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse {
   270  	p.Lock()
   271  	defer p.Unlock()
   272  
   273  	p.ReadResourceCalled = true
   274  	p.ReadResourceRequest = r
   275  
   276  	if p.ReadResourceFn != nil {
   277  		return p.ReadResourceFn(r)
   278  	}
   279  
   280  	// make sure the NewState fits the schema
   281  	newState, err := p.GetSchemaReturn.ResourceTypes[r.TypeName].CoerceValue(p.ReadResourceResponse.NewState)
   282  	if err != nil {
   283  		panic(err)
   284  	}
   285  	resp := p.ReadResourceResponse
   286  	resp.NewState = newState
   287  
   288  	return resp
   289  }
   290  
   291  func (p *MockProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
   292  	p.Lock()
   293  	defer p.Unlock()
   294  
   295  	p.PlanResourceChangeCalled = true
   296  	p.PlanResourceChangeRequest = r
   297  
   298  	if p.DiffFn != nil {
   299  		ps := p.getSchema()
   300  		if ps.ResourceTypes == nil || ps.ResourceTypes[r.TypeName].Block == nil {
   301  			return providers.PlanResourceChangeResponse{
   302  				Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Printf("mock provider has no schema for resource type %s", r.TypeName)),
   303  			}
   304  		}
   305  		schema := ps.ResourceTypes[r.TypeName].Block
   306  		info := &InstanceInfo{
   307  			Type: r.TypeName,
   308  		}
   309  		priorState := NewInstanceStateShimmedFromValue(r.PriorState, 0)
   310  		cfg := NewResourceConfigShimmed(r.Config, schema)
   311  
   312  		legacyDiff, err := p.DiffFn(info, priorState, cfg)
   313  
   314  		var res providers.PlanResourceChangeResponse
   315  		res.PlannedState = r.ProposedNewState
   316  		if err != nil {
   317  			res.Diagnostics = res.Diagnostics.Append(err)
   318  		}
   319  		if legacyDiff != nil {
   320  			newVal, err := legacyDiff.ApplyToValue(r.PriorState, schema)
   321  			if err != nil {
   322  				res.Diagnostics = res.Diagnostics.Append(err)
   323  			}
   324  
   325  			res.PlannedState = newVal
   326  
   327  			var requiresNew []string
   328  			for attr, d := range legacyDiff.Attributes {
   329  				if d.RequiresNew {
   330  					requiresNew = append(requiresNew, attr)
   331  				}
   332  			}
   333  			requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, schema.ImpliedType())
   334  			if err != nil {
   335  				res.Diagnostics = res.Diagnostics.Append(err)
   336  			}
   337  			res.RequiresReplace = requiresReplace
   338  		}
   339  		return res
   340  	}
   341  	if p.PlanResourceChangeFn != nil {
   342  		return p.PlanResourceChangeFn(r)
   343  	}
   344  
   345  	return p.PlanResourceChangeResponse
   346  }
   347  
   348  func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
   349  	p.Lock()
   350  	p.ApplyResourceChangeCalled = true
   351  	p.ApplyResourceChangeRequest = r
   352  	p.Unlock()
   353  
   354  	if p.ApplyFn != nil {
   355  		// ApplyFn is a special callback fashioned after our old provider
   356  		// interface, which expected to be given an actual diff rather than
   357  		// separate old/new values to apply. Therefore we need to approximate
   358  		// a diff here well enough that _most_ of our legacy ApplyFns in old
   359  		// tests still see the behavior they are expecting. New tests should
   360  		// not use this, and should instead use ApplyResourceChangeFn directly.
   361  		providerSchema := p.getSchema()
   362  		schema, ok := providerSchema.ResourceTypes[r.TypeName]
   363  		if !ok {
   364  			return providers.ApplyResourceChangeResponse{
   365  				Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("no mocked schema available for resource type %s", r.TypeName)),
   366  			}
   367  		}
   368  
   369  		info := &InstanceInfo{
   370  			Type: r.TypeName,
   371  		}
   372  
   373  		priorVal := r.PriorState
   374  		plannedVal := r.PlannedState
   375  		priorMap := hcl2shim.FlatmapValueFromHCL2(priorVal)
   376  		plannedMap := hcl2shim.FlatmapValueFromHCL2(plannedVal)
   377  		s := NewInstanceStateShimmedFromValue(priorVal, 0)
   378  		d := &InstanceDiff{
   379  			Attributes: make(map[string]*ResourceAttrDiff),
   380  		}
   381  		if plannedMap == nil { // destroying, then
   382  			d.Destroy = true
   383  			// Destroy diffs don't have any attribute diffs
   384  		} else {
   385  			if priorMap == nil { // creating, then
   386  				// We'll just make an empty prior map to make things easier below.
   387  				priorMap = make(map[string]string)
   388  			}
   389  
   390  			for k, new := range plannedMap {
   391  				old := priorMap[k]
   392  				newComputed := false
   393  				if new == hcl2shim.UnknownVariableValue {
   394  					new = ""
   395  					newComputed = true
   396  				}
   397  				d.Attributes[k] = &ResourceAttrDiff{
   398  					Old:         old,
   399  					New:         new,
   400  					NewComputed: newComputed,
   401  					Type:        DiffAttrInput, // not generally used in tests, so just hard-coded
   402  				}
   403  			}
   404  			// Also need any attributes that were removed in "planned"
   405  			for k, old := range priorMap {
   406  				if _, ok := plannedMap[k]; ok {
   407  					continue
   408  				}
   409  				d.Attributes[k] = &ResourceAttrDiff{
   410  					Old:        old,
   411  					NewRemoved: true,
   412  					Type:       DiffAttrInput,
   413  				}
   414  			}
   415  		}
   416  		newState, err := p.ApplyFn(info, s, d)
   417  		resp := providers.ApplyResourceChangeResponse{}
   418  		if err != nil {
   419  			resp.Diagnostics = resp.Diagnostics.Append(err)
   420  		}
   421  		if newState != nil {
   422  			var newVal cty.Value
   423  			if newState != nil {
   424  				var err error
   425  				newVal, err = newState.AttrsAsObjectValue(schema.Block.ImpliedType())
   426  				if err != nil {
   427  					resp.Diagnostics = resp.Diagnostics.Append(err)
   428  				}
   429  			} else {
   430  				// If apply returned a nil new state then that's the old way to
   431  				// indicate that the object was destroyed. Our new interface calls
   432  				// for that to be signalled as a null value.
   433  				newVal = cty.NullVal(schema.Block.ImpliedType())
   434  			}
   435  			resp.NewState = newVal
   436  		}
   437  
   438  		return resp
   439  	}
   440  	if p.ApplyResourceChangeFn != nil {
   441  		return p.ApplyResourceChangeFn(r)
   442  	}
   443  
   444  	return p.ApplyResourceChangeResponse
   445  }
   446  
   447  func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
   448  	p.Lock()
   449  	defer p.Unlock()
   450  
   451  	if p.ImportStateReturn != nil {
   452  		for _, is := range p.ImportStateReturn {
   453  			if is.Attributes == nil {
   454  				is.Attributes = make(map[string]string)
   455  			}
   456  			is.Attributes["id"] = is.ID
   457  
   458  			typeName := is.Ephemeral.Type
   459  			// Use the requested type if the resource has no type of it's own.
   460  			// We still return the empty type, which will error, but this prevents a panic.
   461  			if typeName == "" {
   462  				typeName = r.TypeName
   463  			}
   464  
   465  			schema := p.GetSchemaReturn.ResourceTypes[typeName]
   466  			if schema == nil {
   467  				panic("no schema found for " + typeName)
   468  			}
   469  
   470  			private, err := json.Marshal(is.Meta)
   471  			if err != nil {
   472  				panic(err)
   473  			}
   474  
   475  			state, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schema.ImpliedType())
   476  			if err != nil {
   477  				panic(err)
   478  			}
   479  
   480  			state, err = schema.CoerceValue(state)
   481  			if err != nil {
   482  				panic(err)
   483  			}
   484  
   485  			p.ImportResourceStateResponse.ImportedResources = append(
   486  				p.ImportResourceStateResponse.ImportedResources,
   487  				providers.ImportedResource{
   488  					TypeName: is.Ephemeral.Type,
   489  					State:    state,
   490  					Private:  private,
   491  				})
   492  		}
   493  	}
   494  
   495  	p.ImportResourceStateCalled = true
   496  	p.ImportResourceStateRequest = r
   497  	if p.ImportResourceStateFn != nil {
   498  		return p.ImportResourceStateFn(r)
   499  	}
   500  
   501  	return p.ImportResourceStateResponse
   502  }
   503  
   504  func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
   505  	p.Lock()
   506  	defer p.Unlock()
   507  
   508  	p.ReadDataSourceCalled = true
   509  	p.ReadDataSourceRequest = r
   510  
   511  	if p.ReadDataSourceFn != nil {
   512  		return p.ReadDataSourceFn(r)
   513  	}
   514  
   515  	return p.ReadDataSourceResponse
   516  }
   517  
   518  func (p *MockProvider) Close() error {
   519  	p.CloseCalled = true
   520  	return p.CloseError
   521  }