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