github.com/sylr/terraform@v0.11.12-beta1/helper/config/validator.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/flatmap"
     9  	"github.com/hashicorp/terraform/terraform"
    10  )
    11  
    12  // Validator is a helper that helps you validate the configuration
    13  // of your resource, resource provider, etc.
    14  //
    15  // At the most basic level, set the Required and Optional lists to be
    16  // specifiers of keys that are required or optional. If a key shows up
    17  // that isn't in one of these two lists, then an error is generated.
    18  //
    19  // The "specifiers" allowed in this is a fairly rich syntax to help
    20  // describe the format of your configuration:
    21  //
    22  //   * Basic keys are just strings. For example: "foo" will match the
    23  //       "foo" key.
    24  //
    25  //   * Nested structure keys can be matched by doing
    26  //       "listener.*.foo". This will verify that there is at least one
    27  //       listener element that has the "foo" key set.
    28  //
    29  //   * The existence of a nested structure can be checked by simply
    30  //       doing "listener.*" which will verify that there is at least
    31  //       one element in the "listener" structure. This is NOT
    32  //       validating that "listener" is an array. It is validating
    33  //       that it is a nested structure in the configuration.
    34  //
    35  type Validator struct {
    36  	Required []string
    37  	Optional []string
    38  }
    39  
    40  func (v *Validator) Validate(
    41  	c *terraform.ResourceConfig) (ws []string, es []error) {
    42  	// Flatten the configuration so it is easier to reason about
    43  	flat := flatmap.Flatten(c.Raw)
    44  
    45  	keySet := make(map[string]validatorKey)
    46  	for i, vs := range [][]string{v.Required, v.Optional} {
    47  		req := i == 0
    48  		for _, k := range vs {
    49  			vk, err := newValidatorKey(k, req)
    50  			if err != nil {
    51  				es = append(es, err)
    52  				continue
    53  			}
    54  
    55  			keySet[k] = vk
    56  		}
    57  	}
    58  
    59  	purged := make([]string, 0)
    60  	for _, kv := range keySet {
    61  		p, w, e := kv.Validate(flat)
    62  		if len(w) > 0 {
    63  			ws = append(ws, w...)
    64  		}
    65  		if len(e) > 0 {
    66  			es = append(es, e...)
    67  		}
    68  
    69  		purged = append(purged, p...)
    70  	}
    71  
    72  	// Delete all the keys we processed in order to find
    73  	// the unknown keys.
    74  	for _, p := range purged {
    75  		delete(flat, p)
    76  	}
    77  
    78  	// The rest are unknown
    79  	for k, _ := range flat {
    80  		es = append(es, fmt.Errorf("Unknown configuration: %s", k))
    81  	}
    82  
    83  	return
    84  }
    85  
    86  type validatorKey interface {
    87  	// Validate validates the given configuration and returns viewed keys,
    88  	// warnings, and errors.
    89  	Validate(map[string]string) ([]string, []string, []error)
    90  }
    91  
    92  func newValidatorKey(k string, req bool) (validatorKey, error) {
    93  	var result validatorKey
    94  
    95  	parts := strings.Split(k, ".")
    96  	if len(parts) > 1 && parts[1] == "*" {
    97  		result = &nestedValidatorKey{
    98  			Parts:    parts,
    99  			Required: req,
   100  		}
   101  	} else {
   102  		result = &basicValidatorKey{
   103  			Key:      k,
   104  			Required: req,
   105  		}
   106  	}
   107  
   108  	return result, nil
   109  }
   110  
   111  // basicValidatorKey validates keys that are basic such as "foo"
   112  type basicValidatorKey struct {
   113  	Key      string
   114  	Required bool
   115  }
   116  
   117  func (v *basicValidatorKey) Validate(
   118  	m map[string]string) ([]string, []string, []error) {
   119  	for k, _ := range m {
   120  		// If we have the exact key its a match
   121  		if k == v.Key {
   122  			return []string{k}, nil, nil
   123  		}
   124  	}
   125  
   126  	if !v.Required {
   127  		return nil, nil, nil
   128  	}
   129  
   130  	return nil, nil, []error{fmt.Errorf(
   131  		"Key not found: %s", v.Key)}
   132  }
   133  
   134  type nestedValidatorKey struct {
   135  	Parts    []string
   136  	Required bool
   137  }
   138  
   139  func (v *nestedValidatorKey) validate(
   140  	m map[string]string,
   141  	prefix string,
   142  	offset int) ([]string, []string, []error) {
   143  	if offset >= len(v.Parts) {
   144  		// We're at the end. Look for a specific key.
   145  		v2 := &basicValidatorKey{Key: prefix, Required: v.Required}
   146  		return v2.Validate(m)
   147  	}
   148  
   149  	current := v.Parts[offset]
   150  
   151  	// If we're at offset 0, special case to start at the next one.
   152  	if offset == 0 {
   153  		return v.validate(m, current, offset+1)
   154  	}
   155  
   156  	// Determine if we're doing a "for all" or a specific key
   157  	if current != "*" {
   158  		// We're looking at a specific key, continue on.
   159  		return v.validate(m, prefix+"."+current, offset+1)
   160  	}
   161  
   162  	// We're doing a "for all", so we loop over.
   163  	countStr, ok := m[prefix+".#"]
   164  	if !ok {
   165  		if !v.Required {
   166  			// It wasn't required, so its no problem.
   167  			return nil, nil, nil
   168  		}
   169  
   170  		return nil, nil, []error{fmt.Errorf(
   171  			"Key not found: %s", prefix)}
   172  	}
   173  
   174  	count, err := strconv.ParseInt(countStr, 0, 0)
   175  	if err != nil {
   176  		// This shouldn't happen if flatmap works properly
   177  		panic("invalid flatmap array")
   178  	}
   179  
   180  	var e []error
   181  	var w []string
   182  	u := make([]string, 1, count+1)
   183  	u[0] = prefix + ".#"
   184  	for i := 0; i < int(count); i++ {
   185  		prefix := fmt.Sprintf("%s.%d", prefix, i)
   186  
   187  		// Mark that we saw this specific key
   188  		u = append(u, prefix)
   189  
   190  		// Mark all prefixes of this
   191  		for k, _ := range m {
   192  			if !strings.HasPrefix(k, prefix+".") {
   193  				continue
   194  			}
   195  			u = append(u, k)
   196  		}
   197  
   198  		// If we have more parts, then validate deeper
   199  		if offset+1 < len(v.Parts) {
   200  			u2, w2, e2 := v.validate(m, prefix, offset+1)
   201  
   202  			u = append(u, u2...)
   203  			w = append(w, w2...)
   204  			e = append(e, e2...)
   205  		}
   206  	}
   207  
   208  	return u, w, e
   209  }
   210  
   211  func (v *nestedValidatorKey) Validate(
   212  	m map[string]string) ([]string, []string, []error) {
   213  	return v.validate(m, "", 0)
   214  }