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