github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/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 meta interface{} 54 55 stopCtx context.Context 56 stopCtxCancel context.CancelFunc 57 stopOnce sync.Once 58 } 59 60 // ConfigureFunc is the function used to configure a Provider. 61 // 62 // The interface{} value returned by this function is stored and passed into 63 // the subsequent resources as the meta parameter. This return value is 64 // usually used to pass along a configured API client, a configuration 65 // structure, etc. 66 type ConfigureFunc func(*ResourceData) (interface{}, error) 67 68 // InternalValidate should be called to validate the structure 69 // of the provider. 70 // 71 // This should be called in a unit test for any provider to verify 72 // before release that a provider is properly configured for use with 73 // this library. 74 func (p *Provider) InternalValidate() error { 75 if p == nil { 76 return errors.New("provider is nil") 77 } 78 79 var validationErrors error 80 sm := schemaMap(p.Schema) 81 if err := sm.InternalValidate(sm); err != nil { 82 validationErrors = multierror.Append(validationErrors, err) 83 } 84 85 for k, r := range p.ResourcesMap { 86 if err := r.InternalValidate(nil, true); err != nil { 87 validationErrors = multierror.Append(validationErrors, fmt.Errorf("resource %s: %s", k, err)) 88 } 89 } 90 91 for k, r := range p.DataSourcesMap { 92 if err := r.InternalValidate(nil, false); err != nil { 93 validationErrors = multierror.Append(validationErrors, fmt.Errorf("data source %s: %s", k, err)) 94 } 95 } 96 97 return validationErrors 98 } 99 100 // Meta returns the metadata associated with this provider that was 101 // returned by the Configure call. It will be nil until Configure is called. 102 func (p *Provider) Meta() interface{} { 103 return p.meta 104 } 105 106 // SetMeta can be used to forcefully set the Meta object of the provider. 107 // Note that if Configure is called the return value will override anything 108 // set here. 109 func (p *Provider) SetMeta(v interface{}) { 110 p.meta = v 111 } 112 113 // Stopped reports whether the provider has been stopped or not. 114 func (p *Provider) Stopped() bool { 115 ctx := p.StopContext() 116 select { 117 case <-ctx.Done(): 118 return true 119 default: 120 return false 121 } 122 } 123 124 // StopCh returns a channel that is closed once the provider is stopped. 125 func (p *Provider) StopContext() context.Context { 126 p.stopOnce.Do(p.stopInit) 127 return p.stopCtx 128 } 129 130 func (p *Provider) stopInit() { 131 p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) 132 } 133 134 // Stop implementation of terraform.ResourceProvider interface. 135 func (p *Provider) Stop() error { 136 p.stopOnce.Do(p.stopInit) 137 p.stopCtxCancel() 138 return nil 139 } 140 141 // Input implementation of terraform.ResourceProvider interface. 142 func (p *Provider) Input( 143 input terraform.UIInput, 144 c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { 145 return schemaMap(p.Schema).Input(input, c) 146 } 147 148 // Validate implementation of terraform.ResourceProvider interface. 149 func (p *Provider) Validate(c *terraform.ResourceConfig) ([]string, []error) { 150 if err := p.InternalValidate(); err != nil { 151 return nil, []error{fmt.Errorf( 152 "Internal validation of the provider failed! This is always a bug\n"+ 153 "with the provider itself, and not a user issue. Please report\n"+ 154 "this bug:\n\n%s", err)} 155 } 156 157 return schemaMap(p.Schema).Validate(c) 158 } 159 160 // ValidateResource implementation of terraform.ResourceProvider interface. 161 func (p *Provider) ValidateResource( 162 t string, c *terraform.ResourceConfig) ([]string, []error) { 163 r, ok := p.ResourcesMap[t] 164 if !ok { 165 return nil, []error{fmt.Errorf( 166 "Provider doesn't support resource: %s", t)} 167 } 168 169 return r.Validate(c) 170 } 171 172 // Configure implementation of terraform.ResourceProvider interface. 173 func (p *Provider) Configure(c *terraform.ResourceConfig) error { 174 // No configuration 175 if p.ConfigureFunc == nil { 176 return nil 177 } 178 179 sm := schemaMap(p.Schema) 180 181 // Get a ResourceData for this configuration. To do this, we actually 182 // generate an intermediary "diff" although that is never exposed. 183 diff, err := sm.Diff(nil, c) 184 if err != nil { 185 return err 186 } 187 188 data, err := sm.Data(nil, diff) 189 if err != nil { 190 return err 191 } 192 193 meta, err := p.ConfigureFunc(data) 194 if err != nil { 195 return err 196 } 197 198 p.meta = meta 199 return nil 200 } 201 202 // Apply implementation of terraform.ResourceProvider interface. 203 func (p *Provider) Apply( 204 info *terraform.InstanceInfo, 205 s *terraform.InstanceState, 206 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 207 r, ok := p.ResourcesMap[info.Type] 208 if !ok { 209 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 210 } 211 212 return r.Apply(s, d, p.meta) 213 } 214 215 // Diff implementation of terraform.ResourceProvider interface. 216 func (p *Provider) Diff( 217 info *terraform.InstanceInfo, 218 s *terraform.InstanceState, 219 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 220 r, ok := p.ResourcesMap[info.Type] 221 if !ok { 222 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 223 } 224 225 return r.Diff(s, c) 226 } 227 228 // Refresh implementation of terraform.ResourceProvider interface. 229 func (p *Provider) Refresh( 230 info *terraform.InstanceInfo, 231 s *terraform.InstanceState) (*terraform.InstanceState, error) { 232 r, ok := p.ResourcesMap[info.Type] 233 if !ok { 234 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 235 } 236 237 return r.Refresh(s, p.meta) 238 } 239 240 // Resources implementation of terraform.ResourceProvider interface. 241 func (p *Provider) Resources() []terraform.ResourceType { 242 keys := make([]string, 0, len(p.ResourcesMap)) 243 for k, _ := range p.ResourcesMap { 244 keys = append(keys, k) 245 } 246 sort.Strings(keys) 247 248 result := make([]terraform.ResourceType, 0, len(keys)) 249 for _, k := range keys { 250 resource := p.ResourcesMap[k] 251 252 // This isn't really possible (it'd fail InternalValidate), but 253 // we do it anyways to avoid a panic. 254 if resource == nil { 255 resource = &Resource{} 256 } 257 258 result = append(result, terraform.ResourceType{ 259 Name: k, 260 Importable: resource.Importer != nil, 261 }) 262 } 263 264 return result 265 } 266 267 func (p *Provider) ImportState( 268 info *terraform.InstanceInfo, 269 id string) ([]*terraform.InstanceState, error) { 270 // Find the resource 271 r, ok := p.ResourcesMap[info.Type] 272 if !ok { 273 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 274 } 275 276 // If it doesn't support import, error 277 if r.Importer == nil { 278 return nil, fmt.Errorf("resource %s doesn't support import", info.Type) 279 } 280 281 // Create the data 282 data := r.Data(nil) 283 data.SetId(id) 284 data.SetType(info.Type) 285 286 // Call the import function 287 results := []*ResourceData{data} 288 if r.Importer.State != nil { 289 var err error 290 results, err = r.Importer.State(data, p.meta) 291 if err != nil { 292 return nil, err 293 } 294 } 295 296 // Convert the results to InstanceState values and return it 297 states := make([]*terraform.InstanceState, len(results)) 298 for i, r := range results { 299 states[i] = r.State() 300 } 301 302 // Verify that all are non-nil. If there are any nil the error 303 // isn't obvious so we circumvent that with a friendlier error. 304 for _, s := range states { 305 if s == nil { 306 return nil, fmt.Errorf( 307 "nil entry in ImportState results. This is always a bug with\n" + 308 "the resource that is being imported. Please report this as\n" + 309 "a bug to Terraform.") 310 } 311 } 312 313 return states, nil 314 } 315 316 // ValidateDataSource implementation of terraform.ResourceProvider interface. 317 func (p *Provider) ValidateDataSource( 318 t string, c *terraform.ResourceConfig) ([]string, []error) { 319 r, ok := p.DataSourcesMap[t] 320 if !ok { 321 return nil, []error{fmt.Errorf( 322 "Provider doesn't support data source: %s", t)} 323 } 324 325 return r.Validate(c) 326 } 327 328 // ReadDataDiff implementation of terraform.ResourceProvider interface. 329 func (p *Provider) ReadDataDiff( 330 info *terraform.InstanceInfo, 331 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 332 333 r, ok := p.DataSourcesMap[info.Type] 334 if !ok { 335 return nil, fmt.Errorf("unknown data source: %s", info.Type) 336 } 337 338 return r.Diff(nil, c) 339 } 340 341 // RefreshData implementation of terraform.ResourceProvider interface. 342 func (p *Provider) ReadDataApply( 343 info *terraform.InstanceInfo, 344 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 345 346 r, ok := p.DataSourcesMap[info.Type] 347 if !ok { 348 return nil, fmt.Errorf("unknown data source: %s", info.Type) 349 } 350 351 return r.ReadDataApply(d, p.meta) 352 } 353 354 // DataSources implementation of terraform.ResourceProvider interface. 355 func (p *Provider) DataSources() []terraform.DataSource { 356 keys := make([]string, 0, len(p.DataSourcesMap)) 357 for k, _ := range p.DataSourcesMap { 358 keys = append(keys, k) 359 } 360 sort.Strings(keys) 361 362 result := make([]terraform.DataSource, 0, len(keys)) 363 for _, k := range keys { 364 result = append(result, terraform.DataSource{ 365 Name: k, 366 }) 367 } 368 369 return result 370 }