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