github.com/dtx/terraform@v0.7.4-0.20160907225157-926acfd0821e/command/flag_kv.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/hcl"
    10  	"github.com/mitchellh/go-homedir"
    11  )
    12  
    13  // FlagTypedKVis a flag.Value implementation for parsing user variables
    14  // from the command-line in the format of '-var key=value', where value is
    15  // a type intended for use as a Terraform variable
    16  type FlagTypedKV map[string]interface{}
    17  
    18  func (v *FlagTypedKV) String() string {
    19  	return ""
    20  }
    21  
    22  func (v *FlagTypedKV) Set(raw string) error {
    23  	key, value, err := parseVarFlagAsHCL(raw)
    24  	if err != nil {
    25  		return err
    26  	}
    27  
    28  	if *v == nil {
    29  		*v = make(map[string]interface{})
    30  	}
    31  
    32  	(*v)[key] = value
    33  	return nil
    34  }
    35  
    36  // FlagStringKV is a flag.Value implementation for parsing user variables
    37  // from the command-line in the format of '-var key=value', where value is
    38  // only ever a primitive.
    39  type FlagStringKV map[string]string
    40  
    41  func (v *FlagStringKV) String() string {
    42  	return ""
    43  }
    44  
    45  func (v *FlagStringKV) Set(raw string) error {
    46  	idx := strings.Index(raw, "=")
    47  	if idx == -1 {
    48  		return fmt.Errorf("No '=' value in arg: %s", raw)
    49  	}
    50  
    51  	if *v == nil {
    52  		*v = make(map[string]string)
    53  	}
    54  
    55  	key, value := raw[0:idx], raw[idx+1:]
    56  	(*v)[key] = value
    57  	return nil
    58  }
    59  
    60  // FlagKVFile is a flag.Value implementation for parsing user variables
    61  // from the command line in the form of files. i.e. '-var-file=foo'
    62  type FlagKVFile map[string]interface{}
    63  
    64  func (v *FlagKVFile) String() string {
    65  	return ""
    66  }
    67  
    68  func (v *FlagKVFile) Set(raw string) error {
    69  	vs, err := loadKVFile(raw)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	if *v == nil {
    75  		*v = make(map[string]interface{})
    76  	}
    77  
    78  	for key, value := range vs {
    79  		(*v)[key] = value
    80  	}
    81  
    82  	return nil
    83  }
    84  
    85  func loadKVFile(rawPath string) (map[string]interface{}, error) {
    86  	path, err := homedir.Expand(rawPath)
    87  	if err != nil {
    88  		return nil, fmt.Errorf(
    89  			"Error expanding path: %s", err)
    90  	}
    91  
    92  	// Read the HCL file and prepare for parsing
    93  	d, err := ioutil.ReadFile(path)
    94  	if err != nil {
    95  		return nil, fmt.Errorf(
    96  			"Error reading %s: %s", path, err)
    97  	}
    98  
    99  	// Parse it
   100  	obj, err := hcl.Parse(string(d))
   101  	if err != nil {
   102  		return nil, fmt.Errorf(
   103  			"Error parsing %s: %s", path, err)
   104  	}
   105  
   106  	var result map[string]interface{}
   107  	if err := hcl.DecodeObject(&result, obj); err != nil {
   108  		return nil, fmt.Errorf(
   109  			"Error decoding Terraform vars file: %s\n\n"+
   110  				"The vars file should be in the format of `key = \"value\"`.\n"+
   111  				"Decoding errors are usually caused by an invalid format.",
   112  			err)
   113  	}
   114  
   115  	return result, nil
   116  }
   117  
   118  // FlagStringSlice is a flag.Value implementation for parsing targets from the
   119  // command line, e.g. -target=aws_instance.foo -target=aws_vpc.bar
   120  
   121  type FlagStringSlice []string
   122  
   123  func (v *FlagStringSlice) String() string {
   124  	return ""
   125  }
   126  func (v *FlagStringSlice) Set(raw string) error {
   127  	*v = append(*v, raw)
   128  
   129  	return nil
   130  }
   131  
   132  var (
   133  	// This regular expression is how we check if a value for a variable
   134  	// matches what we'd expect a rich HCL value to be. For example: {
   135  	// definitely signals a map. If a value DOESN'T match this, we return
   136  	// it as a raw string.
   137  	varFlagHCLRe = regexp.MustCompile(`^["\[\{]`)
   138  )
   139  
   140  // parseVarFlagAsHCL parses the value of a single variable as would have been specified
   141  // on the command line via -var or in an environment variable named TF_VAR_x, where x is
   142  // the name of the variable. In order to get around the restriction of HCL requiring a
   143  // top level object, we prepend a sentinel key, decode the user-specified value as its
   144  // value and pull the value back out of the resulting map.
   145  func parseVarFlagAsHCL(input string) (string, interface{}, error) {
   146  	idx := strings.Index(input, "=")
   147  	if idx == -1 {
   148  		return "", nil, fmt.Errorf("No '=' value in variable: %s", input)
   149  	}
   150  	probablyName := input[0:idx]
   151  
   152  	parsed, err := hcl.Parse(input)
   153  	if err != nil {
   154  		value := input[idx+1:]
   155  
   156  		// If it didn't parse as HCL, we check if it doesn't match our
   157  		// whitelist of TF-accepted HCL types for inputs. If not, then
   158  		// we let it through as a raw string.
   159  		trimmed := strings.TrimSpace(value)
   160  		if !varFlagHCLRe.MatchString(trimmed) {
   161  			return probablyName, value, nil
   162  		}
   163  
   164  		// This covers flags of the form `foo=bar` which is not valid HCL
   165  		// At this point, probablyName is actually the name, and the remainder
   166  		// of the expression after the equals sign is the value.
   167  		if regexp.MustCompile(`Unknown token: \d+:\d+ IDENT`).Match([]byte(err.Error())) {
   168  			return probablyName, value, nil
   169  		}
   170  
   171  		return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL: %s", probablyName, input, err)
   172  	}
   173  
   174  	var decoded map[string]interface{}
   175  	if hcl.DecodeObject(&decoded, parsed); err != nil {
   176  		return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL: %s", probablyName, input, err)
   177  	}
   178  
   179  	// Cover cases such as key=
   180  	if len(decoded) == 0 {
   181  		return probablyName, "", nil
   182  	}
   183  
   184  	if len(decoded) > 1 {
   185  		return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. Only one value may be specified.", probablyName, input)
   186  	}
   187  
   188  	for k, v := range decoded {
   189  		return k, v, nil
   190  	}
   191  
   192  	// Should be unreachable
   193  	return "", nil, fmt.Errorf("No value for variable: %s", input)
   194  }