github.com/bfallik/terraform@v0.7.1-0.20160814101525-d3a4714efbf5/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  // parseVarFlagAsHCL parses the value of a single variable as would have been specified
   133  // on the command line via -var or in an environment variable named TF_VAR_x, where x is
   134  // the name of the variable. In order to get around the restriction of HCL requiring a
   135  // top level object, we prepend a sentinel key, decode the user-specified value as its
   136  // value and pull the value back out of the resulting map.
   137  func parseVarFlagAsHCL(input string) (string, interface{}, error) {
   138  	idx := strings.Index(input, "=")
   139  	if idx == -1 {
   140  		return "", nil, fmt.Errorf("No '=' value in variable: %s", input)
   141  	}
   142  	probablyName := input[0:idx]
   143  
   144  	parsed, err := hcl.Parse(input)
   145  	if err != nil {
   146  		// This covers flags of the form `foo=bar` which is not valid HCL
   147  		// At this point, probablyName is actually the name, and the remainder
   148  		// of the expression after the equals sign is the value.
   149  		if regexp.MustCompile(`Unknown token: \d+:\d+ IDENT`).Match([]byte(err.Error())) {
   150  			value := input[idx+1:]
   151  			return probablyName, value, nil
   152  		}
   153  		return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL: %s", probablyName, input, err)
   154  	}
   155  
   156  	var decoded map[string]interface{}
   157  	if hcl.DecodeObject(&decoded, parsed); err != nil {
   158  		return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL: %s", probablyName, input, err)
   159  	}
   160  
   161  	// Cover cases such as key=
   162  	if len(decoded) == 0 {
   163  		return probablyName, "", nil
   164  	}
   165  
   166  	if len(decoded) > 1 {
   167  		return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. Only one value may be specified.", probablyName, input)
   168  	}
   169  
   170  	for k, v := range decoded {
   171  		return k, v, nil
   172  	}
   173  
   174  	// Should be unreachable
   175  	return "", nil, fmt.Errorf("No value for variable: %s", input)
   176  }