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