Skip to content

Commit

Permalink
implement variables for aviator files
Browse files Browse the repository at this point in the history
  • Loading branch information
herrjulz committed Aug 30, 2018
1 parent 986df00 commit 45b2895
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 19 deletions.
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ If you have to handle rather complex YAML files (for BOSH or Concourse), you jus
### OS X

```
$ wget -O /usr/local/bin/aviator https://github.com/JulzDiverse/aviator/releases/download/v0.19.0/aviator-darwin-amd64 && chmod +x /usr/local/bin/aviator
$ wget -O /usr/local/bin/aviator https://github.com/JulzDiverse/aviator/releases/download/v0.20.0/aviator-darwin-amd64 && chmod +x /usr/local/bin/aviator
```

**Via Homebrew**
Expand All @@ -26,13 +26,13 @@ $ brew install aviator
### Linux

```
$ wget -O /usr/bin/aviator https://github.com/JulzDiverse/aviator/releases/download/v0.19.0/aviator-linux-amd64 && chmod +x /usr/bin/aviator
$ wget -O /usr/bin/aviator https://github.com/JulzDiverse/aviator/releases/download/v0.20.0/aviator-linux-amd64 && chmod +x /usr/bin/aviator
```

### Windows (NOT TESTED)

```
https://github.com/JulzDiverse/aviator/releases/download/v0.19.0/aviator-win
https://github.com/JulzDiverse/aviator/releases/download/v0.20.0/aviator-win
```

## Executors
Expand Down Expand Up @@ -459,6 +459,34 @@ spruce:
to: result.yml
```

#### Variables

You can provide variables to aviator files using the `--var` flag. Basic CLI usage:

`$ aviator --var key=value`

In you aviator file you need to specify the name of the variable you want to interpolate. The syntax for a variable is the following `(( varName ))` (note the space before and after the variable name!)

Example:

```yaml
---
spruce:
- base: (( key ))
...
```

In this example the variable `key` will be replaced by the value `value`. So the result would look like this:

```yaml
---
spruce:
- base: value
...
```

Values for aviator variables can be multi-line

#### Modifier

With modifier you can modify the resulting (merged) YAML file. You can either delete, set, or update a property. The modifier will always be applied on the result. If you use `for_each` it will be applied on each `for_each` merge step.
Expand Down
10 changes: 7 additions & 3 deletions cmd/aviator/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func setCli() *cli.App {
}
cmd.Name = "Aviator"
cmd.Usage = "Navigate to a aviator.yml file and run aviator"
cmd.Version = "0.19.0"
cmd.Version = "0.20.0"
cmd.Flags = getFlags()
return cmd
}
Expand All @@ -23,16 +23,20 @@ func getFlags() []cli.Flag {
cli.StringFlag{
Name: "file, f",
Value: "aviator.yml",
Usage: "Path to a AVIATOR YAML",
Usage: "Specifies a path to an aviator yaml",
},
cli.BoolFlag{
Name: "verbose, vv",
Usage: "print warnings",
Usage: "prints warnings",
},
cli.BoolFlag{
Name: "silent, s",
Usage: "silent mode (no prints)",
},
cli.StringSliceFlag{
Name: "var",
Usage: "provides a variable to an aviator file: [key=value]",
},
}
return flags
}
15 changes: 13 additions & 2 deletions cmd/aviator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"os"
"strings"

"github.com/JulzDiverse/aviator/cockpit"
"github.com/JulzDiverse/aviator/validator"
Expand All @@ -15,19 +16,20 @@ func main() {
cmd := setCli()

cmd.Action = func(c *cli.Context) error {

aviatorFile := c.String("file")
if !verifyAviatorFileExists(aviatorFile) {

exitWithNoAviatorFile()

} else {
vars := c.StringSlice("var")
varsMap := varsToMap(vars)

aviatorYml, err := ioutil.ReadFile(aviatorFile)
exitWithError(err)

cockpit := cockpit.New()
aviator, err := cockpit.NewAviator(aviatorYml)
aviator, err := cockpit.NewAviator(aviatorYml, varsMap)
handleError(err)

err = aviator.ProcessSprucePlan(c.Bool("verbose"), c.Bool("silent"))
Expand All @@ -45,6 +47,15 @@ func main() {
cmd.Run(os.Args)
}

func varsToMap(vars []string) map[string]string {
result := map[string]string{}
for _, v := range vars {
sl := strings.Split(v, "=")
result[sl[0]] = sl[1]
}
return result
}

func verifyAviatorFileExists(file string) bool {
if file == "aviator.yml" {
if _, err := os.Stat(file); !os.IsNotExist(err) {
Expand Down
8 changes: 7 additions & 1 deletion cockpit/cockpit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"regexp"

"github.com/JulzDiverse/aviator"
"github.com/JulzDiverse/aviator/evaluator"
"github.com/JulzDiverse/aviator/executor"
"github.com/JulzDiverse/aviator/processor"
"github.com/JulzDiverse/aviator/validator"
Expand Down Expand Up @@ -41,13 +42,18 @@ func New() *Cockpit {
}
}

func (c *Cockpit) NewAviator(aviatorYml []byte) (*Aviator, error) {
func (c *Cockpit) NewAviator(aviatorYml []byte, varsMap map[string]string) (*Aviator, error) {
var aviator aviator.AviatorYaml
aviatorYml, err := resolveEnvVars(aviatorYml)
if err != nil {
return nil, errors.Wrap(err, ansi.Sprintf("@R{Reading Failed}"))
}

aviatorYml, err = evaluator.Evaluate(aviatorYml, varsMap)
if err != nil {
return nil, err
}

aviatorYml = quoteCurlyBraces(aviatorYml)
err = yaml.Unmarshal(aviatorYml, &aviator)
if err != nil {
Expand Down
22 changes: 12 additions & 10 deletions cockpit/cockpit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ var _ = Describe("Cockpit", func() {
var flyExecuter *fakes.FakeFlyExecuter
var validator *fakes.FakeValidator
var cockpit *Cockpit
var vars map[string]string

BeforeEach(func() {
spruceProcessor = new(fakes.FakeSpruceProcessor)
flyExecuter = new(fakes.FakeFlyExecuter)
validator = new(fakes.FakeValidator)
cockpit = Init(spruceProcessor, flyExecuter, validator)
vars = map[string]string{}
})

Context("New", func() {
Expand All @@ -49,7 +51,7 @@ var _ = Describe("Cockpit", func() {
to: result.yml`

var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).ToNot(HaveOccurred())

Expect(len(aviator.AviatorYaml.Spruce[0].Merge[0].With.Files)).To(Equal(2))
Expand All @@ -74,7 +76,7 @@ var _ = Describe("Cockpit", func() {
to_dir: foo/bar/`

var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).ToNot(HaveOccurred())

Expect(len(aviator.AviatorYaml.Spruce[0].CherryPicks)).To(Equal(3))
Expand All @@ -95,7 +97,7 @@ var _ = Describe("Cockpit", func() {
to: $RESULT`

var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).ToNot(HaveOccurred())
Expect(aviator.AviatorYaml.Spruce[0].Base).To(Equal("envVar"))
Expect(aviator.AviatorYaml.Spruce[0].Merge[0].With.Files[0]).To(Equal("another"))
Expand All @@ -115,7 +117,7 @@ var _ = Describe("Cockpit", func() {
to: $RESULT`

var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).To(HaveOccurred())
})

Expand All @@ -132,7 +134,7 @@ var _ = Describe("Cockpit", func() {
to: {{result}}`

var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).ToNot(HaveOccurred())
})
})
Expand All @@ -153,7 +155,7 @@ var _ = Describe("Cockpit", func() {
to_dir: some/tmp/dir/`

var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).ToNot(HaveOccurred())

Expect(len(aviator.AviatorYaml.Spruce[0].ForEach.Files)).To(Equal(2))
Expand All @@ -176,7 +178,7 @@ var _ = Describe("Cockpit", func() {
to_dir: some/tmp/dir/`

var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).ToNot(HaveOccurred())

Expect(aviator.AviatorYaml.Spruce[0].ForEach.In).To(Equal("path/"))
Expand All @@ -197,7 +199,7 @@ var _ = Describe("Cockpit", func() {
to_dir: some/tmp/dir/`

var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).ToNot(HaveOccurred())

Expect(aviator.AviatorYaml.Spruce[0].ForEach.SubDirs).To(Equal(true))
Expand All @@ -222,7 +224,7 @@ var _ = Describe("Cockpit", func() {

It("is able to read all properties from the fly section", func() {
var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).ToNot(HaveOccurred())

Expect(aviator.AviatorYaml.Fly.Name).To(Equal("pipelineName"))
Expand Down Expand Up @@ -250,7 +252,7 @@ var _ = Describe("Cockpit", func() {

It("returns a valid error message", func() {
var err error
aviator, err = cockpit.NewAviator([]byte(aviatorYaml))
aviator, err = cockpit.NewAviator([]byte(aviatorYaml), vars)
Expect(err).ToNot(HaveOccurred())
err = aviator.ProcessSprucePlan(false, false)

Expand Down
32 changes: 32 additions & 0 deletions evaluator/evaluate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package evaluator

import (
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
)

var variableFormatRegex = regexp.MustCompile(`\(\(\s([-\w\p{L}]+)\s\)\)`)

func Evaluate(aviatorFile []byte, vars map[string]string) ([]byte, error) {
var err error
return variableFormatRegex.ReplaceAllFunc(aviatorFile, func(match []byte) []byte {
key := string(variableFormatRegex.FindSubmatch(match)[1])

val, ok := vars[key]
if !ok {
err = errors.New(fmt.Sprintf("Variable (( %s )) not provided", key))
}

var replace []byte
if strings.Contains(val, "\n") {
replace, _ = json.Marshal(val)
} else {
replace = []byte(val)
}

return []byte(replace)
}), err
}
90 changes: 90 additions & 0 deletions evaluator/evaluate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package evaluator_test

import (
"strings"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

. "github.com/JulzDiverse/aviator/evaluator"
)

var _ = Describe("Evaluate", func() {
Context("When evaluating an aviator file variable", func() {

var (
aviatorYaml string
expectedYaml string
evaluated []byte
err error
vars map[string]string
)

JustBeforeEach(func() {
evaluated, err = Evaluate([]byte(aviatorYaml), vars)
})

Context("When the varialbe key is an existing key", func() {
Context("When the value is a usual value", func() {
BeforeEach(func() {
vars = map[string]string{
"base_yaml": "base.yml",
}

aviatorYaml = `---
spruce:
- base: (( base_yaml )).old`

expectedYaml = `---
spruce:
- base: base.yml.old`
})

It("should replace the key with it's provided value", func() {
Expect(err).ToNot(HaveOccurred())
Expect(strings.TrimSpace(string(evaluated))).To(Equal(expectedYaml))
})
})

Context("When the value is a mulit-line value", func() {
BeforeEach(func() {
vars = map[string]string{
"multi_line": `hello
world`,
}

aviatorYaml = `---
fly:
vars:
key: (( multi_line ))`

expectedYaml = `---
fly:
vars:
key: "hello\nworld"`

})

It("should replace the key with it's provided value", func() {
Expect(err).ToNot(HaveOccurred())
Expect(strings.TrimSpace(string(evaluated))).To(Equal(expectedYaml))
})
})
})

Context("When the variable key is not existing", func() {
BeforeEach(func() {
aviatorYaml = `---
key: (( not_provided ))`
})

It("should fail", func() {
Expect(err).To(HaveOccurred())
})

It("should return a meaningful error message", func() {
Expect(err).To(MatchError(ContainSubstring("Variable (( not_provided )) not provided")))
})
})
})
})
Loading

0 comments on commit 45b2895

Please sign in to comment.