github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/helper/schema/resource_timeout.go (about)

     1  package schema
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/hashicorp/terraform/terraform"
     9  	"github.com/mitchellh/copystructure"
    10  )
    11  
    12  const TimeoutKey = "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0"
    13  const TimeoutsConfigKey = "timeouts"
    14  
    15  const (
    16  	TimeoutCreate  = "create"
    17  	TimeoutRead    = "read"
    18  	TimeoutUpdate  = "update"
    19  	TimeoutDelete  = "delete"
    20  	TimeoutDefault = "default"
    21  )
    22  
    23  func timeoutKeys() []string {
    24  	return []string{
    25  		TimeoutCreate,
    26  		TimeoutRead,
    27  		TimeoutUpdate,
    28  		TimeoutDelete,
    29  		TimeoutDefault,
    30  	}
    31  }
    32  
    33  // could be time.Duration, int64 or float64
    34  func DefaultTimeout(tx interface{}) *time.Duration {
    35  	var td time.Duration
    36  	switch raw := tx.(type) {
    37  	case time.Duration:
    38  		return &raw
    39  	case int64:
    40  		td = time.Duration(raw)
    41  	case float64:
    42  		td = time.Duration(int64(raw))
    43  	default:
    44  		log.Printf("[WARN] Unknown type in DefaultTimeout: %#v", tx)
    45  	}
    46  	return &td
    47  }
    48  
    49  type ResourceTimeout struct {
    50  	Create, Read, Update, Delete, Default *time.Duration
    51  }
    52  
    53  // ConfigDecode takes a schema and the configuration (available in Diff) and
    54  // validates, parses the timeouts into `t`
    55  func (t *ResourceTimeout) ConfigDecode(s *Resource, c *terraform.ResourceConfig) error {
    56  	if s.Timeouts != nil {
    57  		raw, err := copystructure.Copy(s.Timeouts)
    58  		if err != nil {
    59  			log.Printf("[DEBUG] Error with deep copy: %s", err)
    60  		}
    61  		*t = *raw.(*ResourceTimeout)
    62  	}
    63  
    64  	if raw, ok := c.Config[TimeoutsConfigKey]; ok {
    65  		if configTimeouts, ok := raw.([]map[string]interface{}); ok {
    66  			for _, timeoutValues := range configTimeouts {
    67  				// loop through each Timeout given in the configuration and validate they
    68  				// the Timeout defined in the resource
    69  				for timeKey, timeValue := range timeoutValues {
    70  					// validate that we're dealing with the normal CRUD actions
    71  					var found bool
    72  					for _, key := range timeoutKeys() {
    73  						if timeKey == key {
    74  							found = true
    75  							break
    76  						}
    77  					}
    78  
    79  					if !found {
    80  						return fmt.Errorf("Unsupported Timeout configuration key found (%s)", timeKey)
    81  					}
    82  
    83  					// Get timeout
    84  					rt, err := time.ParseDuration(timeValue.(string))
    85  					if err != nil {
    86  						return fmt.Errorf("Error parsing Timeout for (%s): %s", timeKey, err)
    87  					}
    88  
    89  					var timeout *time.Duration
    90  					switch timeKey {
    91  					case TimeoutCreate:
    92  						timeout = t.Create
    93  					case TimeoutUpdate:
    94  						timeout = t.Update
    95  					case TimeoutRead:
    96  						timeout = t.Read
    97  					case TimeoutDelete:
    98  						timeout = t.Delete
    99  					case TimeoutDefault:
   100  						timeout = t.Default
   101  					}
   102  
   103  					// If the resource has not delcared this in the definition, then error
   104  					// with an unsupported message
   105  					if timeout == nil {
   106  						return unsupportedTimeoutKeyError(timeKey)
   107  					}
   108  
   109  					*timeout = rt
   110  				}
   111  			}
   112  		} else {
   113  			log.Printf("[WARN] Invalid Timeout structure found, skipping timeouts")
   114  		}
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  func unsupportedTimeoutKeyError(key string) error {
   121  	return fmt.Errorf("Timeout Key (%s) is not supported", key)
   122  }
   123  
   124  // DiffEncode, StateEncode, and MetaDecode are analogous to the Go stdlib JSONEncoder
   125  // interface: they encode/decode a timeouts struct from an instance diff, which is
   126  // where the timeout data is stored after a diff to pass into Apply.
   127  //
   128  // StateEncode encodes the timeout into the ResourceData's InstanceState for
   129  // saving to state
   130  //
   131  func (t *ResourceTimeout) DiffEncode(id *terraform.InstanceDiff) error {
   132  	return t.metaEncode(id)
   133  }
   134  
   135  func (t *ResourceTimeout) StateEncode(is *terraform.InstanceState) error {
   136  	return t.metaEncode(is)
   137  }
   138  
   139  // metaEncode encodes the ResourceTimeout into a map[string]interface{} format
   140  // and stores it in the Meta field of the interface it's given.
   141  // Assumes the interface is either *terraform.InstanceState or
   142  // *terraform.InstanceDiff, returns an error otherwise
   143  func (t *ResourceTimeout) metaEncode(ids interface{}) error {
   144  	m := make(map[string]interface{})
   145  
   146  	if t.Create != nil {
   147  		m[TimeoutCreate] = t.Create.Nanoseconds()
   148  	}
   149  	if t.Read != nil {
   150  		m[TimeoutRead] = t.Read.Nanoseconds()
   151  	}
   152  	if t.Update != nil {
   153  		m[TimeoutUpdate] = t.Update.Nanoseconds()
   154  	}
   155  	if t.Delete != nil {
   156  		m[TimeoutDelete] = t.Delete.Nanoseconds()
   157  	}
   158  	if t.Default != nil {
   159  		m[TimeoutDefault] = t.Default.Nanoseconds()
   160  		// for any key above that is nil, if default is specified, we need to
   161  		// populate it with the default
   162  		for _, k := range timeoutKeys() {
   163  			if _, ok := m[k]; !ok {
   164  				m[k] = t.Default.Nanoseconds()
   165  			}
   166  		}
   167  	}
   168  
   169  	// only add the Timeout to the Meta if we have values
   170  	if len(m) > 0 {
   171  		switch instance := ids.(type) {
   172  		case *terraform.InstanceDiff:
   173  			if instance.Meta == nil {
   174  				instance.Meta = make(map[string]interface{})
   175  			}
   176  			instance.Meta[TimeoutKey] = m
   177  		case *terraform.InstanceState:
   178  			if instance.Meta == nil {
   179  				instance.Meta = make(map[string]interface{})
   180  			}
   181  			instance.Meta[TimeoutKey] = m
   182  		default:
   183  			return fmt.Errorf("Error matching type for Diff Encode")
   184  		}
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  func (t *ResourceTimeout) StateDecode(id *terraform.InstanceState) error {
   191  	return t.metaDecode(id)
   192  }
   193  func (t *ResourceTimeout) DiffDecode(is *terraform.InstanceDiff) error {
   194  	return t.metaDecode(is)
   195  }
   196  
   197  func (t *ResourceTimeout) metaDecode(ids interface{}) error {
   198  	var rawMeta interface{}
   199  	var ok bool
   200  	switch rawInstance := ids.(type) {
   201  	case *terraform.InstanceDiff:
   202  		rawMeta, ok = rawInstance.Meta[TimeoutKey]
   203  		if !ok {
   204  			return nil
   205  		}
   206  	case *terraform.InstanceState:
   207  		rawMeta, ok = rawInstance.Meta[TimeoutKey]
   208  		if !ok {
   209  			return nil
   210  		}
   211  	default:
   212  		return fmt.Errorf("Unknown or unsupported type in metaDecode: %#v", ids)
   213  	}
   214  
   215  	times := rawMeta.(map[string]interface{})
   216  	if len(times) == 0 {
   217  		return nil
   218  	}
   219  
   220  	if v, ok := times[TimeoutCreate]; ok {
   221  		t.Create = DefaultTimeout(v)
   222  	}
   223  	if v, ok := times[TimeoutRead]; ok {
   224  		t.Read = DefaultTimeout(v)
   225  	}
   226  	if v, ok := times[TimeoutUpdate]; ok {
   227  		t.Update = DefaultTimeout(v)
   228  	}
   229  	if v, ok := times[TimeoutDelete]; ok {
   230  		t.Delete = DefaultTimeout(v)
   231  	}
   232  	if v, ok := times[TimeoutDefault]; ok {
   233  		t.Default = DefaultTimeout(v)
   234  	}
   235  
   236  	return nil
   237  }