github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/resource_timeout.go (about)

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