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