From f1a4ab27ab53940de75bdb9f3a907270debccbd1 Mon Sep 17 00:00:00 2001 From: Paul Annesley Date: Tue, 28 Aug 2018 23:34:46 +1000 Subject: [PATCH] Support --tags for YAML tag files. --- Makefile | 2 +- README.md | 41 ++++++++++++++++++++++++++++++++++++ example/tags-production.yaml | 3 +++ main.go | 23 ++++++++++++++++---- main_test.go | 18 ++++++++++++++++ tags.go | 25 ++++++++++++++++++++++ 6 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 example/tags-production.yaml create mode 100644 tags.go diff --git a/Makefile b/Makefile index ba7d6bc..db29e8d 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PACKAGE = github.com/cultureamp/cfparams VERSION = $(shell git describe --tags --candidates=1 --dirty) FLAGS=-X main.Version=$(VERSION) -s -w -cfparams: main.go parameters.go template.go parameterstore/store.go +cfparams: main.go parameters.go tags.go template.go parameterstore/store.go go build -ldflags="$(FLAGS)" .PHONY: install diff --git a/README.md b/README.md index d743873..34d1aa6 100644 --- a/README.md +++ b/README.md @@ -190,3 +190,44 @@ Resulting JSON: {"ParameterKey": "Cluster", "ParameterValue": "staging"} ] ``` + +### Bonus feature: Stack Tags + +CloudFormation stacks can be tagged, and those tags flow into all taggable +resources the stack creates. As with `--parameters`, the `aws cloudformation` +commands expect these in an awkward format. `cfparams --tags file.yaml` helps. + +```sh +aws cloudformation create-stack \ + ... \ + --tags "$(cfparams --tags=tags-production.yaml)" \ + ... +``` + +```yaml +# tags-production.yaml +Name: Widgets as a Service +asset: widget-api +workload: production +``` + +```sh +cfparams --tags=tags-production.yaml +``` + +```json +[ + { + "Key": "Name", + "Value": "Widgets as a Service" + }, + { + "Key": "asset", + "Value": "widget-api" + }, + { + "Key": "workload", + "Value": "production" + } +] +``` diff --git a/example/tags-production.yaml b/example/tags-production.yaml new file mode 100644 index 0000000..a5e09e3 --- /dev/null +++ b/example/tags-production.yaml @@ -0,0 +1,3 @@ +Name: Widgets as a Service +asset: widget-api +workload: production diff --git a/main.go b/main.go index 700c7e5..b8a20cc 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ var Version = "dev" type Input struct { TemplateBody []byte + TagsBody []byte AcceptDefaults bool NoPrevious bool ParametersCLI []string @@ -21,7 +22,7 @@ type Input struct { func main() { input := &Input{} - var tplFile, paramFile string + var tplFile, paramFile, tagFile string flag.Usage = func() { fmt.Fprintf(os.Stderr, "cfparams %s\n\n", Version) @@ -31,11 +32,19 @@ func main() { } flag.StringVar(&tplFile, "template", "", "CloudFormation YAML template path") flag.StringVar(¶mFile, "parameters", "", "Parameters YAML file") + flag.StringVar(&tagFile, "tags", "", "Tags YAML file") flag.BoolVar(&input.AcceptDefaults, "accept-defaults", false, "Accept defaults from CloudFormation template, omit from JSON") flag.BoolVar(&input.NoPrevious, "no-previous", false, "Disable UsePreviousValue, fail if a parameter has no default and is not specified") flag.Parse() - if tplFile != "" { + if tagFile != "" { + data, err := ioutil.ReadFile(tagFile) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot read tags file: %s\n", tplFile) + os.Exit(1) + } + input.TagsBody = data + } else if tplFile != "" { data, err := ioutil.ReadFile(tplFile) if err != nil { fmt.Fprintf(os.Stderr, "cannot read CloudFormation template: %s\n", tplFile) @@ -43,7 +52,9 @@ func main() { } input.TemplateBody = data } else { - fmt.Fprintf(os.Stderr, "CloudFormation template required, e.g: --template=cfn.yaml\n") + fmt.Fprintf(os.Stderr, "CloudFormation template or tags file required\n") + fmt.Fprintf(os.Stderr, " e.g: --template=cloudformation.yaml\n") + fmt.Fprintf(os.Stderr, " e.g: --tags=tags-foo.yaml\n") os.Exit(1) } @@ -70,5 +81,9 @@ func main() { } func getJsonForInput(input *Input) ([]byte, error) { - return getJsonForInputParams(input) // parameters.go + if len(input.TagsBody) > 0 { + return getJsonForInputTags(input) // tags.go + } else { + return getJsonForInputParams(input) // parameters.go + } } diff --git a/main_test.go b/main_test.go index 7954022..43a910c 100644 --- a/main_test.go +++ b/main_test.go @@ -165,6 +165,24 @@ func TestCustomYamlTags(t *testing.T) { require.Equal(t, false, item.UsePreviousValue) } +func TestTags(t *testing.T) { + input := &Input{ + TagsBody: []byte("Foo: 1\nBar: two\n"), + } + j, err := getJsonForInput(input) + require.NoError(t, err) + type tagItem struct { + Key string + Value string + } + var items []tagItem + err = json.Unmarshal(j, &items) + require.ElementsMatch(t, []tagItem{ + {Key: "Foo", Value: "1"}, + {Key: "Bar", Value: "two"}, + }, items) +} + func mustGetJson(t *testing.T, input *Input) string { j, err := getJsonForInput(input) require.NoError(t, err) diff --git a/tags.go b/tags.go new file mode 100644 index 0000000..276001c --- /dev/null +++ b/tags.go @@ -0,0 +1,25 @@ +package main + +import ( + "encoding/json" + + yaml "github.com/sanathkr/go-yaml" +) + +type jsonItem struct { + Key string + Value string +} + +func getJsonForInputTags(input *Input) ([]byte, error) { + data := map[string]string{} + yaml.Unmarshal(input.TagsBody, &data) + items := []jsonItem{} + for key, value := range data { + items = append(items, jsonItem{Key: key, Value: value}) + } + for key, value := range input.Parameters { + items = append(items, jsonItem{Key: key, Value: value}) + } + return json.MarshalIndent(items, "", " ") +}