github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/resource_timeout.go (about)

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