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  }