From b817b60183044acf0930756cd3b55d5116c2220f Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 10 Mar 2016 13:36:17 -0500 Subject: [PATCH] Parse HCL keys in command config --- command/config.go | 69 ++++++++++++++++++++++++++++++++++-------- command/config_test.go | 26 ++++++++++++++++ 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/command/config.go b/command/config.go index 73b0a4a9c..8078b016a 100644 --- a/command/config.go +++ b/command/config.go @@ -5,7 +5,9 @@ import ( "io/ioutil" "os" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/hcl" + "github.com/hashicorp/hcl/hcl/ast" "github.com/mitchellh/go-homedir" ) @@ -44,22 +46,65 @@ func LoadConfig(path string) (*Config, error) { return nil, fmt.Errorf("Error expanding config path: %s", err) } - var config Config contents, err := ioutil.ReadFile(path) - if !os.IsNotExist(err) { - if err != nil { - return nil, err - } + if err != nil && !os.IsNotExist(err) { + return nil, err + } - obj, err := hcl.Parse(string(contents)) - if err != nil { - return nil, err - } + return ParseConfig(string(contents)) +} - if err := hcl.DecodeObject(&config, obj); err != nil { - return nil, err +// ParseConfig parses the given configuration as a string. +func ParseConfig(contents string) (*Config, error) { + root, err := hcl.Parse(contents) + if err != nil { + return nil, err + } + + // Top-level item should be the object list + list, ok := root.Node.(*ast.ObjectList) + if !ok { + return nil, fmt.Errorf("Failed to parse config: does not contain a root object") + } + + valid := []string{ + "token_helper", + } + if err := checkHCLKeys(list, valid); err != nil { + return nil, err + } + + var c Config + if err := hcl.DecodeObject(&c, list); err != nil { + return nil, err + } + return &c, nil +} + +func checkHCLKeys(node ast.Node, valid []string) error { + var list *ast.ObjectList + switch n := node.(type) { + case *ast.ObjectList: + list = n + case *ast.ObjectType: + list = n.List + default: + return fmt.Errorf("cannot check HCL keys of type %T", n) + } + + validMap := make(map[string]struct{}, len(valid)) + for _, v := range valid { + validMap[v] = struct{}{} + } + + var result error + for _, item := range list.Items { + key := item.Keys[0].Token.Value().(string) + if _, ok := validMap[key]; !ok { + result = multierror.Append(result, fmt.Errorf( + "invalid key '%s' on line %d", key, item.Assign.Line)) } } - return &config, nil + return result } diff --git a/command/config_test.go b/command/config_test.go index 3ef182d9e..e8c94d3d5 100644 --- a/command/config_test.go +++ b/command/config_test.go @@ -3,6 +3,7 @@ package command import ( "path/filepath" "reflect" + "strings" "testing" ) @@ -19,3 +20,28 @@ func TestLoadConfig(t *testing.T) { t.Fatalf("bad: %#v", config) } } + +func TestLoadConfig_noExist(t *testing.T) { + config, err := LoadConfig("nope/not-once/.never") + if err != nil { + t.Fatal(err) + } + + if config.TokenHelper != "" { + t.Errorf("expected %q to be %q", config.TokenHelper, "") + } +} + +func TestParseConfig_badKeys(t *testing.T) { + _, err := ParseConfig(` +token_helper = "/token" +nope = "true" +`) + if err == nil { + t.Fatal("expected error") + } + + if !strings.Contains(err.Error(), "invalid key 'nope' on line 3") { + t.Errorf("bad error: %s", err.Error()) + } +}