github.com/rmenn/terraform@v0.3.8-0.20150225065417-fc84b3a78802/helper/schema/resource.go (about)

     1  package schema
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/hashicorp/terraform/terraform"
     8  )
     9  
    10  // Resource represents a thing in Terraform that has a set of configurable
    11  // attributes and a lifecycle (create, read, update, delete).
    12  //
    13  // The Resource schema is an abstraction that allows provider writers to
    14  // worry only about CRUD operations while off-loading validation, diff
    15  // generation, etc. to this higher level library.
    16  type Resource struct {
    17  	// Schema is the schema for the configuration of this resource.
    18  	//
    19  	// The keys of this map are the configuration keys, and the values
    20  	// describe the schema of the configuration value.
    21  	//
    22  	// The schema is used to represent both configurable data as well
    23  	// as data that might be computed in the process of creating this
    24  	// resource.
    25  	Schema map[string]*Schema
    26  
    27  	// The functions below are the CRUD operations for this resource.
    28  	//
    29  	// The only optional operation is Update. If Update is not implemented,
    30  	// then updates will not be supported for this resource.
    31  	//
    32  	// The ResourceData parameter in the functions below are used to
    33  	// query configuration and changes for the resource as well as to set
    34  	// the ID, computed data, etc.
    35  	//
    36  	// The interface{} parameter is the result of the ConfigureFunc in
    37  	// the provider for this resource. If the provider does not define
    38  	// a ConfigureFunc, this will be nil. This parameter should be used
    39  	// to store API clients, configuration structures, etc.
    40  	//
    41  	// If any errors occur during each of the operation, an error should be
    42  	// returned. If a resource was partially updated, be careful to enable
    43  	// partial state mode for ResourceData and use it accordingly.
    44  	//
    45  	// Exists is a function that is called to check if a resource still
    46  	// exists. If this returns false, then this will affect the diff
    47  	// accordingly. If this function isn't set, it will not be called. It
    48  	// is highly recommended to set it. The *ResourceData passed to Exists
    49  	// should _not_ be modified.
    50  	Create CreateFunc
    51  	Read   ReadFunc
    52  	Update UpdateFunc
    53  	Delete DeleteFunc
    54  	Exists ExistsFunc
    55  }
    56  
    57  // See Resource documentation.
    58  type CreateFunc func(*ResourceData, interface{}) error
    59  
    60  // See Resource documentation.
    61  type ReadFunc func(*ResourceData, interface{}) error
    62  
    63  // See Resource documentation.
    64  type UpdateFunc func(*ResourceData, interface{}) error
    65  
    66  // See Resource documentation.
    67  type DeleteFunc func(*ResourceData, interface{}) error
    68  
    69  // See Resource documentation.
    70  type ExistsFunc func(*ResourceData, interface{}) (bool, error)
    71  
    72  // Apply creates, updates, and/or deletes a resource.
    73  func (r *Resource) Apply(
    74  	s *terraform.InstanceState,
    75  	d *terraform.InstanceDiff,
    76  	meta interface{}) (*terraform.InstanceState, error) {
    77  	data, err := schemaMap(r.Schema).Data(s, d)
    78  	if err != nil {
    79  		return s, err
    80  	}
    81  
    82  	if s == nil {
    83  		// The Terraform API dictates that this should never happen, but
    84  		// it doesn't hurt to be safe in this case.
    85  		s = new(terraform.InstanceState)
    86  	}
    87  
    88  	if d.Destroy || d.RequiresNew() {
    89  		if s.ID != "" {
    90  			// Destroy the resource since it is created
    91  			if err := r.Delete(data, meta); err != nil {
    92  				return data.State(), err
    93  			}
    94  
    95  			// Make sure the ID is gone.
    96  			data.SetId("")
    97  		}
    98  
    99  		// If we're only destroying, and not creating, then return
   100  		// now since we're done!
   101  		if !d.RequiresNew() {
   102  			return nil, nil
   103  		}
   104  
   105  		// Reset the data to be stateless since we just destroyed
   106  		data, err = schemaMap(r.Schema).Data(nil, d)
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  	}
   111  
   112  	err = nil
   113  	if data.Id() == "" {
   114  		// We're creating, it is a new resource.
   115  		err = r.Create(data, meta)
   116  	} else {
   117  		if r.Update == nil {
   118  			return s, fmt.Errorf("doesn't support update")
   119  		}
   120  
   121  		err = r.Update(data, meta)
   122  	}
   123  
   124  	return data.State(), err
   125  }
   126  
   127  // Diff returns a diff of this resource and is API compatible with the
   128  // ResourceProvider interface.
   129  func (r *Resource) Diff(
   130  	s *terraform.InstanceState,
   131  	c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   132  	return schemaMap(r.Schema).Diff(s, c)
   133  }
   134  
   135  // Validate validates the resource configuration against the schema.
   136  func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) {
   137  	return schemaMap(r.Schema).Validate(c)
   138  }
   139  
   140  // Refresh refreshes the state of the resource.
   141  func (r *Resource) Refresh(
   142  	s *terraform.InstanceState,
   143  	meta interface{}) (*terraform.InstanceState, error) {
   144  	if r.Exists != nil {
   145  		// Make a copy of data so that if it is modified it doesn't
   146  		// affect our Read later.
   147  		data, err := schemaMap(r.Schema).Data(s, nil)
   148  		if err != nil {
   149  			return s, err
   150  		}
   151  
   152  		exists, err := r.Exists(data, meta)
   153  		if err != nil {
   154  			return s, err
   155  		}
   156  		if !exists {
   157  			return nil, nil
   158  		}
   159  	}
   160  
   161  	data, err := schemaMap(r.Schema).Data(s, nil)
   162  	if err != nil {
   163  		return s, err
   164  	}
   165  
   166  	err = r.Read(data, meta)
   167  	state := data.State()
   168  	if state != nil && state.ID == "" {
   169  		state = nil
   170  	}
   171  
   172  	return state, err
   173  }
   174  
   175  // InternalValidate should be called to validate the structure
   176  // of the resource.
   177  //
   178  // This should be called in a unit test for any resource to verify
   179  // before release that a resource is properly configured for use with
   180  // this library.
   181  //
   182  // Provider.InternalValidate() will automatically call this for all of
   183  // the resources it manages, so you don't need to call this manually if it
   184  // is part of a Provider.
   185  func (r *Resource) InternalValidate() error {
   186  	if r == nil {
   187  		return errors.New("resource is nil")
   188  	}
   189  
   190  	return schemaMap(r.Schema).InternalValidate()
   191  }