package terraform import ( "fmt" "sync" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" "github.com/hashicorp/terraform/provisioners" ) var _ provisioners.Interface = (*MockProvisioner)(nil) // MockProvisioner implements provisioners.Interface but mocks out all the // calls for testing purposes. type MockProvisioner struct { sync.Mutex // Anything you want, in case you need to store extra data with the mock. Meta interface{} GetSchemaCalled bool GetSchemaResponse provisioners.GetSchemaResponse ValidateProvisionerConfigCalled bool ValidateProvisionerConfigRequest provisioners.ValidateProvisionerConfigRequest ValidateProvisionerConfigResponse provisioners.ValidateProvisionerConfigResponse ValidateProvisionerConfigFn func(provisioners.ValidateProvisionerConfigRequest) provisioners.ValidateProvisionerConfigResponse ProvisionResourceCalled bool ProvisionResourceRequest provisioners.ProvisionResourceRequest ProvisionResourceResponse provisioners.ProvisionResourceResponse ProvisionResourceFn func(provisioners.ProvisionResourceRequest) provisioners.ProvisionResourceResponse StopCalled bool StopResponse error StopFn func() error CloseCalled bool CloseResponse error CloseFn func() error // Legacy callbacks: if these are set, we will shim incoming calls for // new-style methods to these old-fashioned terraform.ResourceProvider // mock callbacks, for the benefit of older tests that were written against // the old mock API. ApplyFn func(rs *InstanceState, c *ResourceConfig) error } func (p *MockProvisioner) GetSchema() provisioners.GetSchemaResponse { p.Lock() defer p.Unlock() p.GetSchemaCalled = true return p.getSchema() } // getSchema is the implementation of GetSchema, which can be called from other // methods on MockProvisioner that may already be holding the lock. func (p *MockProvisioner) getSchema() provisioners.GetSchemaResponse { return p.GetSchemaResponse } func (p *MockProvisioner) ValidateProvisionerConfig(r provisioners.ValidateProvisionerConfigRequest) provisioners.ValidateProvisionerConfigResponse { p.Lock() defer p.Unlock() p.ValidateProvisionerConfigCalled = true p.ValidateProvisionerConfigRequest = r if p.ValidateProvisionerConfigFn != nil { return p.ValidateProvisionerConfigFn(r) } return p.ValidateProvisionerConfigResponse } func (p *MockProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequest) provisioners.ProvisionResourceResponse { p.Lock() defer p.Unlock() p.ProvisionResourceCalled = true p.ProvisionResourceRequest = r if p.ApplyFn != nil { if !r.Config.IsKnown() { panic(fmt.Sprintf("cannot provision with unknown value: %#v", r.Config)) } schema := p.getSchema() rc := NewResourceConfigShimmed(r.Config, schema.Provisioner) connVal := r.Connection connMap := map[string]string{} if !connVal.IsNull() && connVal.IsKnown() { for it := connVal.ElementIterator(); it.Next(); { ak, av := it.Element() name := ak.AsString() if !av.IsKnown() || av.IsNull() { continue } av, _ = convert.Convert(av, cty.String) connMap[name] = av.AsString() } } // We no longer pass the full instance state to a provisioner, so we'll // construct a partial one that should be good enough for what existing // test mocks need. is := &InstanceState{ Ephemeral: EphemeralState{ ConnInfo: connMap, }, } var resp provisioners.ProvisionResourceResponse err := p.ApplyFn(is, rc) if err != nil { resp.Diagnostics = resp.Diagnostics.Append(err) } return resp } if p.ProvisionResourceFn != nil { fn := p.ProvisionResourceFn return fn(r) } return p.ProvisionResourceResponse } func (p *MockProvisioner) Stop() error { // We intentionally don't lock in this one because the whole point of this // method is to be called concurrently with another operation that can // be cancelled. The provisioner itself is responsible for handling // any concurrency concerns in this case. p.StopCalled = true if p.StopFn != nil { return p.StopFn() } return p.StopResponse } func (p *MockProvisioner) Close() error { p.Lock() defer p.Unlock() p.CloseCalled = true if p.CloseFn != nil { return p.CloseFn() } return p.CloseResponse }