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