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