github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/helper/schema/provisioner.go (about)

     1  package schema
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  
     9  	"github.com/hashicorp/go-multierror"
    10  	"github.com/hashicorp/terraform/configs/configschema"
    11  	"github.com/hashicorp/terraform/terraform"
    12  )
    13  
    14  // Provisioner represents a resource provisioner in Terraform and properly
    15  // implements all of the ResourceProvisioner API.
    16  //
    17  // This higher level structure makes it much easier to implement a new or
    18  // custom provisioner for Terraform.
    19  //
    20  // The function callbacks for this structure are all passed a context object.
    21  // This context object has a number of pre-defined values that can be accessed
    22  // via the global functions defined in context.go.
    23  type Provisioner struct {
    24  	// ConnSchema is the schema for the connection settings for this
    25  	// provisioner.
    26  	//
    27  	// The keys of this map are the configuration keys, and the value is
    28  	// the schema describing the value of the configuration.
    29  	//
    30  	// NOTE: The value of connection keys can only be strings for now.
    31  	ConnSchema map[string]*Schema
    32  
    33  	// Schema is the schema for the usage of this provisioner.
    34  	//
    35  	// The keys of this map are the configuration keys, and the value is
    36  	// the schema describing the value of the configuration.
    37  	Schema map[string]*Schema
    38  
    39  	// ApplyFunc is the function for executing the provisioner. This is required.
    40  	// It is given a context. See the Provisioner struct docs for more
    41  	// information.
    42  	ApplyFunc func(ctx context.Context) error
    43  
    44  	// ValidateFunc is a function for extended validation. This is optional
    45  	// and should be used when individual field validation is not enough.
    46  	ValidateFunc func(*terraform.ResourceConfig) ([]string, []error)
    47  
    48  	stopCtx       context.Context
    49  	stopCtxCancel context.CancelFunc
    50  	stopOnce      sync.Once
    51  }
    52  
    53  // Keys that can be used to access data in the context parameters for
    54  // Provisioners.
    55  var (
    56  	connDataInvalid = contextKey("data invalid")
    57  
    58  	// This returns a *ResourceData for the connection information.
    59  	// Guaranteed to never be nil.
    60  	ProvConnDataKey = contextKey("provider conn data")
    61  
    62  	// This returns a *ResourceData for the config information.
    63  	// Guaranteed to never be nil.
    64  	ProvConfigDataKey = contextKey("provider config data")
    65  
    66  	// This returns a terraform.UIOutput. Guaranteed to never be nil.
    67  	ProvOutputKey = contextKey("provider output")
    68  
    69  	// This returns the raw InstanceState passed to Apply. Guaranteed to
    70  	// be set, but may be nil.
    71  	ProvRawStateKey = contextKey("provider raw state")
    72  )
    73  
    74  // InternalValidate should be called to validate the structure
    75  // of the provisioner.
    76  //
    77  // This should be called in a unit test to verify before release that this
    78  // structure is properly configured for use.
    79  func (p *Provisioner) InternalValidate() error {
    80  	if p == nil {
    81  		return errors.New("provisioner is nil")
    82  	}
    83  
    84  	var validationErrors error
    85  	{
    86  		sm := schemaMap(p.ConnSchema)
    87  		if err := sm.InternalValidate(sm); err != nil {
    88  			validationErrors = multierror.Append(validationErrors, err)
    89  		}
    90  	}
    91  
    92  	{
    93  		sm := schemaMap(p.Schema)
    94  		if err := sm.InternalValidate(sm); err != nil {
    95  			validationErrors = multierror.Append(validationErrors, err)
    96  		}
    97  	}
    98  
    99  	if p.ApplyFunc == nil {
   100  		validationErrors = multierror.Append(validationErrors, fmt.Errorf(
   101  			"ApplyFunc must not be nil"))
   102  	}
   103  
   104  	return validationErrors
   105  }
   106  
   107  // StopContext returns a context that checks whether a provisioner is stopped.
   108  func (p *Provisioner) StopContext() context.Context {
   109  	p.stopOnce.Do(p.stopInit)
   110  	return p.stopCtx
   111  }
   112  
   113  func (p *Provisioner) stopInit() {
   114  	p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background())
   115  }
   116  
   117  // Stop implementation of terraform.ResourceProvisioner interface.
   118  func (p *Provisioner) Stop() error {
   119  	p.stopOnce.Do(p.stopInit)
   120  	p.stopCtxCancel()
   121  	return nil
   122  }
   123  
   124  // GetConfigSchema implementation of terraform.ResourceProvisioner interface.
   125  func (p *Provisioner) GetConfigSchema() (*configschema.Block, error) {
   126  	return schemaMap(p.Schema).CoreConfigSchema(), nil
   127  }
   128  
   129  // Apply implementation of terraform.ResourceProvisioner interface.
   130  func (p *Provisioner) Apply(
   131  	o terraform.UIOutput,
   132  	s *terraform.InstanceState,
   133  	c *terraform.ResourceConfig) error {
   134  	var connData, configData *ResourceData
   135  
   136  	{
   137  		// We first need to turn the connection information into a
   138  		// terraform.ResourceConfig so that we can use that type to more
   139  		// easily build a ResourceData structure. We do this by simply treating
   140  		// the conn info as configuration input.
   141  		raw := make(map[string]interface{})
   142  		if s != nil {
   143  			for k, v := range s.Ephemeral.ConnInfo {
   144  				raw[k] = v
   145  			}
   146  		}
   147  
   148  		c := terraform.NewResourceConfigRaw(raw)
   149  		sm := schemaMap(p.ConnSchema)
   150  		diff, err := sm.Diff(nil, c, nil, nil, true)
   151  		if err != nil {
   152  			return err
   153  		}
   154  		connData, err = sm.Data(nil, diff)
   155  		if err != nil {
   156  			return err
   157  		}
   158  	}
   159  
   160  	{
   161  		// Build the configuration data. Doing this requires making a "diff"
   162  		// even though that's never used. We use that just to get the correct types.
   163  		configMap := schemaMap(p.Schema)
   164  		diff, err := configMap.Diff(nil, c, nil, nil, true)
   165  		if err != nil {
   166  			return err
   167  		}
   168  		configData, err = configMap.Data(nil, diff)
   169  		if err != nil {
   170  			return err
   171  		}
   172  	}
   173  
   174  	// Build the context and call the function
   175  	ctx := p.StopContext()
   176  	ctx = context.WithValue(ctx, ProvConnDataKey, connData)
   177  	ctx = context.WithValue(ctx, ProvConfigDataKey, configData)
   178  	ctx = context.WithValue(ctx, ProvOutputKey, o)
   179  	ctx = context.WithValue(ctx, ProvRawStateKey, s)
   180  	return p.ApplyFunc(ctx)
   181  }
   182  
   183  // Validate implements the terraform.ResourceProvisioner interface.
   184  func (p *Provisioner) Validate(c *terraform.ResourceConfig) (ws []string, es []error) {
   185  	if err := p.InternalValidate(); err != nil {
   186  		return nil, []error{fmt.Errorf(
   187  			"Internal validation of the provisioner failed! This is always a bug\n"+
   188  				"with the provisioner itself, and not a user issue. Please report\n"+
   189  				"this bug:\n\n%s", err)}
   190  	}
   191  
   192  	if p.Schema != nil {
   193  		w, e := schemaMap(p.Schema).Validate(c)
   194  		ws = append(ws, w...)
   195  		es = append(es, e...)
   196  	}
   197  
   198  	if p.ValidateFunc != nil {
   199  		w, e := p.ValidateFunc(c)
   200  		ws = append(ws, w...)
   201  		es = append(es, e...)
   202  	}
   203  
   204  	return ws, es
   205  }