| package schema |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "sync" |
| |
| "github.com/hashicorp/go-multierror" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/legacy/terraform" |
| ) |
| |
| // Provisioner represents a resource provisioner in Terraform and properly |
| // implements all of the ResourceProvisioner API. |
| // |
| // This higher level structure makes it much easier to implement a new or |
| // custom provisioner for Terraform. |
| // |
| // The function callbacks for this structure are all passed a context object. |
| // This context object has a number of pre-defined values that can be accessed |
| // via the global functions defined in context.go. |
| type Provisioner struct { |
| // ConnSchema is the schema for the connection settings for this |
| // provisioner. |
| // |
| // The keys of this map are the configuration keys, and the value is |
| // the schema describing the value of the configuration. |
| // |
| // NOTE: The value of connection keys can only be strings for now. |
| ConnSchema map[string]*Schema |
| |
| // Schema is the schema for the usage of this provisioner. |
| // |
| // The keys of this map are the configuration keys, and the value is |
| // the schema describing the value of the configuration. |
| Schema map[string]*Schema |
| |
| // ApplyFunc is the function for executing the provisioner. This is required. |
| // It is given a context. See the Provisioner struct docs for more |
| // information. |
| ApplyFunc func(ctx context.Context) error |
| |
| // ValidateFunc is a function for extended validation. This is optional |
| // and should be used when individual field validation is not enough. |
| ValidateFunc func(*terraform.ResourceConfig) ([]string, []error) |
| |
| stopCtx context.Context |
| stopCtxCancel context.CancelFunc |
| stopOnce sync.Once |
| } |
| |
| // Keys that can be used to access data in the context parameters for |
| // Provisioners. |
| var ( |
| connDataInvalid = contextKey("data invalid") |
| |
| // This returns a *ResourceData for the connection information. |
| // Guaranteed to never be nil. |
| ProvConnDataKey = contextKey("provider conn data") |
| |
| // This returns a *ResourceData for the config information. |
| // Guaranteed to never be nil. |
| ProvConfigDataKey = contextKey("provider config data") |
| |
| // This returns a terraform.UIOutput. Guaranteed to never be nil. |
| ProvOutputKey = contextKey("provider output") |
| |
| // This returns the raw InstanceState passed to Apply. Guaranteed to |
| // be set, but may be nil. |
| ProvRawStateKey = contextKey("provider raw state") |
| ) |
| |
| // InternalValidate should be called to validate the structure |
| // of the provisioner. |
| // |
| // This should be called in a unit test to verify before release that this |
| // structure is properly configured for use. |
| func (p *Provisioner) InternalValidate() error { |
| if p == nil { |
| return errors.New("provisioner is nil") |
| } |
| |
| var validationErrors error |
| { |
| sm := schemaMap(p.ConnSchema) |
| if err := sm.InternalValidate(sm); err != nil { |
| validationErrors = multierror.Append(validationErrors, err) |
| } |
| } |
| |
| { |
| sm := schemaMap(p.Schema) |
| if err := sm.InternalValidate(sm); err != nil { |
| validationErrors = multierror.Append(validationErrors, err) |
| } |
| } |
| |
| if p.ApplyFunc == nil { |
| validationErrors = multierror.Append(validationErrors, fmt.Errorf( |
| "ApplyFunc must not be nil")) |
| } |
| |
| return validationErrors |
| } |
| |
| // StopContext returns a context that checks whether a provisioner is stopped. |
| func (p *Provisioner) StopContext() context.Context { |
| p.stopOnce.Do(p.stopInit) |
| return p.stopCtx |
| } |
| |
| func (p *Provisioner) stopInit() { |
| p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) |
| } |
| |
| // Stop implementation of terraform.ResourceProvisioner interface. |
| func (p *Provisioner) Stop() error { |
| p.stopOnce.Do(p.stopInit) |
| p.stopCtxCancel() |
| return nil |
| } |
| |
| // GetConfigSchema implementation of terraform.ResourceProvisioner interface. |
| func (p *Provisioner) GetConfigSchema() (*configschema.Block, error) { |
| return schemaMap(p.Schema).CoreConfigSchema(), nil |
| } |
| |
| // Apply implementation of terraform.ResourceProvisioner interface. |
| func (p *Provisioner) Apply( |
| o terraform.UIOutput, |
| s *terraform.InstanceState, |
| c *terraform.ResourceConfig) error { |
| var connData, configData *ResourceData |
| |
| { |
| // We first need to turn the connection information into a |
| // terraform.ResourceConfig so that we can use that type to more |
| // easily build a ResourceData structure. We do this by simply treating |
| // the conn info as configuration input. |
| raw := make(map[string]interface{}) |
| if s != nil { |
| for k, v := range s.Ephemeral.ConnInfo { |
| raw[k] = v |
| } |
| } |
| |
| c := terraform.NewResourceConfigRaw(raw) |
| sm := schemaMap(p.ConnSchema) |
| diff, err := sm.Diff(nil, c, nil, nil, true) |
| if err != nil { |
| return err |
| } |
| connData, err = sm.Data(nil, diff) |
| if err != nil { |
| return err |
| } |
| } |
| |
| { |
| // Build the configuration data. Doing this requires making a "diff" |
| // even though that's never used. We use that just to get the correct types. |
| configMap := schemaMap(p.Schema) |
| diff, err := configMap.Diff(nil, c, nil, nil, true) |
| if err != nil { |
| return err |
| } |
| configData, err = configMap.Data(nil, diff) |
| if err != nil { |
| return err |
| } |
| } |
| |
| // Build the context and call the function |
| ctx := p.StopContext() |
| ctx = context.WithValue(ctx, ProvConnDataKey, connData) |
| ctx = context.WithValue(ctx, ProvConfigDataKey, configData) |
| ctx = context.WithValue(ctx, ProvOutputKey, o) |
| ctx = context.WithValue(ctx, ProvRawStateKey, s) |
| return p.ApplyFunc(ctx) |
| } |
| |
| // Validate implements the terraform.ResourceProvisioner interface. |
| func (p *Provisioner) Validate(c *terraform.ResourceConfig) (ws []string, es []error) { |
| if err := p.InternalValidate(); err != nil { |
| return nil, []error{fmt.Errorf( |
| "Internal validation of the provisioner failed! This is always a bug\n"+ |
| "with the provisioner itself, and not a user issue. Please report\n"+ |
| "this bug:\n\n%s", err)} |
| } |
| |
| if p.Schema != nil { |
| w, e := schemaMap(p.Schema).Validate(c) |
| ws = append(ws, w...) |
| es = append(es, e...) |
| } |
| |
| if p.ValidateFunc != nil { |
| w, e := p.ValidateFunc(c) |
| ws = append(ws, w...) |
| es = append(es, e...) |
| } |
| |
| return ws, es |
| } |