kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/terraform/provider_mock.go (about)

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