github.com/opentofu/opentofu@v1.7.1/internal/tofu/provider_mock.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"fmt"
    10  	"sync"
    11  
    12  	"github.com/zclconf/go-cty/cty"
    13  	ctyjson "github.com/zclconf/go-cty/cty/json"
    14  	"github.com/zclconf/go-cty/cty/msgpack"
    15  
    16  	"github.com/opentofu/opentofu/internal/configs/hcl2shim"
    17  	"github.com/opentofu/opentofu/internal/providers"
    18  )
    19  
    20  var _ providers.Interface = (*MockProvider)(nil)
    21  
    22  // MockProvider implements providers.Interface but mocks out all the
    23  // calls for testing purposes.
    24  type MockProvider struct {
    25  	sync.Mutex
    26  
    27  	// Anything you want, in case you need to store extra data with the mock.
    28  	Meta interface{}
    29  
    30  	GetProviderSchemaCalled   bool
    31  	GetProviderSchemaResponse *providers.GetProviderSchemaResponse
    32  
    33  	ValidateProviderConfigCalled   bool
    34  	ValidateProviderConfigResponse *providers.ValidateProviderConfigResponse
    35  	ValidateProviderConfigRequest  providers.ValidateProviderConfigRequest
    36  	ValidateProviderConfigFn       func(providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse
    37  
    38  	ValidateResourceConfigCalled   bool
    39  	ValidateResourceConfigTypeName string
    40  	ValidateResourceConfigResponse *providers.ValidateResourceConfigResponse
    41  	ValidateResourceConfigRequest  providers.ValidateResourceConfigRequest
    42  	ValidateResourceConfigFn       func(providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse
    43  
    44  	ValidateDataResourceConfigCalled   bool
    45  	ValidateDataResourceConfigTypeName string
    46  	ValidateDataResourceConfigResponse *providers.ValidateDataResourceConfigResponse
    47  	ValidateDataResourceConfigRequest  providers.ValidateDataResourceConfigRequest
    48  	ValidateDataResourceConfigFn       func(providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse
    49  
    50  	UpgradeResourceStateCalled   bool
    51  	UpgradeResourceStateTypeName string
    52  	UpgradeResourceStateResponse *providers.UpgradeResourceStateResponse
    53  	UpgradeResourceStateRequest  providers.UpgradeResourceStateRequest
    54  	UpgradeResourceStateFn       func(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse
    55  
    56  	ConfigureProviderCalled   bool
    57  	ConfigureProviderResponse *providers.ConfigureProviderResponse
    58  	ConfigureProviderRequest  providers.ConfigureProviderRequest
    59  	ConfigureProviderFn       func(providers.ConfigureProviderRequest) providers.ConfigureProviderResponse
    60  
    61  	StopCalled   bool
    62  	StopFn       func() error
    63  	StopResponse error
    64  
    65  	ReadResourceCalled   bool
    66  	ReadResourceResponse *providers.ReadResourceResponse
    67  	ReadResourceRequest  providers.ReadResourceRequest
    68  	ReadResourceFn       func(providers.ReadResourceRequest) providers.ReadResourceResponse
    69  
    70  	PlanResourceChangeCalled   bool
    71  	PlanResourceChangeResponse *providers.PlanResourceChangeResponse
    72  	PlanResourceChangeRequest  providers.PlanResourceChangeRequest
    73  	PlanResourceChangeFn       func(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse
    74  
    75  	ApplyResourceChangeCalled   bool
    76  	ApplyResourceChangeResponse *providers.ApplyResourceChangeResponse
    77  	ApplyResourceChangeRequest  providers.ApplyResourceChangeRequest
    78  	ApplyResourceChangeFn       func(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse
    79  
    80  	ImportResourceStateCalled   bool
    81  	ImportResourceStateResponse *providers.ImportResourceStateResponse
    82  	ImportResourceStateRequest  providers.ImportResourceStateRequest
    83  	ImportResourceStateFn       func(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse
    84  
    85  	ReadDataSourceCalled   bool
    86  	ReadDataSourceResponse *providers.ReadDataSourceResponse
    87  	ReadDataSourceRequest  providers.ReadDataSourceRequest
    88  	ReadDataSourceFn       func(providers.ReadDataSourceRequest) providers.ReadDataSourceResponse
    89  
    90  	GetFunctionsCalled   bool
    91  	GetFunctionsResponse *providers.GetFunctionsResponse
    92  	GetFunctionsFn       func() providers.GetFunctionsResponse
    93  
    94  	CallFunctionCalled   bool
    95  	CallFunctionResponse *providers.CallFunctionResponse
    96  	CallFunctionRequest  providers.CallFunctionRequest
    97  	CallFunctionFn       func(providers.CallFunctionRequest) providers.CallFunctionResponse
    98  
    99  	CloseCalled bool
   100  	CloseError  error
   101  }
   102  
   103  func (p *MockProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
   104  	p.Lock()
   105  	defer p.Unlock()
   106  	p.GetProviderSchemaCalled = true
   107  	return p.getProviderSchema()
   108  }
   109  
   110  func (p *MockProvider) getProviderSchema() providers.GetProviderSchemaResponse {
   111  	// This version of getProviderSchema doesn't do any locking, so it's suitable to
   112  	// call from other methods of this mock as long as they are already
   113  	// holding the lock.
   114  	if p.GetProviderSchemaResponse != nil {
   115  		return *p.GetProviderSchemaResponse
   116  	}
   117  
   118  	return providers.GetProviderSchemaResponse{
   119  		Provider:      providers.Schema{},
   120  		DataSources:   map[string]providers.Schema{},
   121  		ResourceTypes: map[string]providers.Schema{},
   122  	}
   123  }
   124  
   125  func (p *MockProvider) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
   126  	p.Lock()
   127  	defer p.Unlock()
   128  
   129  	p.ValidateProviderConfigCalled = true
   130  	p.ValidateProviderConfigRequest = r
   131  	if p.ValidateProviderConfigFn != nil {
   132  		return p.ValidateProviderConfigFn(r)
   133  	}
   134  
   135  	if p.ValidateProviderConfigResponse != nil {
   136  		return *p.ValidateProviderConfigResponse
   137  	}
   138  
   139  	resp.PreparedConfig = r.Config
   140  	return resp
   141  }
   142  
   143  func (p *MockProvider) ValidateResourceConfig(r providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) {
   144  	p.Lock()
   145  	defer p.Unlock()
   146  
   147  	p.ValidateResourceConfigCalled = true
   148  	p.ValidateResourceConfigRequest = r
   149  
   150  	// Marshall the value to replicate behavior by the GRPC protocol,
   151  	// and return any relevant errors
   152  	resourceSchema, ok := p.getProviderSchema().ResourceTypes[r.TypeName]
   153  	if !ok {
   154  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName))
   155  		return resp
   156  	}
   157  
   158  	_, err := msgpack.Marshal(r.Config, resourceSchema.Block.ImpliedType())
   159  	if err != nil {
   160  		resp.Diagnostics = resp.Diagnostics.Append(err)
   161  		return resp
   162  	}
   163  
   164  	if p.ValidateResourceConfigFn != nil {
   165  		return p.ValidateResourceConfigFn(r)
   166  	}
   167  
   168  	if p.ValidateResourceConfigResponse != nil {
   169  		return *p.ValidateResourceConfigResponse
   170  	}
   171  
   172  	return resp
   173  }
   174  
   175  func (p *MockProvider) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) (resp providers.ValidateDataResourceConfigResponse) {
   176  	p.Lock()
   177  	defer p.Unlock()
   178  
   179  	p.ValidateDataResourceConfigCalled = true
   180  	p.ValidateDataResourceConfigRequest = r
   181  
   182  	// Marshall the value to replicate behavior by the GRPC protocol
   183  	dataSchema, ok := p.getProviderSchema().DataSources[r.TypeName]
   184  	if !ok {
   185  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName))
   186  		return resp
   187  	}
   188  	_, err := msgpack.Marshal(r.Config, dataSchema.Block.ImpliedType())
   189  	if err != nil {
   190  		resp.Diagnostics = resp.Diagnostics.Append(err)
   191  		return resp
   192  	}
   193  
   194  	if p.ValidateDataResourceConfigFn != nil {
   195  		return p.ValidateDataResourceConfigFn(r)
   196  	}
   197  
   198  	if p.ValidateDataResourceConfigResponse != nil {
   199  		return *p.ValidateDataResourceConfigResponse
   200  	}
   201  
   202  	return resp
   203  }
   204  
   205  func (p *MockProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
   206  	p.Lock()
   207  	defer p.Unlock()
   208  
   209  	if !p.ConfigureProviderCalled {
   210  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before UpgradeResourceState %q", r.TypeName))
   211  		return resp
   212  	}
   213  
   214  	schema, ok := p.getProviderSchema().ResourceTypes[r.TypeName]
   215  	if !ok {
   216  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName))
   217  		return resp
   218  	}
   219  
   220  	schemaType := schema.Block.ImpliedType()
   221  
   222  	p.UpgradeResourceStateCalled = true
   223  	p.UpgradeResourceStateRequest = r
   224  
   225  	if p.UpgradeResourceStateFn != nil {
   226  		return p.UpgradeResourceStateFn(r)
   227  	}
   228  
   229  	if p.UpgradeResourceStateResponse != nil {
   230  		return *p.UpgradeResourceStateResponse
   231  	}
   232  
   233  	switch {
   234  	case r.RawStateFlatmap != nil:
   235  		v, err := hcl2shim.HCL2ValueFromFlatmap(r.RawStateFlatmap, schemaType)
   236  		if err != nil {
   237  			resp.Diagnostics = resp.Diagnostics.Append(err)
   238  			return resp
   239  		}
   240  		resp.UpgradedState = v
   241  	case len(r.RawStateJSON) > 0:
   242  		v, err := ctyjson.Unmarshal(r.RawStateJSON, schemaType)
   243  
   244  		if err != nil {
   245  			resp.Diagnostics = resp.Diagnostics.Append(err)
   246  			return resp
   247  		}
   248  		resp.UpgradedState = v
   249  	}
   250  
   251  	return resp
   252  }
   253  
   254  func (p *MockProvider) ConfigureProvider(r providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   255  	p.Lock()
   256  	defer p.Unlock()
   257  
   258  	p.ConfigureProviderCalled = true
   259  	p.ConfigureProviderRequest = r
   260  
   261  	if p.ConfigureProviderFn != nil {
   262  		return p.ConfigureProviderFn(r)
   263  	}
   264  
   265  	if p.ConfigureProviderResponse != nil {
   266  		return *p.ConfigureProviderResponse
   267  	}
   268  
   269  	return resp
   270  }
   271  
   272  func (p *MockProvider) Stop() error {
   273  	// We intentionally don't lock in this one because the whole point of this
   274  	// method is to be called concurrently with another operation that can
   275  	// be cancelled.  The provider itself is responsible for handling
   276  	// any concurrency concerns in this case.
   277  
   278  	p.StopCalled = true
   279  	if p.StopFn != nil {
   280  		return p.StopFn()
   281  	}
   282  
   283  	return p.StopResponse
   284  }
   285  
   286  func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   287  	p.Lock()
   288  	defer p.Unlock()
   289  
   290  	p.ReadResourceCalled = true
   291  	p.ReadResourceRequest = r
   292  
   293  	if !p.ConfigureProviderCalled {
   294  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before ReadResource %q", r.TypeName))
   295  		return resp
   296  	}
   297  
   298  	if p.ReadResourceFn != nil {
   299  		return p.ReadResourceFn(r)
   300  	}
   301  
   302  	if p.ReadResourceResponse != nil {
   303  		resp = *p.ReadResourceResponse
   304  
   305  		// Make sure the NewState conforms to the schema.
   306  		// This isn't always the case for the existing tests.
   307  		schema, ok := p.getProviderSchema().ResourceTypes[r.TypeName]
   308  		if !ok {
   309  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName))
   310  			return resp
   311  		}
   312  
   313  		newState, err := schema.Block.CoerceValue(resp.NewState)
   314  		if err != nil {
   315  			resp.Diagnostics = resp.Diagnostics.Append(err)
   316  		}
   317  		resp.NewState = newState
   318  		return resp
   319  	}
   320  
   321  	// otherwise just return the same state we received
   322  	resp.NewState = r.PriorState
   323  	resp.Private = r.Private
   324  	return resp
   325  }
   326  
   327  func (p *MockProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   328  	p.Lock()
   329  	defer p.Unlock()
   330  
   331  	if !p.ConfigureProviderCalled {
   332  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before PlanResourceChange %q", r.TypeName))
   333  		return resp
   334  	}
   335  
   336  	p.PlanResourceChangeCalled = true
   337  	p.PlanResourceChangeRequest = r
   338  
   339  	if p.PlanResourceChangeFn != nil {
   340  		return p.PlanResourceChangeFn(r)
   341  	}
   342  
   343  	if p.PlanResourceChangeResponse != nil {
   344  		return *p.PlanResourceChangeResponse
   345  	}
   346  
   347  	// this is a destroy plan,
   348  	if r.ProposedNewState.IsNull() {
   349  		resp.PlannedState = r.ProposedNewState
   350  		resp.PlannedPrivate = r.PriorPrivate
   351  		return resp
   352  	}
   353  
   354  	schema, ok := p.getProviderSchema().ResourceTypes[r.TypeName]
   355  	if !ok {
   356  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName))
   357  		return resp
   358  	}
   359  
   360  	// The default plan behavior is to accept the proposed value, and mark all
   361  	// nil computed attributes as unknown.
   362  	val, err := cty.Transform(r.ProposedNewState, func(path cty.Path, v cty.Value) (cty.Value, error) {
   363  		// We're only concerned with known null values, which can be computed
   364  		// by the provider.
   365  		if !v.IsKnown() {
   366  			return v, nil
   367  		}
   368  
   369  		attrSchema := schema.Block.AttributeByPath(path)
   370  		if attrSchema == nil {
   371  			// this is an intermediate path which does not represent an attribute
   372  			return v, nil
   373  		}
   374  
   375  		// get the current configuration value, to detect when a
   376  		// computed+optional attributes has become unset
   377  		configVal, err := path.Apply(r.Config)
   378  		if err != nil {
   379  			return v, err
   380  		}
   381  
   382  		switch {
   383  		case attrSchema.Computed && !attrSchema.Optional && v.IsNull():
   384  			// this is the easy path, this value is not yet set, and _must_ be computed
   385  			return cty.UnknownVal(v.Type()), nil
   386  
   387  		case attrSchema.Computed && attrSchema.Optional && !v.IsNull() && configVal.IsNull():
   388  			// If an optional+computed value has gone from set to unset, it
   389  			// becomes computed. (this was not possible to do with legacy
   390  			// providers)
   391  			return cty.UnknownVal(v.Type()), nil
   392  		}
   393  
   394  		return v, nil
   395  	})
   396  	if err != nil {
   397  		resp.Diagnostics = resp.Diagnostics.Append(err)
   398  		return resp
   399  	}
   400  
   401  	resp.PlannedPrivate = r.PriorPrivate
   402  	resp.PlannedState = val
   403  
   404  	return resp
   405  }
   406  
   407  func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   408  	p.Lock()
   409  	defer p.Unlock()
   410  	p.ApplyResourceChangeCalled = true
   411  	p.ApplyResourceChangeRequest = r
   412  
   413  	if !p.ConfigureProviderCalled {
   414  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before ApplyResourceChange %q", r.TypeName))
   415  		return resp
   416  	}
   417  
   418  	if p.ApplyResourceChangeFn != nil {
   419  		return p.ApplyResourceChangeFn(r)
   420  	}
   421  
   422  	if p.ApplyResourceChangeResponse != nil {
   423  		return *p.ApplyResourceChangeResponse
   424  	}
   425  
   426  	// if the value is nil, we return that directly to correspond to a delete
   427  	if r.PlannedState.IsNull() {
   428  		resp.NewState = r.PlannedState
   429  		return resp
   430  	}
   431  
   432  	// the default behavior will be to create the minimal valid apply value by
   433  	// setting unknowns (which correspond to computed attributes) to a zero
   434  	// value.
   435  	val, _ := cty.Transform(r.PlannedState, func(path cty.Path, v cty.Value) (cty.Value, error) {
   436  		if !v.IsKnown() {
   437  			ty := v.Type()
   438  			switch {
   439  			case ty == cty.String:
   440  				return cty.StringVal(""), nil
   441  			case ty == cty.Number:
   442  				return cty.NumberIntVal(0), nil
   443  			case ty == cty.Bool:
   444  				return cty.False, nil
   445  			case ty.IsMapType():
   446  				return cty.MapValEmpty(ty.ElementType()), nil
   447  			case ty.IsListType():
   448  				return cty.ListValEmpty(ty.ElementType()), nil
   449  			default:
   450  				return cty.NullVal(ty), nil
   451  			}
   452  		}
   453  		return v, nil
   454  	})
   455  
   456  	resp.NewState = val
   457  	resp.Private = r.PlannedPrivate
   458  
   459  	return resp
   460  }
   461  
   462  func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) {
   463  	p.Lock()
   464  	defer p.Unlock()
   465  
   466  	if !p.ConfigureProviderCalled {
   467  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before ImportResourceState %q", r.TypeName))
   468  		return resp
   469  	}
   470  
   471  	p.ImportResourceStateCalled = true
   472  	p.ImportResourceStateRequest = r
   473  	if p.ImportResourceStateFn != nil {
   474  		return p.ImportResourceStateFn(r)
   475  	}
   476  
   477  	if p.ImportResourceStateResponse != nil {
   478  		// There's no guarantee that the imported resources slice isn't being read somewhere else
   479  		// As such, any changes we make on it (including through pointers) would lead to data races.
   480  		// To avoid that, copy and make changes on the copy
   481  		resp.ImportedResources = make([]providers.ImportedResource, len(p.ImportResourceStateResponse.ImportedResources))
   482  
   483  		// fixup the cty value to match the schema
   484  		for i, res := range p.ImportResourceStateResponse.ImportedResources {
   485  			schema, ok := p.getProviderSchema().ResourceTypes[res.TypeName]
   486  			if !ok {
   487  				resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", res.TypeName))
   488  				return resp
   489  			}
   490  
   491  			var err error
   492  			res.State, err = schema.Block.CoerceValue(res.State)
   493  			if err != nil {
   494  				resp.Diagnostics = resp.Diagnostics.Append(err)
   495  				return resp
   496  			}
   497  
   498  			resp.ImportedResources[i] = res
   499  		}
   500  	}
   501  
   502  	return resp
   503  }
   504  
   505  func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   506  	p.Lock()
   507  	defer p.Unlock()
   508  
   509  	if !p.ConfigureProviderCalled {
   510  		resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before ReadDataSource %q", r.TypeName))
   511  		return resp
   512  	}
   513  
   514  	p.ReadDataSourceCalled = true
   515  	p.ReadDataSourceRequest = r
   516  
   517  	if p.ReadDataSourceFn != nil {
   518  		return p.ReadDataSourceFn(r)
   519  	}
   520  
   521  	if p.ReadDataSourceResponse != nil {
   522  		resp = *p.ReadDataSourceResponse
   523  	}
   524  
   525  	return resp
   526  }
   527  
   528  func (p *MockProvider) GetFunctions() (resp providers.GetFunctionsResponse) {
   529  	p.Lock()
   530  	defer p.Unlock()
   531  
   532  	p.GetFunctionsCalled = true
   533  
   534  	if p.GetFunctionsFn != nil {
   535  		return p.GetFunctionsFn()
   536  	}
   537  
   538  	if p.GetFunctionsResponse != nil {
   539  		resp = *p.GetFunctionsResponse
   540  	}
   541  	return resp
   542  }
   543  
   544  func (p *MockProvider) CallFunction(r providers.CallFunctionRequest) (resp providers.CallFunctionResponse) {
   545  	p.Lock()
   546  	defer p.Unlock()
   547  
   548  	p.CallFunctionCalled = true
   549  	p.CallFunctionRequest = r
   550  
   551  	if p.CallFunctionFn != nil {
   552  		return p.CallFunctionFn(r)
   553  	}
   554  
   555  	if p.CallFunctionResponse != nil {
   556  		resp = *p.CallFunctionResponse
   557  	}
   558  	return resp
   559  }
   560  
   561  func (p *MockProvider) Close() error {
   562  	p.Lock()
   563  	defer p.Unlock()
   564  
   565  	p.CloseCalled = true
   566  	return p.CloseError
   567  }