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