1package schema 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "sort" 8 "sync" 9 10 "github.com/hashicorp/go-multierror" 11 "github.com/hashicorp/terraform/config" 12 "github.com/hashicorp/terraform/configs/configschema" 13 "github.com/hashicorp/terraform/terraform" 14) 15 16// Provider represents a resource provider in Terraform, and properly 17// implements all of the ResourceProvider API. 18// 19// By defining a schema for the configuration of the provider, the 20// map of supporting resources, and a configuration function, the schema 21// framework takes over and handles all the provider operations for you. 22// 23// After defining the provider structure, it is unlikely that you'll require any 24// of the methods on Provider itself. 25type Provider struct { 26 // Schema is the schema for the configuration of this provider. If this 27 // provider has no configuration, this can be omitted. 28 // 29 // The keys of this map are the configuration keys, and the value is 30 // the schema describing the value of the configuration. 31 Schema map[string]*Schema 32 33 // ResourcesMap is the list of available resources that this provider 34 // can manage, along with their Resource structure defining their 35 // own schemas and CRUD operations. 36 // 37 // Provider automatically handles routing operations such as Apply, 38 // Diff, etc. to the proper resource. 39 ResourcesMap map[string]*Resource 40 41 // DataSourcesMap is the collection of available data sources that 42 // this provider implements, with a Resource instance defining 43 // the schema and Read operation of each. 44 // 45 // Resource instances for data sources must have a Read function 46 // and must *not* implement Create, Update or Delete. 47 DataSourcesMap map[string]*Resource 48 49 // ConfigureFunc is a function for configuring the provider. If the 50 // provider doesn't need to be configured, this can be omitted. 51 // 52 // See the ConfigureFunc documentation for more information. 53 ConfigureFunc ConfigureFunc 54 55 // MetaReset is called by TestReset to reset any state stored in the meta 56 // interface. This is especially important if the StopContext is stored by 57 // the provider. 58 MetaReset func() error 59 60 meta interface{} 61 62 // a mutex is required because TestReset can directly replace the stopCtx 63 stopMu sync.Mutex 64 stopCtx context.Context 65 stopCtxCancel context.CancelFunc 66 stopOnce sync.Once 67 68 TerraformVersion string 69} 70 71// ConfigureFunc is the function used to configure a Provider. 72// 73// The interface{} value returned by this function is stored and passed into 74// the subsequent resources as the meta parameter. This return value is 75// usually used to pass along a configured API client, a configuration 76// structure, etc. 77type ConfigureFunc func(*ResourceData) (interface{}, error) 78 79// InternalValidate should be called to validate the structure 80// of the provider. 81// 82// This should be called in a unit test for any provider to verify 83// before release that a provider is properly configured for use with 84// this library. 85func (p *Provider) InternalValidate() error { 86 if p == nil { 87 return errors.New("provider is nil") 88 } 89 90 var validationErrors error 91 sm := schemaMap(p.Schema) 92 if err := sm.InternalValidate(sm); err != nil { 93 validationErrors = multierror.Append(validationErrors, err) 94 } 95 96 // Provider-specific checks 97 for k, _ := range sm { 98 if isReservedProviderFieldName(k) { 99 return fmt.Errorf("%s is a reserved field name for a provider", k) 100 } 101 } 102 103 for k, r := range p.ResourcesMap { 104 if err := r.InternalValidate(nil, true); err != nil { 105 validationErrors = multierror.Append(validationErrors, fmt.Errorf("resource %s: %s", k, err)) 106 } 107 } 108 109 for k, r := range p.DataSourcesMap { 110 if err := r.InternalValidate(nil, false); err != nil { 111 validationErrors = multierror.Append(validationErrors, fmt.Errorf("data source %s: %s", k, err)) 112 } 113 } 114 115 return validationErrors 116} 117 118func isReservedProviderFieldName(name string) bool { 119 for _, reservedName := range config.ReservedProviderFields { 120 if name == reservedName { 121 return true 122 } 123 } 124 return false 125} 126 127// Meta returns the metadata associated with this provider that was 128// returned by the Configure call. It will be nil until Configure is called. 129func (p *Provider) Meta() interface{} { 130 return p.meta 131} 132 133// SetMeta can be used to forcefully set the Meta object of the provider. 134// Note that if Configure is called the return value will override anything 135// set here. 136func (p *Provider) SetMeta(v interface{}) { 137 p.meta = v 138} 139 140// Stopped reports whether the provider has been stopped or not. 141func (p *Provider) Stopped() bool { 142 ctx := p.StopContext() 143 select { 144 case <-ctx.Done(): 145 return true 146 default: 147 return false 148 } 149} 150 151// StopCh returns a channel that is closed once the provider is stopped. 152func (p *Provider) StopContext() context.Context { 153 p.stopOnce.Do(p.stopInit) 154 155 p.stopMu.Lock() 156 defer p.stopMu.Unlock() 157 158 return p.stopCtx 159} 160 161func (p *Provider) stopInit() { 162 p.stopMu.Lock() 163 defer p.stopMu.Unlock() 164 165 p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) 166} 167 168// Stop implementation of terraform.ResourceProvider interface. 169func (p *Provider) Stop() error { 170 p.stopOnce.Do(p.stopInit) 171 172 p.stopMu.Lock() 173 defer p.stopMu.Unlock() 174 175 p.stopCtxCancel() 176 return nil 177} 178 179// TestReset resets any state stored in the Provider, and will call TestReset 180// on Meta if it implements the TestProvider interface. 181// This may be used to reset the schema.Provider at the start of a test, and is 182// automatically called by resource.Test. 183func (p *Provider) TestReset() error { 184 p.stopInit() 185 if p.MetaReset != nil { 186 return p.MetaReset() 187 } 188 return nil 189} 190 191// GetSchema implementation of terraform.ResourceProvider interface 192func (p *Provider) GetSchema(req *terraform.ProviderSchemaRequest) (*terraform.ProviderSchema, error) { 193 resourceTypes := map[string]*configschema.Block{} 194 dataSources := map[string]*configschema.Block{} 195 196 for _, name := range req.ResourceTypes { 197 if r, exists := p.ResourcesMap[name]; exists { 198 resourceTypes[name] = r.CoreConfigSchema() 199 } 200 } 201 for _, name := range req.DataSources { 202 if r, exists := p.DataSourcesMap[name]; exists { 203 dataSources[name] = r.CoreConfigSchema() 204 } 205 } 206 207 return &terraform.ProviderSchema{ 208 Provider: schemaMap(p.Schema).CoreConfigSchema(), 209 ResourceTypes: resourceTypes, 210 DataSources: dataSources, 211 }, nil 212} 213 214// Input implementation of terraform.ResourceProvider interface. 215func (p *Provider) Input( 216 input terraform.UIInput, 217 c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { 218 return schemaMap(p.Schema).Input(input, c) 219} 220 221// Validate implementation of terraform.ResourceProvider interface. 222func (p *Provider) Validate(c *terraform.ResourceConfig) ([]string, []error) { 223 if err := p.InternalValidate(); err != nil { 224 return nil, []error{fmt.Errorf( 225 "Internal validation of the provider failed! This is always a bug\n"+ 226 "with the provider itself, and not a user issue. Please report\n"+ 227 "this bug:\n\n%s", err)} 228 } 229 230 return schemaMap(p.Schema).Validate(c) 231} 232 233// ValidateResource implementation of terraform.ResourceProvider interface. 234func (p *Provider) ValidateResource( 235 t string, c *terraform.ResourceConfig) ([]string, []error) { 236 r, ok := p.ResourcesMap[t] 237 if !ok { 238 return nil, []error{fmt.Errorf( 239 "Provider doesn't support resource: %s", t)} 240 } 241 242 return r.Validate(c) 243} 244 245// Configure implementation of terraform.ResourceProvider interface. 246func (p *Provider) Configure(c *terraform.ResourceConfig) error { 247 // No configuration 248 if p.ConfigureFunc == nil { 249 return nil 250 } 251 252 sm := schemaMap(p.Schema) 253 254 // Get a ResourceData for this configuration. To do this, we actually 255 // generate an intermediary "diff" although that is never exposed. 256 diff, err := sm.Diff(nil, c, nil, p.meta, true) 257 if err != nil { 258 return err 259 } 260 261 data, err := sm.Data(nil, diff) 262 if err != nil { 263 return err 264 } 265 266 meta, err := p.ConfigureFunc(data) 267 if err != nil { 268 return err 269 } 270 271 p.meta = meta 272 return nil 273} 274 275// Apply implementation of terraform.ResourceProvider interface. 276func (p *Provider) Apply( 277 info *terraform.InstanceInfo, 278 s *terraform.InstanceState, 279 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 280 r, ok := p.ResourcesMap[info.Type] 281 if !ok { 282 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 283 } 284 285 return r.Apply(s, d, p.meta) 286} 287 288// Diff implementation of terraform.ResourceProvider interface. 289func (p *Provider) Diff( 290 info *terraform.InstanceInfo, 291 s *terraform.InstanceState, 292 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 293 r, ok := p.ResourcesMap[info.Type] 294 if !ok { 295 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 296 } 297 298 return r.Diff(s, c, p.meta) 299} 300 301// SimpleDiff is used by the new protocol wrappers to get a diff that doesn't 302// attempt to calculate ignore_changes. 303func (p *Provider) SimpleDiff( 304 info *terraform.InstanceInfo, 305 s *terraform.InstanceState, 306 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 307 r, ok := p.ResourcesMap[info.Type] 308 if !ok { 309 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 310 } 311 312 return r.simpleDiff(s, c, p.meta) 313} 314 315// Refresh implementation of terraform.ResourceProvider interface. 316func (p *Provider) Refresh( 317 info *terraform.InstanceInfo, 318 s *terraform.InstanceState) (*terraform.InstanceState, error) { 319 r, ok := p.ResourcesMap[info.Type] 320 if !ok { 321 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 322 } 323 324 return r.Refresh(s, p.meta) 325} 326 327// Resources implementation of terraform.ResourceProvider interface. 328func (p *Provider) Resources() []terraform.ResourceType { 329 keys := make([]string, 0, len(p.ResourcesMap)) 330 for k := range p.ResourcesMap { 331 keys = append(keys, k) 332 } 333 sort.Strings(keys) 334 335 result := make([]terraform.ResourceType, 0, len(keys)) 336 for _, k := range keys { 337 resource := p.ResourcesMap[k] 338 339 // This isn't really possible (it'd fail InternalValidate), but 340 // we do it anyways to avoid a panic. 341 if resource == nil { 342 resource = &Resource{} 343 } 344 345 result = append(result, terraform.ResourceType{ 346 Name: k, 347 Importable: resource.Importer != nil, 348 349 // Indicates that a provider is compiled against a new enough 350 // version of core to support the GetSchema method. 351 SchemaAvailable: true, 352 }) 353 } 354 355 return result 356} 357 358func (p *Provider) ImportState( 359 info *terraform.InstanceInfo, 360 id string) ([]*terraform.InstanceState, error) { 361 // Find the resource 362 r, ok := p.ResourcesMap[info.Type] 363 if !ok { 364 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 365 } 366 367 // If it doesn't support import, error 368 if r.Importer == nil { 369 return nil, fmt.Errorf("resource %s doesn't support import", info.Type) 370 } 371 372 // Create the data 373 data := r.Data(nil) 374 data.SetId(id) 375 data.SetType(info.Type) 376 377 // Call the import function 378 results := []*ResourceData{data} 379 if r.Importer.State != nil { 380 var err error 381 results, err = r.Importer.State(data, p.meta) 382 if err != nil { 383 return nil, err 384 } 385 } 386 387 // Convert the results to InstanceState values and return it 388 states := make([]*terraform.InstanceState, len(results)) 389 for i, r := range results { 390 states[i] = r.State() 391 } 392 393 // Verify that all are non-nil. If there are any nil the error 394 // isn't obvious so we circumvent that with a friendlier error. 395 for _, s := range states { 396 if s == nil { 397 return nil, fmt.Errorf( 398 "nil entry in ImportState results. This is always a bug with\n" + 399 "the resource that is being imported. Please report this as\n" + 400 "a bug to Terraform.") 401 } 402 } 403 404 return states, nil 405} 406 407// ValidateDataSource implementation of terraform.ResourceProvider interface. 408func (p *Provider) ValidateDataSource( 409 t string, c *terraform.ResourceConfig) ([]string, []error) { 410 r, ok := p.DataSourcesMap[t] 411 if !ok { 412 return nil, []error{fmt.Errorf( 413 "Provider doesn't support data source: %s", t)} 414 } 415 416 return r.Validate(c) 417} 418 419// ReadDataDiff implementation of terraform.ResourceProvider interface. 420func (p *Provider) ReadDataDiff( 421 info *terraform.InstanceInfo, 422 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 423 424 r, ok := p.DataSourcesMap[info.Type] 425 if !ok { 426 return nil, fmt.Errorf("unknown data source: %s", info.Type) 427 } 428 429 return r.Diff(nil, c, p.meta) 430} 431 432// RefreshData implementation of terraform.ResourceProvider interface. 433func (p *Provider) ReadDataApply( 434 info *terraform.InstanceInfo, 435 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 436 437 r, ok := p.DataSourcesMap[info.Type] 438 if !ok { 439 return nil, fmt.Errorf("unknown data source: %s", info.Type) 440 } 441 442 return r.ReadDataApply(d, p.meta) 443} 444 445// DataSources implementation of terraform.ResourceProvider interface. 446func (p *Provider) DataSources() []terraform.DataSource { 447 keys := make([]string, 0, len(p.DataSourcesMap)) 448 for k, _ := range p.DataSourcesMap { 449 keys = append(keys, k) 450 } 451 sort.Strings(keys) 452 453 result := make([]terraform.DataSource, 0, len(keys)) 454 for _, k := range keys { 455 result = append(result, terraform.DataSource{ 456 Name: k, 457 458 // Indicates that a provider is compiled against a new enough 459 // version of core to support the GetSchema method. 460 SchemaAvailable: true, 461 }) 462 } 463 464 return result 465} 466