Skip to content

Commit

Permalink
Add extra var functionality (#11)
Browse files Browse the repository at this point in the history
* Add extra var functionality

* Newlines and ignore rise
  • Loading branch information
cam-stitt committed Jan 22, 2019
1 parent 3ab177a commit 2bf57a5
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ docs/_build
.idea
_book
node_modules
rise
11 changes: 7 additions & 4 deletions cmd/rise.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ const version = "v0.2.0"
var inputs string
var outputs string
var configFiles []string
var extraVars []string

func init() {
RootCmd.PersistentFlags().StringVarP(&inputs, "input", "i", "", "The file to perform interpolation on")
RootCmd.PersistentFlags().StringVarP(&outputs, "output", "o", "", "The file to output")
RootCmd.PersistentFlags().StringSliceVarP(&configFiles, "config", "c", []string{}, "The files that define the configuration to use for interpolation")
flags := RootCmd.PersistentFlags()
flags.StringVarP(&inputs, "input", "i", "", "The file to perform interpolation on")
flags.StringVarP(&outputs, "output", "o", "", "The file to output")
flags.StringSliceVarP(&configFiles, "config", "c", []string{}, "The files that define the configuration to use for interpolation")
flags.StringArrayVar(&extraVars, "var", []string{}, "Additional variables to apply. These always take priority.")

RootCmd.AddCommand(versionCmd)
}
Expand All @@ -30,7 +33,7 @@ var RootCmd = &cobra.Command{
if inputs == "" {
log.Fatal("Must have an input")
}
err := Run(inputs, outputs, configFiles)
err := Run(inputs, outputs, configFiles, extraVars)
if err != nil {
log.Fatal(err)
}
Expand Down
10 changes: 8 additions & 2 deletions cmd/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@ import (
"io/ioutil"
"os"


"github.com/openpixel/rise/internal/config"
"github.com/openpixel/rise/internal/template"
)

// Run accepts an input, output and config files and performs interpolation.
// If the output is empty, it writes to stdout
func Run(inputFile, outputFile string, configFiles []string) error {
func Run(inputFile, outputFile string, configFiles []string, extraVars []string) error {
contents, err := ioutil.ReadFile(inputFile)
if err != nil {
return err
}

configResult, err := config.LoadConfigFiles(configFiles)
extras, err := config.LoadExtras(extraVars)
if err != nil {
return err
}

configResult, err := config.LoadConfigFiles(configFiles, extras)
if err != nil {
return err
}
Expand Down
33 changes: 33 additions & 0 deletions cmd/runner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cmd

import "testing"

func BenchmarkRun_Simple(b *testing.B) {
input := "./examples/basic.txt"
output := "./examples/basic_output.txt"
configs := []string{"./examples/basic.hcl"}
extras := []string{}
for i := 0; i < b.N; i++ {
Run(input, output, configs, extras)
}
}

func BenchmarkRun_Complex(b *testing.B) {
input := "./examples/input.txt"
output := "./examples/output.txt"
configs := []string{"./examples/vars.hcl", "./examples/vars2.hcl"}
extras := []string{}
for i := 0; i < b.N; i++ {
Run(input, output, configs, extras)
}
}

func BenchmarkRun_Extras(b *testing.B) {
input := "./examples/input.txt"
output := "./examples/output.txt"
configs := []string{"./examples/vars.hcl", "./examples/vars2.hcl"}
extras := []string{`{"i": "value of i"}`}
for i := 0; i < b.N; i++ {
Run(input, output, configs, extras)
}
}
3 changes: 3 additions & 0 deletions examples/basic.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
variable "name" {
value = "Joe"
}
1 change: 1 addition & 0 deletions examples/basic.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello ${var.name}
52 changes: 39 additions & 13 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
Expand All @@ -11,20 +12,20 @@ import (
"github.com/hashicorp/hil/ast"
)

// Config is a variable file config definition
type Config struct {
Variables []VariableConfig `hcl:"variable"`
Templates []TemplateConfig `hcl:"template"`
// config is a variable file config definition
type config struct {
Variables []variableConfig `hcl:"variable"`
Templates []templateConfig `hcl:"template"`
}

// VariableConfig defines the structure for our variable config sections
type VariableConfig struct {
// variableConfig defines the structure for our variable config sections
type variableConfig struct {
Name string `hcl:",key"`
Value interface{} `hcl:"value"`
}

// TemplateConfig defines the structure for our template config sections
type TemplateConfig struct {
// templateConfig defines the structure for our template config sections
type templateConfig struct {
Name string `hcl:",key"`
Content string `hcl:"content"` // a string that contains a simple template
File string `hcl:"file"` // a reference to a file that is relative to the config file
Expand All @@ -37,8 +38,28 @@ type Result struct {
Templates map[string]ast.Variable
}

func LoadExtras(args []string) (map[string]ast.Variable, error) {
extras := map[string]ast.Variable{}
for _, extra := range args {
extraResult := map[string]interface{}{}

err := json.Unmarshal([]byte(extra), &extraResult)
if err != nil {
return nil, err
}

for k, v := range extraResult {
extras[fmt.Sprintf("var.%s", k)], err = hil.InterfaceToVariable(v)
if err != nil {
return nil, err
}
}
}
return extras, nil
}

// LoadConfigFiles will load all config files and merge values into appropriate values
func LoadConfigFiles(configFiles []string) (*Result, error) {
func LoadConfigFiles(configFiles []string, extras map[string]ast.Variable) (*Result, error) {
vars := make(map[string]ast.Variable)
templates := make(map[string]ast.Variable)
for _, file := range configFiles {
Expand All @@ -61,14 +82,19 @@ func LoadConfigFiles(configFiles []string) (*Result, error) {
}
}

// apply extras to variable map
for k, v := range extras {
vars[k] = v
}

result := &Result{
Variables: vars,
Templates: templates,
}
return result, nil
}

func prepareVariables(vars map[string]ast.Variable, config *Config) error {
func prepareVariables(vars map[string]ast.Variable, config *config) error {
for _, variable := range config.Variables {
astVar, err := hil.InterfaceToVariable(variable.Value)
if err != nil {
Expand All @@ -79,7 +105,7 @@ func prepareVariables(vars map[string]ast.Variable, config *Config) error {
return nil
}

func prepareTemplates(baseFilePath string, templates map[string]ast.Variable, config *Config) (map[string]ast.Variable, error) {
func prepareTemplates(baseFilePath string, templates map[string]ast.Variable, config *config) (map[string]ast.Variable, error) {
for _, template := range config.Templates {
var astVar ast.Variable
var err error
Expand Down Expand Up @@ -110,8 +136,8 @@ func prepareTemplates(baseFilePath string, templates map[string]ast.Variable, co
}

// parseConfig will parse the text into variable definitions
func parseConfig(text string) (*Config, error) {
result := &Config{}
func parseConfig(text string) (*config, error) {
result := &config{}

hclParseTree, err := hcl.Parse(text)
if err != nil {
Expand Down
94 changes: 83 additions & 11 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ func TestParseConfig(t *testing.T) {
testCases := []struct {
description string
input string
result []VariableConfig
result []variableConfig
error bool
}{
{
"String parse",
`variable "foo" { value = "bar"}`,
[]VariableConfig{
[]variableConfig{
{
Name: "foo",
Value: "bar",
Expand All @@ -28,7 +28,7 @@ func TestParseConfig(t *testing.T) {
{
"List parse",
`variable "foo" { value = ["bar"]}`,
[]VariableConfig{
[]variableConfig{
{
Name: "foo",
Value: []interface{}{"bar"},
Expand All @@ -39,7 +39,7 @@ func TestParseConfig(t *testing.T) {
{
"Map parse",
`variable "foo" { value = { "bar" = "zoo" } }`,
[]VariableConfig{
[]variableConfig{
{
Name: "foo",
Value: []map[string]interface{}{
Expand All @@ -54,7 +54,7 @@ func TestParseConfig(t *testing.T) {
{
"Int parse",
`variable "foo" { value = 6}`,
[]VariableConfig{
[]variableConfig{
{
Name: "foo",
Value: 6,
Expand All @@ -80,13 +80,13 @@ func TestParseConfig(t *testing.T) {
func TestPrepareVariables(t *testing.T) {
testCases := []struct {
description string
config []VariableConfig
config []variableConfig
result map[string]ast.Variable
error bool
}{
{
"Valid interpolation",
[]VariableConfig{
[]variableConfig{
{
Name: "foo",
Value: `${lower("BAR")}`,
Expand All @@ -105,7 +105,7 @@ func TestPrepareVariables(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
vars := make(map[string]ast.Variable)
config := &Config{
config := &config{
Variables: tc.config,
}
err := prepareVariables(vars, config)
Expand All @@ -119,16 +119,57 @@ func TestPrepareVariables(t *testing.T) {
}
}

func TestLoadExtras(t *testing.T) {
testCases := []struct {
desc string
args []string
expectation map[string]ast.Variable
error bool
}{
{
"successfully load vars",
[]string{`{"i": "foo"}`},
map[string]ast.Variable{
"var.i": {
Value: "foo",
Type: ast.TypeString,
},
},
false,
},
{
"invalid json",
[]string{"s"},
nil,
true,
},
}

for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
result, err := LoadExtras(tC.args)
if err != nil != tC.error {
t.Fatalf("Unexpected error: %s", err)
}
if !reflect.DeepEqual(result, tC.expectation) {
t.Fatalf("Wrong results:\nGot: %#v\nWanted: %#v\n", result, tC.expectation)
}
})
}
}

func TestLoadConfigFiles(t *testing.T) {
testCases := []struct {
description string
filenames []string
extras map[string]ast.Variable
result *Result
error bool
}{
{
"Config file inheritance",
"config file inheritance",
[]string{"testdata/var1.hcl", "testdata/var2.hcl"},
nil,
&Result{
Variables: map[string]ast.Variable{
"var.i": ast.Variable{
Expand All @@ -150,8 +191,9 @@ func TestLoadConfigFiles(t *testing.T) {
false,
},
{
"Config with file template",
"config with file template",
[]string{"testdata/var3.hcl"},
nil,
&Result{
Variables: map[string]ast.Variable{
"var.j": ast.Variable{
Expand All @@ -168,17 +210,47 @@ func TestLoadConfigFiles(t *testing.T) {
},
false,
},
{
"config with extras",
[]string{"testdata/var3.hcl"},
map[string]ast.Variable{
"var.h": {
Value: "foo",
Type: ast.TypeString,
},
},
&Result{
Variables: map[string]ast.Variable{
"var.j": ast.Variable{
Value: "world",
Type: ast.TypeString,
},
"var.h": {
Value: "foo",
Type: ast.TypeString,
},
},
Templates: map[string]ast.Variable{
"tmpl.advanced": ast.Variable{
Value: "hello, ${var.j}",
Type: ast.TypeString,
},
},
},
false,
},
{
"Bad file should error",
[]string{"bad"},
nil,
nil,
true,
},
}

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
res, err := LoadConfigFiles(tc.filenames)
res, err := LoadConfigFiles(tc.filenames, tc.extras)
if err != nil != tc.error {
t.Fatalf("unexpected error: %s", err)
}
Expand Down

0 comments on commit 2bf57a5

Please sign in to comment.