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