From 174227e774fac7146cd145da4e20e68d62940e50 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 5 Jan 2017 11:05:09 -0800 Subject: [PATCH 01/31] bump version, only notify on change --- .travis.yml | 5 +++-- VERSION | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8def4794..2d697a38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,9 +35,10 @@ deploy: on: branch: master -## Send notifiations to 'builds' channel +## Send notifications to 'builds' channel notifications: email: false slack: + rooms: + - secure: g7a0GZU36Ix+6IhwWoPZCTDIG4JrqQj4ZW5yYU+nIwLahrxWm2FknztNrNBS/akswVAZFfvgFRsPJWY2SdLcCYGWmVb62VRlKrKiiXUFjXG2/RXRRk3Ivmm7ZMEWe9xFbvK8OwwV6aVK/A+lepB0ory5EE5kwdYCvDj2OWj3Hjeoy7vVMrVcFn+MnUek81XWRzdN0aiTeGZrDA/eT1BNyytk90Mc7AYsAsuMlRelo9s3x9bk0dVqjIpVJZ13+R/Kdoc/N8q1GznwaXScTfhG0UW/aLYLhlWy76QISsQUqM1wOPtuuRmnkezR7NOyEh8Bdo4NfuYfDFWhDru9mHuNMMXJKdh6R51uWmiTNxflZ2la2IoONDsQ/5PAhyVBqGbc30vqVF3IkLNkI+w3rGxI6+1dcBB+otpvwm6e/dQ0azHT8t5bdT2XJXndsdbDeVyAr2XHG01tHRrxZFG2eeEXCqabvNPI0vRqdo193W0eP0fnUDHUMrx1wHbVSEDrbzwM3DcOE9ZYzVv0YByXf8HHsRHNDUj+WjeeGFbYcOaGw3XQYZZ4ooqa9ttvA6dKvbrS9+JeePgfT/vKhEdUqbRZtn8zUBbNiEmT0cIaiF73kmjZh3+kep66hTji/NchAroTD/VQzgTmj2+8/p/uqU/7jSMa0COevb+xx1rgMPpHLV0= on_success: change - secure: g7a0GZU36Ix+6IhwWoPZCTDIG4JrqQj4ZW5yYU+nIwLahrxWm2FknztNrNBS/akswVAZFfvgFRsPJWY2SdLcCYGWmVb62VRlKrKiiXUFjXG2/RXRRk3Ivmm7ZMEWe9xFbvK8OwwV6aVK/A+lepB0ory5EE5kwdYCvDj2OWj3Hjeoy7vVMrVcFn+MnUek81XWRzdN0aiTeGZrDA/eT1BNyytk90Mc7AYsAsuMlRelo9s3x9bk0dVqjIpVJZ13+R/Kdoc/N8q1GznwaXScTfhG0UW/aLYLhlWy76QISsQUqM1wOPtuuRmnkezR7NOyEh8Bdo4NfuYfDFWhDru9mHuNMMXJKdh6R51uWmiTNxflZ2la2IoONDsQ/5PAhyVBqGbc30vqVF3IkLNkI+w3rGxI6+1dcBB+otpvwm6e/dQ0azHT8t5bdT2XJXndsdbDeVyAr2XHG01tHRrxZFG2eeEXCqabvNPI0vRqdo193W0eP0fnUDHUMrx1wHbVSEDrbzwM3DcOE9ZYzVv0YByXf8HHsRHNDUj+WjeeGFbYcOaGw3XQYZZ4ooqa9ttvA6dKvbrS9+JeePgfT/vKhEdUqbRZtn8zUBbNiEmT0cIaiF73kmjZh3+kep66hTji/NchAroTD/VQzgTmj2+8/p/uqU/7jSMa0COevb+xx1rgMPpHLV0= \ No newline at end of file diff --git a/VERSION b/VERSION index 17e51c38..d917d3e2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.1 +0.1.2 From 16b64d31f7877759a7da105db0be86575d1ce162 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 5 Jan 2017 16:44:10 -0800 Subject: [PATCH 02/31] refactor environments to have reference to config --- common/config.go | 11 ++++++++++ environments/list.go | 20 +++++++++++++++-- environments/show.go | 22 ++++++++++++++++--- environments/terminate.go | 21 ++++++++++++++++-- environments/upsert.go | 23 +++++++++++++++++--- main.go | 45 ++++++--------------------------------- 6 files changed, 94 insertions(+), 48 deletions(-) create mode 100644 common/config.go diff --git a/common/config.go b/common/config.go new file mode 100644 index 00000000..82eb7f4d --- /dev/null +++ b/common/config.go @@ -0,0 +1,11 @@ +package common + +// Config defines the structure of the yml file for the mu config +type Config struct { +} + +// LoadConfig creates a new config object and loads from local file +func LoadConfig() *Config { + return &Config { + } +} diff --git a/environments/list.go b/environments/list.go index 8b0794da..788b7121 100644 --- a/environments/list.go +++ b/environments/list.go @@ -2,9 +2,25 @@ package environments import( "fmt" + "github.com/stelligent/mu/common" + "github.com/urfave/cli" ) -// List all environments -func List() { +// NewListCommand returns a cli.Command to list environments +func NewListCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "list", + Aliases: []string{"ls"}, + Usage: "list environments", + Action: func(c *cli.Context) error { + runList(config) + return nil + }, + } + + return cmd +} + +func runList(config *common.Config) { fmt.Println("listing environments") } diff --git a/environments/show.go b/environments/show.go index 3a18576f..f0732d39 100644 --- a/environments/show.go +++ b/environments/show.go @@ -2,9 +2,25 @@ package environments import( "fmt" + "github.com/urfave/cli" + "github.com/stelligent/mu/common" ) -// Show enviroments -func Show(environment string) { - fmt.Printf("showing environment: %s\n",environment) +// NewShowCommand returns a cli.Command to show environments +func NewShowCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "show", + Usage: "show environment details", + ArgsUsage: "", + Action: func(c *cli.Context) error { + runShow(config, c.Args().First()) + return nil + }, + } + + return cmd } + +func runShow(config *common.Config, environment string) { + fmt.Printf("showing environment: %s\n",environment) +} \ No newline at end of file diff --git a/environments/terminate.go b/environments/terminate.go index ba860db3..117d3cd9 100644 --- a/environments/terminate.go +++ b/environments/terminate.go @@ -2,9 +2,26 @@ package environments import( "fmt" + "github.com/urfave/cli" + "github.com/stelligent/mu/common" ) -// Terminate an environment -func Terminate(environment string) { +// NewShowCommand returns a cli.Command to show environments +func NewTerminateCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "terminate", + Aliases: []string{"term"}, + Usage: "terminate an environment", + ArgsUsage: "", + Action: func(c *cli.Context) error { + runTerminate(config, c.Args().First()) + return nil + }, + } + + return cmd +} + +func runTerminate(config *common.Config, environment string) { fmt.Printf("terminating environment: %s\n",environment) } diff --git a/environments/upsert.go b/environments/upsert.go index 32bcdc83..abc166b4 100644 --- a/environments/upsert.go +++ b/environments/upsert.go @@ -2,9 +2,26 @@ package environments import( "fmt" + "github.com/urfave/cli" + "github.com/stelligent/mu/common" ) -// Upsert an environment -func Upsert(environment string) { - fmt.Printf("upserting environment: %s\n",environment) +// NewUpsertCommand returns a cli.Command to upsert environments +func NewUpsertCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "upsert", + Aliases: []string{"up"}, + Usage: "create/update an environment", + ArgsUsage: "", + Action: func(c *cli.Context) error { + runUpsert(config, c.Args().First()) + return nil + }, + } + + return cmd } + +func runUpsert(config *common.Config, environment string) { + fmt.Printf("upserting environment: %s\n",environment) +} \ No newline at end of file diff --git a/main.go b/main.go index 9ab7482b..9e663422 100644 --- a/main.go +++ b/main.go @@ -6,11 +6,14 @@ import ( "github.com/stelligent/mu/environments" "github.com/stelligent/mu/services" "github.com/stelligent/mu/pipelines" + "github.com/stelligent/mu/common" ) var version string func main() { + config := common.LoadConfig() + app := cli.NewApp() app.Name = "mu" app.Usage = "Microservice Platform on AWS" @@ -23,44 +26,10 @@ func main() { Aliases: []string{"env"}, Usage: "options for managing environments", Subcommands: []cli.Command{ - { - Name: "list", - Aliases: []string{"ls"}, - Usage: "list environments", - Action: func(c *cli.Context) error { - environments.List() - return nil - }, - }, - { - Name: "show", - Usage: "show environment details", - ArgsUsage: "", - Action: func(c *cli.Context) error { - environments.Show(c.Args().First()) - return nil - }, - }, - { - Name: "upsert", - Aliases: []string{"up"}, - Usage: "create/update an environment", - ArgsUsage: "", - Action: func(c *cli.Context) error { - environments.Upsert(c.Args().First()) - return nil - }, - }, - { - Name: "terminate", - Aliases: []string{"term"}, - Usage: "terminate an environment", - ArgsUsage: "", - Action: func(c *cli.Context) error { - environments.Terminate(c.Args().First()) - return nil - }, - }, + *environments.NewListCommand(config), + *environments.NewShowCommand(config), + *environments.NewUpsertCommand(config), + *environments.NewTerminateCommand(config), }, }, { From 17c2f6edd3e1ba90057a6496a7347be7c523fa69 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 5 Jan 2017 21:46:49 -0800 Subject: [PATCH 03/31] refator commands to increase cohesion of command definition, and to take in the yml config --- environments/commands.go | 23 ++++++++ environments/list.go | 3 +- environments/show.go | 3 +- environments/terminate.go | 3 +- environments/upsert.go | 3 +- main.go | 108 ++------------------------------------ pipelines/commands.go | 21 ++++++++ pipelines/list.go | 20 ++++++- pipelines/show.go | 24 ++++++++- services/deploy.go | 26 ++++++++- services/setenv.go | 25 ++++++++- services/show.go | 25 ++++++++- services/undeploy.go | 25 ++++++++- 13 files changed, 184 insertions(+), 125 deletions(-) create mode 100644 environments/commands.go create mode 100644 pipelines/commands.go diff --git a/environments/commands.go b/environments/commands.go new file mode 100644 index 00000000..254d399d --- /dev/null +++ b/environments/commands.go @@ -0,0 +1,23 @@ +package environments + +import( + "github.com/stelligent/mu/common" + "github.com/urfave/cli" +) + +// NewCommand returns a cli.Command with all the environment subcommands +func NewCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "environment", + Aliases: []string{"env"}, + Usage: "options for managing environments", + Subcommands: []cli.Command{ + *newListCommand(config), + *newShowCommand(config), + *newUpsertCommand(config), + *newTerminateCommand(config), + }, + } + + return cmd +} diff --git a/environments/list.go b/environments/list.go index 788b7121..cb440be9 100644 --- a/environments/list.go +++ b/environments/list.go @@ -6,8 +6,7 @@ import( "github.com/urfave/cli" ) -// NewListCommand returns a cli.Command to list environments -func NewListCommand(config *common.Config) *cli.Command { +func newListCommand(config *common.Config) *cli.Command { cmd := &cli.Command { Name: "list", Aliases: []string{"ls"}, diff --git a/environments/show.go b/environments/show.go index f0732d39..2e5e139b 100644 --- a/environments/show.go +++ b/environments/show.go @@ -6,8 +6,7 @@ import( "github.com/stelligent/mu/common" ) -// NewShowCommand returns a cli.Command to show environments -func NewShowCommand(config *common.Config) *cli.Command { +func newShowCommand(config *common.Config) *cli.Command { cmd := &cli.Command { Name: "show", Usage: "show environment details", diff --git a/environments/terminate.go b/environments/terminate.go index 117d3cd9..fa598d2d 100644 --- a/environments/terminate.go +++ b/environments/terminate.go @@ -6,8 +6,7 @@ import( "github.com/stelligent/mu/common" ) -// NewShowCommand returns a cli.Command to show environments -func NewTerminateCommand(config *common.Config) *cli.Command { +func newTerminateCommand(config *common.Config) *cli.Command { cmd := &cli.Command { Name: "terminate", Aliases: []string{"term"}, diff --git a/environments/upsert.go b/environments/upsert.go index abc166b4..82390a76 100644 --- a/environments/upsert.go +++ b/environments/upsert.go @@ -6,8 +6,7 @@ import( "github.com/stelligent/mu/common" ) -// NewUpsertCommand returns a cli.Command to upsert environments -func NewUpsertCommand(config *common.Config) *cli.Command { +func newUpsertCommand(config *common.Config) *cli.Command { cmd := &cli.Command { Name: "upsert", Aliases: []string{"up"}, diff --git a/main.go b/main.go index 9e663422..1a7c8f7b 100644 --- a/main.go +++ b/main.go @@ -21,111 +21,9 @@ func main() { app.EnableBashCompletion = true app.Commands = []cli.Command{ - { - Name: "environment", - Aliases: []string{"env"}, - Usage: "options for managing environments", - Subcommands: []cli.Command{ - *environments.NewListCommand(config), - *environments.NewShowCommand(config), - *environments.NewUpsertCommand(config), - *environments.NewTerminateCommand(config), - }, - }, - { - Name: "service", - Aliases: []string{"svc"}, - Usage: "options for managing services", - Subcommands: []cli.Command{ - { - Name: "show", - Usage: "show service details", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to show", - }, - }, - Action: func(c *cli.Context) error { - services.Show(c.String("service")) - return nil - }, - }, - { - Name: "deploy", - Usage: "deploy service to environment", - ArgsUsage: "", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to deploy", - }, - }, - Action: func(c *cli.Context) error { - services.Deploy(c.Args().First(), c.String("service")) - return nil - }, - }, - { - Name: "setenv", - Usage: "set environment variable", - ArgsUsage: " =...", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to deploy", - }, - }, - Action: func(c *cli.Context) error { - services.Setenv(c.Args().First(), c.String("service"), c.Args().Tail()) - return nil - }, - }, - { - Name: "undeploy", - Usage: "undeploy service from environment", - ArgsUsage: "", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to undeploy", - }, - }, - Action: func(c *cli.Context) error { - services.Undeploy(c.Args().First(), c.String("service")) - return nil - }, - }, - }, - }, - { - Name: "pipeline", - Usage: "options for managing pipelines", - Subcommands: []cli.Command{ - { - Name: "list", - Usage: "list pipelines", - Action: func(c *cli.Context) error { - pipelines.List() - return nil - }, - }, - { - Name: "show", - Usage: "show pipeline details", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to show", - }, - }, - Action: func(c *cli.Context) error { - pipelines.Show(c.String("service")) - return nil - }, - }, - }, - }, + *environments.NewCommand(config), + *services.NewCommand(config), + *pipelines.NewCommand(config), } app.Run(os.Args) diff --git a/pipelines/commands.go b/pipelines/commands.go new file mode 100644 index 00000000..9bdda007 --- /dev/null +++ b/pipelines/commands.go @@ -0,0 +1,21 @@ +package pipelines + +import( + "github.com/stelligent/mu/common" + "github.com/urfave/cli" +) + +// NewCommand returns a cli.Command with all the pipeline subcommands +func NewCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "pipeline", + Usage: "options for managing pipelines", + Subcommands: []cli.Command{ + *newListCommand(config), + *newShowCommand(config), + }, + } + + return cmd +} + diff --git a/pipelines/list.go b/pipelines/list.go index 3e5f8cfd..773955c9 100644 --- a/pipelines/list.go +++ b/pipelines/list.go @@ -2,9 +2,25 @@ package pipelines import( "fmt" + "github.com/stelligent/mu/common" + "github.com/urfave/cli" ) -// List pipelines -func List() { + +func newListCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "list", + Aliases: []string{"ls"}, + Usage: "list pipelines", + Action: func(c *cli.Context) error { + runList(config) + return nil + }, + } + + return cmd +} + +func runList(config *common.Config) { fmt.Println("listing pipelines") } diff --git a/pipelines/show.go b/pipelines/show.go index a3f54f57..7c1eaa60 100644 --- a/pipelines/show.go +++ b/pipelines/show.go @@ -2,9 +2,29 @@ package pipelines import( "fmt" + "github.com/urfave/cli" + "github.com/stelligent/mu/common" ) -// Show pipeline -func Show(service string) { +func newShowCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "show", + Usage: "show pipeline details", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to show", + }, + }, + Action: func(c *cli.Context) error { + runShow(config, c.String("service")) + return nil + }, + } + + return cmd +} + +func runShow(config *common.Config, service string) { fmt.Printf("showing pipeline: %s\n",service) } diff --git a/services/deploy.go b/services/deploy.go index 92c4dae1..e9280a71 100644 --- a/services/deploy.go +++ b/services/deploy.go @@ -2,9 +2,31 @@ package services import( "fmt" + "github.com/stelligent/mu/common" + "github.com/urfave/cli" ) -// Deploy a service -func Deploy(environment string, service string) { +func newDeployCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "deploy", + Usage: "deploy service to environment", + ArgsUsage: "", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to deploy", + }, + }, + Action: func(c *cli.Context) error { + runDeploy(config, c.Args().First(), c.String("service")) + return nil + }, + } + + return cmd +} + +func runDeploy(config *common.Config, environment string, service string) { fmt.Printf("deploying service: %s to environment: %s\n",service, environment) } + diff --git a/services/setenv.go b/services/setenv.go index 8909d75e..d3d0c84d 100644 --- a/services/setenv.go +++ b/services/setenv.go @@ -2,9 +2,30 @@ package services import( "fmt" + "github.com/urfave/cli" + "github.com/stelligent/mu/common" ) -// Setenv on a service -func Setenv(environment string, service string, keyvals []string) { +func newSetenvCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "setenv", + Usage: "set environment variable", + ArgsUsage: " =...", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to deploy", + }, + }, + Action: func(c *cli.Context) error { + runSetenv(config, c.Args().First(), c.String("service"), c.Args().Tail()) + return nil + }, + } + + return cmd +} + +func runSetenv(config *common.Config, environment string, service string, keyvals []string) { fmt.Printf("setenv service: %s to environment: %s with vals: %s\n",service, environment, keyvals) } diff --git a/services/show.go b/services/show.go index 49685b50..b52af6b4 100644 --- a/services/show.go +++ b/services/show.go @@ -2,9 +2,30 @@ package services import( "fmt" + "github.com/urfave/cli" + "github.com/stelligent/mu/common" ) -// Show a service -func Show(service string) { +func newShowCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "show", + Usage: "show service details", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to show", + }, + }, + Action: func(c *cli.Context) error { + runShow(config, c.String("service")) + return nil + }, + } + + return cmd +} + +func runShow(config *common.Config, service string) { fmt.Printf("showing service: %s\n",service) } + diff --git a/services/undeploy.go b/services/undeploy.go index fdea860f..73fe6a0c 100644 --- a/services/undeploy.go +++ b/services/undeploy.go @@ -2,9 +2,30 @@ package services import( "fmt" + "github.com/urfave/cli" + "github.com/stelligent/mu/common" ) -// Undeploy a service -func Undeploy(environment string, service string) { +func newUndeployCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "undeploy", + Usage: "undeploy service from environment", + ArgsUsage: "", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to undeploy", + }, + }, + Action: func(c *cli.Context) error { + runUndeploy(config, c.Args().First(), c.String("service")) + return nil + }, + } + + return cmd +} + +func runUndeploy(config *common.Config, environment string, service string) { fmt.Printf("undeploying service: %s to environment: %s\n",service, environment) } From ffb15a1aa97d61e4985f78cf56120198a934c050 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 5 Jan 2017 21:47:47 -0800 Subject: [PATCH 04/31] add services commands file --- services/commands.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 services/commands.go diff --git a/services/commands.go b/services/commands.go new file mode 100644 index 00000000..d89dbdee --- /dev/null +++ b/services/commands.go @@ -0,0 +1,29 @@ +package services + +import( + "github.com/stelligent/mu/common" + "github.com/urfave/cli" +) + +// NewCommand returns a cli.Command with all the service subcommands +func NewCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "service", + Aliases: []string{"svc"}, + Usage: "options for managing services", + Subcommands: []cli.Command{ + *newShowCommand(config), + *newDeployCommand(config), + *newSetenvCommand(config), + *newUndeployCommand(config), + }, + } + + return cmd +} + + + + + + From 63251a124c78a800a010a4f8fae06a24d85ea84a Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 5 Jan 2017 22:44:11 -0800 Subject: [PATCH 05/31] add test coverage --- Makefile | 2 +- common/config_test.go | 15 +++++++++++++++ environments/commands_test.go | 22 ++++++++++++++++++++++ environments/list_test.go | 22 ++++++++++++++++++++++ environments/show_test.go | 21 +++++++++++++++++++++ environments/terminate_test.go | 23 +++++++++++++++++++++++ environments/upsert_test.go | 23 +++++++++++++++++++++++ main.go | 6 +++++- main_test.go | 23 +++++++++++++++++++++++ pipelines/commands_test.go | 20 ++++++++++++++++++++ pipelines/list_test.go | 22 ++++++++++++++++++++++ pipelines/show_test.go | 22 ++++++++++++++++++++++ services/commands_test.go | 22 ++++++++++++++++++++++ services/deploy_test.go | 23 +++++++++++++++++++++++ services/setenv_test.go | 23 +++++++++++++++++++++++ services/show_test.go | 22 ++++++++++++++++++++++ services/undeploy_test.go | 23 +++++++++++++++++++++++ 17 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 common/config_test.go create mode 100644 environments/commands_test.go create mode 100644 environments/list_test.go create mode 100644 environments/show_test.go create mode 100644 environments/terminate_test.go create mode 100644 environments/upsert_test.go create mode 100644 main_test.go create mode 100644 pipelines/commands_test.go create mode 100644 pipelines/list_test.go create mode 100644 pipelines/show_test.go create mode 100644 services/commands_test.go create mode 100644 services/deploy_test.go create mode 100644 services/setenv_test.go create mode 100644 services/show_test.go create mode 100644 services/undeploy_test.go diff --git a/Makefile b/Makefile index 170beda0..27929d1f 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ setup: lint: setup @echo "=== linting ===" go vet ./... - golint ./... + golint -set_exit_status ./... test: lint @echo "=== testing ===" diff --git a/common/config_test.go b/common/config_test.go new file mode 100644 index 00000000..25274c1e --- /dev/null +++ b/common/config_test.go @@ -0,0 +1,15 @@ +package common + +import ( + "testing" + "github.com/stretchr/testify/assert" + +) + +func TestLoadConfig(t *testing.T) { + assert := assert.New(t) + + config := LoadConfig() + + assert.NotNil(config) +} \ No newline at end of file diff --git a/environments/commands_test.go b/environments/commands_test.go new file mode 100644 index 00000000..e34ebb38 --- /dev/null +++ b/environments/commands_test.go @@ -0,0 +1,22 @@ +package environments + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := NewCommand(config) + + assert.NotNil(command) + assert.Equal("environment", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("env", command.Aliases[0], "Aliases should match") + assert.Equal("options for managing environments", command.Usage, "Usage should match") + assert.Equal(4, len(command.Subcommands), "Subcommands len should match") +} \ No newline at end of file diff --git a/environments/list_test.go b/environments/list_test.go new file mode 100644 index 00000000..81db9ca9 --- /dev/null +++ b/environments/list_test.go @@ -0,0 +1,22 @@ +package environments + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewListCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newListCommand(config) + + assert.NotNil(command) + assert.Equal("list", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("ls", command.Aliases[0], "Aliases should match") + assert.Equal("list environments", command.Usage, "Usage should match") + assert.NotNil(command.Action) +} \ No newline at end of file diff --git a/environments/show_test.go b/environments/show_test.go new file mode 100644 index 00000000..6aa581ab --- /dev/null +++ b/environments/show_test.go @@ -0,0 +1,21 @@ +package environments + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewShowCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newShowCommand(config) + + assert.NotNil(command) + assert.Equal("show", command.Name, "Name should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.NotNil(command.Action) +} + diff --git a/environments/terminate_test.go b/environments/terminate_test.go new file mode 100644 index 00000000..119b5539 --- /dev/null +++ b/environments/terminate_test.go @@ -0,0 +1,23 @@ +package environments + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewTerminateCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newTerminateCommand(config) + + assert.NotNil(command) + assert.Equal("terminate", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("term", command.Aliases[0], "Aliases should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.NotNil(command.Action) +} + diff --git a/environments/upsert_test.go b/environments/upsert_test.go new file mode 100644 index 00000000..a9805d4b --- /dev/null +++ b/environments/upsert_test.go @@ -0,0 +1,23 @@ +package environments + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewUpsertCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newUpsertCommand(config) + + assert.NotNil(command) + assert.Equal("upsert", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("up", command.Aliases[0], "Aliases should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.NotNil(command.Action) +} + diff --git a/main.go b/main.go index 1a7c8f7b..657f50dd 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,11 @@ var version string func main() { config := common.LoadConfig() + app := newApp(config) + app.Run(os.Args) +} +func newApp(config *common.Config) *cli.App { app := cli.NewApp() app.Name = "mu" app.Usage = "Microservice Platform on AWS" @@ -26,6 +30,6 @@ func main() { *pipelines.NewCommand(config), } - app.Run(os.Args) + return app } diff --git a/main_test.go b/main_test.go new file mode 100644 index 00000000..7b9347b6 --- /dev/null +++ b/main_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewApp(t *testing.T) { + assert := assert.New(t) + config := &common.Config {} + app := newApp(config) + + assert.NotNil(app) + assert.Equal("mu", app.Name, "Name should match") + assert.Equal("Microservice Platform on AWS", app.Usage, "usage should match") + assert.Equal(true, app.EnableBashCompletion, "bash completion should match") + assert.Equal(3, len(app.Commands), "Commands len should match") + assert.Equal("environment", app.Commands[0].Name, "Command[0].name should match") + assert.Equal("service", app.Commands[1].Name, "Command[1].name should match") + assert.Equal("pipeline", app.Commands[2].Name, "Command[2].name should match") +} + diff --git a/pipelines/commands_test.go b/pipelines/commands_test.go new file mode 100644 index 00000000..717b77c5 --- /dev/null +++ b/pipelines/commands_test.go @@ -0,0 +1,20 @@ +package pipelines + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := NewCommand(config) + + assert.NotNil(command) + assert.Equal("pipeline", command.Name, "Name should match") + assert.Equal("options for managing pipelines", command.Usage, "Usage should match") + assert.Equal(2, len(command.Subcommands), "Subcommands len should match") +} \ No newline at end of file diff --git a/pipelines/list_test.go b/pipelines/list_test.go new file mode 100644 index 00000000..80c7ff30 --- /dev/null +++ b/pipelines/list_test.go @@ -0,0 +1,22 @@ +package pipelines + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewListCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newListCommand(config) + + assert.NotNil(command) + assert.Equal("list", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("ls", command.Aliases[0], "Aliases should match") + assert.Equal("list pipelines", command.Usage, "Usage should match") + assert.NotNil(command.Action) +} \ No newline at end of file diff --git a/pipelines/show_test.go b/pipelines/show_test.go new file mode 100644 index 00000000..8594a16e --- /dev/null +++ b/pipelines/show_test.go @@ -0,0 +1,22 @@ +package pipelines + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewShowCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newShowCommand(config) + + assert.NotNil(command) + assert.Equal("show", command.Name, "Name should match") + assert.Equal(1, len(command.Flags), "Flag len should match") + assert.Equal("service, s", command.Flags[0].GetName(), "Flag should match") + assert.NotNil(command.Action) +} + diff --git a/services/commands_test.go b/services/commands_test.go new file mode 100644 index 00000000..fb2cc37e --- /dev/null +++ b/services/commands_test.go @@ -0,0 +1,22 @@ +package services + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := NewCommand(config) + + assert.NotNil(command) + assert.Equal("service", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("svc", command.Aliases[0], "Aliases should match") + assert.Equal("options for managing services", command.Usage, "Usage should match") + assert.Equal(4, len(command.Subcommands), "Subcommands len should match") +} \ No newline at end of file diff --git a/services/deploy_test.go b/services/deploy_test.go new file mode 100644 index 00000000..6d166ca0 --- /dev/null +++ b/services/deploy_test.go @@ -0,0 +1,23 @@ +package services + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewDeployCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newDeployCommand(config) + + assert.NotNil(command) + assert.Equal("deploy", command.Name, "Name should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.Equal(1, len(command.Flags), "Flags length") + assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") + assert.NotNil(command.Action) +} + diff --git a/services/setenv_test.go b/services/setenv_test.go new file mode 100644 index 00000000..c13b0aaf --- /dev/null +++ b/services/setenv_test.go @@ -0,0 +1,23 @@ +package services + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewSetenvCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newSetenvCommand(config) + + assert.NotNil(command) + assert.Equal("setenv", command.Name, "Name should match") + assert.Equal(" =...", command.ArgsUsage, "ArgsUsage should match") + assert.Equal(1, len(command.Flags), "Flags length") + assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") + assert.NotNil(command.Action) +} + diff --git a/services/show_test.go b/services/show_test.go new file mode 100644 index 00000000..ef8ec813 --- /dev/null +++ b/services/show_test.go @@ -0,0 +1,22 @@ +package services + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewShowCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newShowCommand(config) + + assert.NotNil(command) + assert.Equal("show", command.Name, "Name should match") + assert.Equal(1, len(command.Flags), "Flags length") + assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") + assert.NotNil(command.Action) +} + diff --git a/services/undeploy_test.go b/services/undeploy_test.go new file mode 100644 index 00000000..6a743872 --- /dev/null +++ b/services/undeploy_test.go @@ -0,0 +1,23 @@ +package services + +import ( + "testing" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" +) + +func TestNewUndeployCommand(t *testing.T) { + assert := assert.New(t) + + config := &common.Config {} + + command := newUndeployCommand(config) + + assert.NotNil(command) + assert.Equal("undeploy", command.Name, "Name should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.Equal(1, len(command.Flags), "Flags length") + assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") + assert.NotNil(command.Action) +} + From 5e18d1d6260dc1f6121f1465b509c4473eecc7e6 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 5 Jan 2017 23:47:29 -0800 Subject: [PATCH 06/31] added ability to specify config file, test coverage --- common/config.go | 57 ++++++++++++++++++++++++++++++++++-- common/config_test.go | 67 +++++++++++++++++++++++++++++++++++++++++-- main.go | 19 ++++++++++-- main_test.go | 6 ++-- 4 files changed, 137 insertions(+), 12 deletions(-) diff --git a/common/config.go b/common/config.go index 82eb7f4d..c74cabb4 100644 --- a/common/config.go +++ b/common/config.go @@ -1,11 +1,62 @@ package common +import ( + "gopkg.in/yaml.v2" + "io/ioutil" + "fmt" + "log" +) + // Config defines the structure of the yml file for the mu config type Config struct { + Environments []Environment `yaml:"environments,omitempty"` + Service Service `yaml:"service,omitempty"` +} + +type Environment struct { + Name string `yaml:"name"` + Loadbalancer EnvironmentLoadBalancer `yaml:"loadbalancer,omitempty"` + Cluster EnvironmentCluster `yaml:"cluster,omitempty"` +} + +type EnvironmentLoadBalancer struct { + Hostname string `yaml:"hostname,omitempty"` +} + +type EnvironmentCluster struct { + DesiredCapacity int `yaml:"desiredCapacity,omitempty"` + MaxSize int `yaml:"maxSize,omitempty"` +} + +type Service struct { + DesiredCount int `yaml:"desiredCount,omitempty"` + Pipeline ServicePipeline `yaml:"pipeline,omitempty"` } -// LoadConfig creates a new config object and loads from local file -func LoadConfig() *Config { - return &Config { +type ServicePipeline struct { +} + +// NewConfig create a new config object +func NewConfig() *Config { + return &Config{} +} + +// LoadConfig loads config object from local file +func LoadConfig(config *Config, configFile string) { + yamlConfig, err := ioutil.ReadFile( configFile ) + if err != nil { + fmt.Printf("WARN: Unable to find config file: %v\n", err) + } else { + loadYamlConfig(config, yamlConfig) } + +} + +func loadYamlConfig(config *Config, yamlConfig []byte) *Config { + err := yaml.Unmarshal(yamlConfig, config) + if err != nil { + log.Panicf("Invalid config file: %v", err) + } + + return config } diff --git a/common/config_test.go b/common/config_test.go index 25274c1e..a5684e30 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -6,10 +6,71 @@ import ( ) -func TestLoadConfig(t *testing.T) { +func TestNewConfig(t *testing.T) { + assert := assert.New(t) + + config := NewConfig() + + assert.NotNil(config) +} + +func TestLoadYamlConfig(t *testing.T) { assert := assert.New(t) - config := LoadConfig() + yamlConfig := +` +--- +environments: + - name: dev + loadbalancer: + hostname: api-dev.example.com + cluster: + desiredCapacity: 1 + maxSize: 1 + - name: production + loadbalancer: + hostname: api.example.com + cluster: + desiredCapacity: 2 + maxSize: 5 +service: + desiredCount: 2 +` + + + config := NewConfig() + loadYamlConfig(config, []byte(yamlConfig)) assert.NotNil(config) -} \ No newline at end of file + assert.Equal(2,len(config.Environments)) + assert.Equal("dev",config.Environments[0].Name) + assert.Equal("api-dev.example.com",config.Environments[0].Loadbalancer.Hostname) + assert.Equal(1,config.Environments[0].Cluster.DesiredCapacity) + assert.Equal(1,config.Environments[0].Cluster.MaxSize) + assert.Equal("production",config.Environments[1].Name) + assert.Equal("api.example.com",config.Environments[1].Loadbalancer.Hostname) + assert.Equal(2,config.Environments[1].Cluster.DesiredCapacity) + assert.Equal(5,config.Environments[1].Cluster.MaxSize) + + assert.Equal(2,config.Service.DesiredCount) + +} + +func TestLoadBadYamlConfig(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } + }() + + yamlConfig := ` blah blah blah ` + + config := NewConfig() + loadYamlConfig(config, []byte(yamlConfig)) +} + +func TestLoadConfig(t *testing.T) { + + config := NewConfig() + LoadConfig(config, "foobarbaz.yml") +} diff --git a/main.go b/main.go index 657f50dd..de6d57ac 100644 --- a/main.go +++ b/main.go @@ -12,12 +12,12 @@ import ( var version string func main() { - config := common.LoadConfig() - app := newApp(config) + app := newApp() app.Run(os.Args) } -func newApp(config *common.Config) *cli.App { +func newApp() *cli.App { + config := common.NewConfig() app := cli.NewApp() app.Name = "mu" app.Usage = "Microservice Platform on AWS" @@ -30,6 +30,19 @@ func newApp(config *common.Config) *cli.App { *pipelines.NewCommand(config), } + app.Before = func(c *cli.Context) error { + common.LoadConfig(config, c.String("config")) + return nil + } + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "config, c", + Usage: "path to config file", + Value: "mu.yml", + }, + } + return app } diff --git a/main_test.go b/main_test.go index 7b9347b6..5ed824e0 100644 --- a/main_test.go +++ b/main_test.go @@ -2,19 +2,19 @@ package main import ( "testing" - "github.com/stelligent/mu/common" "github.com/stretchr/testify/assert" ) func TestNewApp(t *testing.T) { assert := assert.New(t) - config := &common.Config {} - app := newApp(config) + app := newApp() assert.NotNil(app) assert.Equal("mu", app.Name, "Name should match") assert.Equal("Microservice Platform on AWS", app.Usage, "usage should match") assert.Equal(true, app.EnableBashCompletion, "bash completion should match") + assert.Equal(1, len(app.Flags), "Flags len should match") + assert.Equal("config, c", app.Flags[0].GetName(), "Flags name should match") assert.Equal(3, len(app.Commands), "Commands len should match") assert.Equal("environment", app.Commands[0].Name, "Command[0].name should match") assert.Equal("service", app.Commands[1].Name, "Command[1].name should match") From b18ae15ac9958095c8c3ee4124b6760e2c19c7fc Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 5 Jan 2017 23:51:04 -0800 Subject: [PATCH 07/31] resolve lint findings --- common/config.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/config.go b/common/config.go index c74cabb4..9470e703 100644 --- a/common/config.go +++ b/common/config.go @@ -13,26 +13,31 @@ type Config struct { Service Service `yaml:"service,omitempty"` } +// Environment defines the env that will be created type Environment struct { Name string `yaml:"name"` Loadbalancer EnvironmentLoadBalancer `yaml:"loadbalancer,omitempty"` Cluster EnvironmentCluster `yaml:"cluster,omitempty"` } +// EnvironmentLoadBalancer defines the env load balancer that will be created type EnvironmentLoadBalancer struct { Hostname string `yaml:"hostname,omitempty"` } +// EnvironmentCluster defines the env cluster that will be created type EnvironmentCluster struct { DesiredCapacity int `yaml:"desiredCapacity,omitempty"` MaxSize int `yaml:"maxSize,omitempty"` } +// Service defines the service that will be created type Service struct { DesiredCount int `yaml:"desiredCount,omitempty"` Pipeline ServicePipeline `yaml:"pipeline,omitempty"` } +// ServicePipeline defines the service pipeline that will be created type ServicePipeline struct { } From 126da0d525f734d9ac8dded612843b931dfab6ea Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Sat, 7 Jan 2017 09:41:00 -0800 Subject: [PATCH 08/31] update year in license file --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index f0468dea..3dfe3804 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 Stelligent +Copyright (c) 2016-2017 Stelligent Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 8dbedb51991622e1739fe78ebcbd3645a6df1a84 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Sun, 8 Jan 2017 23:22:45 -0800 Subject: [PATCH 09/31] Pull environment template from yml file in assets and generate template for env upsert --- Makefile | 2 + common/config.go | 13 + common/config_test.go | 25 ++ environments/assets.go | 237 +++++++++++ environments/assets/environment-template.yml | 416 +++++++++++++++++++ environments/upsert.go | 90 +++- 6 files changed, 772 insertions(+), 11 deletions(-) create mode 100644 environments/assets.go create mode 100644 environments/assets/environment-template.yml diff --git a/Makefile b/Makefile index 27929d1f..87796e11 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,9 @@ setup: mkdir -p release go get -u "github.com/golang/lint/golint" go get -u "github.com/aktau/github-release" + go get -u "github.com/jteeuwen/go-bindata/..." go get -t -d -v ./... + go generate ./... lint: setup @echo "=== linting ===" diff --git a/common/config.go b/common/config.go index 9470e703..38b1fa01 100644 --- a/common/config.go +++ b/common/config.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "fmt" "log" + "strings" ) // Config defines the structure of the yml file for the mu config @@ -65,3 +66,15 @@ func loadYamlConfig(config *Config, yamlConfig []byte) *Config { return config } + +func GetEnvironment(config *Config, environment string) (*Environment, error) { + + for _, e := range config.Environments { + if(strings.EqualFold(environment, e.Name)) { + return &e, nil + } + } + + return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml",environment) + +} diff --git a/common/config_test.go b/common/config_test.go index a5684e30..cfb5cf86 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -74,3 +74,28 @@ func TestLoadConfig(t *testing.T) { config := NewConfig() LoadConfig(config, "foobarbaz.yml") } + +func TestGetEnvironment(t *testing.T) { + assert := assert.New(t) + config := NewConfig() + + env1 := Environment{ + Name: "foo", + } + env2 := Environment{ + Name: "bar", + } + config.Environments = []Environment{env1, env2} + + fooEnv, fooErr := GetEnvironment(config, "foo") + assert.Equal("foo", fooEnv.Name) + assert.Nil(fooErr) + + barEnv, barErr := GetEnvironment(config, "BAR") + assert.Equal("bar", barEnv.Name) + assert.Nil(barErr) + + bazEnv, bazErr := GetEnvironment(config, "baz") + assert.Nil(bazEnv) + assert.NotNil(bazErr) +} diff --git a/environments/assets.go b/environments/assets.go new file mode 100644 index 00000000..6b33a072 --- /dev/null +++ b/environments/assets.go @@ -0,0 +1,237 @@ +// Code generated by go-bindata. +// sources: +// assets/environment-template.yml +// DO NOT EDIT! + +package environments + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x5a\x7b\x4f\x1b\xb9\x16\xff\x9f\x4f\x61\x72\x2b\x65\xbb\x62\xf2\x98\x00\xa5\x23\xb1\xab\x10\xd2\x12\x95\xd0\x28\x93\x52\x69\x69\x55\x39\x1e\x27\xb1\x3a\x63\xcf\xb5\x3d\x40\xb6\xb7\xdf\xfd\xca\xf6\xbc\x1f\x59\xe8\xe3\xd2\x4b\x85\x4a\xec\xe3\xe3\xdf\xf9\xf9\x9c\x63\xfb\x38\x96\x65\xed\x0d\xdf\xbb\x0b\x1c\x84\x3e\x94\xf8\x15\xe3\x01\x94\xd7\x98\x0b\xc2\xa8\x03\xda\x76\xaf\xdf\xb3\x7a\x2f\xad\xde\xcb\xf6\xde\x0c\x72\x18\x60\x89\xb9\x70\xf6\x00\x98\x50\x21\x21\x45\x78\xb1\x0d\xb1\xfa\x0c\x80\xfe\x0b\xb8\x92\x13\xba\xd6\x0d\xe7\x58\x20\x4e\x42\xa9\x55\x25\xf2\x40\x6e\x43\x0c\x24\x03\x91\xc0\x9d\x58\x6c\x05\x23\x5f\x3a\x40\xda\x9d\x80\x20\xce\xf2\xda\x31\x85\x14\x6d\x9d\x5d\xfa\x8c\x48\xac\x12\xac\x18\x07\xd7\xb3\x51\x3d\xa2\xa1\xef\xb3\x3b\xec\x5d\x43\x3f\xc2\xc2\x28\xb5\x80\x67\xe6\x4f\x3f\x79\x04\x41\x89\xbd\x22\xb6\x4c\xe8\x1c\x0b\xc2\xb1\x37\x82\x21\x44\x44\x6e\xf3\xb6\x5f\x45\xc1\x12\xf3\xe2\xc0\x76\xbf\x5d\x05\x6f\x04\x01\x5b\x01\x12\x9b\x21\x14\x7e\x1f\x46\x14\x6d\x00\xa1\x60\xcb\x22\x0e\xc6\x23\x17\x20\x3f\x12\x52\xeb\x9c\xc2\x7b\x97\xfc\x8d\xff\x71\x3e\xbb\x66\xbe\x29\xbc\x27\x41\x14\x00\x5a\x37\xef\x06\x4a\x80\x20\x05\x4b\x1c\x03\xc0\x5e\x03\x84\x37\x78\x7b\x05\x83\x07\x2d\x77\x2c\xaa\xac\x82\x42\x30\x44\xa0\xc4\xe0\x8e\xc8\x0d\xb8\x63\xfc\x33\xe6\x19\x80\x0e\x00\x97\x18\xde\x62\xb0\xf4\x21\xfd\xac\x06\x78\x44\xc0\xa5\x8f\x81\xeb\x5e\x00\x88\x10\x16\xa2\xe4\x28\x6d\x65\xa2\xeb\x5e\xe8\xe5\xac\xf1\x0d\x37\x5a\x52\x2c\xc1\x8a\xb3\x00\xdc\x6d\x08\xda\x68\x18\x4a\xb8\xa2\xb3\x62\xc5\x94\xd0\x4b\x4c\xd7\x72\xe3\x80\xf6\x4b\x43\xe5\x14\xde\xa7\x4d\xfd\x93\x76\x11\x4b\xaf\xa3\xff\x75\x7b\x79\x07\x9b\x41\x29\x31\xa7\x0e\x68\xfd\xf6\xe1\x83\xf7\xa5\x7f\x30\xf8\xfa\xfc\xc3\x87\xce\x43\x3e\x74\xe3\x3f\xed\xaf\xcf\x5b\x5a\xe5\x88\x51\x21\x39\x24\x54\x16\x6c\x6c\x07\x91\x90\x6a\xcd\x20\xb8\x85\x3e\xf1\xc0\x68\x72\x3e\x07\x4b\x9f\xa1\xcf\x0e\xb8\xef\xe8\x7f\xdd\xfb\x4e\x7b\x6f\x8a\x25\xf4\xa0\x84\x8a\xa7\xe1\x7b\xd7\x71\x46\x3e\x8b\x3c\x13\xe8\x4a\x93\x33\xa1\x12\xf3\x15\x44\xf1\xba\xa6\x61\xfe\x9a\xb3\x28\x8c\xa3\x44\x45\xc6\x25\x5c\x62\x3f\xf9\xa8\x7e\xbc\x84\x83\x56\x1a\x8c\x23\x46\x57\x64\x1d\x71\xad\xba\x95\xca\x16\x53\x47\xf2\x63\x15\x92\x48\x7d\x87\x09\xee\x42\x5f\xec\x5a\x85\xb6\xc4\x19\x1e\x02\x76\x18\x49\x06\x5c\x04\x7d\x42\xd7\x8f\x05\x5c\x0a\xfe\x42\x5f\x1c\xa0\x45\x12\x35\x8e\x54\x49\x35\x69\x36\xf0\x98\x24\x49\x13\x90\x7f\xb6\xca\xe3\xf3\x69\xb1\x49\x45\x21\x2f\xa6\x2a\x0a\x31\x5c\x1c\xfa\x06\x6b\xe9\x35\x87\x54\xe6\x02\x05\xfc\x66\x22\x53\x65\x56\xca\x28\x7e\xfe\x10\x5d\x99\x37\xd6\xa9\x4c\x35\xd4\x26\xd3\xa2\xa6\x58\x24\x9f\x8a\xd2\xe4\x01\x10\x8b\xa8\x4c\xb5\x15\x52\x64\x51\x4b\x92\x01\x77\x6a\x19\x31\xea\x11\xe5\x08\x7a\xc1\x2e\xa0\x28\x18\xd8\x7a\x45\x1d\xe7\x8a\xc9\x56\x16\x12\xba\x69\xfc\xef\x08\xfa\xa2\xe5\x80\x9b\xfd\x39\x5e\x25\xa4\x1c\x80\x76\xfb\xe3\xde\x14\x86\x21\xa1\x6b\x11\x07\xdf\x1c\xaf\x09\xa3\x0b\x36\x9c\x4e\x8c\x92\x48\x58\x18\x0a\x69\xf5\x13\x9d\xc3\xe9\x64\x72\xee\x00\x18\x10\xcb\x5e\x0e\x96\xc7\xbd\xc3\x7e\x22\x78\x87\x85\xb4\xec\x1a\x41\x88\x8e\x4f\x5e\xd8\xc8\xec\x58\x38\x32\x82\x75\x1a\x7b\x03\x7b\x70\xb2\x7c\x61\x52\x15\x0c\x2d\xca\xb8\xdc\x34\xce\xbf\x5a\xda\xab\xbe\xfd\xf2\x28\x91\x16\x2c\x8a\xa5\xeb\x40\x1c\x0e\x8e\x0e\x5f\xf4\xed\x5e\x01\x6d\x9d\xda\xe5\x0a\xf7\x5e\x1e\x79\xab\xaa\xda\x3a\x69\xf4\xe2\x64\x75\x38\x80\x87\x89\x6d\x08\x53\xc9\xa1\x5f\x2b\x8b\xfb\xf8\x78\x75\x72\xa2\x78\x30\x3b\x80\x09\x6e\x23\x79\x3d\x1b\x25\x43\x94\x6f\x3a\xa0\x9f\x64\xee\xfe\xb1\x09\xd9\x68\xe9\x13\x34\xfc\xab\xdf\x20\x66\x1f\x16\xc4\xec\x1a\xb1\x7e\x55\x6c\x50\x23\x66\x1b\xb1\x39\x16\x2c\xe2\xc8\x1c\x44\x52\x78\x66\x43\xd2\x89\x7a\x3c\xb2\x1d\x27\x39\xcb\xcc\x38\x0b\x31\x97\x04\xa7\xc9\x64\x44\x3c\x7e\x66\xf2\xfd\xfe\x2b\x42\xbd\x09\x9d\xc2\x10\xdc\x14\x6c\x3f\x50\x8a\x0f\xcc\xde\xf0\x31\x1e\x37\xa6\x6a\x77\x3d\xa7\xc2\x8d\xc2\x90\x71\xb5\x9f\x4a\x1e\xe1\x76\xb9\xfb\x82\x09\x49\x61\x80\x45\x49\xa0\x9c\x84\x80\x76\xfb\x6a\xc6\x36\x14\x18\x34\x29\xad\x65\xf3\x4c\x77\x83\x85\xd7\x21\x9a\x78\xb1\xfe\x84\x87\x87\xda\x9d\x2e\x67\xc9\xfa\x29\x0c\x4d\xd7\x24\x7c\x4b\x2f\x75\x7a\x75\x80\x32\x2f\xf1\xa7\x5b\x48\x7c\xb8\x24\x3e\x91\xdb\xbf\x18\xc5\x0e\xd8\x77\xb1\x8f\x91\x04\x37\xa0\x77\x00\xf6\x5f\x2b\x63\x84\x0e\x6e\x33\x60\x01\xd7\xb9\x4d\xf2\x0d\xde\x3a\xe0\x0a\x4b\x75\xca\x49\xd3\x90\x3e\x6f\x3a\x31\xa4\x0a\x33\xf6\x13\x31\x63\xff\x40\x66\xfa\x3f\x85\x99\xc1\x13\x31\x33\xf8\x81\xcc\xd8\x3f\x88\x19\x7d\x4a\xa3\x58\xbe\x86\x12\xdf\xc1\x6d\x3d\x33\x25\xa1\x06\x8a\xbe\x61\xf6\xeb\xd9\xe8\x41\x00\xae\x67\xa3\xb8\x7f\x28\x25\x44\x9b\x00\xd3\xc7\x2d\x54\x69\x96\x54\xa2\x6a\x99\xc1\x36\x67\x91\xc4\x0b\x95\xb1\xea\x01\x65\xfd\x8f\x82\xf1\xcd\xae\xab\xe7\xdb\x01\x25\xbe\x3c\x84\x98\x7a\xe2\x2d\x75\x6a\x88\x6d\xc0\x99\x19\x92\xc2\x2d\x33\x90\x9d\xa9\x24\xa1\xfa\x40\x9b\x73\xfb\xe2\x3d\x05\x80\x87\x12\x9c\xe6\xef\x6c\x9e\x61\x7c\xa1\x53\xb7\x87\x1d\x01\x5a\x3b\xa0\xc1\x3a\x33\xa2\x64\x59\x3a\xf5\xa3\x28\x28\x25\xd7\xa7\x83\x6d\x7f\x07\xec\xc1\xd3\xc1\x1e\x7c\x03\xec\x38\x36\x86\xc8\xaf\x87\x98\xf5\xff\xec\x28\x9c\xd0\x25\x8b\xa8\x37\x0e\x37\x38\xc0\x1c\xfa\x33\xc6\x65\x19\xe3\x98\x4a\xde\x90\xbf\x4a\x42\x0d\x68\x33\xa9\x12\x35\x25\x3b\x01\x98\x47\x3e\x36\x55\x1a\x07\xb4\xfb\xbd\x41\x72\x86\x9a\x71\x26\x19\x62\xbe\x03\xda\xc7\xed\x9c\xec\x10\x99\xdb\x3d\xcc\x5d\x66\xc7\x6b\x8e\x85\x3a\x84\xad\xa0\x2f\xd2\x53\xd8\x8e\xd0\x56\x36\xcf\x21\x5d\xe7\x2e\x42\xaf\x38\x0b\x34\x02\xfb\xb0\x9d\x36\x2e\x98\x9a\xfe\xe8\x68\x70\xd4\xce\x98\x73\xdd\x8b\x5f\x87\xaf\xc3\x9f\xc2\x97\x06\x50\xaa\x18\x34\x73\x66\xdb\x25\xc6\x4c\x43\x4c\xd7\x85\x94\xe1\xaf\xc3\xd7\xd1\x13\xfb\xd7\x49\xaf\xc4\x95\x69\x78\x1b\x49\x4d\xd6\xaf\x43\x54\xef\xbb\x88\xca\xdf\x86\xbe\x89\xa7\x32\x4d\x69\x10\x96\x36\xbe\xb2\x31\x0f\xdc\x0b\x6a\x07\x7c\xcf\xce\xfb\x30\x9e\x4b\xdb\xdf\x53\x83\xb7\xbf\x03\xfc\xe0\xa9\xc1\x0f\x1e\x05\x7e\x8c\xc4\xc8\x14\xb5\x6a\xc0\xe9\x92\x6f\x52\xc2\x1f\x8f\xdc\x61\x24\x59\x5c\xfe\xd4\x25\xde\xca\x90\x9c\x40\xe1\x83\x96\x2e\x1f\x60\x6f\x3e\x36\x6d\xe9\xb3\x91\xba\x0b\x4d\x3c\x4c\x25\x59\x91\x04\x9a\xda\xc9\x77\x78\x5a\x6d\xa7\xbd\xab\x33\x61\xca\xdc\xcb\x0a\xf5\x5c\x5d\xbb\x33\x63\x46\x8c\x4a\x48\x28\xe6\x49\x9d\x42\x24\x77\x3b\x42\x75\xcd\x30\x7d\xa8\xc9\xca\x88\x66\x64\xbe\xac\x5b\x2d\x58\x1a\x99\xba\xb2\xf0\x88\x63\x0d\x62\xc6\x7c\x92\x95\x6a\x93\xa2\x8f\x4b\xd6\x14\xe6\x0a\xd4\x0b\x12\x60\x16\x49\x07\xcc\x16\xfd\xa3\xa9\x6e\x7e\x17\x7a\x50\xe2\xe2\xf0\xdc\x6a\xcc\x99\xaf\xfe\x33\x52\x99\xa2\x29\xa1\xa9\x89\x13\xea\x62\x7e\x4b\x50\xc1\x3a\x6d\xdf\x19\x94\x68\x53\xb6\x1b\x80\x19\x8c\x04\x56\x50\xf2\x38\xd4\xcf\x7b\x48\xe4\x5b\x5a\x04\x9f\xcf\x84\x55\x7a\x77\x7b\x55\xcd\x62\x69\xf9\xfc\x63\x85\xb6\xb7\xfe\xc1\x82\xc8\xcc\x60\x94\x2b\xf2\x25\x2d\x41\x00\xa9\x57\x28\xdd\x03\xd0\xeb\x7f\x82\x9e\xf7\x29\x29\xfa\x7e\x92\xec\x13\xca\xc7\x4c\x65\xbc\xba\xc5\x47\x4b\xf0\x9f\x52\x2f\x00\xff\xda\xef\x2e\x09\xed\x2e\xa1\xd8\x54\xfa\x30\xda\x30\x15\x64\x9f\x46\x97\xef\xdc\xc5\x78\x7e\xfa\xec\x4b\x16\x9c\x5f\x01\xf8\xe3\x0f\xd0\xc5\x12\x75\x31\x12\xea\xb7\x63\xd0\xe7\xd4\xac\x88\x8f\x4b\xc8\x5b\x7a\x04\x5a\x51\xf5\x6b\x6d\xa2\x50\x8f\x6a\x55\x61\x53\x89\xa9\x6c\x84\x7d\x13\x40\x42\x3f\x56\x9a\x85\x84\xe8\xf3\xe9\xb3\x2f\x9a\x6a\x57\x7d\x98\x78\x5f\x2b\x52\x5c\x57\xae\x13\x31\x53\xc7\x2e\x4b\x05\xcc\x53\xfe\xd4\xeb\xf5\x0e\x7b\xb9\x1d\xce\xfc\xb0\x3b\xaa\xf6\x5e\xce\x98\x2c\xf5\xac\x75\x12\xaa\xf6\x64\x66\x6f\x18\xfb\x2c\x3a\x9e\x36\x1f\x46\x92\x59\x1c\xfb\x0c\x7a\x98\x7f\x23\x11\x15\x3d\x96\x9a\xa1\x4a\x8d\xe4\x64\xbd\xc6\x5c\x9c\x86\x4c\xc8\x4e\xa4\x23\xad\x22\x14\x42\xb9\x39\x4d\x6b\xb9\x9d\x6a\x24\x74\x12\xa7\xee\x34\x7a\x73\x45\x29\xd4\xc7\x8f\xd3\x2e\x0b\x65\x17\xde\x09\xed\x6f\x0a\x35\xa1\x44\x02\xeb\x16\x58\x96\x5e\x36\x90\x5f\x36\x95\xed\xbe\x02\xcb\xe2\x31\x96\x9a\xa0\xd4\xbd\x6a\xe9\xc0\xce\x85\x04\x80\x47\x14\x8a\xd3\xd2\x92\x08\x93\x4c\x4a\xde\x29\xb6\xe2\x96\x14\x22\x32\x5e\x05\xe3\xab\xe5\x66\x00\xb0\x2e\x30\x7b\xa5\x73\x54\xbe\x5f\x44\x1c\xcf\x23\x4a\x55\xaa\x68\x92\xaa\x89\x13\x60\xde\x64\xea\xa3\x65\xa7\xe4\x3f\x38\x58\xc3\x0e\x37\x09\xe0\xda\x5c\x8e\xf3\x25\xc5\xe2\x33\xcf\x81\xd9\x20\x5a\x39\xb6\x5b\x07\xe6\xd1\x22\x2d\x2f\xba\x18\x45\x9c\xc8\x6d\xfc\xe0\x0a\x6e\xcc\x98\x0b\x26\xa4\xfb\x3a\x95\x2a\xbc\x1d\x96\x6a\xee\xd9\xf3\xe9\x04\x06\x49\xeb\x8c\x33\x45\x52\x2c\x3b\x1e\xd9\xa5\x8e\x78\x44\xf2\xc6\x05\xf6\x27\x2b\x70\x93\x7b\xf5\x8a\xa1\x17\x3f\x19\x43\xae\x98\xbe\x77\xb7\x12\x6c\xef\x04\xe6\xe7\xb9\xb4\x0d\xc0\x2b\xea\x38\x67\x50\xe0\xe3\xc3\xfc\x1a\xd5\x04\x64\x2e\x99\x02\xeb\xbe\x18\x5e\xdb\x28\x30\x8f\x74\xbe\x0f\xac\x2d\x80\x77\xc2\x52\x2b\xb4\x64\x4c\x0a\xc9\x61\x58\x10\x7e\x92\x58\xa9\x4c\x2a\xf4\xd6\x08\x2c\x0c\x9e\xfd\xf9\xb0\x99\x6b\x4e\x64\x3b\xa6\xae\x2e\x63\x65\xa3\x9d\x0c\xa7\x2a\xab\x54\xd7\xba\xea\xc1\x33\x28\x37\x0e\x68\x75\x93\xe8\x98\xb3\x5c\x50\x59\xa9\xe3\xa8\x66\x33\xb7\xfa\xab\x7e\xc2\x58\xa6\x6e\x96\xa1\x10\x51\x80\x95\x80\x39\xcc\x9c\x33\x14\x05\x2a\x41\xa7\x54\xba\x12\x4a\x5c\x6c\xb2\xc0\x78\xb5\xc2\x48\x3a\x20\x7f\x4d\x37\x13\x10\x8a\x48\x08\xfd\x62\xf4\x27\x47\x9d\xbd\x62\x90\x63\x64\x77\x60\x00\xff\x66\x14\xde\xa9\xed\x36\xc8\xf5\xc7\x97\xbc\xbd\xbc\xbc\x90\xc2\xc9\x00\x37\xf0\xa4\xed\x20\x79\xaa\x8c\x65\x26\x90\x30\x12\x56\x9c\x2b\xb3\x93\x55\x83\xe5\xb5\xb6\xef\xb2\xbe\x0e\xb5\xb1\x53\x38\xfa\xc8\x89\xb3\xc3\x7e\xb9\x5f\xb9\x91\xea\xaa\x38\x7b\x8d\xec\x39\xe6\x8f\x91\x26\x02\xb1\x5b\xcc\x67\xcc\xf7\xc7\xd4\x0b\x19\xa1\xb2\x46\xcc\x8d\x96\x01\x91\xbf\x57\x7a\xb8\x53\x6d\x13\x8e\x52\x56\x68\x4e\x76\x59\x07\xb4\x7e\x57\x4b\x61\x32\x64\xc3\x7d\x2c\x9f\x54\x9b\xae\x28\xb5\x55\x47\x3d\xa2\xf0\x5d\x9b\xf1\xc8\xd5\x73\xa5\x99\x1a\x64\x5a\x4b\xe9\x7b\x42\x4d\x91\x20\x75\x8c\x49\x98\x95\x18\x24\x0a\x0b\x95\x80\x99\x79\x7d\x2d\x56\x98\x6a\x1a\x47\xc4\xe3\x93\xb0\x5a\xb8\x1a\xfb\xcb\xff\x89\xf9\x97\x67\x3f\xc9\xf2\x62\xbd\xa8\xa6\x31\xb1\x3c\x5f\x5a\x19\x5f\x9e\xd9\x6a\x35\xe6\x51\xd3\xa3\x4f\x1d\xae\xa6\x3d\xbc\x16\x64\x0e\x62\x0a\x26\xc5\x97\x16\x6b\x34\x05\xe6\x42\x54\x98\x50\x71\xfa\x1a\xcb\xa1\x94\x66\x85\x3a\x71\x73\x9e\xe0\xbc\x90\x71\xe3\x9c\x94\x6a\xb0\xc7\x97\x67\xff\x0f\x16\x56\xc0\xd7\x9a\x58\xe6\x61\x8c\xc4\xd8\x5f\x56\x6d\xf3\xa1\x90\x04\x5d\x32\xe8\x9d\x41\x1f\x52\x44\xe8\xfa\xda\x76\x9c\xac\x21\xce\x6b\x4d\xd5\x94\xf2\xe6\xf5\xa3\x8b\x0c\xa5\xa3\x5a\x69\xa7\x54\x46\xa6\xc6\x5d\xaa\xe4\x49\xeb\x2a\x32\x4d\x46\xc6\x03\x1a\x0c\xcc\x73\x30\xe4\x34\x39\xd6\xe9\xb9\x62\x91\xf8\x4b\x89\x66\x83\xc8\xa1\x33\x93\xaf\x18\xbf\x83\xdc\xcb\x02\x0e\xf2\x35\x96\xda\x92\xb2\xbe\x58\x51\x4e\x22\xdd\xf8\x4a\x21\x9a\xf9\xd6\xc5\x62\x31\x4b\x8d\xaf\x2a\x78\x30\x0d\xe5\x49\x6b\x4e\x2d\x09\x88\x1d\x30\xea\xf2\xdb\xdb\x48\x86\x91\x71\x11\x75\x34\x7d\xc7\xe3\x13\x44\xfc\x86\xa4\x0f\xa7\x1b\x29\x43\xa7\xdb\xd5\xb7\xf6\xb1\xbf\xec\x9c\x5f\xb9\xfa\xc4\xd6\xdd\x03\xe5\x6f\x99\xaa\xb4\xf8\x6e\x7e\x59\x59\x70\x45\x66\x41\x6f\xc6\x6b\x61\x89\x0b\xca\x86\x9c\x02\xb6\x02\x72\x83\xb5\xde\x44\xb0\x53\x53\xdc\x2b\xa9\xcd\x6f\xf7\xc5\x2f\x19\xc3\x00\xa7\x3a\xb3\xaf\xbd\x75\xe2\xe7\x7c\xaf\xaa\x2d\xd9\x04\x0a\x6a\x16\x1b\x0c\x88\x97\x28\xba\x0d\xd1\xde\x7f\x03\x00\x00\xff\xff\xc8\x38\x05\x7c\x34\x2e\x00\x00") + +func assetsEnvironmentTemplateYmlBytes() ([]byte, error) { + return bindataRead( + _assetsEnvironmentTemplateYml, + "assets/environment-template.yml", + ) +} + +func assetsEnvironmentTemplateYml() (*asset, error) { + bytes, err := assetsEnvironmentTemplateYmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/environment-template.yml", size: 11828, mode: os.FileMode(420), modTime: time.Unix(1483945855, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "assets/environment-template.yml": assetsEnvironmentTemplateYml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "assets": &bintree{nil, map[string]*bintree{ + "environment-template.yml": &bintree{assetsEnvironmentTemplateYml, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + diff --git a/environments/assets/environment-template.yml b/environments/assets/environment-template.yml new file mode 100644 index 00000000..d0559a80 --- /dev/null +++ b/environments/assets/environment-template.yml @@ -0,0 +1,416 @@ +--- +AWSTemplateFormatVersion: '2010-09-09' +Parameters: + InstanceType: + Type: String + Description: Instance type to use. + Default: t2.micro + InstanceTenancy: + Description: Instance tenancy to use for VPC + Type: String + AllowedValues: + - default + - dedicated + Default: default + DesiredCapacity: + Type: Number + Default: '1' + Description: Number of instances to launch in your ECS cluster + MaxSize: + Type: Number + Default: '2' + Description: Maximum number of instances that can be launched in your ECS cluster + KeyName: + Type: String + Description: KeyName to associate with worker instances. Leave blank to disable SSH access. + Default: '' + SSHAllow: + Description: Subnet from which to allow SSH access. + Type: String + MinLength: '9' + MaxLength: '18' + Default: 0.0.0.0/0 + AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" + ConstraintDescription: 'must be a valid CIDR block: x.x.x.x/x.' +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Instance Configuration" + Parameters: + - InstanceType + - InstanceTenancy + - KeyName + - SSHAllow + - Label: + default: "Auto Scaling Configuration" + Parameters: + - DesiredCapacity + - MaxSize + ParameterLabels: + InstanceType: + default: "Instance type to launch?" + InstanceTenancy: + default: "Instance tenancy to use?" + KeyName: + default: "Key to grant SSH access (blank for none)?" + KeyName: + default: "CIDR block to grant SSH access?" + DesiredCapacity: + default: "Desired ECS cluster instance count?" + MaxSize: + default: "Maximum ECS cluster instance count?" +Conditions: + HasKeyName: + "Fn::Not": + - "Fn::Equals": [!Ref KeyName, ''] +Mappings: + AWSRegionToAMI: + us-east-1: + AMIID: ami-2b3b6041 + us-west-2: + AMIID: ami-ac6872cd + eu-west-1: + AMIID: ami-03238b70 + ap-northeast-1: + AMIID: ami-fb2f1295 + ap-southeast-2: + AMIID: ami-43547120 + us-west-1: + AMIID: ami-bfe095df + ap-southeast-1: + AMIID: ami-c78f43a4 + eu-central-1: + AMIID: ami-e1e6f88d + SubnetConfig: + VPC: + CIDR: 10.0.0.0/16 + PublicAZ1: + CIDR: 10.0.0.0/24 + PublicAZ2: + CIDR: 10.0.1.0/24 + PublicAZ3: + CIDR: 10.0.2.0/24 +Resources: + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: !FindInMap [ SubnetConfig, VPC, CIDR ] + EnableDnsSupport: 'true' + EnableDnsHostnames: 'true' + InstanceTenancy: !Ref InstanceTenancy + PublicSubnetAZ1: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: !FindInMap [ SubnetConfig, PublicAZ1, CIDR ] + MapPublicIpOnLaunch: true + AvailabilityZone: !Select [ 0, !GetAZs ''] + Tags: + - Key: Network + Value: Public + PublicSubnetAZ2: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: !FindInMap [ SubnetConfig, PublicAZ2, CIDR ] + MapPublicIpOnLaunch: true + AvailabilityZone: !Select [ 1, !GetAZs ''] + Tags: + - Key: Network + Value: Public + PublicSubnetAZ3: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: !FindInMap [ SubnetConfig, PublicAZ3, CIDR ] + MapPublicIpOnLaunch: true + AvailabilityZone: !Select [ 2, !GetAZs ''] + Tags: + - Key: Network + Value: Public + InternetGateway: + Type: AWS::EC2::InternetGateway + Properties: + Tags: + - Key: Network + Value: Public + VPCInternetGateway: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref InternetGateway + PublicRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Network + Value: Public + PublicRoute: + Type: AWS::EC2::Route + DependsOn: VPCInternetGateway + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + PublicSubnetAZ1RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnetAZ1 + RouteTableId: !Ref PublicRouteTable + PublicSubnetAZ2RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnetAZ2 + RouteTableId: !Ref PublicRouteTable + PublicSubnetAZ3RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnetAZ3 + RouteTableId: !Ref PublicRouteTable + PublicNetworkAcl: + Type: AWS::EC2::NetworkAcl + Properties: + VpcId: !Ref VPC + Tags: + - Key: Network + Value: Public + InboundEphemeralPortPublicNetworkAclEntry: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: !Ref PublicNetworkAcl + RuleNumber: '103' + Protocol: '6' + RuleAction: allow + Egress: 'false' + CidrBlock: 0.0.0.0/0 + PortRange: + From: '1024' + To: '65535' + InboundSSHPublicNetworkAclEntry: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: !Ref PublicNetworkAcl + RuleNumber: '104' + Protocol: '6' + RuleAction: allow + Egress: 'false' + CidrBlock: !Ref SSHAllow + PortRange: + From: '22' + To: '22' + InboundHttpPublicNetworkAclEntry: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: !Ref PublicNetworkAcl + RuleNumber: '105' + Protocol: '6' + RuleAction: allow + Egress: 'false' + CidrBlock: 0.0.0.0/0 + PortRange: + From: '80' + To: '80' + OutboundPublicNetworkAclEntry: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: !Ref PublicNetworkAcl + RuleNumber: '100' + Protocol: '6' + RuleAction: allow + Egress: 'true' + CidrBlock: 0.0.0.0/0 + PortRange: + From: '0' + To: '65535' + PublicSubnetAZ1PublicNetworkAclAssociation: + Type: AWS::EC2::SubnetNetworkAclAssociation + Properties: + SubnetId: !Ref PublicSubnetAZ1 + NetworkAclId: !Ref PublicNetworkAcl + PublicSubnetAZ2PublicNetworkAclAssociation: + Type: AWS::EC2::SubnetNetworkAclAssociation + Properties: + SubnetId: !Ref PublicSubnetAZ2 + NetworkAclId: !Ref PublicNetworkAcl + PublicSubnetAZ3PublicNetworkAclAssociation: + Type: AWS::EC2::SubnetNetworkAclAssociation + Properties: + SubnetId: !Ref PublicSubnetAZ3 + NetworkAclId: !Ref PublicNetworkAcl + EcsCluster: + Type: AWS::ECS::Cluster + ECSAutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + DependsOn: [] + Properties: + VPCZoneIdentifier: + - !Ref PublicSubnetAZ1 + - !Ref PublicSubnetAZ2 + - !Ref PublicSubnetAZ3 + LaunchConfigurationName: !Ref ContainerInstances + MinSize: '1' + MaxSize: !Ref MaxSize + DesiredCapacity: !Ref DesiredCapacity + CreationPolicy: + ResourceSignal: + Timeout: PT15M + UpdatePolicy: + AutoScalingRollingUpdate: + MinInstancesInService: '1' + MaxBatchSize: '1' + PauseTime: PT15M + WaitOnResourceSignals: 'true' + ContainerInstances: + Type: AWS::AutoScaling::LaunchConfiguration + Metadata: + AWS::CloudFormation::Init: + config: + commands: + 01_add_instance_to_cluster: + command: !Sub | + #!/bin/bash + echo ECS_CLUSTER=${EcsCluster} >> /etc/ecs/ecs.config + files: + "/etc/cfn/cfn-hup.conf": + content: !Sub | + [main] + stack=${AWS::StackId} + region=${AWS::Region} + mode: '000400' + owner: root + group: root + "/etc/cfn/hooks.d/cfn-auto-reloader.conf": + content: !Sub | + [cfn-auto-reloader-hook] + triggers=post.update + path=Resources.ContainerInstances.Metadata.AWS::CloudFormation::Init + action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ContainerInstances --region ${AWS::Region} + runas=root + services: + sysvinit: + cfn-hup: + enabled: 'true' + ensureRunning: 'true' + files: + - "/etc/cfn/cfn-hup.conf" + - "/etc/cfn/hooks.d/cfn-auto-reloader.conf" + Properties: + ImageId: !FindInMap [ AWSRegionToAMI, !Ref "AWS::Region", AMIID ] + SecurityGroups: [ !Ref HostSG ] + InstanceType: !Ref InstanceType + IamInstanceProfile: !Ref EC2InstanceProfile + KeyName: !If [ HasKeyName, !Ref KeyName, !Ref "AWS::NoValue"] + UserData: + Fn::Base64: + !Sub | + #!/bin/bash -xe + yum install -y aws-cfn-bootstrap + /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ContainerInstances --region ${AWS::Region} + /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region} + EC2InstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Path: "/" + Roles: + - !Ref EC2Role + EC2Role: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - sts:AssumeRole + Path: "/" + Policies: + - PolicyName: ecs-service + PolicyDocument: + Statement: + - Effect: Allow + Action: + - ecs:CreateCluster + - ecs:RegisterContainerInstance + - ecs:DeregisterContainerInstance + - ecs:DiscoverPollEndpoint + - ecs:Submit* + - ecr:* + - ecs:Poll + Resource: "*" + HostSG: + Type: AWS::EC2::SecurityGroup + Properties: + VpcId: !Ref VPC + GroupDescription: ECS Host Security Group + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: '22' + ToPort: '22' + CidrIp: !Ref SSHAllow + ElbSG: + Type: AWS::EC2::SecurityGroup + Properties: + VpcId: !Ref VPC + GroupDescription: ELB Security Group + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: '80' + ToPort: '80' + CidrIp: 0.0.0.0/0 + ELB2HostRule: + Type: AWS::EC2::SecurityGroupIngress + Properties: + IpProtocol: tcp + FromPort: '0' + ToPort: '65535' + SourceSecurityGroupId: !GetAtt ElbSG.GroupId + GroupId: !GetAtt HostSG.GroupId + Host2ELBRule: + Type: AWS::EC2::SecurityGroupIngress + Properties: + IpProtocol: tcp + FromPort: '0' + ToPort: '65535' + SourceSecurityGroupId: !GetAtt HostSG.GroupId + GroupId: !GetAtt ElbSG.GroupId + EcsElb: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Subnets: + - !Ref PublicSubnetAZ1 + - !Ref PublicSubnetAZ2 + - !Ref PublicSubnetAZ3 + SecurityGroups: + - !Ref ElbSG + EcsElbListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + LoadBalancerArn: !Ref EcsElb + DefaultActions: + - Type: forward + TargetGroupArn: !Ref EcsElbDefaultTargetGroup + Port: '80' + Protocol: HTTP + EcsElbDefaultTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + Port: '8080' + Protocol: HTTP + VpcId: !Ref VPC +Outputs: + BaseUrl: + Value: !Sub http://${EcsElb.DNSName}/ + Description: ELB URL + EcsElbListenerArn: + Value: !Ref EcsElbListener + Description: Arn of the ELB Listener. + EcsCluster: + Value: !Ref EcsCluster + Description: Name of the ECS cluster. + VPCId: + Value: !Ref VPC + Description: The id of the vpc diff --git a/environments/upsert.go b/environments/upsert.go index 82390a76..496959cf 100644 --- a/environments/upsert.go +++ b/environments/upsert.go @@ -1,26 +1,94 @@ package environments -import( +import ( "fmt" - "github.com/urfave/cli" + "text/template" "github.com/stelligent/mu/common" + "github.com/urfave/cli" + "os" ) +type cfnTemplate struct { + StackName string + TemplatePath string +} func newUpsertCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "upsert", - Aliases: []string{"up"}, - Usage: "create/update an environment", + cmd := &cli.Command{ + Name: "upsert", + Aliases: []string{"up"}, + Usage: "create/update an environment", ArgsUsage: "", Action: func(c *cli.Context) error { - runUpsert(config, c.Args().First()) - return nil + environmentName := c.Args().First() + if(len(environmentName) == 0) { + cli.ShowCommandHelp(c, "upsert") + return fmt.Errorf("ERROR: environment must be provided!") + } + return runUpsert(config, environmentName) }, } return cmd } -func runUpsert(config *common.Config, environment string) { - fmt.Printf("upserting environment: %s\n",environment) -} \ No newline at end of file +func runUpsert(config *common.Config, environmentName string) error { + // get the environment from config by name + environment, err := common.GetEnvironment(config, environmentName) + + if err != nil { + return err + } + + // generate the CFN template + template, err := generateCFNTemplate(environment) + if err != nil { + return err + } + + // determine if stack exists + + // create/update the stack + + // wait for stack to be updated + + fmt.Printf("upserting environment:%s stack:%s path:%s\n",environment.Name, template.StackName, template.TemplatePath) + + return nil +} + +//go:generate go-bindata -pkg $GOPACKAGE -o assets.go assets/ + +func generateCFNTemplate(environment *common.Environment) (*cfnTemplate, error) { + stackName := fmt.Sprintf("mu-env-%s", environment.Name) + templatePath := fmt.Sprintf("%s%s.yml",os.TempDir(), stackName) + + environmentTemplate, err := Asset("assets/environment-template.yml") + if err != nil { + return nil, err + } + + tmpl, err := template.New("environment").Parse(string(environmentTemplate[:])) + if err != nil { + return nil, err + } + + templateOut, err := os.Create(templatePath) + defer templateOut.Close() + if err != nil { + return nil, err + } + + err = tmpl.Execute(templateOut, environment) + if err != nil { + return nil, err + } + + templateOut.Sync() + + return &cfnTemplate{ + StackName: stackName, + TemplatePath: templatePath, + }, nil +} + + From 487550983977e32d6799d2b43e340cae91f39a8d Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 9 Jan 2017 07:59:40 -0800 Subject: [PATCH 10/31] fix lint issues --- common/config.go | 1 + environments/upsert.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/config.go b/common/config.go index 38b1fa01..d508e43e 100644 --- a/common/config.go +++ b/common/config.go @@ -67,6 +67,7 @@ func loadYamlConfig(config *Config, yamlConfig []byte) *Config { return config } +// GetEnvironment loads the environment by name from the config func GetEnvironment(config *Config, environment string) (*Environment, error) { for _, e := range config.Environments { diff --git a/environments/upsert.go b/environments/upsert.go index 496959cf..9b4c053b 100644 --- a/environments/upsert.go +++ b/environments/upsert.go @@ -22,7 +22,7 @@ func newUpsertCommand(config *common.Config) *cli.Command { environmentName := c.Args().First() if(len(environmentName) == 0) { cli.ShowCommandHelp(c, "upsert") - return fmt.Errorf("ERROR: environment must be provided!") + return fmt.Errorf("environment must be provided") } return runUpsert(config, environmentName) }, From 8d954dd99457347f08bd3c9b10b1af8f5c6bfca2 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 9 Jan 2017 08:30:54 -0800 Subject: [PATCH 11/31] blank line --- environments/upsert.go | 1 - 1 file changed, 1 deletion(-) diff --git a/environments/upsert.go b/environments/upsert.go index 9b4c053b..3a752644 100644 --- a/environments/upsert.go +++ b/environments/upsert.go @@ -57,7 +57,6 @@ func runUpsert(config *common.Config, environmentName string) error { } //go:generate go-bindata -pkg $GOPACKAGE -o assets.go assets/ - func generateCFNTemplate(environment *common.Environment) (*cfnTemplate, error) { stackName := fmt.Sprintf("mu-env-%s", environment.Name) templatePath := fmt.Sprintf("%s%s.yml",os.TempDir(), stackName) From 69a0ca5d7e51d709ced20f0c4221d4b25c611974 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 9 Jan 2017 23:19:36 -0800 Subject: [PATCH 12/31] refactor to create common code for managing stacks --- .../assets/environment-template.yml | 0 common/config.go | 33 +-- common/config_test.go | 12 +- common/environment.go | 36 +++ common/environment_test.go | 21 ++ common/stack.go | 49 ++++ common/stack_test.go | 16 ++ environments/assets.go | 237 ------------------ environments/upsert.go | 46 +--- environments/upsert_test.go | 4 +- main.go | 2 +- 11 files changed, 140 insertions(+), 316 deletions(-) rename {environments => common}/assets/environment-template.yml (100%) create mode 100644 common/environment.go create mode 100644 common/environment_test.go create mode 100644 common/stack.go create mode 100644 common/stack_test.go delete mode 100644 environments/assets.go diff --git a/environments/assets/environment-template.yml b/common/assets/environment-template.yml similarity index 100% rename from environments/assets/environment-template.yml rename to common/assets/environment-template.yml diff --git a/common/config.go b/common/config.go index d508e43e..a7da8f72 100644 --- a/common/config.go +++ b/common/config.go @@ -14,24 +14,6 @@ type Config struct { Service Service `yaml:"service,omitempty"` } -// Environment defines the env that will be created -type Environment struct { - Name string `yaml:"name"` - Loadbalancer EnvironmentLoadBalancer `yaml:"loadbalancer,omitempty"` - Cluster EnvironmentCluster `yaml:"cluster,omitempty"` -} - -// EnvironmentLoadBalancer defines the env load balancer that will be created -type EnvironmentLoadBalancer struct { - Hostname string `yaml:"hostname,omitempty"` -} - -// EnvironmentCluster defines the env cluster that will be created -type EnvironmentCluster struct { - DesiredCapacity int `yaml:"desiredCapacity,omitempty"` - MaxSize int `yaml:"maxSize,omitempty"` -} - // Service defines the service that will be created type Service struct { DesiredCount int `yaml:"desiredCount,omitempty"` @@ -47,18 +29,18 @@ func NewConfig() *Config { return &Config{} } -// LoadConfig loads config object from local file -func LoadConfig(config *Config, configFile string) { +// LoadFromFile loads config object from local file +func (config *Config) LoadFromFile(configFile string) { yamlConfig, err := ioutil.ReadFile( configFile ) if err != nil { fmt.Printf("WARN: Unable to find config file: %v\n", err) } else { - loadYamlConfig(config, yamlConfig) + config.loadFromYaml(yamlConfig) } } -func loadYamlConfig(config *Config, yamlConfig []byte) *Config { +func (config *Config) loadFromYaml(yamlConfig []byte) *Config { err := yaml.Unmarshal(yamlConfig, config) if err != nil { log.Panicf("Invalid config file: %v", err) @@ -68,14 +50,13 @@ func loadYamlConfig(config *Config, yamlConfig []byte) *Config { } // GetEnvironment loads the environment by name from the config -func GetEnvironment(config *Config, environment string) (*Environment, error) { +func (config *Config) GetEnvironment(environmentName string) (*Environment, error) { for _, e := range config.Environments { - if(strings.EqualFold(environment, e.Name)) { + if(strings.EqualFold(environmentName, e.Name)) { return &e, nil } } - return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml",environment) - + return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml",environmentName) } diff --git a/common/config_test.go b/common/config_test.go index cfb5cf86..95f9baf5 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -39,7 +39,7 @@ service: config := NewConfig() - loadYamlConfig(config, []byte(yamlConfig)) + config.loadFromYaml([]byte(yamlConfig)) assert.NotNil(config) assert.Equal(2,len(config.Environments)) @@ -66,13 +66,13 @@ func TestLoadBadYamlConfig(t *testing.T) { yamlConfig := ` blah blah blah ` config := NewConfig() - loadYamlConfig(config, []byte(yamlConfig)) + config.loadFromYaml([]byte(yamlConfig)) } func TestLoadConfig(t *testing.T) { config := NewConfig() - LoadConfig(config, "foobarbaz.yml") + config.LoadFromFile("foobarbaz.yml") } func TestGetEnvironment(t *testing.T) { @@ -87,15 +87,15 @@ func TestGetEnvironment(t *testing.T) { } config.Environments = []Environment{env1, env2} - fooEnv, fooErr := GetEnvironment(config, "foo") + fooEnv, fooErr := config.GetEnvironment("foo") assert.Equal("foo", fooEnv.Name) assert.Nil(fooErr) - barEnv, barErr := GetEnvironment(config, "BAR") + barEnv, barErr := config.GetEnvironment("BAR") assert.Equal("bar", barEnv.Name) assert.Nil(barErr) - bazEnv, bazErr := GetEnvironment(config, "baz") + bazEnv, bazErr := config.GetEnvironment("baz") assert.Nil(bazEnv) assert.NotNil(bazErr) } diff --git a/common/environment.go b/common/environment.go new file mode 100644 index 00000000..a6204d39 --- /dev/null +++ b/common/environment.go @@ -0,0 +1,36 @@ +package common + +import ( + "fmt" +) + +// Environment defines the env that will be created +type Environment struct { + Name string `yaml:"name"` + Loadbalancer EnvironmentLoadBalancer `yaml:"loadbalancer,omitempty"` + Cluster EnvironmentCluster `yaml:"cluster,omitempty"` +} + +// EnvironmentLoadBalancer defines the env load balancer that will be created +type EnvironmentLoadBalancer struct { + Hostname string `yaml:"hostname,omitempty"` +} + +// EnvironmentCluster defines the env cluster that will be created +type EnvironmentCluster struct { + DesiredCapacity int `yaml:"desiredCapacity,omitempty"` + MaxSize int `yaml:"maxSize,omitempty"` +} + +// NewStack will create a new stack instance and write the template for the stack +func (environment *Environment) NewStack() (*Stack, error) { + stackName := fmt.Sprintf("mu-env-%s", environment.Name) + stack := NewStack(stackName) + + err := stack.WriteTemplate("environment-template.yml", environment) + if err != nil { + return nil, err + } + + return stack,nil +} diff --git a/common/environment_test.go b/common/environment_test.go new file mode 100644 index 00000000..0cdae8b9 --- /dev/null +++ b/common/environment_test.go @@ -0,0 +1,21 @@ +package common + +import ( + "testing" + "github.com/stretchr/testify/assert" + +) + +func TestNewEnvironmentStack(t *testing.T) { + assert := assert.New(t) + + env := Environment{ + Name: "test", + } + + stack, err := env.NewStack() + + assert.Nil(err) + assert.NotNil(stack) + assert.Equal("mu-env-test",stack.Name) +} diff --git a/common/stack.go b/common/stack.go new file mode 100644 index 00000000..86b10445 --- /dev/null +++ b/common/stack.go @@ -0,0 +1,49 @@ +package common + +import ( + "fmt" + "os" + "text/template" +) + +// Stack contains the data about a CloudFormation stack +type Stack struct { + Name string + TemplatePath string +} + +// NewStack will create a new stack instance +func NewStack(name string) *Stack { + return &Stack{ + Name: name, + TemplatePath: fmt.Sprintf("%s%s.yml",os.TempDir(), name), + } +} + +// WriteTemplate will create a temp file with the template for a CFN stack +//go:generate go-bindata -pkg $GOPACKAGE -o assets.go assets/ +func (stack *Stack) WriteTemplate(assetName string, data interface{}) (error) { + asset, err := Asset(fmt.Sprintf("assets/%s",assetName)) + if err != nil { + return err + } + + tmpl, err := template.New(assetName).Parse(string(asset[:])) + if err != nil { + return err + } + + templateOut, err := os.Create(stack.TemplatePath) + defer templateOut.Close() + if err != nil { + return err + } + + err = tmpl.Execute(templateOut, data) + if err != nil { + return err + } + + templateOut.Sync() + return nil +} diff --git a/common/stack_test.go b/common/stack_test.go new file mode 100644 index 00000000..25d328fa --- /dev/null +++ b/common/stack_test.go @@ -0,0 +1,16 @@ +package common + +import ( + "testing" + "github.com/stretchr/testify/assert" + +) + +func TestNewStack(t *testing.T) { + assert := assert.New(t) + + stack := NewStack("foo") + + assert.NotNil(stack) + assert.Equal("foo",stack.Name) +} diff --git a/environments/assets.go b/environments/assets.go deleted file mode 100644 index 6b33a072..00000000 --- a/environments/assets.go +++ /dev/null @@ -1,237 +0,0 @@ -// Code generated by go-bindata. -// sources: -// assets/environment-template.yml -// DO NOT EDIT! - -package environments - -import ( - "bytes" - "compress/gzip" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -func (fi bindataFileInfo) Name() string { - return fi.name -} -func (fi bindataFileInfo) Size() int64 { - return fi.size -} -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} -func (fi bindataFileInfo) IsDir() bool { - return false -} -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x5a\x7b\x4f\x1b\xb9\x16\xff\x9f\x4f\x61\x72\x2b\x65\xbb\x62\xf2\x98\x00\xa5\x23\xb1\xab\x10\xd2\x12\x95\xd0\x28\x93\x52\x69\x69\x55\x39\x1e\x27\xb1\x3a\x63\xcf\xb5\x3d\x40\xb6\xb7\xdf\xfd\xca\xf6\xbc\x1f\x59\xe8\xe3\xd2\x4b\x85\x4a\xec\xe3\xe3\xdf\xf9\xf9\x9c\x63\xfb\x38\x96\x65\xed\x0d\xdf\xbb\x0b\x1c\x84\x3e\x94\xf8\x15\xe3\x01\x94\xd7\x98\x0b\xc2\xa8\x03\xda\x76\xaf\xdf\xb3\x7a\x2f\xad\xde\xcb\xf6\xde\x0c\x72\x18\x60\x89\xb9\x70\xf6\x00\x98\x50\x21\x21\x45\x78\xb1\x0d\xb1\xfa\x0c\x80\xfe\x0b\xb8\x92\x13\xba\xd6\x0d\xe7\x58\x20\x4e\x42\xa9\x55\x25\xf2\x40\x6e\x43\x0c\x24\x03\x91\xc0\x9d\x58\x6c\x05\x23\x5f\x3a\x40\xda\x9d\x80\x20\xce\xf2\xda\x31\x85\x14\x6d\x9d\x5d\xfa\x8c\x48\xac\x12\xac\x18\x07\xd7\xb3\x51\x3d\xa2\xa1\xef\xb3\x3b\xec\x5d\x43\x3f\xc2\xc2\x28\xb5\x80\x67\xe6\x4f\x3f\x79\x04\x41\x89\xbd\x22\xb6\x4c\xe8\x1c\x0b\xc2\xb1\x37\x82\x21\x44\x44\x6e\xf3\xb6\x5f\x45\xc1\x12\xf3\xe2\xc0\x76\xbf\x5d\x05\x6f\x04\x01\x5b\x01\x12\x9b\x21\x14\x7e\x1f\x46\x14\x6d\x00\xa1\x60\xcb\x22\x0e\xc6\x23\x17\x20\x3f\x12\x52\xeb\x9c\xc2\x7b\x97\xfc\x8d\xff\x71\x3e\xbb\x66\xbe\x29\xbc\x27\x41\x14\x00\x5a\x37\xef\x06\x4a\x80\x20\x05\x4b\x1c\x03\xc0\x5e\x03\x84\x37\x78\x7b\x05\x83\x07\x2d\x77\x2c\xaa\xac\x82\x42\x30\x44\xa0\xc4\xe0\x8e\xc8\x0d\xb8\x63\xfc\x33\xe6\x19\x80\x0e\x00\x97\x18\xde\x62\xb0\xf4\x21\xfd\xac\x06\x78\x44\xc0\xa5\x8f\x81\xeb\x5e\x00\x88\x10\x16\xa2\xe4\x28\x6d\x65\xa2\xeb\x5e\xe8\xe5\xac\xf1\x0d\x37\x5a\x52\x2c\xc1\x8a\xb3\x00\xdc\x6d\x08\xda\x68\x18\x4a\xb8\xa2\xb3\x62\xc5\x94\xd0\x4b\x4c\xd7\x72\xe3\x80\xf6\x4b\x43\xe5\x14\xde\xa7\x4d\xfd\x93\x76\x11\x4b\xaf\xa3\xff\x75\x7b\x79\x07\x9b\x41\x29\x31\xa7\x0e\x68\xfd\xf6\xe1\x83\xf7\xa5\x7f\x30\xf8\xfa\xfc\xc3\x87\xce\x43\x3e\x74\xe3\x3f\xed\xaf\xcf\x5b\x5a\xe5\x88\x51\x21\x39\x24\x54\x16\x6c\x6c\x07\x91\x90\x6a\xcd\x20\xb8\x85\x3e\xf1\xc0\x68\x72\x3e\x07\x4b\x9f\xa1\xcf\x0e\xb8\xef\xe8\x7f\xdd\xfb\x4e\x7b\x6f\x8a\x25\xf4\xa0\x84\x8a\xa7\xe1\x7b\xd7\x71\x46\x3e\x8b\x3c\x13\xe8\x4a\x93\x33\xa1\x12\xf3\x15\x44\xf1\xba\xa6\x61\xfe\x9a\xb3\x28\x8c\xa3\x44\x45\xc6\x25\x5c\x62\x3f\xf9\xa8\x7e\xbc\x84\x83\x56\x1a\x8c\x23\x46\x57\x64\x1d\x71\xad\xba\x95\xca\x16\x53\x47\xf2\x63\x15\x92\x48\x7d\x87\x09\xee\x42\x5f\xec\x5a\x85\xb6\xc4\x19\x1e\x02\x76\x18\x49\x06\x5c\x04\x7d\x42\xd7\x8f\x05\x5c\x0a\xfe\x42\x5f\x1c\xa0\x45\x12\x35\x8e\x54\x49\x35\x69\x36\xf0\x98\x24\x49\x13\x90\x7f\xb6\xca\xe3\xf3\x69\xb1\x49\x45\x21\x2f\xa6\x2a\x0a\x31\x5c\x1c\xfa\x06\x6b\xe9\x35\x87\x54\xe6\x02\x05\xfc\x66\x22\x53\x65\x56\xca\x28\x7e\xfe\x10\x5d\x99\x37\xd6\xa9\x4c\x35\xd4\x26\xd3\xa2\xa6\x58\x24\x9f\x8a\xd2\xe4\x01\x10\x8b\xa8\x4c\xb5\x15\x52\x64\x51\x4b\x92\x01\x77\x6a\x19\x31\xea\x11\xe5\x08\x7a\xc1\x2e\xa0\x28\x18\xd8\x7a\x45\x1d\xe7\x8a\xc9\x56\x16\x12\xba\x69\xfc\xef\x08\xfa\xa2\xe5\x80\x9b\xfd\x39\x5e\x25\xa4\x1c\x80\x76\xfb\xe3\xde\x14\x86\x21\xa1\x6b\x11\x07\xdf\x1c\xaf\x09\xa3\x0b\x36\x9c\x4e\x8c\x92\x48\x58\x18\x0a\x69\xf5\x13\x9d\xc3\xe9\x64\x72\xee\x00\x18\x10\xcb\x5e\x0e\x96\xc7\xbd\xc3\x7e\x22\x78\x87\x85\xb4\xec\x1a\x41\x88\x8e\x4f\x5e\xd8\xc8\xec\x58\x38\x32\x82\x75\x1a\x7b\x03\x7b\x70\xb2\x7c\x61\x52\x15\x0c\x2d\xca\xb8\xdc\x34\xce\xbf\x5a\xda\xab\xbe\xfd\xf2\x28\x91\x16\x2c\x8a\xa5\xeb\x40\x1c\x0e\x8e\x0e\x5f\xf4\xed\x5e\x01\x6d\x9d\xda\xe5\x0a\xf7\x5e\x1e\x79\xab\xaa\xda\x3a\x69\xf4\xe2\x64\x75\x38\x80\x87\x89\x6d\x08\x53\xc9\xa1\x5f\x2b\x8b\xfb\xf8\x78\x75\x72\xa2\x78\x30\x3b\x80\x09\x6e\x23\x79\x3d\x1b\x25\x43\x94\x6f\x3a\xa0\x9f\x64\xee\xfe\xb1\x09\xd9\x68\xe9\x13\x34\xfc\xab\xdf\x20\x66\x1f\x16\xc4\xec\x1a\xb1\x7e\x55\x6c\x50\x23\x66\x1b\xb1\x39\x16\x2c\xe2\xc8\x1c\x44\x52\x78\x66\x43\xd2\x89\x7a\x3c\xb2\x1d\x27\x39\xcb\xcc\x38\x0b\x31\x97\x04\xa7\xc9\x64\x44\x3c\x7e\x66\xf2\xfd\xfe\x2b\x42\xbd\x09\x9d\xc2\x10\xdc\x14\x6c\x3f\x50\x8a\x0f\xcc\xde\xf0\x31\x1e\x37\xa6\x6a\x77\x3d\xa7\xc2\x8d\xc2\x90\x71\xb5\x9f\x4a\x1e\xe1\x76\xb9\xfb\x82\x09\x49\x61\x80\x45\x49\xa0\x9c\x84\x80\x76\xfb\x6a\xc6\x36\x14\x18\x34\x29\xad\x65\xf3\x4c\x77\x83\x85\xd7\x21\x9a\x78\xb1\xfe\x84\x87\x87\xda\x9d\x2e\x67\xc9\xfa\x29\x0c\x4d\xd7\x24\x7c\x4b\x2f\x75\x7a\x75\x80\x32\x2f\xf1\xa7\x5b\x48\x7c\xb8\x24\x3e\x91\xdb\xbf\x18\xc5\x0e\xd8\x77\xb1\x8f\x91\x04\x37\xa0\x77\x00\xf6\x5f\x2b\x63\x84\x0e\x6e\x33\x60\x01\xd7\xb9\x4d\xf2\x0d\xde\x3a\xe0\x0a\x4b\x75\xca\x49\xd3\x90\x3e\x6f\x3a\x31\xa4\x0a\x33\xf6\x13\x31\x63\xff\x40\x66\xfa\x3f\x85\x99\xc1\x13\x31\x33\xf8\x81\xcc\xd8\x3f\x88\x19\x7d\x4a\xa3\x58\xbe\x86\x12\xdf\xc1\x6d\x3d\x33\x25\xa1\x06\x8a\xbe\x61\xf6\xeb\xd9\xe8\x41\x00\xae\x67\xa3\xb8\x7f\x28\x25\x44\x9b\x00\xd3\xc7\x2d\x54\x69\x96\x54\xa2\x6a\x99\xc1\x36\x67\x91\xc4\x0b\x95\xb1\xea\x01\x65\xfd\x8f\x82\xf1\xcd\xae\xab\xe7\xdb\x01\x25\xbe\x3c\x84\x98\x7a\xe2\x2d\x75\x6a\x88\x6d\xc0\x99\x19\x92\xc2\x2d\x33\x90\x9d\xa9\x24\xa1\xfa\x40\x9b\x73\xfb\xe2\x3d\x05\x80\x87\x12\x9c\xe6\xef\x6c\x9e\x61\x7c\xa1\x53\xb7\x87\x1d\x01\x5a\x3b\xa0\xc1\x3a\x33\xa2\x64\x59\x3a\xf5\xa3\x28\x28\x25\xd7\xa7\x83\x6d\x7f\x07\xec\xc1\xd3\xc1\x1e\x7c\x03\xec\x38\x36\x86\xc8\xaf\x87\x98\xf5\xff\xec\x28\x9c\xd0\x25\x8b\xa8\x37\x0e\x37\x38\xc0\x1c\xfa\x33\xc6\x65\x19\xe3\x98\x4a\xde\x90\xbf\x4a\x42\x0d\x68\x33\xa9\x12\x35\x25\x3b\x01\x98\x47\x3e\x36\x55\x1a\x07\xb4\xfb\xbd\x41\x72\x86\x9a\x71\x26\x19\x62\xbe\x03\xda\xc7\xed\x9c\xec\x10\x99\xdb\x3d\xcc\x5d\x66\xc7\x6b\x8e\x85\x3a\x84\xad\xa0\x2f\xd2\x53\xd8\x8e\xd0\x56\x36\xcf\x21\x5d\xe7\x2e\x42\xaf\x38\x0b\x34\x02\xfb\xb0\x9d\x36\x2e\x98\x9a\xfe\xe8\x68\x70\xd4\xce\x98\x73\xdd\x8b\x5f\x87\xaf\xc3\x9f\xc2\x97\x06\x50\xaa\x18\x34\x73\x66\xdb\x25\xc6\x4c\x43\x4c\xd7\x85\x94\xe1\xaf\xc3\xd7\xd1\x13\xfb\xd7\x49\xaf\xc4\x95\x69\x78\x1b\x49\x4d\xd6\xaf\x43\x54\xef\xbb\x88\xca\xdf\x86\xbe\x89\xa7\x32\x4d\x69\x10\x96\x36\xbe\xb2\x31\x0f\xdc\x0b\x6a\x07\x7c\xcf\xce\xfb\x30\x9e\x4b\xdb\xdf\x53\x83\xb7\xbf\x03\xfc\xe0\xa9\xc1\x0f\x1e\x05\x7e\x8c\xc4\xc8\x14\xb5\x6a\xc0\xe9\x92\x6f\x52\xc2\x1f\x8f\xdc\x61\x24\x59\x5c\xfe\xd4\x25\xde\xca\x90\x9c\x40\xe1\x83\x96\x2e\x1f\x60\x6f\x3e\x36\x6d\xe9\xb3\x91\xba\x0b\x4d\x3c\x4c\x25\x59\x91\x04\x9a\xda\xc9\x77\x78\x5a\x6d\xa7\xbd\xab\x33\x61\xca\xdc\xcb\x0a\xf5\x5c\x5d\xbb\x33\x63\x46\x8c\x4a\x48\x28\xe6\x49\x9d\x42\x24\x77\x3b\x42\x75\xcd\x30\x7d\xa8\xc9\xca\x88\x66\x64\xbe\xac\x5b\x2d\x58\x1a\x99\xba\xb2\xf0\x88\x63\x0d\x62\xc6\x7c\x92\x95\x6a\x93\xa2\x8f\x4b\xd6\x14\xe6\x0a\xd4\x0b\x12\x60\x16\x49\x07\xcc\x16\xfd\xa3\xa9\x6e\x7e\x17\x7a\x50\xe2\xe2\xf0\xdc\x6a\xcc\x99\xaf\xfe\x33\x52\x99\xa2\x29\xa1\xa9\x89\x13\xea\x62\x7e\x4b\x50\xc1\x3a\x6d\xdf\x19\x94\x68\x53\xb6\x1b\x80\x19\x8c\x04\x56\x50\xf2\x38\xd4\xcf\x7b\x48\xe4\x5b\x5a\x04\x9f\xcf\x84\x55\x7a\x77\x7b\x55\xcd\x62\x69\xf9\xfc\x63\x85\xb6\xb7\xfe\xc1\x82\xc8\xcc\x60\x94\x2b\xf2\x25\x2d\x41\x00\xa9\x57\x28\xdd\x03\xd0\xeb\x7f\x82\x9e\xf7\x29\x29\xfa\x7e\x92\xec\x13\xca\xc7\x4c\x65\xbc\xba\xc5\x47\x4b\xf0\x9f\x52\x2f\x00\xff\xda\xef\x2e\x09\xed\x2e\xa1\xd8\x54\xfa\x30\xda\x30\x15\x64\x9f\x46\x97\xef\xdc\xc5\x78\x7e\xfa\xec\x4b\x16\x9c\x5f\x01\xf8\xe3\x0f\xd0\xc5\x12\x75\x31\x12\xea\xb7\x63\xd0\xe7\xd4\xac\x88\x8f\x4b\xc8\x5b\x7a\x04\x5a\x51\xf5\x6b\x6d\xa2\x50\x8f\x6a\x55\x61\x53\x89\xa9\x6c\x84\x7d\x13\x40\x42\x3f\x56\x9a\x85\x84\xe8\xf3\xe9\xb3\x2f\x9a\x6a\x57\x7d\x98\x78\x5f\x2b\x52\x5c\x57\xae\x13\x31\x53\xc7\x2e\x4b\x05\xcc\x53\xfe\xd4\xeb\xf5\x0e\x7b\xb9\x1d\xce\xfc\xb0\x3b\xaa\xf6\x5e\xce\x98\x2c\xf5\xac\x75\x12\xaa\xf6\x64\x66\x6f\x18\xfb\x2c\x3a\x9e\x36\x1f\x46\x92\x59\x1c\xfb\x0c\x7a\x98\x7f\x23\x11\x15\x3d\x96\x9a\xa1\x4a\x8d\xe4\x64\xbd\xc6\x5c\x9c\x86\x4c\xc8\x4e\xa4\x23\xad\x22\x14\x42\xb9\x39\x4d\x6b\xb9\x9d\x6a\x24\x74\x12\xa7\xee\x34\x7a\x73\x45\x29\xd4\xc7\x8f\xd3\x2e\x0b\x65\x17\xde\x09\xed\x6f\x0a\x35\xa1\x44\x02\xeb\x16\x58\x96\x5e\x36\x90\x5f\x36\x95\xed\xbe\x02\xcb\xe2\x31\x96\x9a\xa0\xd4\xbd\x6a\xe9\xc0\xce\x85\x04\x80\x47\x14\x8a\xd3\xd2\x92\x08\x93\x4c\x4a\xde\x29\xb6\xe2\x96\x14\x22\x32\x5e\x05\xe3\xab\xe5\x66\x00\xb0\x2e\x30\x7b\xa5\x73\x54\xbe\x5f\x44\x1c\xcf\x23\x4a\x55\xaa\x68\x92\xaa\x89\x13\x60\xde\x64\xea\xa3\x65\xa7\xe4\x3f\x38\x58\xc3\x0e\x37\x09\xe0\xda\x5c\x8e\xf3\x25\xc5\xe2\x33\xcf\x81\xd9\x20\x5a\x39\xb6\x5b\x07\xe6\xd1\x22\x2d\x2f\xba\x18\x45\x9c\xc8\x6d\xfc\xe0\x0a\x6e\xcc\x98\x0b\x26\xa4\xfb\x3a\x95\x2a\xbc\x1d\x96\x6a\xee\xd9\xf3\xe9\x04\x06\x49\xeb\x8c\x33\x45\x52\x2c\x3b\x1e\xd9\xa5\x8e\x78\x44\xf2\xc6\x05\xf6\x27\x2b\x70\x93\x7b\xf5\x8a\xa1\x17\x3f\x19\x43\xae\x98\xbe\x77\xb7\x12\x6c\xef\x04\xe6\xe7\xb9\xb4\x0d\xc0\x2b\xea\x38\x67\x50\xe0\xe3\xc3\xfc\x1a\xd5\x04\x64\x2e\x99\x02\xeb\xbe\x18\x5e\xdb\x28\x30\x8f\x74\xbe\x0f\xac\x2d\x80\x77\xc2\x52\x2b\xb4\x64\x4c\x0a\xc9\x61\x58\x10\x7e\x92\x58\xa9\x4c\x2a\xf4\xd6\x08\x2c\x0c\x9e\xfd\xf9\xb0\x99\x6b\x4e\x64\x3b\xa6\xae\x2e\x63\x65\xa3\x9d\x0c\xa7\x2a\xab\x54\xd7\xba\xea\xc1\x33\x28\x37\x0e\x68\x75\x93\xe8\x98\xb3\x5c\x50\x59\xa9\xe3\xa8\x66\x33\xb7\xfa\xab\x7e\xc2\x58\xa6\x6e\x96\xa1\x10\x51\x80\x95\x80\x39\xcc\x9c\x33\x14\x05\x2a\x41\xa7\x54\xba\x12\x4a\x5c\x6c\xb2\xc0\x78\xb5\xc2\x48\x3a\x20\x7f\x4d\x37\x13\x10\x8a\x48\x08\xfd\x62\xf4\x27\x47\x9d\xbd\x62\x90\x63\x64\x77\x60\x00\xff\x66\x14\xde\xa9\xed\x36\xc8\xf5\xc7\x97\xbc\xbd\xbc\xbc\x90\xc2\xc9\x00\x37\xf0\xa4\xed\x20\x79\xaa\x8c\x65\x26\x90\x30\x12\x56\x9c\x2b\xb3\x93\x55\x83\xe5\xb5\xb6\xef\xb2\xbe\x0e\xb5\xb1\x53\x38\xfa\xc8\x89\xb3\xc3\x7e\xb9\x5f\xb9\x91\xea\xaa\x38\x7b\x8d\xec\x39\xe6\x8f\x91\x26\x02\xb1\x5b\xcc\x67\xcc\xf7\xc7\xd4\x0b\x19\xa1\xb2\x46\xcc\x8d\x96\x01\x91\xbf\x57\x7a\xb8\x53\x6d\x13\x8e\x52\x56\x68\x4e\x76\x59\x07\xb4\x7e\x57\x4b\x61\x32\x64\xc3\x7d\x2c\x9f\x54\x9b\xae\x28\xb5\x55\x47\x3d\xa2\xf0\x5d\x9b\xf1\xc8\xd5\x73\xa5\x99\x1a\x64\x5a\x4b\xe9\x7b\x42\x4d\x91\x20\x75\x8c\x49\x98\x95\x18\x24\x0a\x0b\x95\x80\x99\x79\x7d\x2d\x56\x98\x6a\x1a\x47\xc4\xe3\x93\xb0\x5a\xb8\x1a\xfb\xcb\xff\x89\xf9\x97\x67\x3f\xc9\xf2\x62\xbd\xa8\xa6\x31\xb1\x3c\x5f\x5a\x19\x5f\x9e\xd9\x6a\x35\xe6\x51\xd3\xa3\x4f\x1d\xae\xa6\x3d\xbc\x16\x64\x0e\x62\x0a\x26\xc5\x97\x16\x6b\x34\x05\xe6\x42\x54\x98\x50\x71\xfa\x1a\xcb\xa1\x94\x66\x85\x3a\x71\x73\x9e\xe0\xbc\x90\x71\xe3\x9c\x94\x6a\xb0\xc7\x97\x67\xff\x0f\x16\x56\xc0\xd7\x9a\x58\xe6\x61\x8c\xc4\xd8\x5f\x56\x6d\xf3\xa1\x90\x04\x5d\x32\xe8\x9d\x41\x1f\x52\x44\xe8\xfa\xda\x76\x9c\xac\x21\xce\x6b\x4d\xd5\x94\xf2\xe6\xf5\xa3\x8b\x0c\xa5\xa3\x5a\x69\xa7\x54\x46\xa6\xc6\x5d\xaa\xe4\x49\xeb\x2a\x32\x4d\x46\xc6\x03\x1a\x0c\xcc\x73\x30\xe4\x34\x39\xd6\xe9\xb9\x62\x91\xf8\x4b\x89\x66\x83\xc8\xa1\x33\x93\xaf\x18\xbf\x83\xdc\xcb\x02\x0e\xf2\x35\x96\xda\x92\xb2\xbe\x58\x51\x4e\x22\xdd\xf8\x4a\x21\x9a\xf9\xd6\xc5\x62\x31\x4b\x8d\xaf\x2a\x78\x30\x0d\xe5\x49\x6b\x4e\x2d\x09\x88\x1d\x30\xea\xf2\xdb\xdb\x48\x86\x91\x71\x11\x75\x34\x7d\xc7\xe3\x13\x44\xfc\x86\xa4\x0f\xa7\x1b\x29\x43\xa7\xdb\xd5\xb7\xf6\xb1\xbf\xec\x9c\x5f\xb9\xfa\xc4\xd6\xdd\x03\xe5\x6f\x99\xaa\xb4\xf8\x6e\x7e\x59\x59\x70\x45\x66\x41\x6f\xc6\x6b\x61\x89\x0b\xca\x86\x9c\x02\xb6\x02\x72\x83\xb5\xde\x44\xb0\x53\x53\xdc\x2b\xa9\xcd\x6f\xf7\xc5\x2f\x19\xc3\x00\xa7\x3a\xb3\xaf\xbd\x75\xe2\xe7\x7c\xaf\xaa\x2d\xd9\x04\x0a\x6a\x16\x1b\x0c\x88\x97\x28\xba\x0d\xd1\xde\x7f\x03\x00\x00\xff\xff\xc8\x38\x05\x7c\x34\x2e\x00\x00") - -func assetsEnvironmentTemplateYmlBytes() ([]byte, error) { - return bindataRead( - _assetsEnvironmentTemplateYml, - "assets/environment-template.yml", - ) -} - -func assetsEnvironmentTemplateYml() (*asset, error) { - bytes, err := assetsEnvironmentTemplateYmlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "assets/environment-template.yml", size: 11828, mode: os.FileMode(420), modTime: time.Unix(1483945855, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "assets/environment-template.yml": assetsEnvironmentTemplateYml, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png -// then AssetDir("data") would return []string{"foo.txt", "img"} -// AssetDir("data/img") would return []string{"a.png", "b.png"} -// AssetDir("foo.txt") and AssetDir("notexist") would return an error -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - cannonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(cannonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} -var _bintree = &bintree{nil, map[string]*bintree{ - "assets": &bintree{nil, map[string]*bintree{ - "environment-template.yml": &bintree{assetsEnvironmentTemplateYml, map[string]*bintree{}}, - }}, -}} - -// RestoreAsset restores an asset under the given directory -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) - if err != nil { - return err - } - return nil -} - -// RestoreAssets restores an asset under the given directory recursively -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - cannonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) -} - diff --git a/environments/upsert.go b/environments/upsert.go index 3a752644..edd15207 100644 --- a/environments/upsert.go +++ b/environments/upsert.go @@ -2,15 +2,9 @@ package environments import ( "fmt" - "text/template" "github.com/stelligent/mu/common" "github.com/urfave/cli" - "os" ) -type cfnTemplate struct { - StackName string - TemplatePath string -} func newUpsertCommand(config *common.Config) *cli.Command { cmd := &cli.Command{ @@ -33,14 +27,13 @@ func newUpsertCommand(config *common.Config) *cli.Command { func runUpsert(config *common.Config, environmentName string) error { // get the environment from config by name - environment, err := common.GetEnvironment(config, environmentName) - + environment, err := config.GetEnvironment(environmentName) if err != nil { return err } // generate the CFN template - template, err := generateCFNTemplate(environment) + stack, err := environment.NewStack() if err != nil { return err } @@ -51,43 +44,10 @@ func runUpsert(config *common.Config, environmentName string) error { // wait for stack to be updated - fmt.Printf("upserting environment:%s stack:%s path:%s\n",environment.Name, template.StackName, template.TemplatePath) + fmt.Printf("upserting environment:%s stack:%s path:%s\n",environment.Name, stack.Name, stack.TemplatePath) return nil } -//go:generate go-bindata -pkg $GOPACKAGE -o assets.go assets/ -func generateCFNTemplate(environment *common.Environment) (*cfnTemplate, error) { - stackName := fmt.Sprintf("mu-env-%s", environment.Name) - templatePath := fmt.Sprintf("%s%s.yml",os.TempDir(), stackName) - - environmentTemplate, err := Asset("assets/environment-template.yml") - if err != nil { - return nil, err - } - - tmpl, err := template.New("environment").Parse(string(environmentTemplate[:])) - if err != nil { - return nil, err - } - - templateOut, err := os.Create(templatePath) - defer templateOut.Close() - if err != nil { - return nil, err - } - - err = tmpl.Execute(templateOut, environment) - if err != nil { - return nil, err - } - - templateOut.Sync() - - return &cfnTemplate{ - StackName: stackName, - TemplatePath: templatePath, - }, nil -} diff --git a/environments/upsert_test.go b/environments/upsert_test.go index a9805d4b..d89ca6c4 100644 --- a/environments/upsert_test.go +++ b/environments/upsert_test.go @@ -8,9 +8,7 @@ import ( func TestNewUpsertCommand(t *testing.T) { assert := assert.New(t) - - config := &common.Config {} - + config := common.NewConfig() command := newUpsertCommand(config) assert.NotNil(command) diff --git a/main.go b/main.go index de6d57ac..5abcf3e6 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ func newApp() *cli.App { } app.Before = func(c *cli.Context) error { - common.LoadConfig(config, c.String("config")) + config.LoadFromFile(c.String("config")) return nil } From 489d98b626578741069c160e6fd70a309c41367d Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 9 Jan 2017 23:20:14 -0800 Subject: [PATCH 13/31] commit generated assets file --- common/assets.go | 237 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 common/assets.go diff --git a/common/assets.go b/common/assets.go new file mode 100644 index 00000000..14db1d8e --- /dev/null +++ b/common/assets.go @@ -0,0 +1,237 @@ +// Code generated by go-bindata. +// sources: +// assets/environment-template.yml +// DO NOT EDIT! + +package common + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x5a\x7b\x4f\x1b\xb9\x16\xff\x9f\x4f\x61\x72\x2b\x65\xbb\x62\xf2\x98\x00\xa5\x23\xb1\xab\x10\xd2\x12\x95\xd0\x28\x93\x52\x69\x69\x55\x39\x1e\x27\xb1\x3a\x63\xcf\xb5\x3d\x40\xb6\xb7\xdf\xfd\xca\xf6\xbc\x1f\x59\xe8\xe3\xd2\x4b\x85\x4a\xec\xe3\xe3\xdf\xf9\xf9\x9c\x63\xfb\x38\x96\x65\xed\x0d\xdf\xbb\x0b\x1c\x84\x3e\x94\xf8\x15\xe3\x01\x94\xd7\x98\x0b\xc2\xa8\x03\xda\x76\xaf\xdf\xb3\x7a\x2f\xad\xde\xcb\xf6\xde\x0c\x72\x18\x60\x89\xb9\x70\xf6\x00\x98\x50\x21\x21\x45\x78\xb1\x0d\xb1\xfa\x0c\x80\xfe\x0b\xb8\x92\x13\xba\xd6\x0d\xe7\x58\x20\x4e\x42\xa9\x55\x25\xf2\x40\x6e\x43\x0c\x24\x03\x91\xc0\x9d\x58\x6c\x05\x23\x5f\x3a\x40\xda\x9d\x80\x20\xce\xf2\xda\x31\x85\x14\x6d\x9d\x5d\xfa\x8c\x48\xac\x12\xac\x18\x07\xd7\xb3\x51\x3d\xa2\xa1\xef\xb3\x3b\xec\x5d\x43\x3f\xc2\xc2\x28\xb5\x80\x67\xe6\x4f\x3f\x79\x04\x41\x89\xbd\x22\xb6\x4c\xe8\x1c\x0b\xc2\xb1\x37\x82\x21\x44\x44\x6e\xf3\xb6\x5f\x45\xc1\x12\xf3\xe2\xc0\x76\xbf\x5d\x05\x6f\x04\x01\x5b\x01\x12\x9b\x21\x14\x7e\x1f\x46\x14\x6d\x00\xa1\x60\xcb\x22\x0e\xc6\x23\x17\x20\x3f\x12\x52\xeb\x9c\xc2\x7b\x97\xfc\x8d\xff\x71\x3e\xbb\x66\xbe\x29\xbc\x27\x41\x14\x00\x5a\x37\xef\x06\x4a\x80\x20\x05\x4b\x1c\x03\xc0\x5e\x03\x84\x37\x78\x7b\x05\x83\x07\x2d\x77\x2c\xaa\xac\x82\x42\x30\x44\xa0\xc4\xe0\x8e\xc8\x0d\xb8\x63\xfc\x33\xe6\x19\x80\x0e\x00\x97\x18\xde\x62\xb0\xf4\x21\xfd\xac\x06\x78\x44\xc0\xa5\x8f\x81\xeb\x5e\x00\x88\x10\x16\xa2\xe4\x28\x6d\x65\xa2\xeb\x5e\xe8\xe5\xac\xf1\x0d\x37\x5a\x52\x2c\xc1\x8a\xb3\x00\xdc\x6d\x08\xda\x68\x18\x4a\xb8\xa2\xb3\x62\xc5\x94\xd0\x4b\x4c\xd7\x72\xe3\x80\xf6\x4b\x43\xe5\x14\xde\xa7\x4d\xfd\x93\x76\x11\x4b\xaf\xa3\xff\x75\x7b\x79\x07\x9b\x41\x29\x31\xa7\x0e\x68\xfd\xf6\xe1\x83\xf7\xa5\x7f\x30\xf8\xfa\xfc\xc3\x87\xce\x43\x3e\x74\xe3\x3f\xed\xaf\xcf\x5b\x5a\xe5\x88\x51\x21\x39\x24\x54\x16\x6c\x6c\x07\x91\x90\x6a\xcd\x20\xb8\x85\x3e\xf1\xc0\x68\x72\x3e\x07\x4b\x9f\xa1\xcf\x0e\xb8\xef\xe8\x7f\xdd\xfb\x4e\x7b\x6f\x8a\x25\xf4\xa0\x84\x8a\xa7\xe1\x7b\xd7\x71\x46\x3e\x8b\x3c\x13\xe8\x4a\x93\x33\xa1\x12\xf3\x15\x44\xf1\xba\xa6\x61\xfe\x9a\xb3\x28\x8c\xa3\x44\x45\xc6\x25\x5c\x62\x3f\xf9\xa8\x7e\xbc\x84\x83\x56\x1a\x8c\x23\x46\x57\x64\x1d\x71\xad\xba\x95\xca\x16\x53\x47\xf2\x63\x15\x92\x48\x7d\x87\x09\xee\x42\x5f\xec\x5a\x85\xb6\xc4\x19\x1e\x02\x76\x18\x49\x06\x5c\x04\x7d\x42\xd7\x8f\x05\x5c\x0a\xfe\x42\x5f\x1c\xa0\x45\x12\x35\x8e\x54\x49\x35\x69\x36\xf0\x98\x24\x49\x13\x90\x7f\xb6\xca\xe3\xf3\x69\xb1\x49\x45\x21\x2f\xa6\x2a\x0a\x31\x5c\x1c\xfa\x06\x6b\xe9\x35\x87\x54\xe6\x02\x05\xfc\x66\x22\x53\x65\x56\xca\x28\x7e\xfe\x10\x5d\x99\x37\xd6\xa9\x4c\x35\xd4\x26\xd3\xa2\xa6\x58\x24\x9f\x8a\xd2\xe4\x01\x10\x8b\xa8\x4c\xb5\x15\x52\x64\x51\x4b\x92\x01\x77\x6a\x19\x31\xea\x11\xe5\x08\x7a\xc1\x2e\xa0\x28\x18\xd8\x7a\x45\x1d\xe7\x8a\xc9\x56\x16\x12\xba\x69\xfc\xef\x08\xfa\xa2\xe5\x80\x9b\xfd\x39\x5e\x25\xa4\x1c\x80\x76\xfb\xe3\xde\x14\x86\x21\xa1\x6b\x11\x07\xdf\x1c\xaf\x09\xa3\x0b\x36\x9c\x4e\x8c\x92\x48\x58\x18\x0a\x69\xf5\x13\x9d\xc3\xe9\x64\x72\xee\x00\x18\x10\xcb\x5e\x0e\x96\xc7\xbd\xc3\x7e\x22\x78\x87\x85\xb4\xec\x1a\x41\x88\x8e\x4f\x5e\xd8\xc8\xec\x58\x38\x32\x82\x75\x1a\x7b\x03\x7b\x70\xb2\x7c\x61\x52\x15\x0c\x2d\xca\xb8\xdc\x34\xce\xbf\x5a\xda\xab\xbe\xfd\xf2\x28\x91\x16\x2c\x8a\xa5\xeb\x40\x1c\x0e\x8e\x0e\x5f\xf4\xed\x5e\x01\x6d\x9d\xda\xe5\x0a\xf7\x5e\x1e\x79\xab\xaa\xda\x3a\x69\xf4\xe2\x64\x75\x38\x80\x87\x89\x6d\x08\x53\xc9\xa1\x5f\x2b\x8b\xfb\xf8\x78\x75\x72\xa2\x78\x30\x3b\x80\x09\x6e\x23\x79\x3d\x1b\x25\x43\x94\x6f\x3a\xa0\x9f\x64\xee\xfe\xb1\x09\xd9\x68\xe9\x13\x34\xfc\xab\xdf\x20\x66\x1f\x16\xc4\xec\x1a\xb1\x7e\x55\x6c\x50\x23\x66\x1b\xb1\x39\x16\x2c\xe2\xc8\x1c\x44\x52\x78\x66\x43\xd2\x89\x7a\x3c\xb2\x1d\x27\x39\xcb\xcc\x38\x0b\x31\x97\x04\xa7\xc9\x64\x44\x3c\x7e\x66\xf2\xfd\xfe\x2b\x42\xbd\x09\x9d\xc2\x10\xdc\x14\x6c\x3f\x50\x8a\x0f\xcc\xde\xf0\x31\x1e\x37\xa6\x6a\x77\x3d\xa7\xc2\x8d\xc2\x90\x71\xb5\x9f\x4a\x1e\xe1\x76\xb9\xfb\x82\x09\x49\x61\x80\x45\x49\xa0\x9c\x84\x80\x76\xfb\x6a\xc6\x36\x14\x18\x34\x29\xad\x65\xf3\x4c\x77\x83\x85\xd7\x21\x9a\x78\xb1\xfe\x84\x87\x87\xda\x9d\x2e\x67\xc9\xfa\x29\x0c\x4d\xd7\x24\x7c\x4b\x2f\x75\x7a\x75\x80\x32\x2f\xf1\xa7\x5b\x48\x7c\xb8\x24\x3e\x91\xdb\xbf\x18\xc5\x0e\xd8\x77\xb1\x8f\x91\x04\x37\xa0\x77\x00\xf6\x5f\x2b\x63\x84\x0e\x6e\x33\x60\x01\xd7\xb9\x4d\xf2\x0d\xde\x3a\xe0\x0a\x4b\x75\xca\x49\xd3\x90\x3e\x6f\x3a\x31\xa4\x0a\x33\xf6\x13\x31\x63\xff\x40\x66\xfa\x3f\x85\x99\xc1\x13\x31\x33\xf8\x81\xcc\xd8\x3f\x88\x19\x7d\x4a\xa3\x58\xbe\x86\x12\xdf\xc1\x6d\x3d\x33\x25\xa1\x06\x8a\xbe\x61\xf6\xeb\xd9\xe8\x41\x00\xae\x67\xa3\xb8\x7f\x28\x25\x44\x9b\x00\xd3\xc7\x2d\x54\x69\x96\x54\xa2\x6a\x99\xc1\x36\x67\x91\xc4\x0b\x95\xb1\xea\x01\x65\xfd\x8f\x82\xf1\xcd\xae\xab\xe7\xdb\x01\x25\xbe\x3c\x84\x98\x7a\xe2\x2d\x75\x6a\x88\x6d\xc0\x99\x19\x92\xc2\x2d\x33\x90\x9d\xa9\x24\xa1\xfa\x40\x9b\x73\xfb\xe2\x3d\x05\x80\x87\x12\x9c\xe6\xef\x6c\x9e\x61\x7c\xa1\x53\xb7\x87\x1d\x01\x5a\x3b\xa0\xc1\x3a\x33\xa2\x64\x59\x3a\xf5\xa3\x28\x28\x25\xd7\xa7\x83\x6d\x7f\x07\xec\xc1\xd3\xc1\x1e\x7c\x03\xec\x38\x36\x86\xc8\xaf\x87\x98\xf5\xff\xec\x28\x9c\xd0\x25\x8b\xa8\x37\x0e\x37\x38\xc0\x1c\xfa\x33\xc6\x65\x19\xe3\x98\x4a\xde\x90\xbf\x4a\x42\x0d\x68\x33\xa9\x12\x35\x25\x3b\x01\x98\x47\x3e\x36\x55\x1a\x07\xb4\xfb\xbd\x41\x72\x86\x9a\x71\x26\x19\x62\xbe\x03\xda\xc7\xed\x9c\xec\x10\x99\xdb\x3d\xcc\x5d\x66\xc7\x6b\x8e\x85\x3a\x84\xad\xa0\x2f\xd2\x53\xd8\x8e\xd0\x56\x36\xcf\x21\x5d\xe7\x2e\x42\xaf\x38\x0b\x34\x02\xfb\xb0\x9d\x36\x2e\x98\x9a\xfe\xe8\x68\x70\xd4\xce\x98\x73\xdd\x8b\x5f\x87\xaf\xc3\x9f\xc2\x97\x06\x50\xaa\x18\x34\x73\x66\xdb\x25\xc6\x4c\x43\x4c\xd7\x85\x94\xe1\xaf\xc3\xd7\xd1\x13\xfb\xd7\x49\xaf\xc4\x95\x69\x78\x1b\x49\x4d\xd6\xaf\x43\x54\xef\xbb\x88\xca\xdf\x86\xbe\x89\xa7\x32\x4d\x69\x10\x96\x36\xbe\xb2\x31\x0f\xdc\x0b\x6a\x07\x7c\xcf\xce\xfb\x30\x9e\x4b\xdb\xdf\x53\x83\xb7\xbf\x03\xfc\xe0\xa9\xc1\x0f\x1e\x05\x7e\x8c\xc4\xc8\x14\xb5\x6a\xc0\xe9\x92\x6f\x52\xc2\x1f\x8f\xdc\x61\x24\x59\x5c\xfe\xd4\x25\xde\xca\x90\x9c\x40\xe1\x83\x96\x2e\x1f\x60\x6f\x3e\x36\x6d\xe9\xb3\x91\xba\x0b\x4d\x3c\x4c\x25\x59\x91\x04\x9a\xda\xc9\x77\x78\x5a\x6d\xa7\xbd\xab\x33\x61\xca\xdc\xcb\x0a\xf5\x5c\x5d\xbb\x33\x63\x46\x8c\x4a\x48\x28\xe6\x49\x9d\x42\x24\x77\x3b\x42\x75\xcd\x30\x7d\xa8\xc9\xca\x88\x66\x64\xbe\xac\x5b\x2d\x58\x1a\x99\xba\xb2\xf0\x88\x63\x0d\x62\xc6\x7c\x92\x95\x6a\x93\xa2\x8f\x4b\xd6\x14\xe6\x0a\xd4\x0b\x12\x60\x16\x49\x07\xcc\x16\xfd\xa3\xa9\x6e\x7e\x17\x7a\x50\xe2\xe2\xf0\xdc\x6a\xcc\x99\xaf\xfe\x33\x52\x99\xa2\x29\xa1\xa9\x89\x13\xea\x62\x7e\x4b\x50\xc1\x3a\x6d\xdf\x19\x94\x68\x53\xb6\x1b\x80\x19\x8c\x04\x56\x50\xf2\x38\xd4\xcf\x7b\x48\xe4\x5b\x5a\x04\x9f\xcf\x84\x55\x7a\x77\x7b\x55\xcd\x62\x69\xf9\xfc\x63\x85\xb6\xb7\xfe\xc1\x82\xc8\xcc\x60\x94\x2b\xf2\x25\x2d\x41\x00\xa9\x57\x28\xdd\x03\xd0\xeb\x7f\x82\x9e\xf7\x29\x29\xfa\x7e\x92\xec\x13\xca\xc7\x4c\x65\xbc\xba\xc5\x47\x4b\xf0\x9f\x52\x2f\x00\xff\xda\xef\x2e\x09\xed\x2e\xa1\xd8\x54\xfa\x30\xda\x30\x15\x64\x9f\x46\x97\xef\xdc\xc5\x78\x7e\xfa\xec\x4b\x16\x9c\x5f\x01\xf8\xe3\x0f\xd0\xc5\x12\x75\x31\x12\xea\xb7\x63\xd0\xe7\xd4\xac\x88\x8f\x4b\xc8\x5b\x7a\x04\x5a\x51\xf5\x6b\x6d\xa2\x50\x8f\x6a\x55\x61\x53\x89\xa9\x6c\x84\x7d\x13\x40\x42\x3f\x56\x9a\x85\x84\xe8\xf3\xe9\xb3\x2f\x9a\x6a\x57\x7d\x98\x78\x5f\x2b\x52\x5c\x57\xae\x13\x31\x53\xc7\x2e\x4b\x05\xcc\x53\xfe\xd4\xeb\xf5\x0e\x7b\xb9\x1d\xce\xfc\xb0\x3b\xaa\xf6\x5e\xce\x98\x2c\xf5\xac\x75\x12\xaa\xf6\x64\x66\x6f\x18\xfb\x2c\x3a\x9e\x36\x1f\x46\x92\x59\x1c\xfb\x0c\x7a\x98\x7f\x23\x11\x15\x3d\x96\x9a\xa1\x4a\x8d\xe4\x64\xbd\xc6\x5c\x9c\x86\x4c\xc8\x4e\xa4\x23\xad\x22\x14\x42\xb9\x39\x4d\x6b\xb9\x9d\x6a\x24\x74\x12\xa7\xee\x34\x7a\x73\x45\x29\xd4\xc7\x8f\xd3\x2e\x0b\x65\x17\xde\x09\xed\x6f\x0a\x35\xa1\x44\x02\xeb\x16\x58\x96\x5e\x36\x90\x5f\x36\x95\xed\xbe\x02\xcb\xe2\x31\x96\x9a\xa0\xd4\xbd\x6a\xe9\xc0\xce\x85\x04\x80\x47\x14\x8a\xd3\xd2\x92\x08\x93\x4c\x4a\xde\x29\xb6\xe2\x96\x14\x22\x32\x5e\x05\xe3\xab\xe5\x66\x00\xb0\x2e\x30\x7b\xa5\x73\x54\xbe\x5f\x44\x1c\xcf\x23\x4a\x55\xaa\x68\x92\xaa\x89\x13\x60\xde\x64\xea\xa3\x65\xa7\xe4\x3f\x38\x58\xc3\x0e\x37\x09\xe0\xda\x5c\x8e\xf3\x25\xc5\xe2\x33\xcf\x81\xd9\x20\x5a\x39\xb6\x5b\x07\xe6\xd1\x22\x2d\x2f\xba\x18\x45\x9c\xc8\x6d\xfc\xe0\x0a\x6e\xcc\x98\x0b\x26\xa4\xfb\x3a\x95\x2a\xbc\x1d\x96\x6a\xee\xd9\xf3\xe9\x04\x06\x49\xeb\x8c\x33\x45\x52\x2c\x3b\x1e\xd9\xa5\x8e\x78\x44\xf2\xc6\x05\xf6\x27\x2b\x70\x93\x7b\xf5\x8a\xa1\x17\x3f\x19\x43\xae\x98\xbe\x77\xb7\x12\x6c\xef\x04\xe6\xe7\xb9\xb4\x0d\xc0\x2b\xea\x38\x67\x50\xe0\xe3\xc3\xfc\x1a\xd5\x04\x64\x2e\x99\x02\xeb\xbe\x18\x5e\xdb\x28\x30\x8f\x74\xbe\x0f\xac\x2d\x80\x77\xc2\x52\x2b\xb4\x64\x4c\x0a\xc9\x61\x58\x10\x7e\x92\x58\xa9\x4c\x2a\xf4\xd6\x08\x2c\x0c\x9e\xfd\xf9\xb0\x99\x6b\x4e\x64\x3b\xa6\xae\x2e\x63\x65\xa3\x9d\x0c\xa7\x2a\xab\x54\xd7\xba\xea\xc1\x33\x28\x37\x0e\x68\x75\x93\xe8\x98\xb3\x5c\x50\x59\xa9\xe3\xa8\x66\x33\xb7\xfa\xab\x7e\xc2\x58\xa6\x6e\x96\xa1\x10\x51\x80\x95\x80\x39\xcc\x9c\x33\x14\x05\x2a\x41\xa7\x54\xba\x12\x4a\x5c\x6c\xb2\xc0\x78\xb5\xc2\x48\x3a\x20\x7f\x4d\x37\x13\x10\x8a\x48\x08\xfd\x62\xf4\x27\x47\x9d\xbd\x62\x90\x63\x64\x77\x60\x00\xff\x66\x14\xde\xa9\xed\x36\xc8\xf5\xc7\x97\xbc\xbd\xbc\xbc\x90\xc2\xc9\x00\x37\xf0\xa4\xed\x20\x79\xaa\x8c\x65\x26\x90\x30\x12\x56\x9c\x2b\xb3\x93\x55\x83\xe5\xb5\xb6\xef\xb2\xbe\x0e\xb5\xb1\x53\x38\xfa\xc8\x89\xb3\xc3\x7e\xb9\x5f\xb9\x91\xea\xaa\x38\x7b\x8d\xec\x39\xe6\x8f\x91\x26\x02\xb1\x5b\xcc\x67\xcc\xf7\xc7\xd4\x0b\x19\xa1\xb2\x46\xcc\x8d\x96\x01\x91\xbf\x57\x7a\xb8\x53\x6d\x13\x8e\x52\x56\x68\x4e\x76\x59\x07\xb4\x7e\x57\x4b\x61\x32\x64\xc3\x7d\x2c\x9f\x54\x9b\xae\x28\xb5\x55\x47\x3d\xa2\xf0\x5d\x9b\xf1\xc8\xd5\x73\xa5\x99\x1a\x64\x5a\x4b\xe9\x7b\x42\x4d\x91\x20\x75\x8c\x49\x98\x95\x18\x24\x0a\x0b\x95\x80\x99\x79\x7d\x2d\x56\x98\x6a\x1a\x47\xc4\xe3\x93\xb0\x5a\xb8\x1a\xfb\xcb\xff\x89\xf9\x97\x67\x3f\xc9\xf2\x62\xbd\xa8\xa6\x31\xb1\x3c\x5f\x5a\x19\x5f\x9e\xd9\x6a\x35\xe6\x51\xd3\xa3\x4f\x1d\xae\xa6\x3d\xbc\x16\x64\x0e\x62\x0a\x26\xc5\x97\x16\x6b\x34\x05\xe6\x42\x54\x98\x50\x71\xfa\x1a\xcb\xa1\x94\x66\x85\x3a\x71\x73\x9e\xe0\xbc\x90\x71\xe3\x9c\x94\x6a\xb0\xc7\x97\x67\xff\x0f\x16\x56\xc0\xd7\x9a\x58\xe6\x61\x8c\xc4\xd8\x5f\x56\x6d\xf3\xa1\x90\x04\x5d\x32\xe8\x9d\x41\x1f\x52\x44\xe8\xfa\xda\x76\x9c\xac\x21\xce\x6b\x4d\xd5\x94\xf2\xe6\xf5\xa3\x8b\x0c\xa5\xa3\x5a\x69\xa7\x54\x46\xa6\xc6\x5d\xaa\xe4\x49\xeb\x2a\x32\x4d\x46\xc6\x03\x1a\x0c\xcc\x73\x30\xe4\x34\x39\xd6\xe9\xb9\x62\x91\xf8\x4b\x89\x66\x83\xc8\xa1\x33\x93\xaf\x18\xbf\x83\xdc\xcb\x02\x0e\xf2\x35\x96\xda\x92\xb2\xbe\x58\x51\x4e\x22\xdd\xf8\x4a\x21\x9a\xf9\xd6\xc5\x62\x31\x4b\x8d\xaf\x2a\x78\x30\x0d\xe5\x49\x6b\x4e\x2d\x09\x88\x1d\x30\xea\xf2\xdb\xdb\x48\x86\x91\x71\x11\x75\x34\x7d\xc7\xe3\x13\x44\xfc\x86\xa4\x0f\xa7\x1b\x29\x43\xa7\xdb\xd5\xb7\xf6\xb1\xbf\xec\x9c\x5f\xb9\xfa\xc4\xd6\xdd\x03\xe5\x6f\x99\xaa\xb4\xf8\x6e\x7e\x59\x59\x70\x45\x66\x41\x6f\xc6\x6b\x61\x89\x0b\xca\x86\x9c\x02\xb6\x02\x72\x83\xb5\xde\x44\xb0\x53\x53\xdc\x2b\xa9\xcd\x6f\xf7\xc5\x2f\x19\xc3\x00\xa7\x3a\xb3\xaf\xbd\x75\xe2\xe7\x7c\xaf\xaa\x2d\xd9\x04\x0a\x6a\x16\x1b\x0c\x88\x97\x28\xba\x0d\xd1\xde\x7f\x03\x00\x00\xff\xff\xc8\x38\x05\x7c\x34\x2e\x00\x00") + +func assetsEnvironmentTemplateYmlBytes() ([]byte, error) { + return bindataRead( + _assetsEnvironmentTemplateYml, + "assets/environment-template.yml", + ) +} + +func assetsEnvironmentTemplateYml() (*asset, error) { + bytes, err := assetsEnvironmentTemplateYmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/environment-template.yml", size: 11828, mode: os.FileMode(420), modTime: time.Unix(1483945855, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "assets/environment-template.yml": assetsEnvironmentTemplateYml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "assets": &bintree{nil, map[string]*bintree{ + "environment-template.yml": &bintree{assetsEnvironmentTemplateYml, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + From 8e109747a15db0abf8fed71a3c63d27f60e951ca Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 9 Jan 2017 23:22:29 -0800 Subject: [PATCH 14/31] defect with tmpdir missing '/' on linux vs osx --- common/stack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/stack.go b/common/stack.go index 86b10445..e464e80a 100644 --- a/common/stack.go +++ b/common/stack.go @@ -16,7 +16,7 @@ type Stack struct { func NewStack(name string) *Stack { return &Stack{ Name: name, - TemplatePath: fmt.Sprintf("%s%s.yml",os.TempDir(), name), + TemplatePath: fmt.Sprintf("%s/%s.yml",os.TempDir(), name), } } From b0aa348f5bdcd3d7cc7304e168473da49d08baa3 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 10 Jan 2017 15:03:53 -0800 Subject: [PATCH 15/31] Refactor to extract out interfaces for resource management. Interfaces improve unit testability --- .gitignore | 2 +- Makefile | 8 +- cli/app.go | 38 ++++ main_test.go => cli/app_test.go | 5 +- cli/environments.go | 100 ++++++++++ cli/environments_test.go | 75 ++++++++ cli/pipelines.go | 54 ++++++ cli/pipelines_test.go | 48 +++++ cli/services.go | 112 +++++++++++ cli/services_test.go | 82 ++++++++ common/assets.go | 27 ++- common/assets/environment-template.yml | 237 ++++++----------------- common/assets/vpc-template.yml | 250 +++++++++++++++++++++++++ common/config.go | 30 +-- common/config_test.go | 26 +-- common/environment.go | 36 ---- common/environment_test.go | 21 --- common/stack.go | 116 +++++++++++- common/stack_test.go | 2 +- common/types.go | 27 +++ environments/commands.go | 23 --- environments/commands_test.go | 22 --- environments/list.go | 25 --- environments/list_test.go | 22 --- environments/show.go | 25 --- environments/show_test.go | 21 --- environments/terminate.go | 26 --- environments/terminate_test.go | 23 --- environments/upsert.go | 53 ------ environments/upsert_test.go | 21 --- main.go | 40 +--- pipelines/commands.go | 21 --- pipelines/commands_test.go | 20 -- pipelines/list.go | 26 --- pipelines/list_test.go | 22 --- pipelines/show.go | 30 --- pipelines/show_test.go | 22 --- resources/environment.go | 108 +++++++++++ resources/environment_test.go | 46 +++++ services/commands.go | 29 --- services/commands_test.go | 22 --- services/deploy.go | 32 ---- services/deploy_test.go | 23 --- services/setenv.go | 31 --- services/setenv_test.go | 23 --- services/show.go | 31 --- services/show_test.go | 22 --- services/undeploy.go | 31 --- services/undeploy_test.go | 23 --- 49 files changed, 1149 insertions(+), 1010 deletions(-) create mode 100644 cli/app.go rename main_test.go => cli/app_test.go (89%) create mode 100644 cli/environments.go create mode 100644 cli/environments_test.go create mode 100644 cli/pipelines.go create mode 100644 cli/pipelines_test.go create mode 100644 cli/services.go create mode 100644 cli/services_test.go create mode 100644 common/assets/vpc-template.yml delete mode 100644 common/environment.go delete mode 100644 common/environment_test.go create mode 100644 common/types.go delete mode 100644 environments/commands.go delete mode 100644 environments/commands_test.go delete mode 100644 environments/list.go delete mode 100644 environments/list_test.go delete mode 100644 environments/show.go delete mode 100644 environments/show_test.go delete mode 100644 environments/terminate.go delete mode 100644 environments/terminate_test.go delete mode 100644 environments/upsert.go delete mode 100644 environments/upsert_test.go delete mode 100644 pipelines/commands.go delete mode 100644 pipelines/commands_test.go delete mode 100644 pipelines/list.go delete mode 100644 pipelines/list_test.go delete mode 100644 pipelines/show.go delete mode 100644 pipelines/show_test.go create mode 100644 resources/environment.go create mode 100644 resources/environment_test.go delete mode 100644 services/commands.go delete mode 100644 services/commands_test.go delete mode 100644 services/deploy.go delete mode 100644 services/deploy_test.go delete mode 100644 services/setenv.go delete mode 100644 services/setenv_test.go delete mode 100644 services/show.go delete mode 100644 services/show_test.go delete mode 100644 services/undeploy.go delete mode 100644 services/undeploy_test.go diff --git a/.gitignore b/.gitignore index 6ce96349..3e58645e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ *.iml .idea -release/ +.release/ diff --git a/Makefile b/Makefile index 87796e11..ed72c5fc 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ BRANCH := $(or $(TRAVIS_BRANCH), $(shell git rev-parse --abbrev-ref HEAD)) IS_MASTER := $(filter master, $(BRANCH)) VERSION := $(shell cat VERSION)$(if $(IS_MASTER),,-$(BRANCH)) ARCH := $(shell go env GOARCH) -BUILD_FILES = $(foreach os, $(TARGET_OS), release/$(PACKAGE)-$(os)-$(ARCH)) +BUILD_FILES = $(foreach os, $(TARGET_OS), .release/$(PACKAGE)-$(os)-$(ARCH)) UPLOAD_FILES = $(foreach os, $(TARGET_OS), $(PACKAGE)-$(os)-$(ARCH)) GOLDFLAGS = "-X main.version=$(VERSION)" TAG_VERSION = v$(VERSION) @@ -16,7 +16,7 @@ default: build setup: @echo "=== preparing $(VERSION) from $(BRANCH) ===" - mkdir -p release + mkdir -p .release go get -u "github.com/golang/lint/golint" go get -u "github.com/aktau/github-release" go get -u "github.com/jteeuwen/go-bindata/..." @@ -54,7 +54,7 @@ release-create: release-clean $(TARGET_OS): release-create @echo "=== uploading $@ ===" - github-release upload -u $(ORG) -r $(PACKAGE) -t $(TAG_VERSION) -n "$(PACKAGE)-$@-$(ARCH)" -f "release/$(PACKAGE)-$@-$(ARCH)" + github-release upload -u $(ORG) -r $(PACKAGE) -t $(TAG_VERSION) -n "$(PACKAGE)-$@-$(ARCH)" -f ".release/$(PACKAGE)-$@-$(ARCH)" dev-release: $(TARGET_OS) @@ -69,6 +69,6 @@ endif clean: @echo "=== cleaning ===" - rm -rf release + rm -rf .release .PHONY: default lint test build setup clean release-clean release-create dev-release release $(UPLOAD_FILES) $(TARGET_OS) diff --git a/cli/app.go b/cli/app.go new file mode 100644 index 00000000..f72edfc7 --- /dev/null +++ b/cli/app.go @@ -0,0 +1,38 @@ +package cli + +import ( + "github.com/urfave/cli" + "github.com/stelligent/mu/common" +) + +// NewApp creates a new CLI app +func NewApp(version string) *cli.App { + config := common.NewConfig() + app := cli.NewApp() + app.Name = "mu" + app.Usage = "Microservice Platform on AWS" + app.Version = version + app.EnableBashCompletion = true + + app.Commands = []cli.Command{ + *newEnvironmentsCommand(config), + *newServicesCommand(config), + *newPipelinesCommand(config), + } + + app.Before = func(c *cli.Context) error { + config.LoadFromFile(c.String("config")) + return nil + } + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "config, c", + Usage: "path to config file", + Value: "mu.yml", + }, + } + + return app +} + diff --git a/main_test.go b/cli/app_test.go similarity index 89% rename from main_test.go rename to cli/app_test.go index 5ed824e0..23c1d9fc 100644 --- a/main_test.go +++ b/cli/app_test.go @@ -1,4 +1,4 @@ -package main +package cli import ( "testing" @@ -7,10 +7,11 @@ import ( func TestNewApp(t *testing.T) { assert := assert.New(t) - app := newApp() + app := NewApp("1.2.3") assert.NotNil(app) assert.Equal("mu", app.Name, "Name should match") + assert.Equal("1.2.3", app.Version, "Version should match") assert.Equal("Microservice Platform on AWS", app.Usage, "usage should match") assert.Equal(true, app.EnableBashCompletion, "bash completion should match") assert.Equal(1, len(app.Flags), "Flags len should match") diff --git a/cli/environments.go b/cli/environments.go new file mode 100644 index 00000000..ffcd12f4 --- /dev/null +++ b/cli/environments.go @@ -0,0 +1,100 @@ +package cli + +import( + "github.com/urfave/cli" + "fmt" + "errors" + "github.com/stelligent/mu/common" + "github.com/stelligent/mu/resources" +) + +func newEnvironmentsCommand(config *common.Config) *cli.Command { + environmentManager := resources.NewEnvironmentManager(config) + + cmd := &cli.Command { + Name: "environment", + Aliases: []string{"env"}, + Usage: "options for managing environments", + Subcommands: []cli.Command{ + *newEnvironmentsListCommand(environmentManager), + *newEnvironmentsShowCommand(environmentManager), + *newEnvironmentsUpsertCommand(environmentManager), + *newEnvironmentsTerminateCommand(environmentManager), + }, + } + + return cmd +} + +func newEnvironmentsUpsertCommand(environmentManager resources.EnvironmentManager) *cli.Command { + cmd := &cli.Command{ + Name: "upsert", + Aliases: []string{"up"}, + Usage: "create/update an environment", + ArgsUsage: "", + Action: func(c *cli.Context) error { + environmentName := c.Args().First() + if(len(environmentName) == 0) { + cli.ShowCommandHelp(c, "upsert") + return errors.New("environment must be provided") + } + + return environmentManager.UpsertEnvironment(environmentName) + }, + } + + return cmd +} + +func newEnvironmentsListCommand(environmentManager resources.EnvironmentManager) *cli.Command { + cmd := &cli.Command { + Name: "list", + Aliases: []string{"ls"}, + Usage: "list environments", + Action: func(c *cli.Context) error { + fmt.Println("listing environments") + return nil + }, + } + + return cmd +} + +func newEnvironmentsShowCommand(environmentManager resources.EnvironmentManager) *cli.Command { + cmd := &cli.Command { + Name: "show", + Usage: "show environment details", + ArgsUsage: "", + Action: func(c *cli.Context) error { + environmentName := c.Args().First() + if(len(environmentName) == 0) { + cli.ShowCommandHelp(c, "show") + return errors.New("environment must be provided") + } + fmt.Printf("showing environment: %s\n",environmentName) + return nil + }, + } + + return cmd +} +func newEnvironmentsTerminateCommand(environmentManager resources.EnvironmentManager) *cli.Command { + cmd := &cli.Command { + Name: "terminate", + Aliases: []string{"term"}, + Usage: "terminate an environment", + ArgsUsage: "", + Action: func(c *cli.Context) error { + environmentName := c.Args().First() + if(len(environmentName) == 0) { + cli.ShowCommandHelp(c, "terminate") + return errors.New("environment must be provided") + } + fmt.Printf("terminating environment: %s\n",environmentName) + return nil + }, + } + + return cmd +} + diff --git a/cli/environments_test.go b/cli/environments_test.go new file mode 100644 index 00000000..9a4b4b24 --- /dev/null +++ b/cli/environments_test.go @@ -0,0 +1,75 @@ +package cli + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/stelligent/mu/common" + "github.com/stelligent/mu/resources" +) + +func TestNewEnvironmentsCommand(t *testing.T) { + assert := assert.New(t) + + config := common.NewConfig() + + command := newEnvironmentsCommand(config) + + assert.NotNil(command) + assert.Equal("environment", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("env", command.Aliases[0], "Aliases should match") + assert.Equal("options for managing environments", command.Usage, "Usage should match") + assert.Equal(4, len(command.Subcommands), "Subcommands len should match") +} + +func TestNewEnvironmentsUpsertCommand(t *testing.T) { + assert := assert.New(t) + config := common.NewConfig() + envMgr := resources.NewEnvironmentManager(config) + command := newEnvironmentsUpsertCommand(envMgr) + + assert.NotNil(command) + assert.Equal("upsert", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("up", command.Aliases[0], "Aliases should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.NotNil(command.Action) +} + +func TestNewEnvironmentsListCommand(t *testing.T) { + assert := assert.New(t) + config := common.NewConfig() + envMgr := resources.NewEnvironmentManager(config) + command := newEnvironmentsListCommand(envMgr) + + assert.NotNil(command) + assert.Equal("list", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("ls", command.Aliases[0], "Aliases should match") + assert.Equal("list environments", command.Usage, "Usage should match") + assert.NotNil(command.Action) +} +func TestNewEnvironmentsShowCommand(t *testing.T) { + assert := assert.New(t) + config := common.NewConfig() + envMgr := resources.NewEnvironmentManager(config) + command := newEnvironmentsShowCommand(envMgr) + + assert.NotNil(command) + assert.Equal("show", command.Name, "Name should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.NotNil(command.Action) +} +func TestNewEnvironmentsTerminateCommand(t *testing.T) { + assert := assert.New(t) + config := common.NewConfig() + envMgr := resources.NewEnvironmentManager(config) + command := newEnvironmentsTerminateCommand(envMgr) + + assert.NotNil(command) + assert.Equal("terminate", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("term", command.Aliases[0], "Aliases should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.NotNil(command.Action) +} diff --git a/cli/pipelines.go b/cli/pipelines.go new file mode 100644 index 00000000..309fcddd --- /dev/null +++ b/cli/pipelines.go @@ -0,0 +1,54 @@ +package cli + +import( + "github.com/urfave/cli" + "fmt" + "github.com/stelligent/mu/common" +) + +func newPipelinesCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "pipeline", + Usage: "options for managing pipelines", + Subcommands: []cli.Command{ + *newPipelinesListCommand(config), + *newPipelinesShowCommand(config), + }, + } + + return cmd +} + +func newPipelinesListCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "list", + Aliases: []string{"ls"}, + Usage: "list pipelines", + Action: func(c *cli.Context) error { + fmt.Println("listing pipelines") + return nil + }, + } + + return cmd +} + +func newPipelinesShowCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "show", + Usage: "show pipeline details", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to show", + }, + }, + Action: func(c *cli.Context) error { + service := c.String("service") + fmt.Printf("showing pipeline: %s\n",service) + return nil + }, + } + + return cmd +} diff --git a/cli/pipelines_test.go b/cli/pipelines_test.go new file mode 100644 index 00000000..b350c311 --- /dev/null +++ b/cli/pipelines_test.go @@ -0,0 +1,48 @@ +package cli + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/stelligent/mu/common" +) + +func TestNewPipelinesCommand(t *testing.T) { + assert := assert.New(t) + + config := common.NewConfig() + + command := newPipelinesCommand(config) + + assert.NotNil(command) + assert.Equal("pipeline", command.Name, "Name should match") + assert.Equal("options for managing pipelines", command.Usage, "Usage should match") + assert.Equal(2, len(command.Subcommands), "Subcommands len should match") +} +func TestNewPipelinesListCommand(t *testing.T) { + assert := assert.New(t) + + config := common.NewConfig() + + command := newPipelinesListCommand(config) + + assert.NotNil(command) + assert.Equal("list", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("ls", command.Aliases[0], "Aliases should match") + assert.Equal("list pipelines", command.Usage, "Usage should match") + assert.NotNil(command.Action) +} +func TestNewPipelinesShowCommand(t *testing.T) { + assert := assert.New(t) + + config := common.NewConfig() + + command := newPipelinesShowCommand(config) + + assert.NotNil(command) + assert.Equal("show", command.Name, "Name should match") + assert.Equal(1, len(command.Flags), "Flag len should match") + assert.Equal("service, s", command.Flags[0].GetName(), "Flag should match") + assert.NotNil(command.Action) +} + diff --git a/cli/services.go b/cli/services.go new file mode 100644 index 00000000..16ea749e --- /dev/null +++ b/cli/services.go @@ -0,0 +1,112 @@ +package cli + +import( + "github.com/urfave/cli" + "fmt" + "github.com/stelligent/mu/common" +) + +func newServicesCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "service", + Aliases: []string{"svc"}, + Usage: "options for managing services", + Subcommands: []cli.Command{ + *newServicesShowCommand(config), + *newServicesDeployCommand(config), + *newServicesSetenvCommand(config), + *newServicesUndeployCommand(config), + }, + } + + return cmd +} + +func newServicesShowCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "show", + Usage: "show service details", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to show", + }, + }, + Action: func(c *cli.Context) error { + service := c.String("service") + fmt.Printf("showing service: %s\n",service) + return nil + }, + } + + return cmd +} + +func newServicesDeployCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "deploy", + Usage: "deploy service to environment", + ArgsUsage: "", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to deploy", + }, + }, + Action: func(c *cli.Context) error { + environmentName := c.Args().First() + serviceName := c.String("service") + fmt.Printf("deploying service: %s to environment: %s\n",serviceName, environmentName) + return nil + }, + } + + return cmd +} + +func newServicesSetenvCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "setenv", + Usage: "set environment variable", + ArgsUsage: " =...", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to deploy", + }, + }, + Action: func(c *cli.Context) error { + environmentName := c.Args().First() + serviceName := c.String("service") + keyvals := c.Args().Tail() + fmt.Printf("setenv service: %s to environment: %s with vals: %s\n",serviceName, environmentName, keyvals) + return nil + }, + } + + return cmd +} + +func newServicesUndeployCommand(config *common.Config) *cli.Command { + cmd := &cli.Command { + Name: "undeploy", + Usage: "undeploy service from environment", + ArgsUsage: "", + Flags: []cli.Flag { + cli.StringFlag{ + Name: "service, s", + Usage: "service to undeploy", + }, + }, + Action: func(c *cli.Context) error { + environmentName := c.Args().First() + serviceName := c.String("service") + fmt.Printf("undeploying service: %s to environment: %s\n",serviceName, environmentName) + return nil + }, + } + + return cmd +} + + diff --git a/cli/services_test.go b/cli/services_test.go new file mode 100644 index 00000000..b928d221 --- /dev/null +++ b/cli/services_test.go @@ -0,0 +1,82 @@ +package cli + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/stelligent/mu/common" +) + +func TestNewServicesCommand(t *testing.T) { + assert := assert.New(t) + + config := common.NewConfig() + + command := newServicesCommand(config) + + assert.NotNil(command) + assert.Equal("service", command.Name, "Name should match") + assert.Equal(1, len(command.Aliases), "Aliases len should match") + assert.Equal("svc", command.Aliases[0], "Aliases should match") + assert.Equal("options for managing services", command.Usage, "Usage should match") + assert.Equal(4, len(command.Subcommands), "Subcommands len should match") +} + +func TestNewServicesShowCommand(t *testing.T) { + assert := assert.New(t) + + config := common.NewConfig() + + command := newServicesShowCommand(config) + + assert.NotNil(command) + assert.Equal("show", command.Name, "Name should match") + assert.Equal(1, len(command.Flags), "Flags length") + assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") + assert.NotNil(command.Action) +} + +func TestNewServicesDeployCommand(t *testing.T) { + assert := assert.New(t) + + config := common.NewConfig() + + command := newServicesDeployCommand(config) + + assert.NotNil(command) + assert.Equal("deploy", command.Name, "Name should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.Equal(1, len(command.Flags), "Flags length") + assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") + assert.NotNil(command.Action) +} + +func TestNewSetenvCommand(t *testing.T) { + assert := assert.New(t) + + config := common.NewConfig() + + command := newServicesSetenvCommand(config) + + assert.NotNil(command) + assert.Equal("setenv", command.Name, "Name should match") + assert.Equal(" =...", command.ArgsUsage, "ArgsUsage should match") + assert.Equal(1, len(command.Flags), "Flags length") + assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") + assert.NotNil(command.Action) +} + +func TestNewUndeployCommand(t *testing.T) { + assert := assert.New(t) + + config := common.NewConfig() + + command := newServicesUndeployCommand(config) + + assert.NotNil(command) + assert.Equal("undeploy", command.Name, "Name should match") + assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.Equal(1, len(command.Flags), "Flags length") + assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") + assert.NotNil(command.Action) +} + diff --git a/common/assets.go b/common/assets.go index 14db1d8e..31c9548e 100644 --- a/common/assets.go +++ b/common/assets.go @@ -1,6 +1,7 @@ // Code generated by go-bindata. // sources: // assets/environment-template.yml +// assets/vpc-template.yml // DO NOT EDIT! package common @@ -68,7 +69,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x5a\x7b\x4f\x1b\xb9\x16\xff\x9f\x4f\x61\x72\x2b\x65\xbb\x62\xf2\x98\x00\xa5\x23\xb1\xab\x10\xd2\x12\x95\xd0\x28\x93\x52\x69\x69\x55\x39\x1e\x27\xb1\x3a\x63\xcf\xb5\x3d\x40\xb6\xb7\xdf\xfd\xca\xf6\xbc\x1f\x59\xe8\xe3\xd2\x4b\x85\x4a\xec\xe3\xe3\xdf\xf9\xf9\x9c\x63\xfb\x38\x96\x65\xed\x0d\xdf\xbb\x0b\x1c\x84\x3e\x94\xf8\x15\xe3\x01\x94\xd7\x98\x0b\xc2\xa8\x03\xda\x76\xaf\xdf\xb3\x7a\x2f\xad\xde\xcb\xf6\xde\x0c\x72\x18\x60\x89\xb9\x70\xf6\x00\x98\x50\x21\x21\x45\x78\xb1\x0d\xb1\xfa\x0c\x80\xfe\x0b\xb8\x92\x13\xba\xd6\x0d\xe7\x58\x20\x4e\x42\xa9\x55\x25\xf2\x40\x6e\x43\x0c\x24\x03\x91\xc0\x9d\x58\x6c\x05\x23\x5f\x3a\x40\xda\x9d\x80\x20\xce\xf2\xda\x31\x85\x14\x6d\x9d\x5d\xfa\x8c\x48\xac\x12\xac\x18\x07\xd7\xb3\x51\x3d\xa2\xa1\xef\xb3\x3b\xec\x5d\x43\x3f\xc2\xc2\x28\xb5\x80\x67\xe6\x4f\x3f\x79\x04\x41\x89\xbd\x22\xb6\x4c\xe8\x1c\x0b\xc2\xb1\x37\x82\x21\x44\x44\x6e\xf3\xb6\x5f\x45\xc1\x12\xf3\xe2\xc0\x76\xbf\x5d\x05\x6f\x04\x01\x5b\x01\x12\x9b\x21\x14\x7e\x1f\x46\x14\x6d\x00\xa1\x60\xcb\x22\x0e\xc6\x23\x17\x20\x3f\x12\x52\xeb\x9c\xc2\x7b\x97\xfc\x8d\xff\x71\x3e\xbb\x66\xbe\x29\xbc\x27\x41\x14\x00\x5a\x37\xef\x06\x4a\x80\x20\x05\x4b\x1c\x03\xc0\x5e\x03\x84\x37\x78\x7b\x05\x83\x07\x2d\x77\x2c\xaa\xac\x82\x42\x30\x44\xa0\xc4\xe0\x8e\xc8\x0d\xb8\x63\xfc\x33\xe6\x19\x80\x0e\x00\x97\x18\xde\x62\xb0\xf4\x21\xfd\xac\x06\x78\x44\xc0\xa5\x8f\x81\xeb\x5e\x00\x88\x10\x16\xa2\xe4\x28\x6d\x65\xa2\xeb\x5e\xe8\xe5\xac\xf1\x0d\x37\x5a\x52\x2c\xc1\x8a\xb3\x00\xdc\x6d\x08\xda\x68\x18\x4a\xb8\xa2\xb3\x62\xc5\x94\xd0\x4b\x4c\xd7\x72\xe3\x80\xf6\x4b\x43\xe5\x14\xde\xa7\x4d\xfd\x93\x76\x11\x4b\xaf\xa3\xff\x75\x7b\x79\x07\x9b\x41\x29\x31\xa7\x0e\x68\xfd\xf6\xe1\x83\xf7\xa5\x7f\x30\xf8\xfa\xfc\xc3\x87\xce\x43\x3e\x74\xe3\x3f\xed\xaf\xcf\x5b\x5a\xe5\x88\x51\x21\x39\x24\x54\x16\x6c\x6c\x07\x91\x90\x6a\xcd\x20\xb8\x85\x3e\xf1\xc0\x68\x72\x3e\x07\x4b\x9f\xa1\xcf\x0e\xb8\xef\xe8\x7f\xdd\xfb\x4e\x7b\x6f\x8a\x25\xf4\xa0\x84\x8a\xa7\xe1\x7b\xd7\x71\x46\x3e\x8b\x3c\x13\xe8\x4a\x93\x33\xa1\x12\xf3\x15\x44\xf1\xba\xa6\x61\xfe\x9a\xb3\x28\x8c\xa3\x44\x45\xc6\x25\x5c\x62\x3f\xf9\xa8\x7e\xbc\x84\x83\x56\x1a\x8c\x23\x46\x57\x64\x1d\x71\xad\xba\x95\xca\x16\x53\x47\xf2\x63\x15\x92\x48\x7d\x87\x09\xee\x42\x5f\xec\x5a\x85\xb6\xc4\x19\x1e\x02\x76\x18\x49\x06\x5c\x04\x7d\x42\xd7\x8f\x05\x5c\x0a\xfe\x42\x5f\x1c\xa0\x45\x12\x35\x8e\x54\x49\x35\x69\x36\xf0\x98\x24\x49\x13\x90\x7f\xb6\xca\xe3\xf3\x69\xb1\x49\x45\x21\x2f\xa6\x2a\x0a\x31\x5c\x1c\xfa\x06\x6b\xe9\x35\x87\x54\xe6\x02\x05\xfc\x66\x22\x53\x65\x56\xca\x28\x7e\xfe\x10\x5d\x99\x37\xd6\xa9\x4c\x35\xd4\x26\xd3\xa2\xa6\x58\x24\x9f\x8a\xd2\xe4\x01\x10\x8b\xa8\x4c\xb5\x15\x52\x64\x51\x4b\x92\x01\x77\x6a\x19\x31\xea\x11\xe5\x08\x7a\xc1\x2e\xa0\x28\x18\xd8\x7a\x45\x1d\xe7\x8a\xc9\x56\x16\x12\xba\x69\xfc\xef\x08\xfa\xa2\xe5\x80\x9b\xfd\x39\x5e\x25\xa4\x1c\x80\x76\xfb\xe3\xde\x14\x86\x21\xa1\x6b\x11\x07\xdf\x1c\xaf\x09\xa3\x0b\x36\x9c\x4e\x8c\x92\x48\x58\x18\x0a\x69\xf5\x13\x9d\xc3\xe9\x64\x72\xee\x00\x18\x10\xcb\x5e\x0e\x96\xc7\xbd\xc3\x7e\x22\x78\x87\x85\xb4\xec\x1a\x41\x88\x8e\x4f\x5e\xd8\xc8\xec\x58\x38\x32\x82\x75\x1a\x7b\x03\x7b\x70\xb2\x7c\x61\x52\x15\x0c\x2d\xca\xb8\xdc\x34\xce\xbf\x5a\xda\xab\xbe\xfd\xf2\x28\x91\x16\x2c\x8a\xa5\xeb\x40\x1c\x0e\x8e\x0e\x5f\xf4\xed\x5e\x01\x6d\x9d\xda\xe5\x0a\xf7\x5e\x1e\x79\xab\xaa\xda\x3a\x69\xf4\xe2\x64\x75\x38\x80\x87\x89\x6d\x08\x53\xc9\xa1\x5f\x2b\x8b\xfb\xf8\x78\x75\x72\xa2\x78\x30\x3b\x80\x09\x6e\x23\x79\x3d\x1b\x25\x43\x94\x6f\x3a\xa0\x9f\x64\xee\xfe\xb1\x09\xd9\x68\xe9\x13\x34\xfc\xab\xdf\x20\x66\x1f\x16\xc4\xec\x1a\xb1\x7e\x55\x6c\x50\x23\x66\x1b\xb1\x39\x16\x2c\xe2\xc8\x1c\x44\x52\x78\x66\x43\xd2\x89\x7a\x3c\xb2\x1d\x27\x39\xcb\xcc\x38\x0b\x31\x97\x04\xa7\xc9\x64\x44\x3c\x7e\x66\xf2\xfd\xfe\x2b\x42\xbd\x09\x9d\xc2\x10\xdc\x14\x6c\x3f\x50\x8a\x0f\xcc\xde\xf0\x31\x1e\x37\xa6\x6a\x77\x3d\xa7\xc2\x8d\xc2\x90\x71\xb5\x9f\x4a\x1e\xe1\x76\xb9\xfb\x82\x09\x49\x61\x80\x45\x49\xa0\x9c\x84\x80\x76\xfb\x6a\xc6\x36\x14\x18\x34\x29\xad\x65\xf3\x4c\x77\x83\x85\xd7\x21\x9a\x78\xb1\xfe\x84\x87\x87\xda\x9d\x2e\x67\xc9\xfa\x29\x0c\x4d\xd7\x24\x7c\x4b\x2f\x75\x7a\x75\x80\x32\x2f\xf1\xa7\x5b\x48\x7c\xb8\x24\x3e\x91\xdb\xbf\x18\xc5\x0e\xd8\x77\xb1\x8f\x91\x04\x37\xa0\x77\x00\xf6\x5f\x2b\x63\x84\x0e\x6e\x33\x60\x01\xd7\xb9\x4d\xf2\x0d\xde\x3a\xe0\x0a\x4b\x75\xca\x49\xd3\x90\x3e\x6f\x3a\x31\xa4\x0a\x33\xf6\x13\x31\x63\xff\x40\x66\xfa\x3f\x85\x99\xc1\x13\x31\x33\xf8\x81\xcc\xd8\x3f\x88\x19\x7d\x4a\xa3\x58\xbe\x86\x12\xdf\xc1\x6d\x3d\x33\x25\xa1\x06\x8a\xbe\x61\xf6\xeb\xd9\xe8\x41\x00\xae\x67\xa3\xb8\x7f\x28\x25\x44\x9b\x00\xd3\xc7\x2d\x54\x69\x96\x54\xa2\x6a\x99\xc1\x36\x67\x91\xc4\x0b\x95\xb1\xea\x01\x65\xfd\x8f\x82\xf1\xcd\xae\xab\xe7\xdb\x01\x25\xbe\x3c\x84\x98\x7a\xe2\x2d\x75\x6a\x88\x6d\xc0\x99\x19\x92\xc2\x2d\x33\x90\x9d\xa9\x24\xa1\xfa\x40\x9b\x73\xfb\xe2\x3d\x05\x80\x87\x12\x9c\xe6\xef\x6c\x9e\x61\x7c\xa1\x53\xb7\x87\x1d\x01\x5a\x3b\xa0\xc1\x3a\x33\xa2\x64\x59\x3a\xf5\xa3\x28\x28\x25\xd7\xa7\x83\x6d\x7f\x07\xec\xc1\xd3\xc1\x1e\x7c\x03\xec\x38\x36\x86\xc8\xaf\x87\x98\xf5\xff\xec\x28\x9c\xd0\x25\x8b\xa8\x37\x0e\x37\x38\xc0\x1c\xfa\x33\xc6\x65\x19\xe3\x98\x4a\xde\x90\xbf\x4a\x42\x0d\x68\x33\xa9\x12\x35\x25\x3b\x01\x98\x47\x3e\x36\x55\x1a\x07\xb4\xfb\xbd\x41\x72\x86\x9a\x71\x26\x19\x62\xbe\x03\xda\xc7\xed\x9c\xec\x10\x99\xdb\x3d\xcc\x5d\x66\xc7\x6b\x8e\x85\x3a\x84\xad\xa0\x2f\xd2\x53\xd8\x8e\xd0\x56\x36\xcf\x21\x5d\xe7\x2e\x42\xaf\x38\x0b\x34\x02\xfb\xb0\x9d\x36\x2e\x98\x9a\xfe\xe8\x68\x70\xd4\xce\x98\x73\xdd\x8b\x5f\x87\xaf\xc3\x9f\xc2\x97\x06\x50\xaa\x18\x34\x73\x66\xdb\x25\xc6\x4c\x43\x4c\xd7\x85\x94\xe1\xaf\xc3\xd7\xd1\x13\xfb\xd7\x49\xaf\xc4\x95\x69\x78\x1b\x49\x4d\xd6\xaf\x43\x54\xef\xbb\x88\xca\xdf\x86\xbe\x89\xa7\x32\x4d\x69\x10\x96\x36\xbe\xb2\x31\x0f\xdc\x0b\x6a\x07\x7c\xcf\xce\xfb\x30\x9e\x4b\xdb\xdf\x53\x83\xb7\xbf\x03\xfc\xe0\xa9\xc1\x0f\x1e\x05\x7e\x8c\xc4\xc8\x14\xb5\x6a\xc0\xe9\x92\x6f\x52\xc2\x1f\x8f\xdc\x61\x24\x59\x5c\xfe\xd4\x25\xde\xca\x90\x9c\x40\xe1\x83\x96\x2e\x1f\x60\x6f\x3e\x36\x6d\xe9\xb3\x91\xba\x0b\x4d\x3c\x4c\x25\x59\x91\x04\x9a\xda\xc9\x77\x78\x5a\x6d\xa7\xbd\xab\x33\x61\xca\xdc\xcb\x0a\xf5\x5c\x5d\xbb\x33\x63\x46\x8c\x4a\x48\x28\xe6\x49\x9d\x42\x24\x77\x3b\x42\x75\xcd\x30\x7d\xa8\xc9\xca\x88\x66\x64\xbe\xac\x5b\x2d\x58\x1a\x99\xba\xb2\xf0\x88\x63\x0d\x62\xc6\x7c\x92\x95\x6a\x93\xa2\x8f\x4b\xd6\x14\xe6\x0a\xd4\x0b\x12\x60\x16\x49\x07\xcc\x16\xfd\xa3\xa9\x6e\x7e\x17\x7a\x50\xe2\xe2\xf0\xdc\x6a\xcc\x99\xaf\xfe\x33\x52\x99\xa2\x29\xa1\xa9\x89\x13\xea\x62\x7e\x4b\x50\xc1\x3a\x6d\xdf\x19\x94\x68\x53\xb6\x1b\x80\x19\x8c\x04\x56\x50\xf2\x38\xd4\xcf\x7b\x48\xe4\x5b\x5a\x04\x9f\xcf\x84\x55\x7a\x77\x7b\x55\xcd\x62\x69\xf9\xfc\x63\x85\xb6\xb7\xfe\xc1\x82\xc8\xcc\x60\x94\x2b\xf2\x25\x2d\x41\x00\xa9\x57\x28\xdd\x03\xd0\xeb\x7f\x82\x9e\xf7\x29\x29\xfa\x7e\x92\xec\x13\xca\xc7\x4c\x65\xbc\xba\xc5\x47\x4b\xf0\x9f\x52\x2f\x00\xff\xda\xef\x2e\x09\xed\x2e\xa1\xd8\x54\xfa\x30\xda\x30\x15\x64\x9f\x46\x97\xef\xdc\xc5\x78\x7e\xfa\xec\x4b\x16\x9c\x5f\x01\xf8\xe3\x0f\xd0\xc5\x12\x75\x31\x12\xea\xb7\x63\xd0\xe7\xd4\xac\x88\x8f\x4b\xc8\x5b\x7a\x04\x5a\x51\xf5\x6b\x6d\xa2\x50\x8f\x6a\x55\x61\x53\x89\xa9\x6c\x84\x7d\x13\x40\x42\x3f\x56\x9a\x85\x84\xe8\xf3\xe9\xb3\x2f\x9a\x6a\x57\x7d\x98\x78\x5f\x2b\x52\x5c\x57\xae\x13\x31\x53\xc7\x2e\x4b\x05\xcc\x53\xfe\xd4\xeb\xf5\x0e\x7b\xb9\x1d\xce\xfc\xb0\x3b\xaa\xf6\x5e\xce\x98\x2c\xf5\xac\x75\x12\xaa\xf6\x64\x66\x6f\x18\xfb\x2c\x3a\x9e\x36\x1f\x46\x92\x59\x1c\xfb\x0c\x7a\x98\x7f\x23\x11\x15\x3d\x96\x9a\xa1\x4a\x8d\xe4\x64\xbd\xc6\x5c\x9c\x86\x4c\xc8\x4e\xa4\x23\xad\x22\x14\x42\xb9\x39\x4d\x6b\xb9\x9d\x6a\x24\x74\x12\xa7\xee\x34\x7a\x73\x45\x29\xd4\xc7\x8f\xd3\x2e\x0b\x65\x17\xde\x09\xed\x6f\x0a\x35\xa1\x44\x02\xeb\x16\x58\x96\x5e\x36\x90\x5f\x36\x95\xed\xbe\x02\xcb\xe2\x31\x96\x9a\xa0\xd4\xbd\x6a\xe9\xc0\xce\x85\x04\x80\x47\x14\x8a\xd3\xd2\x92\x08\x93\x4c\x4a\xde\x29\xb6\xe2\x96\x14\x22\x32\x5e\x05\xe3\xab\xe5\x66\x00\xb0\x2e\x30\x7b\xa5\x73\x54\xbe\x5f\x44\x1c\xcf\x23\x4a\x55\xaa\x68\x92\xaa\x89\x13\x60\xde\x64\xea\xa3\x65\xa7\xe4\x3f\x38\x58\xc3\x0e\x37\x09\xe0\xda\x5c\x8e\xf3\x25\xc5\xe2\x33\xcf\x81\xd9\x20\x5a\x39\xb6\x5b\x07\xe6\xd1\x22\x2d\x2f\xba\x18\x45\x9c\xc8\x6d\xfc\xe0\x0a\x6e\xcc\x98\x0b\x26\xa4\xfb\x3a\x95\x2a\xbc\x1d\x96\x6a\xee\xd9\xf3\xe9\x04\x06\x49\xeb\x8c\x33\x45\x52\x2c\x3b\x1e\xd9\xa5\x8e\x78\x44\xf2\xc6\x05\xf6\x27\x2b\x70\x93\x7b\xf5\x8a\xa1\x17\x3f\x19\x43\xae\x98\xbe\x77\xb7\x12\x6c\xef\x04\xe6\xe7\xb9\xb4\x0d\xc0\x2b\xea\x38\x67\x50\xe0\xe3\xc3\xfc\x1a\xd5\x04\x64\x2e\x99\x02\xeb\xbe\x18\x5e\xdb\x28\x30\x8f\x74\xbe\x0f\xac\x2d\x80\x77\xc2\x52\x2b\xb4\x64\x4c\x0a\xc9\x61\x58\x10\x7e\x92\x58\xa9\x4c\x2a\xf4\xd6\x08\x2c\x0c\x9e\xfd\xf9\xb0\x99\x6b\x4e\x64\x3b\xa6\xae\x2e\x63\x65\xa3\x9d\x0c\xa7\x2a\xab\x54\xd7\xba\xea\xc1\x33\x28\x37\x0e\x68\x75\x93\xe8\x98\xb3\x5c\x50\x59\xa9\xe3\xa8\x66\x33\xb7\xfa\xab\x7e\xc2\x58\xa6\x6e\x96\xa1\x10\x51\x80\x95\x80\x39\xcc\x9c\x33\x14\x05\x2a\x41\xa7\x54\xba\x12\x4a\x5c\x6c\xb2\xc0\x78\xb5\xc2\x48\x3a\x20\x7f\x4d\x37\x13\x10\x8a\x48\x08\xfd\x62\xf4\x27\x47\x9d\xbd\x62\x90\x63\x64\x77\x60\x00\xff\x66\x14\xde\xa9\xed\x36\xc8\xf5\xc7\x97\xbc\xbd\xbc\xbc\x90\xc2\xc9\x00\x37\xf0\xa4\xed\x20\x79\xaa\x8c\x65\x26\x90\x30\x12\x56\x9c\x2b\xb3\x93\x55\x83\xe5\xb5\xb6\xef\xb2\xbe\x0e\xb5\xb1\x53\x38\xfa\xc8\x89\xb3\xc3\x7e\xb9\x5f\xb9\x91\xea\xaa\x38\x7b\x8d\xec\x39\xe6\x8f\x91\x26\x02\xb1\x5b\xcc\x67\xcc\xf7\xc7\xd4\x0b\x19\xa1\xb2\x46\xcc\x8d\x96\x01\x91\xbf\x57\x7a\xb8\x53\x6d\x13\x8e\x52\x56\x68\x4e\x76\x59\x07\xb4\x7e\x57\x4b\x61\x32\x64\xc3\x7d\x2c\x9f\x54\x9b\xae\x28\xb5\x55\x47\x3d\xa2\xf0\x5d\x9b\xf1\xc8\xd5\x73\xa5\x99\x1a\x64\x5a\x4b\xe9\x7b\x42\x4d\x91\x20\x75\x8c\x49\x98\x95\x18\x24\x0a\x0b\x95\x80\x99\x79\x7d\x2d\x56\x98\x6a\x1a\x47\xc4\xe3\x93\xb0\x5a\xb8\x1a\xfb\xcb\xff\x89\xf9\x97\x67\x3f\xc9\xf2\x62\xbd\xa8\xa6\x31\xb1\x3c\x5f\x5a\x19\x5f\x9e\xd9\x6a\x35\xe6\x51\xd3\xa3\x4f\x1d\xae\xa6\x3d\xbc\x16\x64\x0e\x62\x0a\x26\xc5\x97\x16\x6b\x34\x05\xe6\x42\x54\x98\x50\x71\xfa\x1a\xcb\xa1\x94\x66\x85\x3a\x71\x73\x9e\xe0\xbc\x90\x71\xe3\x9c\x94\x6a\xb0\xc7\x97\x67\xff\x0f\x16\x56\xc0\xd7\x9a\x58\xe6\x61\x8c\xc4\xd8\x5f\x56\x6d\xf3\xa1\x90\x04\x5d\x32\xe8\x9d\x41\x1f\x52\x44\xe8\xfa\xda\x76\x9c\xac\x21\xce\x6b\x4d\xd5\x94\xf2\xe6\xf5\xa3\x8b\x0c\xa5\xa3\x5a\x69\xa7\x54\x46\xa6\xc6\x5d\xaa\xe4\x49\xeb\x2a\x32\x4d\x46\xc6\x03\x1a\x0c\xcc\x73\x30\xe4\x34\x39\xd6\xe9\xb9\x62\x91\xf8\x4b\x89\x66\x83\xc8\xa1\x33\x93\xaf\x18\xbf\x83\xdc\xcb\x02\x0e\xf2\x35\x96\xda\x92\xb2\xbe\x58\x51\x4e\x22\xdd\xf8\x4a\x21\x9a\xf9\xd6\xc5\x62\x31\x4b\x8d\xaf\x2a\x78\x30\x0d\xe5\x49\x6b\x4e\x2d\x09\x88\x1d\x30\xea\xf2\xdb\xdb\x48\x86\x91\x71\x11\x75\x34\x7d\xc7\xe3\x13\x44\xfc\x86\xa4\x0f\xa7\x1b\x29\x43\xa7\xdb\xd5\xb7\xf6\xb1\xbf\xec\x9c\x5f\xb9\xfa\xc4\xd6\xdd\x03\xe5\x6f\x99\xaa\xb4\xf8\x6e\x7e\x59\x59\x70\x45\x66\x41\x6f\xc6\x6b\x61\x89\x0b\xca\x86\x9c\x02\xb6\x02\x72\x83\xb5\xde\x44\xb0\x53\x53\xdc\x2b\xa9\xcd\x6f\xf7\xc5\x2f\x19\xc3\x00\xa7\x3a\xb3\xaf\xbd\x75\xe2\xe7\x7c\xaf\xaa\x2d\xd9\x04\x0a\x6a\x16\x1b\x0c\x88\x97\x28\xba\x0d\xd1\xde\x7f\x03\x00\x00\xff\xff\xc8\x38\x05\x7c\x34\x2e\x00\x00") +var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x59\x7b\x6f\xdb\x38\x12\xff\x3f\x9f\x62\xea\x2b\x90\xbb\x45\xe5\x87\x9c\xb4\xa9\x80\xec\xc2\x71\xdc\x56\xd8\x24\x35\xa2\x34\x05\x1a\x14\x05\x45\x51\x36\x51\x89\xd4\x91\x54\x52\xef\x5e\xbf\xfb\x81\xa4\x24\xeb\xe5\x34\xe9\xf6\x7a\xd8\x14\x41\x63\x72\x38\xef\xf9\xcd\x90\x76\x1c\x67\x6f\xf6\x3e\xb8\x22\x69\x96\x20\x45\x5e\x71\x91\x22\x75\x4d\x84\xa4\x9c\x79\xb0\xef\x8e\x27\x63\x67\xfc\xd2\x19\xbf\xdc\xdf\x5b\x22\x81\x52\xa2\x88\x90\xde\x1e\x80\xcf\xa4\x42\x0c\x93\xab\x4d\x46\xf4\x67\x00\xf3\x17\x04\x4a\x50\xb6\x32\x0b\xa7\x44\x62\x41\x33\x65\x58\x95\xf4\xa0\x36\x19\x01\xc5\x21\x97\x64\x58\x90\xc5\x28\x4f\x94\x07\xca\x1d\xa6\x14\x0b\xbe\x67\x8e\x52\x41\xa2\x39\xca\x10\xa6\x6a\x53\x17\x70\x91\xa7\x21\x11\xcd\x93\xfb\x93\xfd\xae\x44\x4b\x08\x3c\x06\x5a\xc8\x96\x5a\x6e\x82\x72\x86\xd7\x40\x19\x6c\x78\x2e\x60\x31\x0f\x00\x27\xb9\x54\x86\xe7\x39\xfa\x12\xd0\x3f\xc8\x37\xe5\xb9\x3d\xf2\xce\xd1\x17\x9a\xe6\x29\xb0\x3e\xb9\x6b\xa4\x00\x23\x06\x21\x29\x14\x20\xd1\x0e\x15\x7e\x27\x9b\x0b\x94\x3e\xc8\xa7\x05\xa9\xb6\x0a\x49\xc9\x31\x45\x8a\xc0\x1d\x55\x6b\xb8\xe3\xe2\x33\x11\x5b\x05\x86\x00\x67\x04\xdd\x12\x08\x13\xc4\x3e\xeb\x03\x11\x95\x28\x4c\x08\x04\xc1\x1b\x40\x18\x13\x29\x5b\xd1\xd8\xd7\x26\x5e\x67\xd8\x8f\x1e\xa2\x8a\xd1\x83\xc7\xa0\xd6\x04\x6e\x51\x92\x1b\xa5\x68\x9a\x71\xa1\x20\xe6\xc2\xac\x1b\x66\x3d\x42\x96\x79\x98\x50\x1c\xe4\x21\x23\x6a\xf6\x61\xf2\xe3\x04\xce\x3e\x4c\x40\x1a\xb6\x40\xbf\x2d\xd8\xfd\x91\x82\xdd\x47\x08\x9e\xfe\x48\xc1\xd3\x7b\x04\x9f\x13\x85\x22\xa4\x90\x96\x36\x7b\x1f\x78\xde\x3c\xe1\x79\x64\x6b\x5e\x8b\xf0\x7c\xa6\x88\x88\x11\x2e\xb2\xaf\xaa\xf8\xd7\x82\xe7\x99\xb4\x8b\x00\x0e\x9c\xa1\x90\x24\xe5\x47\xfd\x13\x95\x52\x06\x55\x9d\xcf\x39\x8b\xe9\x2a\x17\x86\xf5\xa0\xa2\x6d\xa2\x48\xf9\xe3\x34\xf0\xa4\xb1\x51\x24\x79\x63\x2d\x08\xde\xcc\x92\x84\xdf\x3d\x44\xa1\x59\xae\x38\x04\x18\x25\x94\xad\x1e\xab\x54\x0b\x86\x1a\x7b\x05\x54\x34\x1d\x65\xf4\xa8\x98\x74\x31\x72\x87\xaf\x4a\x4c\xb4\xd0\xf0\x5b\xa9\x58\x03\x0a\x9a\x47\x7f\x27\x1b\x7d\x60\x25\x10\x53\xb5\x1a\x86\x7f\xda\x02\xd7\xf9\xc0\x38\x23\xff\xaa\x78\x95\x2e\xeb\x63\x36\xf7\x4f\x2f\x21\x4c\x38\xfe\xdc\xc7\xb3\x62\xd1\x0b\xca\x4d\x4e\x05\x49\x1d\xd2\x2a\x10\x02\xcc\x73\xa6\x2a\x6e\x0d\xa8\x6d\x72\x29\x91\xf4\x5e\x2e\x73\xce\x22\xaa\xc3\x68\xdc\xfd\x06\xc9\x86\xb7\x06\xaf\x98\xe7\x5d\x70\x35\xd8\x26\xad\x59\x5a\xfc\x3b\x47\x89\x1c\x78\x70\xf3\xe4\x92\xc4\xa5\x87\x9f\xc1\xfe\xfe\x47\xcb\xa5\x05\x46\x8f\xe2\xd6\x01\xb2\x9d\x7c\xdd\xbf\xc0\xd7\xbd\x87\xef\xf4\x2f\xf0\x9d\x96\x7c\xcf\x51\x96\x51\xb6\x92\x05\x4c\x5c\x92\x15\xe5\xec\x8a\xcf\xce\x7d\xcb\x2e\x97\x0e\x41\x52\x39\x93\x92\xfb\xec\xdc\xf7\x4f\x3d\x40\x29\x75\xdc\x70\x1a\x3e\x1f\x1f\x4c\x4a\xc2\x3b\x22\x95\xe3\xf6\x10\x22\xfc\xfc\xe8\x85\x8b\x2d\x48\x91\xdc\x12\xf6\x71\x1c\x4f\xdd\xe9\x51\xf8\x62\x6c\x76\x50\xe6\x30\x2e\xd4\x7a\xa7\xfc\x38\x74\xe3\x89\xfb\xf2\xb0\xa4\x96\x3c\x2f\xa8\xfb\x94\x38\x98\x1e\x1e\xbc\x98\xb8\xe3\x86\xb6\x7d\x6c\xc3\x98\x8c\x5f\x1e\x46\x71\x97\x6d\x1f\x35\x7e\x71\x14\x1f\x4c\xd1\x41\x69\x1b\x26\x4c\x09\x94\xf4\xd2\x92\x09\x79\x1e\x1f\x1d\x45\x7b\x97\x44\xf2\x5c\x60\x62\xdc\xbe\xc0\x72\x6e\x13\xbf\xde\x19\x0c\x66\x2f\xe6\x06\xb8\xcb\x71\x61\x31\x0f\x34\xc2\x15\x00\x67\x80\xba\x73\xa4\x46\xd0\xf8\x60\xa8\x8b\x2e\x91\x11\x16\xc9\xb7\xcc\x83\x9b\x8f\x16\xd2\x04\xcf\x88\x50\x94\x54\x68\x76\xbd\x9c\x7f\xe0\x8c\xf8\x11\x61\x8a\xc6\xb4\x54\x4d\x27\xd7\xab\x0b\xcf\xf3\xe3\x6d\x29\x3b\x3d\x95\x54\xdb\x7c\xe2\x9b\xae\x75\x6d\x9a\x58\x5f\xe5\xd4\x69\xf5\xb6\xb1\xe2\x82\x9b\x03\x8f\x11\xea\x3e\x42\xa8\xfb\xa3\x84\x4e\x1f\x21\x74\xfa\x00\xa1\x67\xa6\x29\x34\x3a\x97\xc1\x39\x7b\x60\xce\x99\x42\x94\x11\x51\x36\x13\x59\xe2\x2b\x65\x06\x5f\xab\xe1\x78\x0b\xb9\xf6\x64\xbd\x81\x75\xc1\xdd\xd2\xf4\x35\xc0\xb9\x20\x46\x89\x25\x4f\x28\xae\xba\x40\x99\xbe\x01\x5d\x31\x54\x6b\xc5\x57\x34\x25\x3c\x57\x1e\x2c\xaf\x26\x87\xe7\x66\xf9\x5d\x16\x21\x45\x9a\xc7\x6b\x59\x79\xc9\x13\xfd\x9f\xa5\xda\x32\x3a\xa7\xac\x32\xd1\x67\x01\x11\xb7\x14\x37\xac\x33\xf6\x9d\x20\x85\xd7\x6d\xbb\x75\x83\xce\x25\xd1\xaa\xd4\xf5\xd0\x3f\xef\x11\x55\x6f\x59\x53\x79\xe9\xc1\xbe\x12\x39\xd1\xc7\xbb\xee\xbd\xbf\xba\x7a\x82\x65\xe8\xeb\xa3\x97\xb1\xb7\x7f\xfc\xa2\x6a\x6b\x30\x36\x4c\xea\x03\x09\xe6\x69\x8a\x58\xd4\x18\x52\x00\xc6\x93\x4f\x28\x8a\x3e\x95\x0d\xf2\x93\xe2\x9f\x70\x1d\x3b\x3a\xe7\x3d\x78\x12\xe4\x21\xfc\xa7\xb5\x0b\xf0\x8f\x27\xa3\x90\xb2\x51\x88\xe4\xba\xb3\x47\xf0\x9a\x6b\xb0\xf9\x34\x3f\x7b\x17\x5c\x2d\x2e\x8f\x9f\xfe\xb9\x05\xa9\xaf\x00\xbf\xfe\x0a\x23\xa2\xf0\x88\x60\xa9\x7f\x87\x56\xfb\x1a\x9b\x98\x26\xa4\xa5\xf9\xc0\x9c\xc0\x31\xd3\xbf\xce\x3a\xcf\xcc\xa9\x41\x57\x6d\xa6\x08\x53\x3b\xd5\xbe\x49\x11\x65\x1f\x3b\xcb\x52\x21\xfc\xf9\xf8\xe9\x9f\xc6\xd5\x81\xfe\xe0\x47\x5f\x3b\x54\xc2\x74\xb7\x92\xcc\xf6\xba\x36\x55\xca\x23\x9d\x4f\xe3\xf1\xf8\x60\x3c\xde\x6f\x6d\xf2\x3b\x46\x84\x07\x82\x73\xd5\xda\x59\x19\x30\xee\xee\x6c\xcd\x5e\x73\xfe\x59\x0e\x23\x63\x3e\xca\x15\x77\x04\x49\x38\x8a\x88\xf8\x4e\x47\x74\xf8\x38\x5a\x42\xd7\x35\x4a\xd0\xd5\x8a\x08\x79\x9c\x71\xa9\x86\xb9\xa9\xb4\x0e\x51\x86\xd4\xfa\xb8\xea\x4a\xc3\x6e\x25\x0c\xcb\xa4\x1e\xee\xcc\xe6\x0e\x53\x84\xf5\xe6\xf1\x88\x67\x6a\x84\xee\xa4\xc9\x37\xad\x35\x65\x54\x81\x73\x0b\x8e\x63\xc2\x06\xf5\xb0\x69\xb4\xfb\x0a\x8e\x23\x0a\x5d\x7a\x8a\xd2\xec\xea\xd0\xc1\xbd\x81\x04\x10\x39\x43\xf2\xb8\x15\x12\x69\xc1\xa4\x95\x9d\x72\x23\x6f\x69\xa3\x22\x8b\x28\xd8\x5c\x6d\x2f\x03\x10\xa6\x6f\xd4\x51\x0d\x3d\xda\xfb\x32\x17\xe4\x32\x67\x4c\x43\xc5\x2e\xaa\x9e\x3a\x01\x3b\xc1\xf5\x57\xcb\xbd\x94\xdf\x48\xb0\x1d\x9d\xde\x4f\xd1\x8a\xf8\x1a\x27\x5e\x51\x16\xf9\xec\x1c\x65\x70\xd3\x1a\x05\x9f\xd9\x06\x31\xa8\x79\x7b\xf0\xcc\x0e\x36\x50\x26\x5c\x40\x70\x2e\xa8\xda\x14\xd7\x47\xb8\xb1\x67\xde\x70\xa9\x82\xd7\x15\x55\xe3\x96\x64\x29\x7a\x2e\x83\x3e\x4a\xcb\xd5\xa5\xe0\xda\x49\x05\xed\x62\xee\xb6\x36\x5a\xb7\x27\x78\xe2\xc7\x70\x53\xbb\x21\x14\xaa\x37\x3f\x0d\xea\x9d\x77\x50\xea\xf6\x4e\x12\x71\x5a\x83\x6d\x00\x3d\x46\x9f\x20\x49\x9e\x1f\xd4\x63\xd4\x53\x90\x35\x30\x05\xe7\x4b\xb3\xbc\x36\x79\x6a\x2f\x34\x49\x02\xce\x06\xd0\x9d\x74\x74\x84\x42\xce\x95\x54\x02\x65\x0d\xe2\xff\x4b\xad\x74\x84\x4a\xd3\x1a\xc1\x21\xf0\xf4\xb7\x87\x49\xee\x99\x4c\xef\x11\xdd\x0d\x63\xa7\xd1\xfa\xb3\x73\x8d\x2a\xdd\x58\x77\x33\x78\x89\xd4\xda\x83\xc1\xa8\xac\x8e\x4b\x5e\x2b\x2a\xa7\x4a\x1c\xbd\x6c\x65\xeb\xbf\xfa\x05\x16\x34\x7d\x52\x66\x52\xe6\x29\xd1\x04\x76\x98\x39\xe5\x38\x4f\x35\x40\x57\xae\x0c\x14\x52\xa4\xb9\xe4\xc0\x22\x8e\x09\x56\x1e\xd4\x9f\x30\xac\x00\xca\x30\xcd\x50\xd2\xac\xfe\x72\xd4\xd9\x6b\x16\x39\xc1\xee\x10\xa5\xe8\x0f\xce\xd0\x9d\x6e\xb7\x69\x6d\x7f\x66\x50\xb6\xf9\x96\x21\x95\xf4\xb6\x0a\xef\xf0\x93\xb1\x83\xd6\x5d\x65\x2d\xb3\x85\x44\xb0\x74\x0a\xac\xdc\x4e\x56\x3b\x2c\xef\xb5\xfd\x3e\xeb\xfb\xb4\xb6\x76\x4a\xcf\x8c\x9c\x64\x7b\xe9\x69\xef\xeb\x34\xd2\x5b\x9d\x64\xef\xa1\x3d\x25\xe2\x31\xd4\x54\x62\x7e\x4b\xc4\x92\x27\xc9\x82\x45\x19\xa7\x4c\xf5\x90\x05\x79\x98\x52\xf5\x4b\x67\x47\x78\xdd\x35\xe9\x69\x66\x8d\xe5\xb2\xcb\x7a\x30\xf8\x45\x87\xc2\x22\x64\xcf\xd5\xcf\xf5\xbc\x06\xa8\xee\xba\xaa\x99\x67\x5b\x9b\xe6\xd7\xcb\x79\xb1\x6a\x4e\x34\x9e\x14\x17\xf3\xc0\xc8\xaa\x90\x1a\xb6\x5c\x5b\xf0\xed\xb3\x95\x20\xb2\x96\x18\x7e\xb6\x14\x5c\x71\xcc\x13\x0f\x14\xde\x42\xd6\x2b\xc1\xd3\x25\x17\xe6\x95\xdc\xdd\xb6\xb7\x2b\xde\xb3\x38\xa7\x91\xf0\xb3\x42\xd1\xda\xa3\xde\x22\x09\x7f\x8a\xf9\x67\x27\xff\x23\xcb\x8f\xc6\x3d\x96\xd7\x17\x4b\xcb\xc7\x43\xf3\x6f\x34\xd6\x56\x9f\x9d\xb8\x3a\x1a\x97\x79\x0f\x16\x75\x8d\x2f\xf4\xda\xd5\xc3\x7b\x95\xac\xa9\x58\x29\x53\xe9\xf7\xfc\xf0\x70\x7a\x58\xae\x06\xf6\x42\xd4\x10\xa8\x7d\xfa\x9a\xa8\x99\x52\x36\x42\xc3\x62\xb9\xee\xe0\x3a\x91\x4d\xe3\x1a\x95\x5e\x70\x17\x67\x27\x7f\x07\x0b\x3b\xca\xf7\x9a\xd8\xf6\xc3\x02\xcb\x45\x12\x76\x6d\x4b\x90\x54\x14\x9f\x71\x14\x9d\xa0\x04\x31\x4c\xd9\xea\xda\xf5\xbc\xed\x42\x81\x6b\x5d\x33\xed\x53\x81\xfc\xfe\xc7\x96\x9f\xf5\xbe\xf2\xb3\x9e\x54\xbe\xef\x15\xa5\x35\x8b\xb6\x46\x01\x1d\xc5\x2a\x7a\x67\xba\x3b\xb0\xbe\xa7\xb7\x5d\x51\x2c\x0e\xec\x88\x60\x3d\xc8\x33\xc1\xca\xb9\xd5\xc8\x2a\x48\x8a\x6f\x69\x6c\x07\xac\x69\x67\x85\xc7\x5c\xdc\x21\x11\x6d\x11\x05\x89\x15\x51\xc6\x92\x36\xbf\x82\x51\x8d\xa2\xea\xec\x2d\x0c\xda\x16\xcf\x9b\xab\xab\x65\x65\x7c\x97\xc1\x83\xdd\xd0\x16\xda\x33\x96\x95\x4a\xdc\xa3\x06\xf4\x00\xf8\xdb\x5c\x65\xb9\xad\x01\x3d\x7b\xbf\x13\xc5\x88\x64\x02\x5c\x5c\x87\xd7\x4a\x65\xde\x68\x64\x9e\x25\x16\x49\x38\x3c\xbd\x08\xcc\x48\x3a\xda\x83\xf6\x37\x69\x1a\xf7\xdf\x5d\x9e\x75\x02\xae\x9d\xd9\xe0\xbb\xf5\x6b\x23\xc4\x0d\x66\x33\xc1\xca\x6f\xe5\x34\xdf\x92\xd0\x7e\x9b\xba\xf8\x92\x69\x93\x0b\xb3\x8a\xeb\x88\xd6\xb6\x33\x39\x3b\x1d\x55\x7a\x9e\x81\x5b\x7a\xd5\x07\xa2\x9d\x5f\x15\xd6\xbe\x44\xf9\x1e\x9d\x4a\x19\x7b\xff\x0d\x00\x00\xff\xff\x23\xcd\x84\x66\x2a\x20\x00\x00") func assetsEnvironmentTemplateYmlBytes() ([]byte, error) { return bindataRead( @@ -83,7 +84,27 @@ func assetsEnvironmentTemplateYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/environment-template.yml", size: 11828, mode: os.FileMode(420), modTime: time.Unix(1483945855, 0)} + info := bindataFileInfo{name: "assets/environment-template.yml", size: 8234, mode: os.FileMode(420), modTime: time.Unix(1484068831, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _assetsVpcTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x98\x4b\x6f\xdb\x38\x10\xc7\xef\xf9\x14\x93\x60\x01\x6d\x81\x38\xb1\xa5\xa4\x68\x78\xf3\x3a\x4e\x63\x6c\x9b\x18\x96\xe1\x02\x69\xf6\x40\x4b\xe3\x58\xa8\x44\x6a\x49\x2a\x0f\x14\xf9\xee\x0b\x51\x92\x1f\xb4\xe4\xfa\x99\x4d\xe1\x8b\x45\x0e\x39\xf3\xff\x69\xc8\xa1\x58\xab\xd5\x0e\x9a\xdf\xdc\x3e\x46\x71\x48\x15\x5e\x71\x11\x51\x35\x40\x21\x03\xce\x08\x58\x76\xbd\x51\xaf\xd5\x2f\x6a\xf5\x0b\xeb\xa0\x4b\x05\x8d\x50\xa1\x90\xe4\x00\xa0\xc3\xa4\xa2\xcc\xc3\x3e\x32\xca\xbc\x97\xb4\x09\xe0\x12\xa5\x27\x82\x58\xe9\xc1\x85\x05\xa8\xcc\x04\x14\x87\x44\x22\x8c\xb8\x80\x41\xb7\xa5\x07\xf4\x5f\x62\x24\xe0\x2a\x11\xb0\x07\xdd\xd0\x0c\x43\xfe\x84\xfe\x80\x86\x09\xca\x6c\xd2\x1a\xf8\x38\xa2\x49\xa8\x26\x4f\x7e\xe0\x51\x85\x7e\xee\x52\xf7\x91\x19\x23\x57\x8e\xf5\x34\x25\x31\xb9\xc9\x90\xa1\x82\x91\xe0\x11\x3c\x8d\x03\x6f\x9c\x06\x45\x53\x63\x70\xdd\x6b\xa0\x9e\x87\x52\x9e\x94\x87\xf6\x35\x60\x5f\x90\x3d\xa8\x31\x01\xeb\xc2\xca\x9a\xe8\xf3\xa4\xa9\xf1\xc9\x9a\x0f\xa8\x7e\xa2\x7f\xa7\xf5\x59\x61\x5d\xaa\x14\x0a\x46\xe0\xe8\xcf\xfb\x7b\xff\x67\xe3\xd8\x79\xfd\x70\x7f\x7f\xb2\xca\xc3\x69\xfe\xd7\x7e\xfd\x70\xa4\xa7\x6c\x71\x26\x95\xa0\x01\x53\x73\x1a\xad\x28\x91\x0a\x86\x08\x14\x1e\x69\x18\xf8\xd0\xea\x5c\xf6\x60\x18\x72\xef\x07\x81\xe7\x13\xfd\x3b\x7d\x3e\x49\xa3\x1d\xc4\x5e\x2b\xf0\xc5\x5f\xba\xaf\x92\x96\x1e\xba\xfc\xb5\xad\xcb\xa6\x51\xc0\x69\x7c\x7c\xbf\x74\xba\xc9\x30\x0c\xbc\x0c\x42\xf3\xae\xb1\x16\xa9\xe6\x5d\x63\xc7\xa4\xec\xb3\xdf\x85\x94\xbd\x26\x29\x7b\x87\xa4\x1a\xbf\x15\x29\x67\x4d\x52\xce\x0e\x49\xd9\xef\x9a\x54\x8b\x33\x3f\x48\xc7\xe8\x22\x70\x4d\xa5\xb1\x18\x33\x5e\x47\x57\x8c\x90\x1b\xae\x8e\xb2\xc7\xb4\x3a\xe8\xa6\xf6\xbf\x09\x0d\xe5\x11\x81\xef\x87\x3d\x1c\x55\x2e\xe4\x63\xb0\xac\x7f\xca\xa6\xb7\xb7\x98\xde\xfe\xf5\xf4\xce\x16\xd3\x3b\xc6\xf4\x3d\x94\x3c\x11\x5e\x56\x2c\x07\xdd\x16\x99\x49\x91\xe6\x37\x97\x90\x76\xcb\x26\xa4\xd8\xb8\xbb\x82\xc7\x28\x54\x50\xd4\x56\x80\x69\x06\x82\xf6\x36\x5b\x12\x72\x93\x36\xa3\xc3\x10\x2f\x99\x74\x93\x38\xe6\x42\x11\xb0\x94\x48\xd0\x32\xbb\xaf\xb9\x54\x8c\x46\x28\x0d\x03\xf3\xa8\x90\x39\x32\x5a\x17\xf7\xdb\x72\x25\x59\x77\x91\x63\x59\x8e\x90\x92\x04\xa9\x90\x3b\x88\xbd\x8e\x5f\x48\xcd\xa1\x2c\x42\xa8\x4a\x98\xdc\xfc\x2b\x8d\x33\x8b\x4e\x7c\xcb\xbe\xd0\x84\x79\x63\x02\xa9\xe2\xbc\xbf\xf9\x48\x83\x90\x0e\x83\x30\x50\x2f\x77\x9c\x21\x81\x43\x17\x43\xf4\x14\x7c\x87\xfa\x31\x1c\x7e\x4e\x67\x95\x79\x76\x68\x91\xf4\x41\x4e\x93\xe0\x6f\x7c\x21\x70\x83\xea\x89\x8b\xc2\x23\x80\x3e\x11\x91\x3c\xb2\xc5\x2d\x77\x2b\x58\xf6\x0e\x61\xd9\xbb\x84\xd5\xd8\x0b\x2c\x67\x2b\x58\xce\x0e\x61\x39\xbb\x84\x65\xef\x08\x56\x87\xa5\x55\x00\xd5\x67\xaa\xf0\x89\xbe\x94\xc3\x32\x8c\x2a\x98\x6c\xe0\x7d\xd0\x6d\xad\x14\xc0\xa0\xdb\xca\xfb\x9b\x4a\x51\x6f\x1c\x21\x53\x6b\xbd\x19\xc3\xcb\xc4\x62\x51\x59\x16\x5b\x8f\x27\x0a\xfb\xe9\x56\x57\x1e\xd0\xb4\x7f\xad\x30\x36\xce\x66\xed\x6f\x49\x28\x79\xc9\x8f\x91\xf9\xf2\x96\x91\x12\xb0\x15\x71\x4e\x85\x4c\xc2\x35\x09\xe4\x96\x97\x28\x55\xc0\x68\xba\x50\x66\xf2\x7c\xfe\xbb\x07\x60\x55\xc0\x93\xed\x76\xea\xa7\x29\x25\xf7\x02\xed\x60\xd9\x9a\x2d\x1d\xb0\x71\x89\xc8\xfa\x0d\xed\xf3\x83\x56\x85\x64\x6c\x8d\x6f\x24\xac\x6a\x3b\x5f\x2a\xcc\xde\x42\x98\xf3\x46\xc2\xaa\xb6\xde\xa5\xc2\x9c\x0d\x84\xe5\x2b\xb0\xe9\x85\xe5\x22\xa6\xfd\xfb\x5e\xeb\x1d\x36\xe4\x09\xf3\xdb\xf1\x18\x23\x14\x34\xec\x72\xa1\xcc\x18\xdb\x4c\x89\x8a\x5d\xd2\x30\xaa\x88\x76\x6a\x65\xa0\x31\x74\x02\xf4\x92\x10\x6f\x92\x68\x88\x22\xfd\xae\xa8\x3b\xc5\x11\xaf\x2b\xb8\xe2\x1e\x0f\x09\x58\x1f\xad\x19\xdb\xa6\x97\xbd\x4a\x7d\xc5\x52\x9c\x17\x1f\x04\xca\xf4\x8c\x38\xa2\xa1\x9c\x1c\x12\x97\x6c\x20\xa9\xe6\x1e\x65\x0f\x48\x26\x98\xae\x04\x8f\x74\x04\xf6\x99\x35\x69\xec\xf3\xd4\xfd\xf9\xb9\x73\x6e\x4d\xc9\xb9\xee\xf5\xfb\xe1\x75\xb6\x17\x5e\x3a\x80\xe2\xd2\xeb\x97\xcc\x6c\xdb\x20\x96\x35\xe4\xb8\xae\x95\x8a\xdf\x0f\xaf\xf3\xff\x39\xbf\x3e\xd5\x0d\x56\x59\xc3\x6d\xa2\x34\xac\xf7\x03\xaa\xbe\x15\xa8\xd9\x8f\xb5\x8d\x38\x99\x98\x26\x8b\xd0\x28\x9e\xa6\x98\x15\xab\x45\xe9\x80\xfd\xd6\xf7\xd5\xde\x84\x51\x42\xdf\x54\xde\x56\x55\x7e\x13\x79\xce\x9b\xca\xdb\xaa\xd6\xaf\x22\xef\x36\x51\x71\xa2\xb2\x6b\x13\x5d\xad\xf3\x03\xf3\xcc\x6d\x55\x7f\x8c\x10\xf8\xc0\x47\xa0\xc6\x08\x8f\xb1\xa7\x4d\xf2\x12\x3d\x57\xdb\xdb\xcf\xfa\x62\xa4\x70\x4f\x23\xfd\x65\x96\x0c\xe1\x8f\x9f\x1a\x87\xab\xa8\xf7\x23\x6d\x7e\xad\x69\x67\x8b\x4b\xa3\x32\x80\x58\xdb\x81\xd4\x86\x81\x3f\x77\xbb\x9c\x85\x92\x7b\xbd\x62\x84\x74\x46\xd3\xf3\x45\xc5\x82\x48\xbb\x96\x24\x7e\xde\xa9\xa3\xbe\xe1\xda\xc1\xba\x0a\x17\x94\x2d\xae\x94\x35\xd5\xda\x1b\xa8\xb5\x97\xa9\xb5\xf7\xa5\xd6\x2e\x51\xeb\xac\xa9\xd6\xd9\x40\xad\xb3\x4c\xad\xb3\x2f\xb5\x4e\xc7\x3f\xf8\x2f\x00\x00\xff\xff\x28\xeb\x52\x31\x42\x1c\x00\x00") + +func assetsVpcTemplateYmlBytes() ([]byte, error) { + return bindataRead( + _assetsVpcTemplateYml, + "assets/vpc-template.yml", + ) +} + +func assetsVpcTemplateYml() (*asset, error) { + bytes, err := assetsVpcTemplateYmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/vpc-template.yml", size: 7234, mode: os.FileMode(420), modTime: time.Unix(1484088279, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -141,6 +162,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "assets/environment-template.yml": assetsEnvironmentTemplateYml, + "assets/vpc-template.yml": assetsVpcTemplateYml, } // AssetDir returns the file names below a certain @@ -185,6 +207,7 @@ type bintree struct { var _bintree = &bintree{nil, map[string]*bintree{ "assets": &bintree{nil, map[string]*bintree{ "environment-template.yml": &bintree{assetsEnvironmentTemplateYml, map[string]*bintree{}}, + "vpc-template.yml": &bintree{assetsVpcTemplateYml, map[string]*bintree{}}, }}, }} diff --git a/common/assets/environment-template.yml b/common/assets/environment-template.yml index d0559a80..3d81d657 100644 --- a/common/assets/environment-template.yml +++ b/common/assets/environment-template.yml @@ -5,13 +5,6 @@ Parameters: Type: String Description: Instance type to use. Default: t2.micro - InstanceTenancy: - Description: Instance tenancy to use for VPC - Type: String - AllowedValues: - - default - - dedicated - Default: default DesiredCapacity: Type: Number Default: '1' @@ -24,14 +17,20 @@ Parameters: Type: String Description: KeyName to associate with worker instances. Leave blank to disable SSH access. Default: '' - SSHAllow: - Description: Subnet from which to allow SSH access. + VpcId: + Type: String + Description: Name of the value to import for the VpcId + PublicSubnetAZ1Id: + Type: String + Description: Name of the value to import for the AZ1 subnet id + PublicSubnetAZ2Id: + Type: String + Description: Name of the value to import for the AZ2 subnet id + Default: '' + PublicSubnetAZ3Id: Type: String - MinLength: '9' - MaxLength: '18' - Default: 0.0.0.0/0 - AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" - ConstraintDescription: 'must be a valid CIDR block: x.x.x.x/x.' + Description: Name of the value to import for the AZ3 subnet id + Default: '' Metadata: AWS::CloudFormation::Interface: ParameterGroups: @@ -39,7 +38,6 @@ Metadata: default: "Instance Configuration" Parameters: - InstanceType - - InstanceTenancy - KeyName - SSHAllow - Label: @@ -50,11 +48,9 @@ Metadata: ParameterLabels: InstanceType: default: "Instance type to launch?" - InstanceTenancy: - default: "Instance tenancy to use?" KeyName: default: "Key to grant SSH access (blank for none)?" - KeyName: + SSHAllow: default: "CIDR block to grant SSH access?" DesiredCapacity: default: "Desired ECS cluster instance count?" @@ -64,6 +60,15 @@ Conditions: HasKeyName: "Fn::Not": - "Fn::Equals": [!Ref KeyName, ''] + HasPublicSubnetAZ1: + "Fn::Not": + - "Fn::Equals": [!Ref PublicSubnetAZ1Id, ''] + HasPublicSubnetAZ2: + "Fn::Not": + - "Fn::Equals": [!Ref PublicSubnetAZ2Id, ''] + HasPublicSubnetAZ3: + "Fn::Not": + - "Fn::Equals": [!Ref PublicSubnetAZ3Id, ''] Mappings: AWSRegionToAMI: us-east-1: @@ -82,163 +87,7 @@ Mappings: AMIID: ami-c78f43a4 eu-central-1: AMIID: ami-e1e6f88d - SubnetConfig: - VPC: - CIDR: 10.0.0.0/16 - PublicAZ1: - CIDR: 10.0.0.0/24 - PublicAZ2: - CIDR: 10.0.1.0/24 - PublicAZ3: - CIDR: 10.0.2.0/24 Resources: - VPC: - Type: AWS::EC2::VPC - Properties: - CidrBlock: !FindInMap [ SubnetConfig, VPC, CIDR ] - EnableDnsSupport: 'true' - EnableDnsHostnames: 'true' - InstanceTenancy: !Ref InstanceTenancy - PublicSubnetAZ1: - Type: AWS::EC2::Subnet - Properties: - VpcId: !Ref VPC - CidrBlock: !FindInMap [ SubnetConfig, PublicAZ1, CIDR ] - MapPublicIpOnLaunch: true - AvailabilityZone: !Select [ 0, !GetAZs ''] - Tags: - - Key: Network - Value: Public - PublicSubnetAZ2: - Type: AWS::EC2::Subnet - Properties: - VpcId: !Ref VPC - CidrBlock: !FindInMap [ SubnetConfig, PublicAZ2, CIDR ] - MapPublicIpOnLaunch: true - AvailabilityZone: !Select [ 1, !GetAZs ''] - Tags: - - Key: Network - Value: Public - PublicSubnetAZ3: - Type: AWS::EC2::Subnet - Properties: - VpcId: !Ref VPC - CidrBlock: !FindInMap [ SubnetConfig, PublicAZ3, CIDR ] - MapPublicIpOnLaunch: true - AvailabilityZone: !Select [ 2, !GetAZs ''] - Tags: - - Key: Network - Value: Public - InternetGateway: - Type: AWS::EC2::InternetGateway - Properties: - Tags: - - Key: Network - Value: Public - VPCInternetGateway: - Type: AWS::EC2::VPCGatewayAttachment - Properties: - VpcId: !Ref VPC - InternetGatewayId: !Ref InternetGateway - PublicRouteTable: - Type: AWS::EC2::RouteTable - Properties: - VpcId: !Ref VPC - Tags: - - Key: Network - Value: Public - PublicRoute: - Type: AWS::EC2::Route - DependsOn: VPCInternetGateway - Properties: - RouteTableId: !Ref PublicRouteTable - DestinationCidrBlock: 0.0.0.0/0 - GatewayId: !Ref InternetGateway - PublicSubnetAZ1RouteTableAssociation: - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - SubnetId: !Ref PublicSubnetAZ1 - RouteTableId: !Ref PublicRouteTable - PublicSubnetAZ2RouteTableAssociation: - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - SubnetId: !Ref PublicSubnetAZ2 - RouteTableId: !Ref PublicRouteTable - PublicSubnetAZ3RouteTableAssociation: - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - SubnetId: !Ref PublicSubnetAZ3 - RouteTableId: !Ref PublicRouteTable - PublicNetworkAcl: - Type: AWS::EC2::NetworkAcl - Properties: - VpcId: !Ref VPC - Tags: - - Key: Network - Value: Public - InboundEphemeralPortPublicNetworkAclEntry: - Type: AWS::EC2::NetworkAclEntry - Properties: - NetworkAclId: !Ref PublicNetworkAcl - RuleNumber: '103' - Protocol: '6' - RuleAction: allow - Egress: 'false' - CidrBlock: 0.0.0.0/0 - PortRange: - From: '1024' - To: '65535' - InboundSSHPublicNetworkAclEntry: - Type: AWS::EC2::NetworkAclEntry - Properties: - NetworkAclId: !Ref PublicNetworkAcl - RuleNumber: '104' - Protocol: '6' - RuleAction: allow - Egress: 'false' - CidrBlock: !Ref SSHAllow - PortRange: - From: '22' - To: '22' - InboundHttpPublicNetworkAclEntry: - Type: AWS::EC2::NetworkAclEntry - Properties: - NetworkAclId: !Ref PublicNetworkAcl - RuleNumber: '105' - Protocol: '6' - RuleAction: allow - Egress: 'false' - CidrBlock: 0.0.0.0/0 - PortRange: - From: '80' - To: '80' - OutboundPublicNetworkAclEntry: - Type: AWS::EC2::NetworkAclEntry - Properties: - NetworkAclId: !Ref PublicNetworkAcl - RuleNumber: '100' - Protocol: '6' - RuleAction: allow - Egress: 'true' - CidrBlock: 0.0.0.0/0 - PortRange: - From: '0' - To: '65535' - PublicSubnetAZ1PublicNetworkAclAssociation: - Type: AWS::EC2::SubnetNetworkAclAssociation - Properties: - SubnetId: !Ref PublicSubnetAZ1 - NetworkAclId: !Ref PublicNetworkAcl - PublicSubnetAZ2PublicNetworkAclAssociation: - Type: AWS::EC2::SubnetNetworkAclAssociation - Properties: - SubnetId: !Ref PublicSubnetAZ2 - NetworkAclId: !Ref PublicNetworkAcl - PublicSubnetAZ3PublicNetworkAclAssociation: - Type: AWS::EC2::SubnetNetworkAclAssociation - Properties: - SubnetId: !Ref PublicSubnetAZ3 - NetworkAclId: !Ref PublicNetworkAcl EcsCluster: Type: AWS::ECS::Cluster ECSAutoScalingGroup: @@ -246,9 +95,18 @@ Resources: DependsOn: [] Properties: VPCZoneIdentifier: - - !Ref PublicSubnetAZ1 - - !Ref PublicSubnetAZ2 - - !Ref PublicSubnetAZ3 + - Fn::If: + - HasPublicSubnetAZ1 + - !ImportValue [!Ref PublicSubnetAZ1] + - !Ref AWS::NoValue + - Fn::If: + - HasPublicSubnetAZ2 + - !ImportValue []!Ref PublicSubnetAZ2] + - !Ref AWS::NoValue + - Fn::If: + - HasPublicSubnetAZ3 + - !ImportValue [!Ref PublicSubnetAZ3] + - !Ref AWS::NoValue LaunchConfigurationName: !Ref ContainerInstances MinSize: '1' MaxSize: !Ref MaxSize @@ -354,7 +212,7 @@ Resources: ElbSG: Type: AWS::EC2::SecurityGroup Properties: - VpcId: !Ref VPC + VpcId: !ImportValue [!Ref VpcId] GroupDescription: ELB Security Group SecurityGroupIngress: - IpProtocol: tcp @@ -381,9 +239,18 @@ Resources: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Subnets: - - !Ref PublicSubnetAZ1 - - !Ref PublicSubnetAZ2 - - !Ref PublicSubnetAZ3 + - Fn::If: + - HasPublicSubnetAZ1 + - !Ref PublicSubnetAZ1 + - !Ref AWS::NoValue + - Fn::If: + - HasPublicSubnetAZ2 + - !Ref PublicSubnetAZ2 + - !Ref AWS::NoValue + - Fn::If: + - HasPublicSubnetAZ3 + - !Ref PublicSubnetAZ3 + - !Ref AWS::NoValue SecurityGroups: - !Ref ElbSG EcsElbListener: @@ -408,9 +275,11 @@ Outputs: EcsElbListenerArn: Value: !Ref EcsElbListener Description: Arn of the ELB Listener. + Export: + Name: !Sub ${AWS::StackName}-EcsElbListenerArn EcsCluster: Value: !Ref EcsCluster Description: Name of the ECS cluster. - VPCId: - Value: !Ref VPC - Description: The id of the vpc + Export: + Name: !Sub ${AWS::StackName}-EcsCluster + diff --git a/common/assets/vpc-template.yml b/common/assets/vpc-template.yml new file mode 100644 index 00000000..10ccc091 --- /dev/null +++ b/common/assets/vpc-template.yml @@ -0,0 +1,250 @@ +--- +AWSTemplateFormatVersion: '2010-09-09' +Parameters: + InstanceTenancy: + Description: Instance tenancy to use for VPC + Type: String + AllowedValues: + - default + - dedicated + Default: default + SshAllow: + Description: Subnet from which to allow SSH access. + Type: String + MinLength: '9' + MaxLength: '18' + Default: 0.0.0.0/0 + AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" + ConstraintDescription: 'must be a valid CIDR block: x.x.x.x/x.' + VpcCidrBlock: + Description: Subnet block for VPC + Type: String + MinLength: '9' + MaxLength: '18' + Default: 10.0.0.0/16 + AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" + ConstraintDescription: 'must be a valid CIDR block: x.x.x.x/x.' + PublicSubnetAZ1CidrBlock: + Description: Subnet block for AZ1 + Type: String + MinLength: '9' + MaxLength: '18' + Default: 10.0.0.0/24 + AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" + ConstraintDescription: 'must be a valid CIDR block: x.x.x.x/x.' + PublicSubnetAZ2CidrBlock: + Description: Subnet block for AZ2 + Type: String + MinLength: '9' + MaxLength: '18' + Default: 10.0.1.0/24 + AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" + ConstraintDescription: 'must be a valid CIDR block: x.x.x.x/x.' + PublicSubnetAZ3CidrBlock: + Description: Subnet block for AZ3 + Type: String + MinLength: '9' + MaxLength: '18' + Default: 10.0.2.0/24 + AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" + ConstraintDescription: 'must be a valid CIDR block: x.x.x.x/x.' +Conditions: + HasPublicSubnetAZ1: + "Fn::Not": + - "Fn::Equals": [!Ref PublicSubnetAZ1CidrBlock, ''] + HasPublicSubnetAZ2: + "Fn::Not": + - "Fn::Equals": [!Ref PublicSubnetAZ2CidrBlock, ''] + HasPublicSubnetAZ3: + "Fn::Not": + - "Fn::Equals": [!Ref PublicSubnetAZ3CidrBlock, ''] +Resources: + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: !Ref VpcCidrBlock + EnableDnsSupport: 'true' + EnableDnsHostnames: 'true' + InstanceTenancy: !Ref InstanceTenancy + PublicSubnetAZ1: + Type: AWS::EC2::Subnet + Condition: HasPublicSubnetAZ1 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref PublicSubnetAZ1CidrBlock + MapPublicIpOnLaunch: true + AvailabilityZone: !Select [ 0, !GetAZs ''] + Tags: + - Key: Network + Value: Public + PublicSubnetAZ2: + Type: AWS::EC2::Subnet + Condition: HasPublicSubnetAZ2 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref PublicSubnetAZ2CidrBlock + MapPublicIpOnLaunch: true + AvailabilityZone: !Select [ 1, !GetAZs ''] + Tags: + - Key: Network + Value: Public + PublicSubnetAZ3: + Type: AWS::EC2::Subnet + Condition: HasPublicSubnetAZ3 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref PublicSubnetAZ3CidrBlock + MapPublicIpOnLaunch: true + AvailabilityZone: !Select [ 2, !GetAZs ''] + Tags: + - Key: Network + Value: Public + InternetGateway: + Type: AWS::EC2::InternetGateway + Properties: + Tags: + - Key: Network + Value: Public + VPCInternetGateway: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref InternetGateway + PublicRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Network + Value: Public + PublicRoute: + Type: AWS::EC2::Route + DependsOn: VPCInternetGateway + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + PublicSubnetAZ1RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: HasPublicSubnetAZ1 + Properties: + SubnetId: !Ref PublicSubnetAZ1 + RouteTableId: !Ref PublicRouteTable + PublicSubnetAZ2RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: HasPublicSubnetAZ2 + Properties: + SubnetId: !Ref PublicSubnetAZ2 + RouteTableId: !Ref PublicRouteTable + PublicSubnetAZ3RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: HasPublicSubnetAZ3 + Properties: + SubnetId: !Ref PublicSubnetAZ3 + RouteTableId: !Ref PublicRouteTable + PublicNetworkAcl: + Type: AWS::EC2::NetworkAcl + Properties: + VpcId: !Ref VPC + Tags: + - Key: Network + Value: Public + InboundEphemeralPortPublicNetworkAclEntry: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: !Ref PublicNetworkAcl + RuleNumber: '103' + Protocol: '6' + RuleAction: allow + Egress: 'false' + CidrBlock: 0.0.0.0/0 + PortRange: + From: '1024' + To: '65535' + InboundSSHPublicNetworkAclEntry: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: !Ref PublicNetworkAcl + RuleNumber: '104' + Protocol: '6' + RuleAction: allow + Egress: 'false' + CidrBlock: !Ref SshAllow + PortRange: + From: '22' + To: '22' + InboundHttpPublicNetworkAclEntry: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: !Ref PublicNetworkAcl + RuleNumber: '105' + Protocol: '6' + RuleAction: allow + Egress: 'false' + CidrBlock: 0.0.0.0/0 + PortRange: + From: '80' + To: '80' + OutboundPublicNetworkAclEntry: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: !Ref PublicNetworkAcl + RuleNumber: '100' + Protocol: '6' + RuleAction: allow + Egress: 'true' + CidrBlock: 0.0.0.0/0 + PortRange: + From: '0' + To: '65535' + PublicSubnetAZ1PublicNetworkAclAssociation: + Type: AWS::EC2::SubnetNetworkAclAssociation + Condition: HasPublicSubnetAZ1 + Properties: + SubnetId: !Ref PublicSubnetAZ1 + NetworkAclId: !Ref PublicNetworkAcl + PublicSubnetAZ2PublicNetworkAclAssociation: + Type: AWS::EC2::SubnetNetworkAclAssociation + Condition: HasPublicSubnetAZ2 + Properties: + SubnetId: !Ref PublicSubnetAZ2 + NetworkAclId: !Ref PublicNetworkAcl + PublicSubnetAZ3PublicNetworkAclAssociation: + Type: AWS::EC2::SubnetNetworkAclAssociation + Condition: HasPublicSubnetAZ3 + Properties: + SubnetId: !Ref PublicSubnetAZ3 + NetworkAclId: !Ref PublicNetworkAcl +Outputs: + VpcId: + Description: The id of the vpc + Value: !Ref VPC + Export: + Name: !Sub ${AWS::StackName}-VpcId + PublicSubnetAZ1Id: + Description: The public subnetid for AZ1 + Value: + Fn::If: + - HasPublicSubnetAZ1 + - !Ref PublicSubnetAZ1 + - !Ref AWS::NoValue + Export: + Name: !Sub ${AWS::StackName}-PublicSubnetAZ1Id + PublicSubnetAZ2Id: + Description: The public subnetid for AZ2 + Value: + Fn::If: + - HasPublicSubnetAZ2 + - !Ref PublicSubnetAZ2 + - !Ref AWS::NoValue + Export: + Name: !Sub ${AWS::StackName}-PublicSubnetAZ2Id + PublicSubnetAZ3Id: + Description: The public subnetid for AZ3 + Value: + Fn::If: + - HasPublicSubnetAZ3 + - !Ref PublicSubnetAZ3 + - !Ref AWS::NoValue + Export: + Name: !Sub ${AWS::StackName}-PublicSubnetAZ3Id diff --git a/common/config.go b/common/config.go index a7da8f72..a2875e6c 100644 --- a/common/config.go +++ b/common/config.go @@ -5,30 +5,14 @@ import ( "io/ioutil" "fmt" "log" - "strings" ) -// Config defines the structure of the yml file for the mu config -type Config struct { - Environments []Environment `yaml:"environments,omitempty"` - Service Service `yaml:"service,omitempty"` -} - -// Service defines the service that will be created -type Service struct { - DesiredCount int `yaml:"desiredCount,omitempty"` - Pipeline ServicePipeline `yaml:"pipeline,omitempty"` -} - -// ServicePipeline defines the service pipeline that will be created -type ServicePipeline struct { -} - // NewConfig create a new config object func NewConfig() *Config { - return &Config{} + return new(Config) } + // LoadFromFile loads config object from local file func (config *Config) LoadFromFile(configFile string) { yamlConfig, err := ioutil.ReadFile( configFile ) @@ -49,14 +33,4 @@ func (config *Config) loadFromYaml(yamlConfig []byte) *Config { return config } -// GetEnvironment loads the environment by name from the config -func (config *Config) GetEnvironment(environmentName string) (*Environment, error) { - for _, e := range config.Environments { - if(strings.EqualFold(environmentName, e.Name)) { - return &e, nil - } - } - - return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml",environmentName) -} diff --git a/common/config_test.go b/common/config_test.go index 95f9baf5..96121a9b 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "fmt" ) func TestNewConfig(t *testing.T) { @@ -41,6 +42,8 @@ service: config := NewConfig() config.loadFromYaml([]byte(yamlConfig)) + fmt.Println(config) + assert.NotNil(config) assert.Equal(2,len(config.Environments)) assert.Equal("dev",config.Environments[0].Name) @@ -75,27 +78,4 @@ func TestLoadConfig(t *testing.T) { config.LoadFromFile("foobarbaz.yml") } -func TestGetEnvironment(t *testing.T) { - assert := assert.New(t) - config := NewConfig() - env1 := Environment{ - Name: "foo", - } - env2 := Environment{ - Name: "bar", - } - config.Environments = []Environment{env1, env2} - - fooEnv, fooErr := config.GetEnvironment("foo") - assert.Equal("foo", fooEnv.Name) - assert.Nil(fooErr) - - barEnv, barErr := config.GetEnvironment("BAR") - assert.Equal("bar", barEnv.Name) - assert.Nil(barErr) - - bazEnv, bazErr := config.GetEnvironment("baz") - assert.Nil(bazEnv) - assert.NotNil(bazErr) -} diff --git a/common/environment.go b/common/environment.go deleted file mode 100644 index a6204d39..00000000 --- a/common/environment.go +++ /dev/null @@ -1,36 +0,0 @@ -package common - -import ( - "fmt" -) - -// Environment defines the env that will be created -type Environment struct { - Name string `yaml:"name"` - Loadbalancer EnvironmentLoadBalancer `yaml:"loadbalancer,omitempty"` - Cluster EnvironmentCluster `yaml:"cluster,omitempty"` -} - -// EnvironmentLoadBalancer defines the env load balancer that will be created -type EnvironmentLoadBalancer struct { - Hostname string `yaml:"hostname,omitempty"` -} - -// EnvironmentCluster defines the env cluster that will be created -type EnvironmentCluster struct { - DesiredCapacity int `yaml:"desiredCapacity,omitempty"` - MaxSize int `yaml:"maxSize,omitempty"` -} - -// NewStack will create a new stack instance and write the template for the stack -func (environment *Environment) NewStack() (*Stack, error) { - stackName := fmt.Sprintf("mu-env-%s", environment.Name) - stack := NewStack(stackName) - - err := stack.WriteTemplate("environment-template.yml", environment) - if err != nil { - return nil, err - } - - return stack,nil -} diff --git a/common/environment_test.go b/common/environment_test.go deleted file mode 100644 index 0cdae8b9..00000000 --- a/common/environment_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package common - -import ( - "testing" - "github.com/stretchr/testify/assert" - -) - -func TestNewEnvironmentStack(t *testing.T) { - assert := assert.New(t) - - env := Environment{ - Name: "test", - } - - stack, err := env.NewStack() - - assert.Nil(err) - assert.NotNil(stack) - assert.Equal("mu-env-test",stack.Name) -} diff --git a/common/stack.go b/common/stack.go index e464e80a..09d6890b 100644 --- a/common/stack.go +++ b/common/stack.go @@ -4,18 +4,24 @@ import ( "fmt" "os" "text/template" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/aws/session" + "io/ioutil" ) // Stack contains the data about a CloudFormation stack type Stack struct { Name string TemplatePath string + Region string } // NewStack will create a new stack instance -func NewStack(name string) *Stack { +func NewStack(name string, region string) *Stack { return &Stack{ Name: name, + Region: region, TemplatePath: fmt.Sprintf("%s/%s.yml",os.TempDir(), name), } } @@ -47,3 +53,111 @@ func (stack *Stack) WriteTemplate(assetName string, data interface{}) (error) { templateOut.Sync() return nil } + +// UpsertStack will create/update the cloudformation stack +func (stack *Stack) UpsertStack() (error) { + cfn, err := newCloudFormation(stack.Region) + if err != nil { + return err + } + + + stackStatus := stack.AwaitFinalStatus(cfn) + if stackStatus == "" { + fmt.Printf("creating stack: %s\n", stack.Name) + params := &cloudformation.CreateStackInput{ + StackName: aws.String(stack.Name), + Capabilities: []*string{ + aws.String(cloudformation.CapabilityCapabilityIam), + }, + TemplateBody: aws.String(stack.readTemplatePath()), + } + resp, err := cfn.CreateStack(params) + if err != nil { + return err + } + + fmt.Println(resp) + } else { + fmt.Printf("updating stack: %s\n", stack.Name) + params := &cloudformation.UpdateStackInput{ + StackName: aws.String(stack.Name), + Capabilities: []*string{ + aws.String(cloudformation.CapabilityCapabilityIam), + }, + TemplateBody: aws.String(stack.readTemplatePath()), + } + + resp, err := cfn.UpdateStack(params) + if err != nil { + return err + } + + fmt.Println(resp) + } + stack.AwaitFinalStatus(cfn) + return nil +} + +func (stack *Stack) readTemplatePath() (string) { + templateBytes, err := ioutil.ReadFile(stack.TemplatePath) + if err != nil { + return "" + } + return string(templateBytes) +} + +func newCloudFormation(region string) (*cloudformation.CloudFormation, error) { + sess, err := session.NewSession() + if err != nil { + return nil, err + } + return cloudformation.New(sess, &aws.Config{Region: aws.String(region)}), nil +} + +// AwaitFinalStatus waits for the stack to arrive in a final status +func (stack *Stack) AwaitFinalStatus(cfn *cloudformation.CloudFormation) (string) { + params := &cloudformation.DescribeStacksInput{ + StackName: aws.String(stack.Name), + } + resp, err := cfn.DescribeStacks(params) + + if err == nil && resp != nil && len(resp.Stacks) == 1 { + switch *resp.Stacks[0].StackStatus { + case cloudformation.StackStatusCreateFailed: + case cloudformation.StackStatusCreateComplete: + case cloudformation.StackStatusRollbackFailed: + case cloudformation.StackStatusRollbackComplete: + case cloudformation.StackStatusDeleteFailed: + case cloudformation.StackStatusDeleteComplete: + case cloudformation.StackStatusUpdateComplete: + case cloudformation.StackStatusUpdateRollbackFailed: + case cloudformation.StackStatusUpdateRollbackComplete: + break; + + case cloudformation.StackStatusReviewInProgress: + case cloudformation.StackStatusCreateInProgress: + case cloudformation.StackStatusRollbackInProgress: + // wait for create + cfn.WaitUntilStackCreateComplete(params) + resp, err = cfn.DescribeStacks(params) + break; + case cloudformation.StackStatusDeleteInProgress: + // wait for delete + cfn.WaitUntilStackDeleteComplete(params) + resp, err = cfn.DescribeStacks(params) + break; + case cloudformation.StackStatusUpdateInProgress: + case cloudformation.StackStatusUpdateRollbackInProgress: + case cloudformation.StackStatusUpdateCompleteCleanupInProgress: + case cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress: + // wait for update + cfn.WaitUntilStackUpdateComplete(params) + resp, err = cfn.DescribeStacks(params) + break; + } + return *resp.Stacks[0].StackStatus + } + + return "" +} diff --git a/common/stack_test.go b/common/stack_test.go index 25d328fa..71dcb657 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -9,7 +9,7 @@ import ( func TestNewStack(t *testing.T) { assert := assert.New(t) - stack := NewStack("foo") + stack := NewStack("foo","us-west-2") assert.NotNil(stack) assert.Equal("foo",stack.Name) diff --git a/common/types.go b/common/types.go new file mode 100644 index 00000000..d85d55db --- /dev/null +++ b/common/types.go @@ -0,0 +1,27 @@ +package common + +// Config defines the structure of the yml file for the mu config +type Config struct { + Region string + Environments []Environment + Service Service +} + +// Environment defines the structure of the yml file for an environment +type Environment struct { + Name string + Loadbalancer struct { + Hostname string + } + Cluster struct { + DesiredCapacity int `yaml:"desiredCapacity"` + MaxSize int `yaml:"maxSize"` + } +} + +// Service defines the structure of the yml file for a service +type Service struct { + DesiredCount int`yaml:"desiredCount"` + Pipeline struct { + } +} diff --git a/environments/commands.go b/environments/commands.go deleted file mode 100644 index 254d399d..00000000 --- a/environments/commands.go +++ /dev/null @@ -1,23 +0,0 @@ -package environments - -import( - "github.com/stelligent/mu/common" - "github.com/urfave/cli" -) - -// NewCommand returns a cli.Command with all the environment subcommands -func NewCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "environment", - Aliases: []string{"env"}, - Usage: "options for managing environments", - Subcommands: []cli.Command{ - *newListCommand(config), - *newShowCommand(config), - *newUpsertCommand(config), - *newTerminateCommand(config), - }, - } - - return cmd -} diff --git a/environments/commands_test.go b/environments/commands_test.go deleted file mode 100644 index e34ebb38..00000000 --- a/environments/commands_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package environments - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := NewCommand(config) - - assert.NotNil(command) - assert.Equal("environment", command.Name, "Name should match") - assert.Equal(1, len(command.Aliases), "Aliases len should match") - assert.Equal("env", command.Aliases[0], "Aliases should match") - assert.Equal("options for managing environments", command.Usage, "Usage should match") - assert.Equal(4, len(command.Subcommands), "Subcommands len should match") -} \ No newline at end of file diff --git a/environments/list.go b/environments/list.go deleted file mode 100644 index cb440be9..00000000 --- a/environments/list.go +++ /dev/null @@ -1,25 +0,0 @@ -package environments - -import( - "fmt" - "github.com/stelligent/mu/common" - "github.com/urfave/cli" -) - -func newListCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "list", - Aliases: []string{"ls"}, - Usage: "list environments", - Action: func(c *cli.Context) error { - runList(config) - return nil - }, - } - - return cmd -} - -func runList(config *common.Config) { - fmt.Println("listing environments") -} diff --git a/environments/list_test.go b/environments/list_test.go deleted file mode 100644 index 81db9ca9..00000000 --- a/environments/list_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package environments - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewListCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := newListCommand(config) - - assert.NotNil(command) - assert.Equal("list", command.Name, "Name should match") - assert.Equal(1, len(command.Aliases), "Aliases len should match") - assert.Equal("ls", command.Aliases[0], "Aliases should match") - assert.Equal("list environments", command.Usage, "Usage should match") - assert.NotNil(command.Action) -} \ No newline at end of file diff --git a/environments/show.go b/environments/show.go deleted file mode 100644 index 2e5e139b..00000000 --- a/environments/show.go +++ /dev/null @@ -1,25 +0,0 @@ -package environments - -import( - "fmt" - "github.com/urfave/cli" - "github.com/stelligent/mu/common" -) - -func newShowCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "show", - Usage: "show environment details", - ArgsUsage: "", - Action: func(c *cli.Context) error { - runShow(config, c.Args().First()) - return nil - }, - } - - return cmd -} - -func runShow(config *common.Config, environment string) { - fmt.Printf("showing environment: %s\n",environment) -} \ No newline at end of file diff --git a/environments/show_test.go b/environments/show_test.go deleted file mode 100644 index 6aa581ab..00000000 --- a/environments/show_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package environments - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewShowCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := newShowCommand(config) - - assert.NotNil(command) - assert.Equal("show", command.Name, "Name should match") - assert.Equal("", command.ArgsUsage, "ArgsUsage should match") - assert.NotNil(command.Action) -} - diff --git a/environments/terminate.go b/environments/terminate.go deleted file mode 100644 index fa598d2d..00000000 --- a/environments/terminate.go +++ /dev/null @@ -1,26 +0,0 @@ -package environments - -import( - "fmt" - "github.com/urfave/cli" - "github.com/stelligent/mu/common" -) - -func newTerminateCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "terminate", - Aliases: []string{"term"}, - Usage: "terminate an environment", - ArgsUsage: "", - Action: func(c *cli.Context) error { - runTerminate(config, c.Args().First()) - return nil - }, - } - - return cmd -} - -func runTerminate(config *common.Config, environment string) { - fmt.Printf("terminating environment: %s\n",environment) -} diff --git a/environments/terminate_test.go b/environments/terminate_test.go deleted file mode 100644 index 119b5539..00000000 --- a/environments/terminate_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package environments - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewTerminateCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := newTerminateCommand(config) - - assert.NotNil(command) - assert.Equal("terminate", command.Name, "Name should match") - assert.Equal(1, len(command.Aliases), "Aliases len should match") - assert.Equal("term", command.Aliases[0], "Aliases should match") - assert.Equal("", command.ArgsUsage, "ArgsUsage should match") - assert.NotNil(command.Action) -} - diff --git a/environments/upsert.go b/environments/upsert.go deleted file mode 100644 index edd15207..00000000 --- a/environments/upsert.go +++ /dev/null @@ -1,53 +0,0 @@ -package environments - -import ( - "fmt" - "github.com/stelligent/mu/common" - "github.com/urfave/cli" -) - -func newUpsertCommand(config *common.Config) *cli.Command { - cmd := &cli.Command{ - Name: "upsert", - Aliases: []string{"up"}, - Usage: "create/update an environment", - ArgsUsage: "", - Action: func(c *cli.Context) error { - environmentName := c.Args().First() - if(len(environmentName) == 0) { - cli.ShowCommandHelp(c, "upsert") - return fmt.Errorf("environment must be provided") - } - return runUpsert(config, environmentName) - }, - } - - return cmd -} - -func runUpsert(config *common.Config, environmentName string) error { - // get the environment from config by name - environment, err := config.GetEnvironment(environmentName) - if err != nil { - return err - } - - // generate the CFN template - stack, err := environment.NewStack() - if err != nil { - return err - } - - // determine if stack exists - - // create/update the stack - - // wait for stack to be updated - - fmt.Printf("upserting environment:%s stack:%s path:%s\n",environment.Name, stack.Name, stack.TemplatePath) - - return nil -} - - - diff --git a/environments/upsert_test.go b/environments/upsert_test.go deleted file mode 100644 index d89ca6c4..00000000 --- a/environments/upsert_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package environments - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewUpsertCommand(t *testing.T) { - assert := assert.New(t) - config := common.NewConfig() - command := newUpsertCommand(config) - - assert.NotNil(command) - assert.Equal("upsert", command.Name, "Name should match") - assert.Equal(1, len(command.Aliases), "Aliases len should match") - assert.Equal("up", command.Aliases[0], "Aliases should match") - assert.Equal("", command.ArgsUsage, "ArgsUsage should match") - assert.NotNil(command.Action) -} - diff --git a/main.go b/main.go index 5abcf3e6..f1fdcc38 100644 --- a/main.go +++ b/main.go @@ -2,47 +2,11 @@ package main import ( "os" - "github.com/urfave/cli" - "github.com/stelligent/mu/environments" - "github.com/stelligent/mu/services" - "github.com/stelligent/mu/pipelines" - "github.com/stelligent/mu/common" + "github.com/stelligent/mu/cli" ) var version string func main() { - app := newApp() - app.Run(os.Args) + cli.NewApp(version).Run(os.Args) } - -func newApp() *cli.App { - config := common.NewConfig() - app := cli.NewApp() - app.Name = "mu" - app.Usage = "Microservice Platform on AWS" - app.Version = version - app.EnableBashCompletion = true - - app.Commands = []cli.Command{ - *environments.NewCommand(config), - *services.NewCommand(config), - *pipelines.NewCommand(config), - } - - app.Before = func(c *cli.Context) error { - config.LoadFromFile(c.String("config")) - return nil - } - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "config, c", - Usage: "path to config file", - Value: "mu.yml", - }, - } - - return app -} - diff --git a/pipelines/commands.go b/pipelines/commands.go deleted file mode 100644 index 9bdda007..00000000 --- a/pipelines/commands.go +++ /dev/null @@ -1,21 +0,0 @@ -package pipelines - -import( - "github.com/stelligent/mu/common" - "github.com/urfave/cli" -) - -// NewCommand returns a cli.Command with all the pipeline subcommands -func NewCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "pipeline", - Usage: "options for managing pipelines", - Subcommands: []cli.Command{ - *newListCommand(config), - *newShowCommand(config), - }, - } - - return cmd -} - diff --git a/pipelines/commands_test.go b/pipelines/commands_test.go deleted file mode 100644 index 717b77c5..00000000 --- a/pipelines/commands_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package pipelines - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := NewCommand(config) - - assert.NotNil(command) - assert.Equal("pipeline", command.Name, "Name should match") - assert.Equal("options for managing pipelines", command.Usage, "Usage should match") - assert.Equal(2, len(command.Subcommands), "Subcommands len should match") -} \ No newline at end of file diff --git a/pipelines/list.go b/pipelines/list.go deleted file mode 100644 index 773955c9..00000000 --- a/pipelines/list.go +++ /dev/null @@ -1,26 +0,0 @@ -package pipelines - -import( - "fmt" - "github.com/stelligent/mu/common" - "github.com/urfave/cli" -) - - -func newListCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "list", - Aliases: []string{"ls"}, - Usage: "list pipelines", - Action: func(c *cli.Context) error { - runList(config) - return nil - }, - } - - return cmd -} - -func runList(config *common.Config) { - fmt.Println("listing pipelines") -} diff --git a/pipelines/list_test.go b/pipelines/list_test.go deleted file mode 100644 index 80c7ff30..00000000 --- a/pipelines/list_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package pipelines - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewListCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := newListCommand(config) - - assert.NotNil(command) - assert.Equal("list", command.Name, "Name should match") - assert.Equal(1, len(command.Aliases), "Aliases len should match") - assert.Equal("ls", command.Aliases[0], "Aliases should match") - assert.Equal("list pipelines", command.Usage, "Usage should match") - assert.NotNil(command.Action) -} \ No newline at end of file diff --git a/pipelines/show.go b/pipelines/show.go deleted file mode 100644 index 7c1eaa60..00000000 --- a/pipelines/show.go +++ /dev/null @@ -1,30 +0,0 @@ -package pipelines - -import( - "fmt" - "github.com/urfave/cli" - "github.com/stelligent/mu/common" -) - -func newShowCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "show", - Usage: "show pipeline details", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to show", - }, - }, - Action: func(c *cli.Context) error { - runShow(config, c.String("service")) - return nil - }, - } - - return cmd -} - -func runShow(config *common.Config, service string) { - fmt.Printf("showing pipeline: %s\n",service) -} diff --git a/pipelines/show_test.go b/pipelines/show_test.go deleted file mode 100644 index 8594a16e..00000000 --- a/pipelines/show_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package pipelines - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewShowCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := newShowCommand(config) - - assert.NotNil(command) - assert.Equal("show", command.Name, "Name should match") - assert.Equal(1, len(command.Flags), "Flag len should match") - assert.Equal("service, s", command.Flags[0].GetName(), "Flag should match") - assert.NotNil(command.Action) -} - diff --git a/resources/environment.go b/resources/environment.go new file mode 100644 index 00000000..c5150f3b --- /dev/null +++ b/resources/environment.go @@ -0,0 +1,108 @@ +package resources + +import ( + "fmt" + "github.com/stelligent/mu/common" + "strings" +) + + +// EnvironmentManager defines the env that will be created +type EnvironmentManager interface { + UpsertEnvironment(environmentName string) (error) +} + +// NewEnvironmentManager will construct a manager for environments +func NewEnvironmentManager(config *common.Config) (EnvironmentManager) { + ctx := new(environmentManagerContext) + ctx.config = config + return ctx +} + +type environmentManagerContext struct { + config *common.Config +} + +// getEnvironment loads the environment by name from the config +func (ctx *environmentManagerContext) getEnvironment(environmentName string) (*common.Environment, error) { + + for _, e := range ctx.config.Environments { + if(strings.EqualFold(environmentName, e.Name)) { + return &e, nil + } + } + + return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml",environmentName) +} + +// getRegion determines the region to use +func (ctx *environmentManagerContext) getRegion() (string) { + return ctx.config.Region +} + +// UpsertRegion will create a new stack instance and write the template for the stack +func (ctx *environmentManagerContext) UpsertEnvironment(environmentName string) (error) { + err := ctx.upsertVpc(environmentName) + if err != nil { + return err + } + + err = ctx.upsertEcsCluster(environmentName) + if err != nil { + return err + } + + return nil +} + +func (ctx *environmentManagerContext) upsertVpc(environmentName string) (error) { + env, err := ctx.getEnvironment(environmentName) + if err != nil { + return err + } + + stackName := fmt.Sprintf("mu-vpc-%s", env.Name) + // generate the CFN template + stack := common.NewStack(stackName, ctx.getRegion()) + + err = stack.WriteTemplate("vpc-template.yml", env) + if err != nil { + return err + } + + // create/update the stack + fmt.Printf("upserting VPC environment:%s stack:%s path:%s\n",env.Name, stack.Name, stack.TemplatePath) + err = stack.UpsertStack() + if err != nil { + return err + } + + return nil +} + +func (ctx *environmentManagerContext) upsertEcsCluster(environmentName string) (error) { + env, err := ctx.getEnvironment(environmentName) + if err != nil { + return err + } + + stackName := fmt.Sprintf("mu-env-%s", env.Name) + // generate the CFN template + stack := common.NewStack(stackName, ctx.getRegion()) + + err = stack.WriteTemplate("environment-template.yml", env) + if err != nil { + return err + } + + // create/update the stack + fmt.Printf("upserting environment:%s stack:%s path:%s\n",env.Name, stack.Name, stack.TemplatePath) + err = stack.UpsertStack() + if err != nil { + return err + } + + return nil +} + + diff --git a/resources/environment_test.go b/resources/environment_test.go new file mode 100644 index 00000000..841a9a14 --- /dev/null +++ b/resources/environment_test.go @@ -0,0 +1,46 @@ +package resources + +import ( + "testing" + "github.com/stretchr/testify/assert" + + "github.com/stelligent/mu/common" +) + +func TestEnvironment_UpsertEnvironmentUnknown(t *testing.T) { + assert := assert.New(t) + config := common.NewConfig() + envMgr := NewEnvironmentManager(config) + + err := envMgr.UpsertEnvironment("test-stack-that-is-fake") + + assert.NotNil(err) +} + +func TestGetEnvironment(t *testing.T) { + assert := assert.New(t) + config := common.NewConfig() + + env1 := common.Environment{ + Name: "foo", + } + env2 := common.Environment{ + Name: "bar", + } + config.Environments = []common.Environment{env1, env2} + envMgr := &environmentManagerContext{ + config: config, + } + + fooEnv, fooErr := envMgr.getEnvironment("foo") + assert.Equal("foo", fooEnv.Name) + assert.Nil(fooErr) + + barEnv, barErr := envMgr.getEnvironment("BAR") + assert.Equal("bar", barEnv.Name) + assert.Nil(barErr) + + bazEnv, bazErr := envMgr.getEnvironment("baz") + assert.Nil(bazEnv) + assert.NotNil(bazErr) +} diff --git a/services/commands.go b/services/commands.go deleted file mode 100644 index d89dbdee..00000000 --- a/services/commands.go +++ /dev/null @@ -1,29 +0,0 @@ -package services - -import( - "github.com/stelligent/mu/common" - "github.com/urfave/cli" -) - -// NewCommand returns a cli.Command with all the service subcommands -func NewCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "service", - Aliases: []string{"svc"}, - Usage: "options for managing services", - Subcommands: []cli.Command{ - *newShowCommand(config), - *newDeployCommand(config), - *newSetenvCommand(config), - *newUndeployCommand(config), - }, - } - - return cmd -} - - - - - - diff --git a/services/commands_test.go b/services/commands_test.go deleted file mode 100644 index fb2cc37e..00000000 --- a/services/commands_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package services - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := NewCommand(config) - - assert.NotNil(command) - assert.Equal("service", command.Name, "Name should match") - assert.Equal(1, len(command.Aliases), "Aliases len should match") - assert.Equal("svc", command.Aliases[0], "Aliases should match") - assert.Equal("options for managing services", command.Usage, "Usage should match") - assert.Equal(4, len(command.Subcommands), "Subcommands len should match") -} \ No newline at end of file diff --git a/services/deploy.go b/services/deploy.go deleted file mode 100644 index e9280a71..00000000 --- a/services/deploy.go +++ /dev/null @@ -1,32 +0,0 @@ -package services - -import( - "fmt" - "github.com/stelligent/mu/common" - "github.com/urfave/cli" -) - -func newDeployCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "deploy", - Usage: "deploy service to environment", - ArgsUsage: "", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to deploy", - }, - }, - Action: func(c *cli.Context) error { - runDeploy(config, c.Args().First(), c.String("service")) - return nil - }, - } - - return cmd -} - -func runDeploy(config *common.Config, environment string, service string) { - fmt.Printf("deploying service: %s to environment: %s\n",service, environment) -} - diff --git a/services/deploy_test.go b/services/deploy_test.go deleted file mode 100644 index 6d166ca0..00000000 --- a/services/deploy_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package services - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewDeployCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := newDeployCommand(config) - - assert.NotNil(command) - assert.Equal("deploy", command.Name, "Name should match") - assert.Equal("", command.ArgsUsage, "ArgsUsage should match") - assert.Equal(1, len(command.Flags), "Flags length") - assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") - assert.NotNil(command.Action) -} - diff --git a/services/setenv.go b/services/setenv.go deleted file mode 100644 index d3d0c84d..00000000 --- a/services/setenv.go +++ /dev/null @@ -1,31 +0,0 @@ -package services - -import( - "fmt" - "github.com/urfave/cli" - "github.com/stelligent/mu/common" -) - -func newSetenvCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "setenv", - Usage: "set environment variable", - ArgsUsage: " =...", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to deploy", - }, - }, - Action: func(c *cli.Context) error { - runSetenv(config, c.Args().First(), c.String("service"), c.Args().Tail()) - return nil - }, - } - - return cmd -} - -func runSetenv(config *common.Config, environment string, service string, keyvals []string) { - fmt.Printf("setenv service: %s to environment: %s with vals: %s\n",service, environment, keyvals) -} diff --git a/services/setenv_test.go b/services/setenv_test.go deleted file mode 100644 index c13b0aaf..00000000 --- a/services/setenv_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package services - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewSetenvCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := newSetenvCommand(config) - - assert.NotNil(command) - assert.Equal("setenv", command.Name, "Name should match") - assert.Equal(" =...", command.ArgsUsage, "ArgsUsage should match") - assert.Equal(1, len(command.Flags), "Flags length") - assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") - assert.NotNil(command.Action) -} - diff --git a/services/show.go b/services/show.go deleted file mode 100644 index b52af6b4..00000000 --- a/services/show.go +++ /dev/null @@ -1,31 +0,0 @@ -package services - -import( - "fmt" - "github.com/urfave/cli" - "github.com/stelligent/mu/common" -) - -func newShowCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "show", - Usage: "show service details", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to show", - }, - }, - Action: func(c *cli.Context) error { - runShow(config, c.String("service")) - return nil - }, - } - - return cmd -} - -func runShow(config *common.Config, service string) { - fmt.Printf("showing service: %s\n",service) -} - diff --git a/services/show_test.go b/services/show_test.go deleted file mode 100644 index ef8ec813..00000000 --- a/services/show_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package services - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewShowCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := newShowCommand(config) - - assert.NotNil(command) - assert.Equal("show", command.Name, "Name should match") - assert.Equal(1, len(command.Flags), "Flags length") - assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") - assert.NotNil(command.Action) -} - diff --git a/services/undeploy.go b/services/undeploy.go deleted file mode 100644 index 73fe6a0c..00000000 --- a/services/undeploy.go +++ /dev/null @@ -1,31 +0,0 @@ -package services - -import( - "fmt" - "github.com/urfave/cli" - "github.com/stelligent/mu/common" -) - -func newUndeployCommand(config *common.Config) *cli.Command { - cmd := &cli.Command { - Name: "undeploy", - Usage: "undeploy service from environment", - ArgsUsage: "", - Flags: []cli.Flag { - cli.StringFlag{ - Name: "service, s", - Usage: "service to undeploy", - }, - }, - Action: func(c *cli.Context) error { - runUndeploy(config, c.Args().First(), c.String("service")) - return nil - }, - } - - return cmd -} - -func runUndeploy(config *common.Config, environment string, service string) { - fmt.Printf("undeploying service: %s to environment: %s\n",service, environment) -} diff --git a/services/undeploy_test.go b/services/undeploy_test.go deleted file mode 100644 index 6a743872..00000000 --- a/services/undeploy_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package services - -import ( - "testing" - "github.com/stelligent/mu/common" - "github.com/stretchr/testify/assert" -) - -func TestNewUndeployCommand(t *testing.T) { - assert := assert.New(t) - - config := &common.Config {} - - command := newUndeployCommand(config) - - assert.NotNil(command) - assert.Equal("undeploy", command.Name, "Name should match") - assert.Equal("", command.ArgsUsage, "ArgsUsage should match") - assert.Equal(1, len(command.Flags), "Flags length") - assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") - assert.NotNil(command.Action) -} - From 61cd4aea90dd885baedd7b1bebedd49e695c9bb1 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 10 Jan 2017 16:29:45 -0800 Subject: [PATCH 16/31] Refactor for unit testability --- cli/app.go | 55 ++++++++++++------------ cli/environments.go | 23 ++++++----- cli/environments_test.go | 25 +++++------ cli/pipelines.go | 10 ++--- cli/pipelines_test.go | 12 +++--- cli/services.go | 18 ++++---- cli/services_test.go | 20 ++++----- common/assets.go | 4 +- common/config.go | 19 +-------- common/config_test.go | 14 ++----- common/context.go | 37 +++++++++++++++++ common/stack.go | 29 +++++-------- common/stack_test.go | 2 +- common/types.go | 8 ++++ resources/environment.go | 78 ++++++++++++++++------------------- resources/environment_test.go | 12 +++--- 16 files changed, 183 insertions(+), 183 deletions(-) create mode 100644 common/context.go diff --git a/cli/app.go b/cli/app.go index f72edfc7..0211d701 100644 --- a/cli/app.go +++ b/cli/app.go @@ -7,32 +7,33 @@ import ( // NewApp creates a new CLI app func NewApp(version string) *cli.App { - config := common.NewConfig() - app := cli.NewApp() - app.Name = "mu" - app.Usage = "Microservice Platform on AWS" - app.Version = version - app.EnableBashCompletion = true - - app.Commands = []cli.Command{ - *newEnvironmentsCommand(config), - *newServicesCommand(config), - *newPipelinesCommand(config), - } - - app.Before = func(c *cli.Context) error { - config.LoadFromFile(c.String("config")) - return nil - } - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "config, c", - Usage: "path to config file", - Value: "mu.yml", - }, - } - - return app + context := common.NewContext() + + app := cli.NewApp() + app.Name = "mu" + app.Usage = "Microservice Platform on AWS" + app.Version = version + app.EnableBashCompletion = true + + app.Commands = []cli.Command{ + *newEnvironmentsCommand(context), + *newServicesCommand(context), + *newPipelinesCommand(context), + } + + app.Before = func(c *cli.Context) error { + context.InitializeFromFile(c.String("config")) + return nil + } + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Usage: "path to config file", + Value: "mu.yml", + }, + } + + return app } diff --git a/cli/environments.go b/cli/environments.go index ffcd12f4..dc586f2f 100644 --- a/cli/environments.go +++ b/cli/environments.go @@ -8,25 +8,28 @@ import( "github.com/stelligent/mu/resources" ) -func newEnvironmentsCommand(config *common.Config) *cli.Command { - environmentManager := resources.NewEnvironmentManager(config) + + + +func newEnvironmentsCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "environment", Aliases: []string{"env"}, Usage: "options for managing environments", Subcommands: []cli.Command{ - *newEnvironmentsListCommand(environmentManager), - *newEnvironmentsShowCommand(environmentManager), - *newEnvironmentsUpsertCommand(environmentManager), - *newEnvironmentsTerminateCommand(environmentManager), + *newEnvironmentsListCommand(ctx), + *newEnvironmentsShowCommand(ctx), + *newEnvironmentsUpsertCommand(ctx), + *newEnvironmentsTerminateCommand(ctx), }, } return cmd } -func newEnvironmentsUpsertCommand(environmentManager resources.EnvironmentManager) *cli.Command { +func newEnvironmentsUpsertCommand(ctx *common.Context) *cli.Command { + environmentManager := resources.NewEnvironmentManager(ctx) cmd := &cli.Command{ Name: "upsert", Aliases: []string{"up"}, @@ -46,7 +49,7 @@ func newEnvironmentsUpsertCommand(environmentManager resources.EnvironmentManage return cmd } -func newEnvironmentsListCommand(environmentManager resources.EnvironmentManager) *cli.Command { +func newEnvironmentsListCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "list", Aliases: []string{"ls"}, @@ -60,7 +63,7 @@ func newEnvironmentsListCommand(environmentManager resources.EnvironmentManager) return cmd } -func newEnvironmentsShowCommand(environmentManager resources.EnvironmentManager) *cli.Command { +func newEnvironmentsShowCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "show", Usage: "show environment details", @@ -78,7 +81,7 @@ func newEnvironmentsShowCommand(environmentManager resources.EnvironmentManager) return cmd } -func newEnvironmentsTerminateCommand(environmentManager resources.EnvironmentManager) *cli.Command { +func newEnvironmentsTerminateCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "terminate", Aliases: []string{"term"}, diff --git a/cli/environments_test.go b/cli/environments_test.go index 9a4b4b24..0ea29314 100644 --- a/cli/environments_test.go +++ b/cli/environments_test.go @@ -4,15 +4,14 @@ import ( "testing" "github.com/stretchr/testify/assert" "github.com/stelligent/mu/common" - "github.com/stelligent/mu/resources" ) func TestNewEnvironmentsCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + ctx := common.NewContext() - command := newEnvironmentsCommand(config) + command := newEnvironmentsCommand(ctx) assert.NotNil(command) assert.Equal("environment", command.Name, "Name should match") @@ -24,9 +23,8 @@ func TestNewEnvironmentsCommand(t *testing.T) { func TestNewEnvironmentsUpsertCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() - envMgr := resources.NewEnvironmentManager(config) - command := newEnvironmentsUpsertCommand(envMgr) + ctx := common.NewContext() + command := newEnvironmentsUpsertCommand(ctx) assert.NotNil(command) assert.Equal("upsert", command.Name, "Name should match") @@ -38,9 +36,8 @@ func TestNewEnvironmentsUpsertCommand(t *testing.T) { func TestNewEnvironmentsListCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() - envMgr := resources.NewEnvironmentManager(config) - command := newEnvironmentsListCommand(envMgr) + ctx := common.NewContext() + command := newEnvironmentsListCommand(ctx) assert.NotNil(command) assert.Equal("list", command.Name, "Name should match") @@ -51,9 +48,8 @@ func TestNewEnvironmentsListCommand(t *testing.T) { } func TestNewEnvironmentsShowCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() - envMgr := resources.NewEnvironmentManager(config) - command := newEnvironmentsShowCommand(envMgr) + ctx := common.NewContext() + command := newEnvironmentsShowCommand(ctx) assert.NotNil(command) assert.Equal("show", command.Name, "Name should match") @@ -62,9 +58,8 @@ func TestNewEnvironmentsShowCommand(t *testing.T) { } func TestNewEnvironmentsTerminateCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() - envMgr := resources.NewEnvironmentManager(config) - command := newEnvironmentsTerminateCommand(envMgr) + ctx := common.NewContext() + command := newEnvironmentsTerminateCommand(ctx) assert.NotNil(command) assert.Equal("terminate", command.Name, "Name should match") diff --git a/cli/pipelines.go b/cli/pipelines.go index 309fcddd..a04baaa0 100644 --- a/cli/pipelines.go +++ b/cli/pipelines.go @@ -6,20 +6,20 @@ import( "github.com/stelligent/mu/common" ) -func newPipelinesCommand(config *common.Config) *cli.Command { +func newPipelinesCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "pipeline", Usage: "options for managing pipelines", Subcommands: []cli.Command{ - *newPipelinesListCommand(config), - *newPipelinesShowCommand(config), + *newPipelinesListCommand(ctx), + *newPipelinesShowCommand(ctx), }, } return cmd } -func newPipelinesListCommand(config *common.Config) *cli.Command { +func newPipelinesListCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "list", Aliases: []string{"ls"}, @@ -33,7 +33,7 @@ func newPipelinesListCommand(config *common.Config) *cli.Command { return cmd } -func newPipelinesShowCommand(config *common.Config) *cli.Command { +func newPipelinesShowCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "show", Usage: "show pipeline details", diff --git a/cli/pipelines_test.go b/cli/pipelines_test.go index b350c311..0b310882 100644 --- a/cli/pipelines_test.go +++ b/cli/pipelines_test.go @@ -9,9 +9,9 @@ import ( func TestNewPipelinesCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + ctx := common.NewContext() - command := newPipelinesCommand(config) + command := newPipelinesCommand(ctx) assert.NotNil(command) assert.Equal("pipeline", command.Name, "Name should match") @@ -21,9 +21,9 @@ func TestNewPipelinesCommand(t *testing.T) { func TestNewPipelinesListCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + ctx := common.NewContext() - command := newPipelinesListCommand(config) + command := newPipelinesListCommand(ctx) assert.NotNil(command) assert.Equal("list", command.Name, "Name should match") @@ -35,9 +35,9 @@ func TestNewPipelinesListCommand(t *testing.T) { func TestNewPipelinesShowCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + ctx := common.NewContext() - command := newPipelinesShowCommand(config) + command := newPipelinesShowCommand(ctx) assert.NotNil(command) assert.Equal("show", command.Name, "Name should match") diff --git a/cli/services.go b/cli/services.go index 16ea749e..b01bc8dd 100644 --- a/cli/services.go +++ b/cli/services.go @@ -6,23 +6,23 @@ import( "github.com/stelligent/mu/common" ) -func newServicesCommand(config *common.Config) *cli.Command { +func newServicesCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "service", Aliases: []string{"svc"}, Usage: "options for managing services", Subcommands: []cli.Command{ - *newServicesShowCommand(config), - *newServicesDeployCommand(config), - *newServicesSetenvCommand(config), - *newServicesUndeployCommand(config), + *newServicesShowCommand(ctx), + *newServicesDeployCommand(ctx), + *newServicesSetenvCommand(ctx), + *newServicesUndeployCommand(ctx), }, } return cmd } -func newServicesShowCommand(config *common.Config) *cli.Command { +func newServicesShowCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "show", Usage: "show service details", @@ -42,7 +42,7 @@ func newServicesShowCommand(config *common.Config) *cli.Command { return cmd } -func newServicesDeployCommand(config *common.Config) *cli.Command { +func newServicesDeployCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "deploy", Usage: "deploy service to environment", @@ -64,7 +64,7 @@ func newServicesDeployCommand(config *common.Config) *cli.Command { return cmd } -func newServicesSetenvCommand(config *common.Config) *cli.Command { +func newServicesSetenvCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "setenv", Usage: "set environment variable", @@ -87,7 +87,7 @@ func newServicesSetenvCommand(config *common.Config) *cli.Command { return cmd } -func newServicesUndeployCommand(config *common.Config) *cli.Command { +func newServicesUndeployCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command { Name: "undeploy", Usage: "undeploy service from environment", diff --git a/cli/services_test.go b/cli/services_test.go index b928d221..a1a74609 100644 --- a/cli/services_test.go +++ b/cli/services_test.go @@ -9,9 +9,9 @@ import ( func TestNewServicesCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + ctx := common.NewContext() - command := newServicesCommand(config) + command := newServicesCommand(ctx) assert.NotNil(command) assert.Equal("service", command.Name, "Name should match") @@ -24,9 +24,9 @@ func TestNewServicesCommand(t *testing.T) { func TestNewServicesShowCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + ctx := common.NewContext() - command := newServicesShowCommand(config) + command := newServicesShowCommand(ctx) assert.NotNil(command) assert.Equal("show", command.Name, "Name should match") @@ -38,9 +38,9 @@ func TestNewServicesShowCommand(t *testing.T) { func TestNewServicesDeployCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + ctx := common.NewContext() - command := newServicesDeployCommand(config) + command := newServicesDeployCommand(ctx) assert.NotNil(command) assert.Equal("deploy", command.Name, "Name should match") @@ -53,9 +53,9 @@ func TestNewServicesDeployCommand(t *testing.T) { func TestNewSetenvCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + ctx := common.NewContext() - command := newServicesSetenvCommand(config) + command := newServicesSetenvCommand(ctx) assert.NotNil(command) assert.Equal("setenv", command.Name, "Name should match") @@ -68,9 +68,9 @@ func TestNewSetenvCommand(t *testing.T) { func TestNewUndeployCommand(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + ctx := common.NewContext() - command := newServicesUndeployCommand(config) + command := newServicesUndeployCommand(ctx) assert.NotNil(command) assert.Equal("undeploy", command.Name, "Name should match") diff --git a/common/assets.go b/common/assets.go index 31c9548e..d2b532ca 100644 --- a/common/assets.go +++ b/common/assets.go @@ -69,7 +69,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x59\x7b\x6f\xdb\x38\x12\xff\x3f\x9f\x62\xea\x2b\x90\xbb\x45\xe5\x87\x9c\xb4\xa9\x80\xec\xc2\x71\xdc\x56\xd8\x24\x35\xa2\x34\x05\x1a\x14\x05\x45\x51\x36\x51\x89\xd4\x91\x54\x52\xef\x5e\xbf\xfb\x81\xa4\x24\xeb\xe5\x34\xe9\xf6\x7a\xd8\x14\x41\x63\x72\x38\xef\xf9\xcd\x90\x76\x1c\x67\x6f\xf6\x3e\xb8\x22\x69\x96\x20\x45\x5e\x71\x91\x22\x75\x4d\x84\xa4\x9c\x79\xb0\xef\x8e\x27\x63\x67\xfc\xd2\x19\xbf\xdc\xdf\x5b\x22\x81\x52\xa2\x88\x90\xde\x1e\x80\xcf\xa4\x42\x0c\x93\xab\x4d\x46\xf4\x67\x00\xf3\x17\x04\x4a\x50\xb6\x32\x0b\xa7\x44\x62\x41\x33\x65\x58\x95\xf4\xa0\x36\x19\x01\xc5\x21\x97\x64\x58\x90\xc5\x28\x4f\x94\x07\xca\x1d\xa6\x14\x0b\xbe\x67\x8e\x52\x41\xa2\x39\xca\x10\xa6\x6a\x53\x17\x70\x91\xa7\x21\x11\xcd\x93\xfb\x93\xfd\xae\x44\x4b\x08\x3c\x06\x5a\xc8\x96\x5a\x6e\x82\x72\x86\xd7\x40\x19\x6c\x78\x2e\x60\x31\x0f\x00\x27\xb9\x54\x86\xe7\x39\xfa\x12\xd0\x3f\xc8\x37\xe5\xb9\x3d\xf2\xce\xd1\x17\x9a\xe6\x29\xb0\x3e\xb9\x6b\xa4\x00\x23\x06\x21\x29\x14\x20\xd1\x0e\x15\x7e\x27\x9b\x0b\x94\x3e\xc8\xa7\x05\xa9\xb6\x0a\x49\xc9\x31\x45\x8a\xc0\x1d\x55\x6b\xb8\xe3\xe2\x33\x11\x5b\x05\x86\x00\x67\x04\xdd\x12\x08\x13\xc4\x3e\xeb\x03\x11\x95\x28\x4c\x08\x04\xc1\x1b\x40\x18\x13\x29\x5b\xd1\xd8\xd7\x26\x5e\x67\xd8\x8f\x1e\xa2\x8a\xd1\x83\xc7\xa0\xd6\x04\x6e\x51\x92\x1b\xa5\x68\x9a\x71\xa1\x20\xe6\xc2\xac\x1b\x66\x3d\x42\x96\x79\x98\x50\x1c\xe4\x21\x23\x6a\xf6\x61\xf2\xe3\x04\xce\x3e\x4c\x40\x1a\xb6\x40\xbf\x2d\xd8\xfd\x91\x82\xdd\x47\x08\x9e\xfe\x48\xc1\xd3\x7b\x04\x9f\x13\x85\x22\xa4\x90\x96\x36\x7b\x1f\x78\xde\x3c\xe1\x79\x64\x6b\x5e\x8b\xf0\x7c\xa6\x88\x88\x11\x2e\xb2\xaf\xaa\xf8\xd7\x82\xe7\x99\xb4\x8b\x00\x0e\x9c\xa1\x90\x24\xe5\x47\xfd\x13\x95\x52\x06\x55\x9d\xcf\x39\x8b\xe9\x2a\x17\x86\xf5\xa0\xa2\x6d\xa2\x48\xf9\xe3\x34\xf0\xa4\xb1\x51\x24\x79\x63\x2d\x08\xde\xcc\x92\x84\xdf\x3d\x44\xa1\x59\xae\x38\x04\x18\x25\x94\xad\x1e\xab\x54\x0b\x86\x1a\x7b\x05\x54\x34\x1d\x65\xf4\xa8\x98\x74\x31\x72\x87\xaf\x4a\x4c\xb4\xd0\xf0\x5b\xa9\x58\x03\x0a\x9a\x47\x7f\x27\x1b\x7d\x60\x25\x10\x53\xb5\x1a\x86\x7f\xda\x02\xd7\xf9\xc0\x38\x23\xff\xaa\x78\x95\x2e\xeb\x63\x36\xf7\x4f\x2f\x21\x4c\x38\xfe\xdc\xc7\xb3\x62\xd1\x0b\xca\x4d\x4e\x05\x49\x1d\xd2\x2a\x10\x02\xcc\x73\xa6\x2a\x6e\x0d\xa8\x6d\x72\x29\x91\xf4\x5e\x2e\x73\xce\x22\xaa\xc3\x68\xdc\xfd\x06\xc9\x86\xb7\x06\xaf\x98\xe7\x5d\x70\x35\xd8\x26\xad\x59\x5a\xfc\x3b\x47\x89\x1c\x78\x70\xf3\xe4\x92\xc4\xa5\x87\x9f\xc1\xfe\xfe\x47\xcb\xa5\x05\x46\x8f\xe2\xd6\x01\xb2\x9d\x7c\xdd\xbf\xc0\xd7\xbd\x87\xef\xf4\x2f\xf0\x9d\x96\x7c\xcf\x51\x96\x51\xb6\x92\x05\x4c\x5c\x92\x15\xe5\xec\x8a\xcf\xce\x7d\xcb\x2e\x97\x0e\x41\x52\x39\x93\x92\xfb\xec\xdc\xf7\x4f\x3d\x40\x29\x75\xdc\x70\x1a\x3e\x1f\x1f\x4c\x4a\xc2\x3b\x22\x95\xe3\xf6\x10\x22\xfc\xfc\xe8\x85\x8b\x2d\x48\x91\xdc\x12\xf6\x71\x1c\x4f\xdd\xe9\x51\xf8\x62\x6c\x76\x50\xe6\x30\x2e\xd4\x7a\xa7\xfc\x38\x74\xe3\x89\xfb\xf2\xb0\xa4\x96\x3c\x2f\xa8\xfb\x94\x38\x98\x1e\x1e\xbc\x98\xb8\xe3\x86\xb6\x7d\x6c\xc3\x98\x8c\x5f\x1e\x46\x71\x97\x6d\x1f\x35\x7e\x71\x14\x1f\x4c\xd1\x41\x69\x1b\x26\x4c\x09\x94\xf4\xd2\x92\x09\x79\x1e\x1f\x1d\x45\x7b\x97\x44\xf2\x5c\x60\x62\xdc\xbe\xc0\x72\x6e\x13\xbf\xde\x19\x0c\x66\x2f\xe6\x06\xb8\xcb\x71\x61\x31\x0f\x34\xc2\x15\x00\x67\x80\xba\x73\xa4\x46\xd0\xf8\x60\xa8\x8b\x2e\x91\x11\x16\xc9\xb7\xcc\x83\x9b\x8f\x16\xd2\x04\xcf\x88\x50\x94\x54\x68\x76\xbd\x9c\x7f\xe0\x8c\xf8\x11\x61\x8a\xc6\xb4\x54\x4d\x27\xd7\xab\x0b\xcf\xf3\xe3\x6d\x29\x3b\x3d\x95\x54\xdb\x7c\xe2\x9b\xae\x75\x6d\x9a\x58\x5f\xe5\xd4\x69\xf5\xb6\xb1\xe2\x82\x9b\x03\x8f\x11\xea\x3e\x42\xa8\xfb\xa3\x84\x4e\x1f\x21\x74\xfa\x00\xa1\x67\xa6\x29\x34\x3a\x97\xc1\x39\x7b\x60\xce\x99\x42\x94\x11\x51\x36\x13\x59\xe2\x2b\x65\x06\x5f\xab\xe1\x78\x0b\xb9\xf6\x64\xbd\x81\x75\xc1\xdd\xd2\xf4\x35\xc0\xb9\x20\x46\x89\x25\x4f\x28\xae\xba\x40\x99\xbe\x01\x5d\x31\x54\x6b\xc5\x57\x34\x25\x3c\x57\x1e\x2c\xaf\x26\x87\xe7\x66\xf9\x5d\x16\x21\x45\x9a\xc7\x6b\x59\x79\xc9\x13\xfd\x9f\xa5\xda\x32\x3a\xa7\xac\x32\xd1\x67\x01\x11\xb7\x14\x37\xac\x33\xf6\x9d\x20\x85\xd7\x6d\xbb\x75\x83\xce\x25\xd1\xaa\xd4\xf5\xd0\x3f\xef\x11\x55\x6f\x59\x53\x79\xe9\xc1\xbe\x12\x39\xd1\xc7\xbb\xee\xbd\xbf\xba\x7a\x82\x65\xe8\xeb\xa3\x97\xb1\xb7\x7f\xfc\xa2\x6a\x6b\x30\x36\x4c\xea\x03\x09\xe6\x69\x8a\x58\xd4\x18\x52\x00\xc6\x93\x4f\x28\x8a\x3e\x95\x0d\xf2\x93\xe2\x9f\x70\x1d\x3b\x3a\xe7\x3d\x78\x12\xe4\x21\xfc\xa7\xb5\x0b\xf0\x8f\x27\xa3\x90\xb2\x51\x88\xe4\xba\xb3\x47\xf0\x9a\x6b\xb0\xf9\x34\x3f\x7b\x17\x5c\x2d\x2e\x8f\x9f\xfe\xb9\x05\xa9\xaf\x00\xbf\xfe\x0a\x23\xa2\xf0\x88\x60\xa9\x7f\x87\x56\xfb\x1a\x9b\x98\x26\xa4\xa5\xf9\xc0\x9c\xc0\x31\xd3\xbf\xce\x3a\xcf\xcc\xa9\x41\x57\x6d\xa6\x08\x53\x3b\xd5\xbe\x49\x11\x65\x1f\x3b\xcb\x52\x21\xfc\xf9\xf8\xe9\x9f\xc6\xd5\x81\xfe\xe0\x47\x5f\x3b\x54\xc2\x74\xb7\x92\xcc\xf6\xba\x36\x55\xca\x23\x9d\x4f\xe3\xf1\xf8\x60\x3c\xde\x6f\x6d\xf2\x3b\x46\x84\x07\x82\x73\xd5\xda\x59\x19\x30\xee\xee\x6c\xcd\x5e\x73\xfe\x59\x0e\x23\x63\x3e\xca\x15\x77\x04\x49\x38\x8a\x88\xf8\x4e\x47\x74\xf8\x38\x5a\x42\xd7\x35\x4a\xd0\xd5\x8a\x08\x79\x9c\x71\xa9\x86\xb9\xa9\xb4\x0e\x51\x86\xd4\xfa\xb8\xea\x4a\xc3\x6e\x25\x0c\xcb\xa4\x1e\xee\xcc\xe6\x0e\x53\x84\xf5\xe6\xf1\x88\x67\x6a\x84\xee\xa4\xc9\x37\xad\x35\x65\x54\x81\x73\x0b\x8e\x63\xc2\x06\xf5\xb0\x69\xb4\xfb\x0a\x8e\x23\x0a\x5d\x7a\x8a\xd2\xec\xea\xd0\xc1\xbd\x81\x04\x10\x39\x43\xf2\xb8\x15\x12\x69\xc1\xa4\x95\x9d\x72\x23\x6f\x69\xa3\x22\x8b\x28\xd8\x5c\x6d\x2f\x03\x10\xa6\x6f\xd4\x51\x0d\x3d\xda\xfb\x32\x17\xe4\x32\x67\x4c\x43\xc5\x2e\xaa\x9e\x3a\x01\x3b\xc1\xf5\x57\xcb\xbd\x94\xdf\x48\xb0\x1d\x9d\xde\x4f\xd1\x8a\xf8\x1a\x27\x5e\x51\x16\xf9\xec\x1c\x65\x70\xd3\x1a\x05\x9f\xd9\x06\x31\xa8\x79\x7b\xf0\xcc\x0e\x36\x50\x26\x5c\x40\x70\x2e\xa8\xda\x14\xd7\x47\xb8\xb1\x67\xde\x70\xa9\x82\xd7\x15\x55\xe3\x96\x64\x29\x7a\x2e\x83\x3e\x4a\xcb\xd5\xa5\xe0\xda\x49\x05\xed\x62\xee\xb6\x36\x5a\xb7\x27\x78\xe2\xc7\x70\x53\xbb\x21\x14\xaa\x37\x3f\x0d\xea\x9d\x77\x50\xea\xf6\x4e\x12\x71\x5a\x83\x6d\x00\x3d\x46\x9f\x20\x49\x9e\x1f\xd4\x63\xd4\x53\x90\x35\x30\x05\xe7\x4b\xb3\xbc\x36\x79\x6a\x2f\x34\x49\x02\xce\x06\xd0\x9d\x74\x74\x84\x42\xce\x95\x54\x02\x65\x0d\xe2\xff\x4b\xad\x74\x84\x4a\xd3\x1a\xc1\x21\xf0\xf4\xb7\x87\x49\xee\x99\x4c\xef\x11\xdd\x0d\x63\xa7\xd1\xfa\xb3\x73\x8d\x2a\xdd\x58\x77\x33\x78\x89\xd4\xda\x83\xc1\xa8\xac\x8e\x4b\x5e\x2b\x2a\xa7\x4a\x1c\xbd\x6c\x65\xeb\xbf\xfa\x05\x16\x34\x7d\x52\x66\x52\xe6\x29\xd1\x04\x76\x98\x39\xe5\x38\x4f\x35\x40\x57\xae\x0c\x14\x52\xa4\xb9\xe4\xc0\x22\x8e\x09\x56\x1e\xd4\x9f\x30\xac\x00\xca\x30\xcd\x50\xd2\xac\xfe\x72\xd4\xd9\x6b\x16\x39\xc1\xee\x10\xa5\xe8\x0f\xce\xd0\x9d\x6e\xb7\x69\x6d\x7f\x66\x50\xb6\xf9\x96\x21\x95\xf4\xb6\x0a\xef\xf0\x93\xb1\x83\xd6\x5d\x65\x2d\xb3\x85\x44\xb0\x74\x0a\xac\xdc\x4e\x56\x3b\x2c\xef\xb5\xfd\x3e\xeb\xfb\xb4\xb6\x76\x4a\xcf\x8c\x9c\x64\x7b\xe9\x69\xef\xeb\x34\xd2\x5b\x9d\x64\xef\xa1\x3d\x25\xe2\x31\xd4\x54\x62\x7e\x4b\xc4\x92\x27\xc9\x82\x45\x19\xa7\x4c\xf5\x90\x05\x79\x98\x52\xf5\x4b\x67\x47\x78\xdd\x35\xe9\x69\x66\x8d\xe5\xb2\xcb\x7a\x30\xf8\x45\x87\xc2\x22\x64\xcf\xd5\xcf\xf5\xbc\x06\xa8\xee\xba\xaa\x99\x67\x5b\x9b\xe6\xd7\xcb\x79\xb1\x6a\x4e\x34\x9e\x14\x17\xf3\xc0\xc8\xaa\x90\x1a\xb6\x5c\x5b\xf0\xed\xb3\x95\x20\xb2\x96\x18\x7e\xb6\x14\x5c\x71\xcc\x13\x0f\x14\xde\x42\xd6\x2b\xc1\xd3\x25\x17\xe6\x95\xdc\xdd\xb6\xb7\x2b\xde\xb3\x38\xa7\x91\xf0\xb3\x42\xd1\xda\xa3\xde\x22\x09\x7f\x8a\xf9\x67\x27\xff\x23\xcb\x8f\xc6\x3d\x96\xd7\x17\x4b\xcb\xc7\x43\xf3\x6f\x34\xd6\x56\x9f\x9d\xb8\x3a\x1a\x97\x79\x0f\x16\x75\x8d\x2f\xf4\xda\xd5\xc3\x7b\x95\xac\xa9\x58\x29\x53\xe9\xf7\xfc\xf0\x70\x7a\x58\xae\x06\xf6\x42\xd4\x10\xa8\x7d\xfa\x9a\xa8\x99\x52\x36\x42\xc3\x62\xb9\xee\xe0\x3a\x91\x4d\xe3\x1a\x95\x5e\x70\x17\x67\x27\x7f\x07\x0b\x3b\xca\xf7\x9a\xd8\xf6\xc3\x02\xcb\x45\x12\x76\x6d\x4b\x90\x54\x14\x9f\x71\x14\x9d\xa0\x04\x31\x4c\xd9\xea\xda\xf5\xbc\xed\x42\x81\x6b\x5d\x33\xed\x53\x81\xfc\xfe\xc7\x96\x9f\xf5\xbe\xf2\xb3\x9e\x54\xbe\xef\x15\xa5\x35\x8b\xb6\x46\x01\x1d\xc5\x2a\x7a\x67\xba\x3b\xb0\xbe\xa7\xb7\x5d\x51\x2c\x0e\xec\x88\x60\x3d\xc8\x33\xc1\xca\xb9\xd5\xc8\x2a\x48\x8a\x6f\x69\x6c\x07\xac\x69\x67\x85\xc7\x5c\xdc\x21\x11\x6d\x11\x05\x89\x15\x51\xc6\x92\x36\xbf\x82\x51\x8d\xa2\xea\xec\x2d\x0c\xda\x16\xcf\x9b\xab\xab\x65\x65\x7c\x97\xc1\x83\xdd\xd0\x16\xda\x33\x96\x95\x4a\xdc\xa3\x06\xf4\x00\xf8\xdb\x5c\x65\xb9\xad\x01\x3d\x7b\xbf\x13\xc5\x88\x64\x02\x5c\x5c\x87\xd7\x4a\x65\xde\x68\x64\x9e\x25\x16\x49\x38\x3c\xbd\x08\xcc\x48\x3a\xda\x83\xf6\x37\x69\x1a\xf7\xdf\x5d\x9e\x75\x02\xae\x9d\xd9\xe0\xbb\xf5\x6b\x23\xc4\x0d\x66\x33\xc1\xca\x6f\xe5\x34\xdf\x92\xd0\x7e\x9b\xba\xf8\x92\x69\x93\x0b\xb3\x8a\xeb\x88\xd6\xb6\x33\x39\x3b\x1d\x55\x7a\x9e\x81\x5b\x7a\xd5\x07\xa2\x9d\x5f\x15\xd6\xbe\x44\xf9\x1e\x9d\x4a\x19\x7b\xff\x0d\x00\x00\xff\xff\x23\xcd\x84\x66\x2a\x20\x00\x00") +var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x59\xfb\x6f\xdb\x38\xf2\xff\x3d\x7f\xc5\xd4\xdf\x02\xf9\xde\xa2\xf2\x43\x4e\xda\x54\x40\x76\xe1\x38\x6e\x2b\x6c\xd2\x1a\x51\x9a\x02\x0d\x82\x82\xa2\x28\x9b\x88\x44\xea\x48\x2a\x69\x76\xaf\xff\xfb\x81\xd4\xc3\x7a\xd0\xde\xa4\x9b\xc3\xe1\x52\x04\x8d\xc9\xe1\xbc\xe7\x33\x43\xda\x71\x9c\xbd\xd9\x97\xe0\x92\xa4\x59\x82\x14\x79\xc7\x45\x8a\xd4\x15\x11\x92\x72\xe6\xc1\xbe\x3b\x9e\x8c\x9d\xf1\x5b\x67\xfc\x76\x7f\x6f\x89\x04\x4a\x89\x22\x42\x7a\x7b\x00\x3e\x93\x0a\x31\x4c\x2e\x1f\x32\xa2\x3f\x03\x98\xbf\x20\x50\x82\xb2\x95\x59\x38\x25\x12\x0b\x9a\x29\xc3\xaa\xa2\x07\xf5\x90\x11\x50\x1c\x72\x49\x86\x25\x59\x8c\xf2\x44\x79\xa0\xdc\x61\x4a\xb1\xe0\x7b\xe6\x28\x15\x24\x9a\xa3\x0c\x61\xaa\x1e\x9a\x02\x3e\xe6\x69\x48\x44\xfb\xe4\xfe\x64\xbf\x2f\xb1\x20\x04\x1e\x03\x2d\x65\x4b\x2d\x37\x41\x39\xc3\x6b\xa0\x0c\x1e\x78\x2e\x60\x31\x0f\x00\x27\xb9\x54\x86\xe7\x39\xfa\x1e\xd0\x3f\xc8\x5f\xca\x73\x2d\xf2\xce\xd1\x77\x9a\xe6\x29\x30\x9b\xdc\x35\x52\x80\x11\x83\x90\x94\x0a\x90\x68\x8b\x0a\xbf\x93\x87\x8f\x28\x7d\x94\x4f\x4b\x52\x6d\x15\x92\x92\x63\x8a\x14\x81\x7b\xaa\xd6\x70\xcf\xc5\x2d\x11\x1b\x05\x86\x00\x67\x04\xdd\x11\x08\x13\xc4\x6e\xf5\x81\x88\x4a\x14\x26\x04\x82\xe0\x03\x20\x8c\x89\x94\x9d\x68\xec\x6b\x13\xaf\x32\xec\x47\x8f\x51\xc5\xe8\xc1\x63\x50\x6b\x02\x77\x28\xc9\x8d\x52\x34\xcd\xb8\x50\x10\x73\x61\xd6\x0d\xb3\x3d\x80\x65\x1e\x26\x14\x07\x79\xc8\x88\x9a\x7d\x9d\x3c\x9f\x80\xd9\xd7\x09\x48\xc3\x16\x68\x5f\x90\xfb\x9c\x82\xdc\x96\xa0\xae\xdb\xda\x82\xa7\xcf\x29\x78\xba\x43\xf0\x39\x51\x28\x42\x0a\x69\x69\xb3\x2f\x81\xe7\xcd\x13\x9e\x47\x45\x4d\x6b\x11\x9e\xcf\x14\x11\x31\xc2\x65\x76\xd5\x15\xfd\x5e\xf0\x3c\x93\xc5\x22\x80\x03\x67\x28\x24\x49\xf5\x51\xff\x44\x95\x94\x41\x5d\xc7\x73\xce\x62\xba\xca\x85\x61\x3d\xa8\x69\xdb\x28\x51\xfd\x38\x2d\xbc\x68\x6d\x94\x49\xdc\x5a\x0b\x82\x0f\xb3\x24\xe1\xf7\x8f\x51\x68\x96\x2b\x0e\x01\x46\x09\x65\xab\xa7\x2a\xd5\x81\x99\xd6\x5e\x09\x05\x6d\x47\x19\x3d\x6a\x26\x7d\x0c\xdc\xe2\xab\x0a\xf3\x8a\xd2\xff\xad\x52\xac\x55\xea\xed\xa3\xbf\x93\x07\x7d\x60\x25\x10\x53\x8d\x1a\x85\xff\x2f\x0a\x58\xe7\x03\xe3\x8c\xfc\xa3\xe6\x55\xb9\xcc\xc6\x6c\xee\x9f\x5e\x40\x98\x70\x7c\x6b\xe3\x59\xb3\xb0\x82\x6e\x9b\x53\x49\xd2\x84\xac\x1a\x64\x00\xf3\x9c\xa9\x9a\x5b\x0b\x4a\xdb\x5c\x2a\xa4\xdc\xc9\x65\xce\x59\x44\x75\x18\x8d\xbb\x3f\x20\xd9\xf2\xd6\xe0\x1d\xf3\xbc\x8f\x5c\x0d\x36\x49\x6b\x96\x16\xff\xcc\x51\x22\x07\x1e\x5c\xbf\xb8\x20\x71\xe5\xe1\x57\xb0\xbf\x7f\x53\x70\xe9\x80\xcf\x93\xb8\xf5\x80\x6b\x2b\x5f\xf7\x6f\xf0\x75\x77\xf0\x9d\xfe\x0d\xbe\xd3\x8a\xef\x39\xca\x32\xca\x56\xb2\x84\x89\x0b\xb2\xa2\x9c\x5d\xf2\xd9\xb9\x5f\xb0\xcb\xa5\x43\x90\x54\xce\xa4\xe2\x3e\x3b\xf7\xfd\x53\x0f\x50\x4a\x1d\x37\x9c\x86\xaf\xc7\x07\x93\x8a\xf0\x9e\x48\xe5\xb8\x16\x42\x84\x5f\x1f\xbd\x71\x71\x01\x52\x24\x2f\x08\x6d\x1c\xc7\x53\x77\x7a\x14\xbe\x19\x9b\x1d\x94\x39\x8c\x0b\xb5\xde\x2a\x3f\x0e\xdd\x78\xe2\xbe\x3d\xac\xa8\x25\xcf\x4b\x6a\x9b\x12\x07\xd3\xc3\x83\x37\x13\x77\xdc\xd2\xd6\xc6\x36\x8c\xc9\xf8\xed\x61\x14\xf7\xd9\xda\xa8\xf1\x9b\xa3\xf8\x60\x8a\x0e\x2a\xdb\x30\x61\x4a\xa0\xc4\x4a\x4b\x26\xe4\x75\x7c\x74\x14\xed\x5d\x10\xc9\x73\x81\x89\x71\xfb\x02\xcb\x79\x91\xf8\xcd\xce\x60\x30\x7b\x31\x37\xc0\x5d\x8d\x03\x8b\x79\xa0\x11\xae\x04\x38\x03\xd4\xbd\x23\x0d\x82\xd6\x07\x43\x5d\x76\x89\x8c\xb0\x48\x7e\x62\x1e\x5c\xdf\x14\x90\x26\x78\x46\x84\xa2\xa4\x46\xb3\xab\xe5\xfc\x2b\x67\xc4\x8f\x08\x53\x34\xa6\x95\x6a\x3a\xb9\x74\x6e\xf9\xf1\xa6\x94\x1d\x4b\x25\x35\x36\x5f\xf8\xa6\x6b\x5d\x99\x26\x66\x2d\x9d\x9b\x26\xb5\xde\x37\x76\x7c\xe4\xe6\xc8\x53\xc4\xba\x5b\xc5\xde\xd8\x4a\xeb\xd9\xe4\x4e\x9f\x62\xee\xf4\x31\x62\xcf\x4c\x6f\x68\x35\x30\x03\x77\xc5\x81\x39\x67\x0a\x51\x46\x44\xd5\x53\x64\x05\xb3\x94\x19\x98\xad\x67\xe0\x0d\xf2\x16\x27\x9b\x7d\xac\x8f\xf1\x05\x8d\xad\x0f\xce\x05\x31\x4a\x2c\x79\x42\x71\xdd\x0c\xaa\x2c\x0e\xe8\x8a\xa1\x46\x47\xbe\xa4\x29\xe1\xb9\xf2\x60\x79\x39\x39\x3c\x37\xcb\x9f\xb3\x08\x29\xd2\x3e\xde\x48\xce\x0b\x9e\xe8\xff\x0a\xaa\x0d\xa3\x73\xca\x6a\x13\x7d\x16\x10\x71\x47\x71\xcb\x3a\x63\xdf\x09\x52\x78\xdd\xb5\x5b\xf7\xe9\x5c\x12\xad\x4a\x53\x0f\xfd\xf3\x05\x51\xf5\x89\xb5\x95\x97\x1e\xec\x2b\x91\x13\x7d\xbc\xef\xde\xdd\x45\x66\x09\x96\xa1\x6f\x4e\x60\xc6\x5e\xfb\x14\x46\xd5\xc6\x60\x6c\x98\x34\xe7\x12\xcc\xd3\x14\xb1\xa8\x35\xab\x00\x8c\x27\xdf\x50\x14\x7d\xab\xfa\xe4\x37\xc5\xbf\xe1\x26\x84\xf4\xce\x7b\xf0\x22\xc8\x43\xf8\x57\x67\x17\xe0\xff\x5e\x8c\x42\xca\x46\x21\x92\xeb\xde\x1e\xc1\x6b\xae\x31\xe7\xdb\xfc\xec\x73\x70\xb9\xb8\x38\x7e\xf9\xe7\x06\xab\x7e\x00\xfc\xfa\x2b\x8c\x88\xc2\x23\x82\xa5\xfe\x1d\x16\xda\x37\xd8\xc4\x34\x21\x1d\xcd\x07\xe6\x04\x8e\x99\xfe\x75\xd6\x79\x66\x4e\x0d\xfa\x6a\x33\x45\x98\xda\xaa\xf6\x75\x8a\x28\xbb\xe9\x2d\x4b\x85\xf0\xed\xf1\xcb\x3f\x8d\xab\x03\xfd\xc1\x8f\x7e\xf4\xa8\x84\x69\x72\x15\x59\xd1\xf2\xba\x54\x29\x8f\x74\x3e\x8d\xc7\xe3\x83\xf1\x78\xbf\xb3\xc9\xef\x19\x11\x1e\x08\xce\x55\x67\x67\x65\x30\xb9\xbf\xb3\x31\x7b\xcd\xf9\xad\x1c\x46\xc6\x7c\x94\x2b\xee\x08\x92\x70\x14\x11\xf1\x93\x8e\xe8\xf1\x71\xb4\x84\xbe\x6b\x94\xa0\xab\x15\x11\xf2\x38\xe3\x52\x0d\x73\x53\x69\x3d\xa2\x0c\xa9\xf5\x71\xdd\x9c\x86\xfd\x4a\x18\x56\x49\x3d\xdc\x9a\xcd\x3d\xa6\x08\xeb\xcd\xe3\x11\xcf\xd4\x08\xdd\x4b\x93\x6f\x5a\x6b\xca\xa8\x02\xe7\x0e\x1c\xc7\x84\x0d\x9a\x61\xd3\x68\xf7\x03\x1c\x47\x94\xba\x58\x8a\xd2\xec\xea\xd0\xc1\xce\x40\x02\x88\x9c\x21\x79\xdc\x09\x89\x2c\xc0\xa4\x93\x9d\xf2\x41\xde\xd1\x56\x45\x96\x51\x28\x72\xb5\xbb\x0c\x40\x98\xbe\x38\x47\x0d\xf4\xe8\xee\xcb\x5c\x90\x8b\x9c\x31\x0d\x15\xdb\xa8\x2c\x75\x02\xc5\x20\x67\xaf\x96\x9d\x94\x7f\x91\x60\x5b\x1a\xbe\x9f\xa2\x15\xf1\x35\x4e\xbc\xa3\x2c\xf2\xd9\x39\xca\xe0\xba\x33\x11\xbe\x2a\x1a\xc4\xa0\xe1\xed\xc1\xab\x62\xbe\x81\x2a\xe1\x02\x82\x73\x41\xd5\x43\x79\x8b\x84\xeb\xe2\xcc\x07\x2e\x55\xf0\xbe\xa6\x6a\x5d\x96\x0a\x0a\xcb\x9d\xd0\x47\x69\xb5\xba\x14\x5c\x3b\xa9\xa4\x5d\xcc\xdd\xce\x46\xe7\x12\x05\x2f\xfc\x18\xae\x1b\x17\x85\x52\xf5\xf6\xa7\x41\xb3\xf3\x0e\x2a\xdd\x3e\x4b\x22\x4e\x1b\xb0\x0d\x66\x04\x38\x41\x92\xbc\x3e\x68\xc6\xc8\x52\x90\x0d\x30\x05\xe7\x7b\xbb\xbc\x1e\xf2\xb4\xb8\xd7\x24\x09\x38\x0f\x80\xee\xa5\xa3\x23\x14\x72\xae\xa4\x12\x28\x6b\x11\xff\x57\x6a\xa5\x27\x54\x9a\xd6\x08\x0e\x81\x97\xbf\x3d\x4e\xb2\x65\x40\xdd\x21\xba\x1f\xc6\x5e\xa3\xf5\x67\xe7\x1a\x55\xfa\xb1\xee\x67\xf0\x12\xa9\xb5\x07\x83\x51\x55\x1d\x17\xbc\x51\x54\x4e\x9d\x38\x7a\xb9\x90\xad\xff\xb2\x0b\x2c\x69\x6c\x52\x66\x52\xe6\x29\xd1\x04\xc5\x30\x73\xca\x71\x9e\x6a\x80\xae\x5d\x19\x28\xa4\x48\x7b\xc9\x81\x45\x1c\x13\xac\x3c\x68\xbe\x64\x14\x02\x28\xc3\x34\x43\x49\xbb\xfa\xab\x51\x67\xaf\x5d\xe4\x04\xbb\x43\x94\xa2\x3f\x38\x43\xf7\xba\xdd\xa6\x8d\xfd\x99\x41\xd9\xf6\x93\x86\x54\xd2\xdb\x28\xbc\xc5\x4f\xc6\x0e\xda\x74\x55\x61\x59\x51\x48\x04\x4b\xa7\xc4\xca\xcd\x64\xb5\xc5\x72\xab\xed\xbb\xac\xb7\x69\x5d\xd8\x29\x3d\x33\x72\x92\xcd\xdd\xa7\xbb\xaf\xd3\x48\x6f\xf5\x92\xdd\x42\x7b\x4a\xc4\x53\xa8\xa9\xc4\xfc\x8e\x88\x25\x4f\x92\x05\x8b\x32\x4e\x99\xb2\x90\x05\x79\x98\x52\xf5\x4b\x6f\x47\x78\xfd\x35\xe9\x69\x66\xad\xe5\xaa\xcb\x7a\x30\xf8\x45\x87\xa2\x40\x48\xcb\x0d\xd0\xf5\xbc\x16\xa8\x6e\xbb\xb1\x99\xd7\xd9\x22\xcd\xaf\x96\xf3\x72\xd5\x9c\x68\xbd\x2c\x2e\xe6\x81\x91\x55\x23\x35\x6c\xb8\x76\xe0\xdb\x67\x2b\x41\x64\x23\x31\xfc\x6c\x29\xb8\xe2\x98\x27\x1e\x28\xbc\x81\xac\x77\x82\xa7\x4b\x2e\xcc\x63\xb8\xbb\x69\x6f\x97\xdc\xb2\x38\xa7\x91\xf0\xb3\x52\xd1\xc6\xdb\xde\x22\x09\x9f\xc3\xfc\xfe\xf5\xcb\xec\xdc\x6c\xf7\xc7\xd9\xc9\x7f\xc8\x15\x47\x63\x8b\x2b\x9a\x8b\x95\x2b\xc6\x43\xf3\x6f\x34\xd6\x6e\x38\x3b\x71\x75\x78\x2e\x72\x0b\x38\xf5\xbd\x51\xea\xb5\xad\xa9\x5b\x95\x6c\xa8\x58\x2b\x53\xeb\xf7\xfa\xf0\x70\x7a\x58\xad\x06\xc5\x0d\xa9\x25\x50\x3b\xf9\x3d\x51\x33\xa5\x8a\x90\x0d\xcb\xe5\xa6\x83\x9b\x44\x45\x5e\x37\xa8\xf4\x82\xbb\x38\x3b\xf9\x5f\xb0\xb0\xa7\xbc\xd5\xc4\xae\x1f\x16\x58\x2e\x92\xb0\x6f\x5b\x82\xa4\xa2\xf8\x8c\xa3\xe8\x04\x25\x88\x61\xca\x56\x57\xae\xe7\x6d\x16\x4a\xa0\xeb\x9b\x59\x3c\x1e\xc8\x9f\x7f\x84\xb1\x3c\xbb\x3c\xe2\x19\xe2\xe9\xaf\x2e\x96\x67\x96\xe7\x92\x33\xdd\x2d\xa7\xb7\x6d\x91\xd3\x19\x4e\x3b\xb3\x81\x8e\x62\x1d\xbd\x33\xdd\x2e\x98\xed\x49\x6e\x5b\x14\xcb\x03\x5b\x22\xd8\x0c\xf2\x4c\xb0\x6a\x90\x35\xb2\x4a\x92\xf2\xdb\x9b\xa2\x25\x36\xb4\x2b\x84\xc7\x5c\xdc\x23\x11\x6d\x10\x05\x89\x15\x51\xc6\x92\x2e\xbf\x92\x51\x83\xa2\x6e\xf5\x1d\x0c\xda\x14\xcf\x87\xcb\xcb\x65\x6d\x7c\x9f\xc1\xa3\xdd\xd0\x15\x6a\x99\xd3\x2a\x25\x76\xa8\x01\x96\x86\xf6\x29\x57\x59\x5e\xd4\x80\x1e\xc6\x3f\x8b\x72\x66\x32\x01\x2e\xef\xc7\x6b\xa5\x32\x6f\x34\x32\xef\x14\x8b\x24\x1c\x9e\x7e\x0c\xcc\x8c\x3a\xda\x83\xee\x37\x6c\x1a\xf7\x3f\x5f\x9c\xf5\x02\xae\x9d\xd9\xe2\xbb\xf1\x6b\x2b\xc4\x2d\x66\x33\xc1\xaa\x6f\xeb\x34\xdf\x8a\xb0\xf8\x16\x75\xf1\x5d\xb7\xa4\xca\xfc\xf2\x7e\xa2\xb5\xed\x8d\xd2\x4e\x4f\x15\xcb\xf3\x70\x47\xaf\xe6\x84\xb4\xf5\x2b\xc4\xc6\x97\x2b\x3f\xa3\x53\x25\x63\xef\xdf\x01\x00\x00\xff\xff\x5b\xb0\x49\x10\x22\x20\x00\x00") func assetsEnvironmentTemplateYmlBytes() ([]byte, error) { return bindataRead( @@ -84,7 +84,7 @@ func assetsEnvironmentTemplateYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/environment-template.yml", size: 8234, mode: os.FileMode(420), modTime: time.Unix(1484068831, 0)} + info := bindataFileInfo{name: "assets/environment-template.yml", size: 8226, mode: os.FileMode(420), modTime: time.Unix(1484088447, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/common/config.go b/common/config.go index a2875e6c..62fa353c 100644 --- a/common/config.go +++ b/common/config.go @@ -2,28 +2,13 @@ package common import ( "gopkg.in/yaml.v2" - "io/ioutil" - "fmt" "log" ) -// NewConfig create a new config object -func NewConfig() *Config { +func newConfig() *Config { return new(Config) } - -// LoadFromFile loads config object from local file -func (config *Config) LoadFromFile(configFile string) { - yamlConfig, err := ioutil.ReadFile( configFile ) - if err != nil { - fmt.Printf("WARN: Unable to find config file: %v\n", err) - } else { - config.loadFromYaml(yamlConfig) - } - -} - func (config *Config) loadFromYaml(yamlConfig []byte) *Config { err := yaml.Unmarshal(yamlConfig, config) if err != nil { @@ -32,5 +17,3 @@ func (config *Config) loadFromYaml(yamlConfig []byte) *Config { return config } - - diff --git a/common/config_test.go b/common/config_test.go index 96121a9b..94b05544 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -3,14 +3,13 @@ package common import ( "testing" "github.com/stretchr/testify/assert" - "fmt" ) func TestNewConfig(t *testing.T) { assert := assert.New(t) - config := NewConfig() + config := newConfig() assert.NotNil(config) } @@ -39,7 +38,7 @@ service: ` - config := NewConfig() + config := newConfig() config.loadFromYaml([]byte(yamlConfig)) fmt.Println(config) @@ -68,14 +67,7 @@ func TestLoadBadYamlConfig(t *testing.T) { yamlConfig := ` blah blah blah ` - config := NewConfig() + config := newConfig() config.loadFromYaml([]byte(yamlConfig)) } -func TestLoadConfig(t *testing.T) { - - config := NewConfig() - config.LoadFromFile("foobarbaz.yml") -} - - diff --git a/common/context.go b/common/context.go new file mode 100644 index 00000000..600db513 --- /dev/null +++ b/common/context.go @@ -0,0 +1,37 @@ +package common + +import ( + "io/ioutil" + "fmt" +) + +// NewContext create a new context object +func NewContext() *Context { + ctx := new(Context) + return ctx +} + + + +// InitializeFromFile loads config object from local file +func (ctx *Context) InitializeFromFile(configFile string) { + yamlConfig, err := ioutil.ReadFile( configFile ) + if err != nil { + fmt.Printf("WARN: Unable to find config file: %v\n", err) + } else { + ctx.Config.loadFromYaml(yamlConfig) + } + + ctx.Initialize() +} + +// Initialize will create AWS services +func (ctx *Context) Initialize() error { + cfn, err := newCloudFormation(ctx.Config.Region) + if err != nil { + return err + } + + ctx.CloudFormation = cfn + return nil +} diff --git a/common/stack.go b/common/stack.go index 09d6890b..d29ab44a 100644 --- a/common/stack.go +++ b/common/stack.go @@ -6,25 +6,30 @@ import ( "text/template" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudformation" - "github.com/aws/aws-sdk-go/aws/session" "io/ioutil" + "github.com/aws/aws-sdk-go/aws/session" ) // Stack contains the data about a CloudFormation stack type Stack struct { Name string TemplatePath string - Region string } // NewStack will create a new stack instance -func NewStack(name string, region string) *Stack { +func NewStack(name string) *Stack { return &Stack{ Name: name, - Region: region, TemplatePath: fmt.Sprintf("%s/%s.yml",os.TempDir(), name), } } +func newCloudFormation(region string) (*cloudformation.CloudFormation, error) { + sess, err := session.NewSession() + if err != nil { + return nil, err + } + return cloudformation.New(sess, &aws.Config{Region: aws.String(region)}), nil +} // WriteTemplate will create a temp file with the template for a CFN stack //go:generate go-bindata -pkg $GOPACKAGE -o assets.go assets/ @@ -55,13 +60,7 @@ func (stack *Stack) WriteTemplate(assetName string, data interface{}) (error) { } // UpsertStack will create/update the cloudformation stack -func (stack *Stack) UpsertStack() (error) { - cfn, err := newCloudFormation(stack.Region) - if err != nil { - return err - } - - +func (stack *Stack) UpsertStack(cfn *cloudformation.CloudFormation) (error) { stackStatus := stack.AwaitFinalStatus(cfn) if stackStatus == "" { fmt.Printf("creating stack: %s\n", stack.Name) @@ -107,14 +106,6 @@ func (stack *Stack) readTemplatePath() (string) { return string(templateBytes) } -func newCloudFormation(region string) (*cloudformation.CloudFormation, error) { - sess, err := session.NewSession() - if err != nil { - return nil, err - } - return cloudformation.New(sess, &aws.Config{Region: aws.String(region)}), nil -} - // AwaitFinalStatus waits for the stack to arrive in a final status func (stack *Stack) AwaitFinalStatus(cfn *cloudformation.CloudFormation) (string) { params := &cloudformation.DescribeStacksInput{ diff --git a/common/stack_test.go b/common/stack_test.go index 71dcb657..25d328fa 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -9,7 +9,7 @@ import ( func TestNewStack(t *testing.T) { assert := assert.New(t) - stack := NewStack("foo","us-west-2") + stack := NewStack("foo") assert.NotNil(stack) assert.Equal("foo",stack.Name) diff --git a/common/types.go b/common/types.go index d85d55db..c4bbc262 100644 --- a/common/types.go +++ b/common/types.go @@ -1,5 +1,13 @@ package common +import "github.com/aws/aws-sdk-go/service/cloudformation" + +// Context defines the context object passed around +type Context struct { + Config Config + CloudFormation *cloudformation.CloudFormation +} + // Config defines the structure of the yml file for the mu config type Config struct { Region string diff --git a/resources/environment.go b/resources/environment.go index c5150f3b..0c14b633 100644 --- a/resources/environment.go +++ b/resources/environment.go @@ -12,42 +12,27 @@ type EnvironmentManager interface { UpsertEnvironment(environmentName string) (error) } -// NewEnvironmentManager will construct a manager for environments -func NewEnvironmentManager(config *common.Config) (EnvironmentManager) { - ctx := new(environmentManagerContext) - ctx.config = config - return ctx -} -type environmentManagerContext struct { - config *common.Config +// NewEnvironmentManager will construct a manager for environments +func NewEnvironmentManager(ctx *common.Context) (EnvironmentManager) { + environmentManager := new(environmentManagerImpl) + environmentManager.context = ctx + return environmentManager } -// getEnvironment loads the environment by name from the config -func (ctx *environmentManagerContext) getEnvironment(environmentName string) (*common.Environment, error) { - - for _, e := range ctx.config.Environments { - if(strings.EqualFold(environmentName, e.Name)) { - return &e, nil - } +// UpsertEnvironment will create a new stack instance and write the template for the stack +func (environmentManager *environmentManagerImpl) UpsertEnvironment(environmentName string) (error) { + env, err := environmentManager.getEnvironment(environmentName) + if err != nil { + return err } - return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml",environmentName) -} - -// getRegion determines the region to use -func (ctx *environmentManagerContext) getRegion() (string) { - return ctx.config.Region -} - -// UpsertRegion will create a new stack instance and write the template for the stack -func (ctx *environmentManagerContext) UpsertEnvironment(environmentName string) (error) { - err := ctx.upsertVpc(environmentName) + err = environmentManager.upsertVpc(env) if err != nil { return err } - err = ctx.upsertEcsCluster(environmentName) + err = environmentManager.upsertEcsCluster(env) if err != nil { return err } @@ -55,24 +40,35 @@ func (ctx *environmentManagerContext) UpsertEnvironment(environmentName string) return nil } -func (ctx *environmentManagerContext) upsertVpc(environmentName string) (error) { - env, err := ctx.getEnvironment(environmentName) - if err != nil { - return err +type environmentManagerImpl struct { + context *common.Context +} + +func (environmentManager *environmentManagerImpl) getEnvironment(environmentName string) (*common.Environment, error) { + ctx := environmentManager.context + for _, e := range ctx.Config.Environments { + if(strings.EqualFold(environmentName, e.Name)) { + return &e, nil + } } + return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml",environmentName) +} + +func (environmentManager *environmentManagerImpl) upsertVpc(env *common.Environment) (error) { + cfn := environmentManager.context.CloudFormation stackName := fmt.Sprintf("mu-vpc-%s", env.Name) // generate the CFN template - stack := common.NewStack(stackName, ctx.getRegion()) + stack := common.NewStack(stackName) - err = stack.WriteTemplate("vpc-template.yml", env) + err := stack.WriteTemplate("vpc-template.yml", env) if err != nil { return err } // create/update the stack fmt.Printf("upserting VPC environment:%s stack:%s path:%s\n",env.Name, stack.Name, stack.TemplatePath) - err = stack.UpsertStack() + err = stack.UpsertStack(cfn) if err != nil { return err } @@ -80,24 +76,20 @@ func (ctx *environmentManagerContext) upsertVpc(environmentName string) (error) return nil } -func (ctx *environmentManagerContext) upsertEcsCluster(environmentName string) (error) { - env, err := ctx.getEnvironment(environmentName) - if err != nil { - return err - } - +func (environmentManager *environmentManagerImpl) upsertEcsCluster(env *common.Environment) (error) { + cfn := environmentManager.context.CloudFormation stackName := fmt.Sprintf("mu-env-%s", env.Name) // generate the CFN template - stack := common.NewStack(stackName, ctx.getRegion()) + stack := common.NewStack(stackName) - err = stack.WriteTemplate("environment-template.yml", env) + err := stack.WriteTemplate("environment-template.yml", env) if err != nil { return err } // create/update the stack fmt.Printf("upserting environment:%s stack:%s path:%s\n",env.Name, stack.Name, stack.TemplatePath) - err = stack.UpsertStack() + err = stack.UpsertStack(cfn) if err != nil { return err } diff --git a/resources/environment_test.go b/resources/environment_test.go index 841a9a14..4190783f 100644 --- a/resources/environment_test.go +++ b/resources/environment_test.go @@ -9,8 +9,8 @@ import ( func TestEnvironment_UpsertEnvironmentUnknown(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() - envMgr := NewEnvironmentManager(config) + ctx := common.NewContext() + envMgr := NewEnvironmentManager(ctx) err := envMgr.UpsertEnvironment("test-stack-that-is-fake") @@ -19,7 +19,8 @@ func TestEnvironment_UpsertEnvironmentUnknown(t *testing.T) { func TestGetEnvironment(t *testing.T) { assert := assert.New(t) - config := common.NewConfig() + envMgr := new(environmentManagerImpl) + envMgr.context = common.NewContext() env1 := common.Environment{ Name: "foo", @@ -27,10 +28,7 @@ func TestGetEnvironment(t *testing.T) { env2 := common.Environment{ Name: "bar", } - config.Environments = []common.Environment{env1, env2} - envMgr := &environmentManagerContext{ - config: config, - } + envMgr.context.Config.Environments = []common.Environment{env1, env2} fooEnv, fooErr := envMgr.getEnvironment("foo") assert.Equal("foo", fooEnv.Name) From 97b83685848780d55e0a8e090541b41b44bb8205 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 10 Jan 2017 23:36:18 -0800 Subject: [PATCH 17/31] improve test coverage of stack --- common/stack.go | 79 +++++++++--------- common/stack_test.go | 186 ++++++++++++++++++++++++++++++++++++++++++- common/types.go | 6 +- 3 files changed, 228 insertions(+), 43 deletions(-) diff --git a/common/stack.go b/common/stack.go index d29ab44a..04058c19 100644 --- a/common/stack.go +++ b/common/stack.go @@ -5,9 +5,10 @@ import ( "os" "text/template" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/cloudformation" "io/ioutil" "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" + "github.com/aws/aws-sdk-go/service/cloudformation" ) // Stack contains the data about a CloudFormation stack @@ -23,7 +24,7 @@ func NewStack(name string) *Stack { TemplatePath: fmt.Sprintf("%s/%s.yml",os.TempDir(), name), } } -func newCloudFormation(region string) (*cloudformation.CloudFormation, error) { +func newCloudFormation(region string) (cloudformationiface.CloudFormationAPI, error) { sess, err := session.NewSession() if err != nil { return nil, err @@ -60,7 +61,7 @@ func (stack *Stack) WriteTemplate(assetName string, data interface{}) (error) { } // UpsertStack will create/update the cloudformation stack -func (stack *Stack) UpsertStack(cfn *cloudformation.CloudFormation) (error) { +func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) (error) { stackStatus := stack.AwaitFinalStatus(cfn) if stackStatus == "" { fmt.Printf("creating stack: %s\n", stack.Name) @@ -71,12 +72,16 @@ func (stack *Stack) UpsertStack(cfn *cloudformation.CloudFormation) (error) { }, TemplateBody: aws.String(stack.readTemplatePath()), } - resp, err := cfn.CreateStack(params) + _, err := cfn.CreateStack(params) if err != nil { return err } - fmt.Println(resp) + waitParams := &cloudformation.DescribeStacksInput{ + StackName: aws.String(stack.Name), + } + cfn.WaitUntilStackExists(waitParams) + } else { fmt.Printf("updating stack: %s\n", stack.Name) params := &cloudformation.UpdateStackInput{ @@ -87,12 +92,11 @@ func (stack *Stack) UpsertStack(cfn *cloudformation.CloudFormation) (error) { TemplateBody: aws.String(stack.readTemplatePath()), } - resp, err := cfn.UpdateStack(params) + _, err := cfn.UpdateStack(params) if err != nil { return err } - fmt.Println(resp) } stack.AwaitFinalStatus(cfn) return nil @@ -107,7 +111,7 @@ func (stack *Stack) readTemplatePath() (string) { } // AwaitFinalStatus waits for the stack to arrive in a final status -func (stack *Stack) AwaitFinalStatus(cfn *cloudformation.CloudFormation) (string) { +func (stack *Stack) AwaitFinalStatus(cfn cloudformationiface.CloudFormationAPI) (string) { params := &cloudformation.DescribeStacksInput{ StackName: aws.String(stack.Name), } @@ -115,37 +119,34 @@ func (stack *Stack) AwaitFinalStatus(cfn *cloudformation.CloudFormation) (string if err == nil && resp != nil && len(resp.Stacks) == 1 { switch *resp.Stacks[0].StackStatus { - case cloudformation.StackStatusCreateFailed: - case cloudformation.StackStatusCreateComplete: - case cloudformation.StackStatusRollbackFailed: - case cloudformation.StackStatusRollbackComplete: - case cloudformation.StackStatusDeleteFailed: - case cloudformation.StackStatusDeleteComplete: - case cloudformation.StackStatusUpdateComplete: - case cloudformation.StackStatusUpdateRollbackFailed: - case cloudformation.StackStatusUpdateRollbackComplete: - break; - - case cloudformation.StackStatusReviewInProgress: - case cloudformation.StackStatusCreateInProgress: - case cloudformation.StackStatusRollbackInProgress: - // wait for create - cfn.WaitUntilStackCreateComplete(params) - resp, err = cfn.DescribeStacks(params) - break; - case cloudformation.StackStatusDeleteInProgress: - // wait for delete - cfn.WaitUntilStackDeleteComplete(params) - resp, err = cfn.DescribeStacks(params) - break; - case cloudformation.StackStatusUpdateInProgress: - case cloudformation.StackStatusUpdateRollbackInProgress: - case cloudformation.StackStatusUpdateCompleteCleanupInProgress: - case cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress: - // wait for update - cfn.WaitUntilStackUpdateComplete(params) - resp, err = cfn.DescribeStacks(params) - break; + case cloudformation.StackStatusReviewInProgress, + cloudformation.StackStatusCreateInProgress, + cloudformation.StackStatusRollbackInProgress: + // wait for create + cfn.WaitUntilStackCreateComplete(params) + resp, err = cfn.DescribeStacks(params) + case cloudformation.StackStatusDeleteInProgress: + // wait for delete + cfn.WaitUntilStackDeleteComplete(params) + resp, err = cfn.DescribeStacks(params) + case cloudformation.StackStatusUpdateInProgress, + cloudformation.StackStatusUpdateRollbackInProgress, + cloudformation.StackStatusUpdateCompleteCleanupInProgress, + cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress: + // wait for update + cfn.WaitUntilStackUpdateComplete(params) + resp, err = cfn.DescribeStacks(params) + case cloudformation.StackStatusCreateFailed, + cloudformation.StackStatusCreateComplete, + cloudformation.StackStatusRollbackFailed, + cloudformation.StackStatusRollbackComplete, + cloudformation.StackStatusDeleteFailed, + cloudformation.StackStatusDeleteComplete, + cloudformation.StackStatusUpdateComplete, + cloudformation.StackStatusUpdateRollbackFailed, + cloudformation.StackStatusUpdateRollbackComplete: + // no op + } return *resp.Stacks[0].StackStatus } diff --git a/common/stack_test.go b/common/stack_test.go index 25d328fa..83353775 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -3,14 +3,196 @@ package common import ( "testing" "github.com/stretchr/testify/assert" - + "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" + "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/aws" + "errors" ) +type mockedCloudFormation struct { + cloudformationiface.CloudFormationAPI + responses []*cloudformation.DescribeStacksOutput + responseIndex int + createIndex int + updateIndex int + waitUntilStackExists int + waitUntilStackCreateComplete int + waitUntilStackUpdateComplete int +} + +func (c *mockedCloudFormation) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) { + if(c.responseIndex >= len(c.responses)) { + return nil, errors.New("stack not found") + } + + resp := c.responses[c.responseIndex] + c.responseIndex = c.responseIndex + 1 + if resp == nil { + return nil, errors.New("stack not found") + } + return resp, nil +} + +func (c *mockedCloudFormation) WaitUntilStackCreateComplete(*cloudformation.DescribeStacksInput) error { + c.waitUntilStackCreateComplete = c.waitUntilStackCreateComplete + 1 + return nil +} +func (c *mockedCloudFormation) WaitUntilStackUpdateComplete(*cloudformation.DescribeStacksInput) error { + c.waitUntilStackUpdateComplete = c.waitUntilStackUpdateComplete + 1 + return nil +} +func (c *mockedCloudFormation) WaitUntilStackExists(*cloudformation.DescribeStacksInput) error { + c.waitUntilStackExists = c.waitUntilStackExists + 1 + return nil +} +func (c *mockedCloudFormation) CreateStack(*cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) { + c.createIndex = c.createIndex + 1 + return nil, nil +} +func (c *mockedCloudFormation) UpdateStack(*cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) { + c.updateIndex = c.updateIndex + 1 + return nil, nil +} + func TestNewStack(t *testing.T) { assert := assert.New(t) - stack := NewStack("foo") assert.NotNil(stack) assert.Equal("foo",stack.Name) } + +func TestStack_WriteTemplate(t *testing.T) { + assert := assert.New(t) + + stack := NewStack("foo") + env := &Environment{} + err := stack.WriteTemplate("environment-template.yml", env) + assert.Nil(err) + + template := stack.readTemplatePath() + assert.NotNil(template) +} + +func TestStack_AwaitFinalStatus_CreateComplete(t *testing.T) { + assert := assert.New(t) + stack := NewStack("foo") + + + cfn := new(mockedCloudFormation) + cfn.responses = []*cloudformation.DescribeStacksOutput{ + &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + &cloudformation.Stack{ + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + }, + }, + }, + } + + finalStatus := stack.AwaitFinalStatus(cfn) + + assert.Equal(cloudformation.StackStatusCreateComplete, finalStatus) + assert.Equal(1, cfn.responseIndex) +} + +func TestStack_AwaitFinalStatus_CreateInProgress(t *testing.T) { + assert := assert.New(t) + stack := NewStack("foo") + + + cfn := new(mockedCloudFormation) + cfn.responses = []*cloudformation.DescribeStacksOutput{ + &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + &cloudformation.Stack{ + StackStatus: aws.String(cloudformation.StackStatusCreateInProgress), + }, + }, + }, + &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + &cloudformation.Stack{ + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + }, + }, + }, + } + + finalStatus := stack.AwaitFinalStatus(cfn) + + assert.Equal(cloudformation.StackStatusCreateComplete, finalStatus) + assert.Equal(2, cfn.responseIndex) + assert.Equal(1, cfn.waitUntilStackCreateComplete) +} + +func TestStack_UpsertStack_Create(t *testing.T) { + assert := assert.New(t) + stack := NewStack("foo") + + cfn := new(mockedCloudFormation) + cfn.responses = []*cloudformation.DescribeStacksOutput{ + nil, + &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + &cloudformation.Stack{ + StackStatus: aws.String(cloudformation.StackStatusCreateInProgress), + }, + }, + }, + &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + &cloudformation.Stack{ + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + }, + }, + }, + } + + err := stack.UpsertStack(cfn) + + assert.Nil(err) + assert.Equal(1, cfn.waitUntilStackExists) + assert.Equal(1, cfn.createIndex) + assert.Equal(1, cfn.waitUntilStackCreateComplete) + assert.Equal(3, cfn.responseIndex) +} + +func TestStack_UpsertStack_Update(t *testing.T) { + assert := assert.New(t) + stack := NewStack("foo") + + cfn := new(mockedCloudFormation) + cfn.responses = []*cloudformation.DescribeStacksOutput{ + &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + &cloudformation.Stack{ + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + }, + }, + }, + &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + &cloudformation.Stack{ + StackStatus: aws.String(cloudformation.StackStatusUpdateInProgress), + }, + }, + }, + &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + &cloudformation.Stack{ + StackStatus: aws.String(cloudformation.StackStatusUpdateComplete), + }, + }, + }, + } + + err := stack.UpsertStack(cfn) + + assert.Nil(err) + assert.Equal(0, cfn.waitUntilStackExists) + assert.Equal(0, cfn.createIndex) + assert.Equal(1, cfn.updateIndex) + assert.Equal(1, cfn.waitUntilStackUpdateComplete) + assert.Equal(3, cfn.responseIndex) +} diff --git a/common/types.go b/common/types.go index c4bbc262..244e41a8 100644 --- a/common/types.go +++ b/common/types.go @@ -1,11 +1,13 @@ package common -import "github.com/aws/aws-sdk-go/service/cloudformation" +import ( + "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" +) // Context defines the context object passed around type Context struct { Config Config - CloudFormation *cloudformation.CloudFormation + CloudFormation cloudformationiface.CloudFormationAPI } // Config defines the structure of the yml file for the mu config From e862b1690fdedade4bf74b26c4f7f262259c3065 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Wed, 11 Jan 2017 00:00:47 -0800 Subject: [PATCH 18/31] add test coverage to environment commands --- cli/environments_test.go | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/cli/environments_test.go b/cli/environments_test.go index 0ea29314..a539da0e 100644 --- a/cli/environments_test.go +++ b/cli/environments_test.go @@ -4,6 +4,11 @@ import ( "testing" "github.com/stretchr/testify/assert" "github.com/stelligent/mu/common" + "github.com/urfave/cli" + "io/ioutil" + "flag" + "bytes" + "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" ) func TestNewEnvironmentsCommand(t *testing.T) { @@ -19,12 +24,18 @@ func TestNewEnvironmentsCommand(t *testing.T) { assert.Equal("env", command.Aliases[0], "Aliases should match") assert.Equal("options for managing environments", command.Usage, "Usage should match") assert.Equal(4, len(command.Subcommands), "Subcommands len should match") + + args := []string { "environment","help" } + err := runCommand(command, args) + assert.Nil(err) } + func TestNewEnvironmentsUpsertCommand(t *testing.T) { assert := assert.New(t) ctx := common.NewContext() command := newEnvironmentsUpsertCommand(ctx) + ctx.CloudFormation = new(mockedCloudFormation) assert.NotNil(command) assert.Equal("upsert", command.Name, "Name should match") @@ -32,6 +43,16 @@ func TestNewEnvironmentsUpsertCommand(t *testing.T) { assert.Equal("up", command.Aliases[0], "Aliases should match") assert.Equal("", command.ArgsUsage, "ArgsUsage should match") assert.NotNil(command.Action) + + args := []string { "upsert" } + err := runCommand(command, args) + assert.NotNil(err) + assert.Equal(1,lastExitCode) + + args = []string { "upsert","fooenv" } + err = runCommand(command, args) + assert.NotNil(err) + assert.Equal(1,lastExitCode) } func TestNewEnvironmentsListCommand(t *testing.T) { @@ -68,3 +89,28 @@ func TestNewEnvironmentsTerminateCommand(t *testing.T) { assert.Equal("", command.ArgsUsage, "ArgsUsage should match") assert.NotNil(command.Action) } + +func runCommand(command *cli.Command, args []string) error { + app := cli.NewApp() + app.Writer = ioutil.Discard + set := flag.NewFlagSet("test", 0) + set.Parse(args) + appContext := cli.NewContext(app, set, nil) + return command.Run(appContext) +} + +var ( + lastExitCode = 0 + fakeOsExiter = func(rc int) { + lastExitCode = rc + } + fakeErrWriter = &bytes.Buffer{} +) + +func init() { + cli.OsExiter = fakeOsExiter + cli.ErrWriter = fakeErrWriter +} +type mockedCloudFormation struct { + cloudformationiface.CloudFormationAPI +} From 9cda14cb74d37ce7a57f88f5b12dc4967c4e2ba1 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Wed, 11 Jan 2017 16:57:50 -0800 Subject: [PATCH 19/31] Add check for "no update" to stacks. Support creating new VPC or targeting existing VPC. --- common/assets.go | 4 +-- common/assets/environment-template.yml | 35 +++++++++++++------- common/stack.go | 30 ++++++++++++++++++ common/stack_test.go | 17 ++++++++++ common/types.go | 10 ++++-- resources/environment.go | 44 +++++++++++++++++++------- 6 files changed, 112 insertions(+), 28 deletions(-) diff --git a/common/assets.go b/common/assets.go index d2b532ca..ebd3689a 100644 --- a/common/assets.go +++ b/common/assets.go @@ -69,7 +69,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x59\xfb\x6f\xdb\x38\xf2\xff\x3d\x7f\xc5\xd4\xdf\x02\xf9\xde\xa2\xf2\x43\x4e\xda\x54\x40\x76\xe1\x38\x6e\x2b\x6c\xd2\x1a\x51\x9a\x02\x0d\x82\x82\xa2\x28\x9b\x88\x44\xea\x48\x2a\x69\x76\xaf\xff\xfb\x81\xd4\xc3\x7a\xd0\xde\xa4\x9b\xc3\xe1\x52\x04\x8d\xc9\xe1\xbc\xe7\x33\x43\xda\x71\x9c\xbd\xd9\x97\xe0\x92\xa4\x59\x82\x14\x79\xc7\x45\x8a\xd4\x15\x11\x92\x72\xe6\xc1\xbe\x3b\x9e\x8c\x9d\xf1\x5b\x67\xfc\x76\x7f\x6f\x89\x04\x4a\x89\x22\x42\x7a\x7b\x00\x3e\x93\x0a\x31\x4c\x2e\x1f\x32\xa2\x3f\x03\x98\xbf\x20\x50\x82\xb2\x95\x59\x38\x25\x12\x0b\x9a\x29\xc3\xaa\xa2\x07\xf5\x90\x11\x50\x1c\x72\x49\x86\x25\x59\x8c\xf2\x44\x79\xa0\xdc\x61\x4a\xb1\xe0\x7b\xe6\x28\x15\x24\x9a\xa3\x0c\x61\xaa\x1e\x9a\x02\x3e\xe6\x69\x48\x44\xfb\xe4\xfe\x64\xbf\x2f\xb1\x20\x04\x1e\x03\x2d\x65\x4b\x2d\x37\x41\x39\xc3\x6b\xa0\x0c\x1e\x78\x2e\x60\x31\x0f\x00\x27\xb9\x54\x86\xe7\x39\xfa\x1e\xd0\x3f\xc8\x5f\xca\x73\x2d\xf2\xce\xd1\x77\x9a\xe6\x29\x30\x9b\xdc\x35\x52\x80\x11\x83\x90\x94\x0a\x90\x68\x8b\x0a\xbf\x93\x87\x8f\x28\x7d\x94\x4f\x4b\x52\x6d\x15\x92\x92\x63\x8a\x14\x81\x7b\xaa\xd6\x70\xcf\xc5\x2d\x11\x1b\x05\x86\x00\x67\x04\xdd\x11\x08\x13\xc4\x6e\xf5\x81\x88\x4a\x14\x26\x04\x82\xe0\x03\x20\x8c\x89\x94\x9d\x68\xec\x6b\x13\xaf\x32\xec\x47\x8f\x51\xc5\xe8\xc1\x63\x50\x6b\x02\x77\x28\xc9\x8d\x52\x34\xcd\xb8\x50\x10\x73\x61\xd6\x0d\xb3\x3d\x80\x65\x1e\x26\x14\x07\x79\xc8\x88\x9a\x7d\x9d\x3c\x9f\x80\xd9\xd7\x09\x48\xc3\x16\x68\x5f\x90\xfb\x9c\x82\xdc\x96\xa0\xae\xdb\xda\x82\xa7\xcf\x29\x78\xba\x43\xf0\x39\x51\x28\x42\x0a\x69\x69\xb3\x2f\x81\xe7\xcd\x13\x9e\x47\x45\x4d\x6b\x11\x9e\xcf\x14\x11\x31\xc2\x65\x76\xd5\x15\xfd\x5e\xf0\x3c\x93\xc5\x22\x80\x03\x67\x28\x24\x49\xf5\x51\xff\x44\x95\x94\x41\x5d\xc7\x73\xce\x62\xba\xca\x85\x61\x3d\xa8\x69\xdb\x28\x51\xfd\x38\x2d\xbc\x68\x6d\x94\x49\xdc\x5a\x0b\x82\x0f\xb3\x24\xe1\xf7\x8f\x51\x68\x96\x2b\x0e\x01\x46\x09\x65\xab\xa7\x2a\xd5\x81\x99\xd6\x5e\x09\x05\x6d\x47\x19\x3d\x6a\x26\x7d\x0c\xdc\xe2\xab\x0a\xf3\x8a\xd2\xff\xad\x52\xac\x55\xea\xed\xa3\xbf\x93\x07\x7d\x60\x25\x10\x53\x8d\x1a\x85\xff\x2f\x0a\x58\xe7\x03\xe3\x8c\xfc\xa3\xe6\x55\xb9\xcc\xc6\x6c\xee\x9f\x5e\x40\x98\x70\x7c\x6b\xe3\x59\xb3\xb0\x82\x6e\x9b\x53\x49\xd2\x84\xac\x1a\x64\x00\xf3\x9c\xa9\x9a\x5b\x0b\x4a\xdb\x5c\x2a\xa4\xdc\xc9\x65\xce\x59\x44\x75\x18\x8d\xbb\x3f\x20\xd9\xf2\xd6\xe0\x1d\xf3\xbc\x8f\x5c\x0d\x36\x49\x6b\x96\x16\xff\xcc\x51\x22\x07\x1e\x5c\xbf\xb8\x20\x71\xe5\xe1\x57\xb0\xbf\x7f\x53\x70\xe9\x80\xcf\x93\xb8\xf5\x80\x6b\x2b\x5f\xf7\x6f\xf0\x75\x77\xf0\x9d\xfe\x0d\xbe\xd3\x8a\xef\x39\xca\x32\xca\x56\xb2\x84\x89\x0b\xb2\xa2\x9c\x5d\xf2\xd9\xb9\x5f\xb0\xcb\xa5\x43\x90\x54\xce\xa4\xe2\x3e\x3b\xf7\xfd\x53\x0f\x50\x4a\x1d\x37\x9c\x86\xaf\xc7\x07\x93\x8a\xf0\x9e\x48\xe5\xb8\x16\x42\x84\x5f\x1f\xbd\x71\x71\x01\x52\x24\x2f\x08\x6d\x1c\xc7\x53\x77\x7a\x14\xbe\x19\x9b\x1d\x94\x39\x8c\x0b\xb5\xde\x2a\x3f\x0e\xdd\x78\xe2\xbe\x3d\xac\xa8\x25\xcf\x4b\x6a\x9b\x12\x07\xd3\xc3\x83\x37\x13\x77\xdc\xd2\xd6\xc6\x36\x8c\xc9\xf8\xed\x61\x14\xf7\xd9\xda\xa8\xf1\x9b\xa3\xf8\x60\x8a\x0e\x2a\xdb\x30\x61\x4a\xa0\xc4\x4a\x4b\x26\xe4\x75\x7c\x74\x14\xed\x5d\x10\xc9\x73\x81\x89\x71\xfb\x02\xcb\x79\x91\xf8\xcd\xce\x60\x30\x7b\x31\x37\xc0\x5d\x8d\x03\x8b\x79\xa0\x11\xae\x04\x38\x03\xd4\xbd\x23\x0d\x82\xd6\x07\x43\x5d\x76\x89\x8c\xb0\x48\x7e\x62\x1e\x5c\xdf\x14\x90\x26\x78\x46\x84\xa2\xa4\x46\xb3\xab\xe5\xfc\x2b\x67\xc4\x8f\x08\x53\x34\xa6\x95\x6a\x3a\xb9\x74\x6e\xf9\xf1\xa6\x94\x1d\x4b\x25\x35\x36\x5f\xf8\xa6\x6b\x5d\x99\x26\x66\x2d\x9d\x9b\x26\xb5\xde\x37\x76\x7c\xe4\xe6\xc8\x53\xc4\xba\x5b\xc5\xde\xd8\x4a\xeb\xd9\xe4\x4e\x9f\x62\xee\xf4\x31\x62\xcf\x4c\x6f\x68\x35\x30\x03\x77\xc5\x81\x39\x67\x0a\x51\x46\x44\xd5\x53\x64\x05\xb3\x94\x19\x98\xad\x67\xe0\x0d\xf2\x16\x27\x9b\x7d\xac\x8f\xf1\x05\x8d\xad\x0f\xce\x05\x31\x4a\x2c\x79\x42\x71\xdd\x0c\xaa\x2c\x0e\xe8\x8a\xa1\x46\x47\xbe\xa4\x29\xe1\xb9\xf2\x60\x79\x39\x39\x3c\x37\xcb\x9f\xb3\x08\x29\xd2\x3e\xde\x48\xce\x0b\x9e\xe8\xff\x0a\xaa\x0d\xa3\x73\xca\x6a\x13\x7d\x16\x10\x71\x47\x71\xcb\x3a\x63\xdf\x09\x52\x78\xdd\xb5\x5b\xf7\xe9\x5c\x12\xad\x4a\x53\x0f\xfd\xf3\x05\x51\xf5\x89\xb5\x95\x97\x1e\xec\x2b\x91\x13\x7d\xbc\xef\xde\xdd\x45\x66\x09\x96\xa1\x6f\x4e\x60\xc6\x5e\xfb\x14\x46\xd5\xc6\x60\x6c\x98\x34\xe7\x12\xcc\xd3\x14\xb1\xa8\x35\xab\x00\x8c\x27\xdf\x50\x14\x7d\xab\xfa\xe4\x37\xc5\xbf\xe1\x26\x84\xf4\xce\x7b\xf0\x22\xc8\x43\xf8\x57\x67\x17\xe0\xff\x5e\x8c\x42\xca\x46\x21\x92\xeb\xde\x1e\xc1\x6b\xae\x31\xe7\xdb\xfc\xec\x73\x70\xb9\xb8\x38\x7e\xf9\xe7\x06\xab\x7e\x00\xfc\xfa\x2b\x8c\x88\xc2\x23\x82\xa5\xfe\x1d\x16\xda\x37\xd8\xc4\x34\x21\x1d\xcd\x07\xe6\x04\x8e\x99\xfe\x75\xd6\x79\x66\x4e\x0d\xfa\x6a\x33\x45\x98\xda\xaa\xf6\x75\x8a\x28\xbb\xe9\x2d\x4b\x85\xf0\xed\xf1\xcb\x3f\x8d\xab\x03\xfd\xc1\x8f\x7e\xf4\xa8\x84\x69\x72\x15\x59\xd1\xf2\xba\x54\x29\x8f\x74\x3e\x8d\xc7\xe3\x83\xf1\x78\xbf\xb3\xc9\xef\x19\x11\x1e\x08\xce\x55\x67\x67\x65\x30\xb9\xbf\xb3\x31\x7b\xcd\xf9\xad\x1c\x46\xc6\x7c\x94\x2b\xee\x08\x92\x70\x14\x11\xf1\x93\x8e\xe8\xf1\x71\xb4\x84\xbe\x6b\x94\xa0\xab\x15\x11\xf2\x38\xe3\x52\x0d\x73\x53\x69\x3d\xa2\x0c\xa9\xf5\x71\xdd\x9c\x86\xfd\x4a\x18\x56\x49\x3d\xdc\x9a\xcd\x3d\xa6\x08\xeb\xcd\xe3\x11\xcf\xd4\x08\xdd\x4b\x93\x6f\x5a\x6b\xca\xa8\x02\xe7\x0e\x1c\xc7\x84\x0d\x9a\x61\xd3\x68\xf7\x03\x1c\x47\x94\xba\x58\x8a\xd2\xec\xea\xd0\xc1\xce\x40\x02\x88\x9c\x21\x79\xdc\x09\x89\x2c\xc0\xa4\x93\x9d\xf2\x41\xde\xd1\x56\x45\x96\x51\x28\x72\xb5\xbb\x0c\x40\x98\xbe\x38\x47\x0d\xf4\xe8\xee\xcb\x5c\x90\x8b\x9c\x31\x0d\x15\xdb\xa8\x2c\x75\x02\xc5\x20\x67\xaf\x96\x9d\x94\x7f\x91\x60\x5b\x1a\xbe\x9f\xa2\x15\xf1\x35\x4e\xbc\xa3\x2c\xf2\xd9\x39\xca\xe0\xba\x33\x11\xbe\x2a\x1a\xc4\xa0\xe1\xed\xc1\xab\x62\xbe\x81\x2a\xe1\x02\x82\x73\x41\xd5\x43\x79\x8b\x84\xeb\xe2\xcc\x07\x2e\x55\xf0\xbe\xa6\x6a\x5d\x96\x0a\x0a\xcb\x9d\xd0\x47\x69\xb5\xba\x14\x5c\x3b\xa9\xa4\x5d\xcc\xdd\xce\x46\xe7\x12\x05\x2f\xfc\x18\xae\x1b\x17\x85\x52\xf5\xf6\xa7\x41\xb3\xf3\x0e\x2a\xdd\x3e\x4b\x22\x4e\x1b\xb0\x0d\x66\x04\x38\x41\x92\xbc\x3e\x68\xc6\xc8\x52\x90\x0d\x30\x05\xe7\x7b\xbb\xbc\x1e\xf2\xb4\xb8\xd7\x24\x09\x38\x0f\x80\xee\xa5\xa3\x23\x14\x72\xae\xa4\x12\x28\x6b\x11\xff\x57\x6a\xa5\x27\x54\x9a\xd6\x08\x0e\x81\x97\xbf\x3d\x4e\xb2\x65\x40\xdd\x21\xba\x1f\xc6\x5e\xa3\xf5\x67\xe7\x1a\x55\xfa\xb1\xee\x67\xf0\x12\xa9\xb5\x07\x83\x51\x55\x1d\x17\xbc\x51\x54\x4e\x9d\x38\x7a\xb9\x90\xad\xff\xb2\x0b\x2c\x69\x6c\x52\x66\x52\xe6\x29\xd1\x04\xc5\x30\x73\xca\x71\x9e\x6a\x80\xae\x5d\x19\x28\xa4\x48\x7b\xc9\x81\x45\x1c\x13\xac\x3c\x68\xbe\x64\x14\x02\x28\xc3\x34\x43\x49\xbb\xfa\xab\x51\x67\xaf\x5d\xe4\x04\xbb\x43\x94\xa2\x3f\x38\x43\xf7\xba\xdd\xa6\x8d\xfd\x99\x41\xd9\xf6\x93\x86\x54\xd2\xdb\x28\xbc\xc5\x4f\xc6\x0e\xda\x74\x55\x61\x59\x51\x48\x04\x4b\xa7\xc4\xca\xcd\x64\xb5\xc5\x72\xab\xed\xbb\xac\xb7\x69\x5d\xd8\x29\x3d\x33\x72\x92\xcd\xdd\xa7\xbb\xaf\xd3\x48\x6f\xf5\x92\xdd\x42\x7b\x4a\xc4\x53\xa8\xa9\xc4\xfc\x8e\x88\x25\x4f\x92\x05\x8b\x32\x4e\x99\xb2\x90\x05\x79\x98\x52\xf5\x4b\x6f\x47\x78\xfd\x35\xe9\x69\x66\xad\xe5\xaa\xcb\x7a\x30\xf8\x45\x87\xa2\x40\x48\xcb\x0d\xd0\xf5\xbc\x16\xa8\x6e\xbb\xb1\x99\xd7\xd9\x22\xcd\xaf\x96\xf3\x72\xd5\x9c\x68\xbd\x2c\x2e\xe6\x81\x91\x55\x23\x35\x6c\xb8\x76\xe0\xdb\x67\x2b\x41\x64\x23\x31\xfc\x6c\x29\xb8\xe2\x98\x27\x1e\x28\xbc\x81\xac\x77\x82\xa7\x4b\x2e\xcc\x63\xb8\xbb\x69\x6f\x97\xdc\xb2\x38\xa7\x91\xf0\xb3\x52\xd1\xc6\xdb\xde\x22\x09\x9f\xc3\xfc\xfe\xf5\xcb\xec\xdc\x6c\xf7\xc7\xd9\xc9\x7f\xc8\x15\x47\x63\x8b\x2b\x9a\x8b\x95\x2b\xc6\x43\xf3\x6f\x34\xd6\x6e\x38\x3b\x71\x75\x78\x2e\x72\x0b\x38\xf5\xbd\x51\xea\xb5\xad\xa9\x5b\x95\x6c\xa8\x58\x2b\x53\xeb\xf7\xfa\xf0\x70\x7a\x58\xad\x06\xc5\x0d\xa9\x25\x50\x3b\xf9\x3d\x51\x33\xa5\x8a\x90\x0d\xcb\xe5\xa6\x83\x9b\x44\x45\x5e\x37\xa8\xf4\x82\xbb\x38\x3b\xf9\x5f\xb0\xb0\xa7\xbc\xd5\xc4\xae\x1f\x16\x58\x2e\x92\xb0\x6f\x5b\x82\xa4\xa2\xf8\x8c\xa3\xe8\x04\x25\x88\x61\xca\x56\x57\xae\xe7\x6d\x16\x4a\xa0\xeb\x9b\x59\x3c\x1e\xc8\x9f\x7f\x84\xb1\x3c\xbb\x3c\xe2\x19\xe2\xe9\xaf\x2e\x96\x67\x96\xe7\x92\x33\xdd\x2d\xa7\xb7\x6d\x91\xd3\x19\x4e\x3b\xb3\x81\x8e\x62\x1d\xbd\x33\xdd\x2e\x98\xed\x49\x6e\x5b\x14\xcb\x03\x5b\x22\xd8\x0c\xf2\x4c\xb0\x6a\x90\x35\xb2\x4a\x92\xf2\xdb\x9b\xa2\x25\x36\xb4\x2b\x84\xc7\x5c\xdc\x23\x11\x6d\x10\x05\x89\x15\x51\xc6\x92\x2e\xbf\x92\x51\x83\xa2\x6e\xf5\x1d\x0c\xda\x14\xcf\x87\xcb\xcb\x65\x6d\x7c\x9f\xc1\xa3\xdd\xd0\x15\x6a\x99\xd3\x2a\x25\x76\xa8\x01\x96\x86\xf6\x29\x57\x59\x5e\xd4\x80\x1e\xc6\x3f\x8b\x72\x66\x32\x01\x2e\xef\xc7\x6b\xa5\x32\x6f\x34\x32\xef\x14\x8b\x24\x1c\x9e\x7e\x0c\xcc\x8c\x3a\xda\x83\xee\x37\x6c\x1a\xf7\x3f\x5f\x9c\xf5\x02\xae\x9d\xd9\xe2\xbb\xf1\x6b\x2b\xc4\x2d\x66\x33\xc1\xaa\x6f\xeb\x34\xdf\x8a\xb0\xf8\x16\x75\xf1\x5d\xb7\xa4\xca\xfc\xf2\x7e\xa2\xb5\xed\x8d\xd2\x4e\x4f\x15\xcb\xf3\x70\x47\xaf\xe6\x84\xb4\xf5\x2b\xc4\xc6\x97\x2b\x3f\xa3\x53\x25\x63\xef\xdf\x01\x00\x00\xff\xff\x5b\xb0\x49\x10\x22\x20\x00\x00") +var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x59\x7b\x6f\xdb\x38\x12\xff\x3f\x9f\x62\xea\x2b\xe0\xed\xa2\xf2\x43\x4e\xda\x54\x40\x76\xe1\x38\x6e\x6b\xac\xdd\x1a\x51\xda\x02\x4d\x8b\x82\xa2\x28\x9b\xa8\x44\xea\x48\x2a\x8f\xed\xf5\xbb\x1f\x48\x4a\xb2\x5e\xce\x26\xbb\x3d\xec\x1d\x70\x2e\x82\xda\xe4\x70\xde\xf3\x9b\xa1\xe4\x38\xce\xc1\xf4\x83\x7f\x41\x92\x34\x46\x8a\xbc\xe4\x22\x41\xea\x3d\x11\x92\x72\xe6\x41\xdf\x1d\x8d\x47\xce\xe8\x85\x33\x7a\xd1\x3f\x58\x23\x81\x12\xa2\x88\x90\xde\x01\xc0\x82\x49\x85\x18\x26\x17\xb7\x29\xd1\xbf\x01\xcc\x37\xf0\x95\xa0\x6c\x63\x16\xce\x88\xc4\x82\xa6\xca\xb0\x2a\xe8\x41\xdd\xa6\x04\x14\x87\x4c\x92\x41\x4e\x16\xa1\x2c\x56\x1e\x28\x77\x90\x50\x2c\xf8\x81\x39\x4a\x05\x09\x67\x28\x45\x98\xaa\xdb\xaa\x80\x37\x59\x12\x10\x51\x3f\xd9\x1f\xf7\xdb\x12\x2d\x21\xf0\x08\x68\x2e\x5b\x6a\xb9\x31\xca\x18\xde\x02\x65\x70\xcb\x33\x01\xf3\x99\x0f\x38\xce\xa4\x32\x3c\x57\xe8\xc6\xa7\xbf\x93\x3f\x94\xe7\x76\xc8\x5b\xa1\x1b\x9a\x64\x09\xb0\x2e\xb9\x5b\xa4\x00\x23\x06\x01\xc9\x15\x20\xe1\x1e\x15\x7e\x23\xb7\x6f\x50\x72\x2f\x9f\xe6\xa4\xda\x2a\x24\x25\xc7\x14\x29\x02\xd7\x54\x6d\xe1\x9a\x8b\xaf\x44\xec\x14\x18\x00\x2c\x09\xba\x22\x10\xc4\x88\x7d\xd5\x07\x42\x2a\x51\x10\x13\xf0\xfd\xd7\x80\x30\x26\x52\x36\xa2\xd1\xd7\x26\xfa\x72\x3b\x8d\x63\x7e\xed\xb5\x85\xfb\x59\xc0\x88\x82\x48\xf0\x04\xae\xb7\x14\x6f\x8d\x1a\x9a\xb8\xc5\xb3\x65\xc5\x8a\xb2\x25\x61\x1b\xb5\xf5\xa0\xff\xc2\xba\x72\x85\x6e\xca\xa5\xf1\x71\xbf\xae\xcb\x68\x60\xfe\x0d\x47\x66\xd9\x68\x44\xc2\x35\x52\x8a\x08\xe6\x41\xef\xa7\x4f\x9f\xc2\x6f\xe3\xa7\x93\xef\x4f\x3e\x7d\x1a\xdc\xe7\xc7\x30\xff\xea\x7e\x7f\xd2\x33\x2c\x67\x9c\x49\x25\x10\x65\xaa\x66\x63\x3f\xc9\xa4\xd2\x31\x43\x70\x85\x62\x1a\xc2\x6c\x71\x76\x0e\x41\xcc\xf1\x57\x0f\x6e\x06\xe6\xdf\xf0\x66\xa0\xb5\x7d\x9f\xe2\x45\x78\x9f\xa0\x99\x88\xf1\x08\xd4\x96\x68\xa6\x99\x09\x1f\x4d\x52\x2e\x14\x44\x5c\x98\x75\xc3\xec\x00\x60\x9d\x05\x31\xc5\xd6\xd3\xd3\x8f\xe3\x1f\x27\x60\xfa\x71\x0c\xd2\x06\x90\xb6\x05\xb9\x3f\x52\x90\x5b\x13\xd4\x4c\xb0\xba\xe0\xc9\x8f\x14\x3c\xb9\x43\xf0\x8a\x28\x14\x22\x85\xb4\xb4\xe9\x07\xdf\xf3\x66\x31\xcf\x42\x8b\x7e\x5a\x84\xb7\x60\x8a\x88\x08\xe1\xbc\x0e\x4b\xec\x7b\x25\x78\x96\x4a\xbb\x08\xe0\xc0\x12\x05\x24\x2e\x7e\xea\x4f\x58\x48\xe9\x95\x88\x37\xe3\x2c\xa2\x9b\x4c\x18\xd6\xbd\x92\xb6\x8e\xa7\xc5\xc7\xa9\x21\x6b\x6d\x23\x2f\xf7\xda\x5a\x51\xa0\xf7\x51\x68\x9a\x29\x0e\x3e\x46\x31\x65\x9b\x87\x2a\xd5\x00\xe4\xda\x5e\x0e\x9a\x75\x47\x19\x3d\x4a\x26\xed\x6e\xb1\xc7\x57\x45\x77\xb0\x20\xf9\x6b\xa1\x58\x0d\x14\xeb\x47\x7f\x23\xb7\xfa\xc0\x46\x20\xa6\x2a\xc8\x03\x3f\x59\xa8\xd3\xf9\xc0\x38\x23\x4f\x4a\x5e\x75\x4c\xab\x33\xdb\xd5\x77\x17\xcf\x92\x45\x67\x7b\xaa\x73\xca\x49\xaa\xe0\x5e\xc2\x31\x60\x9e\x31\x55\x72\xab\x35\x9d\x3a\x97\xa2\xa7\xdc\xc9\x65\xc6\x59\x48\x75\x18\x8d\xbb\x5f\x23\x59\xf3\x56\xef\x25\xf3\xbc\x37\x5c\xf5\x76\x49\x6b\x96\xe6\xff\xcc\x50\x2c\x7b\x1e\x5c\x3e\x3a\x27\x51\xe1\xe1\xa7\xd0\xef\x7f\xb6\x5c\x1a\xe0\xf3\x20\x6e\x2d\xe0\xda\xcb\xd7\xfd\x0b\x7c\xdd\x3b\xf8\x4e\xfe\x02\xdf\x49\xc1\x77\x85\xd2\x94\xb2\x8d\xcc\x61\xe2\x9c\x6c\x28\x67\x17\x7c\xba\x5a\x58\x76\x99\x74\x08\x92\xca\x19\x17\xdc\xa7\xab\xc5\xe2\xcc\x03\x94\x50\xc7\x0d\x26\xc1\xb3\xd1\xe1\xb8\x20\xbc\x26\x52\x39\x6e\x07\x21\xc2\xcf\x8e\x9f\xbb\xd8\x82\x14\xc9\x2c\x61\x17\xc7\xd1\xc4\x9d\x1c\x07\xcf\x6d\x13\x44\xa9\xc3\xb8\x50\xdb\xbd\xf2\xa3\xc0\x8d\xc6\xee\x8b\xa3\x82\x5a\xf2\x2c\xa7\xee\x52\xe2\x70\x72\x74\xf8\x7c\xec\x8e\x6a\xda\x76\xb1\x0d\x22\x32\x7a\x71\x14\x46\x6d\xb6\x5d\xd4\xf8\xf9\x71\x74\x38\x41\x87\x85\x6d\x98\x30\x25\x50\xdc\x49\x4b\xc6\xe4\x59\x74\x7c\x1c\x1e\x9c\x13\xc9\x33\x81\x89\x71\xfb\x1c\xcb\x99\x4d\xfc\x6a\x67\x30\x98\x3d\x9f\x19\xe0\x2e\x06\xa7\xf9\xcc\xd7\x08\x97\x03\x9c\x01\xea\xd6\x91\x0a\x41\xed\x87\xa1\xce\xbb\x44\x4a\x58\x28\xdf\x32\x0f\x2e\x3f\x5b\x48\x13\x3c\x25\x42\x51\x52\xa2\xd9\xfb\xf5\xec\x23\x67\x64\x11\x12\xa6\x68\x44\x0b\xd5\x74\x72\xe9\xdc\x5a\x44\xbb\x52\x76\x3a\x2a\xa9\xb2\x69\xc8\x4d\xe3\x7a\xaf\xfb\x98\x07\x8f\xfc\x2c\x80\xc7\xdf\x5a\xf5\xf3\xbd\x72\xc8\x64\xac\x31\xe7\x0d\x37\xc7\x1e\x22\xdd\x7d\xb0\x74\xf7\x07\x4a\x9f\x3c\x58\xfa\xe4\x7e\xd2\x97\xa6\x5f\xd4\x9a\x9a\x81\x40\x7b\x60\xc6\x99\x42\x94\x11\x51\xf4\x19\x59\x40\x2f\x65\x06\x7a\xcb\x1b\xc4\x0e\x8d\xed\xc9\x6a\x6f\x6b\xe3\xbe\xa5\xe9\xea\x8d\x33\x41\x8c\x12\x6b\x1e\x53\x5c\x36\x88\x22\xb3\x7d\xba\x61\xa8\xd2\xa5\x2f\x68\x42\x78\xa6\x3c\x58\x5f\x8c\x8f\x56\x66\xf9\x5d\x1a\x22\x45\xea\xc7\x2b\x09\x7b\xce\x63\xfd\x9f\xa5\xda\x31\x5a\x51\x56\x9a\xb8\x60\x3e\x11\x57\x14\xd7\xac\x33\xf6\x9d\x22\x85\xb7\x4d\xbb\x75\xef\xce\x24\xd1\xaa\x54\xf5\xd0\x9f\x0f\x88\xaa\xb7\xac\xae\xbc\xf4\xa0\xaf\x44\x46\xf4\xf1\xb6\x7b\xef\x2e\xbc\x8e\x60\xd9\x3b\x40\x65\x2a\x33\xf6\x76\x4f\x66\x54\xed\x0c\xc6\x86\x49\x75\x56\xc1\x3c\x49\x10\x0b\x6b\xf3\x0b\xc0\x68\xfc\x05\x85\xe1\x97\xa2\x77\x7e\x51\xfc\x0b\xae\xc2\x4a\xeb\x7c\x9e\x8e\xff\x6a\xec\x02\xfc\xe3\xd1\x30\xa0\x6c\x18\x20\xb9\x6d\xed\x11\xbc\xe5\x1a\x87\xbe\xcc\x96\xef\xfc\x8b\xf9\xf9\xc9\xe3\x6f\x3b\xfc\xfa\x0e\xf0\xcb\x2f\x30\x24\x0a\x0f\x09\x96\xfa\x6f\x60\xb5\xaf\xb0\x89\x68\x4c\x1a\x9a\xf7\xcc\x09\x1c\x31\xfd\xe7\x6c\xb3\xd4\x9c\xea\xb5\xd5\x66\x8a\x30\xb5\x57\xed\xcb\x04\x51\xf6\xb9\xb5\x2c\x15\xc2\x5f\x4f\x1e\x7f\x33\xae\xf6\xf5\x8f\x6a\xbd\x15\x1f\x61\x1a\x5f\x41\x66\xdb\x60\x93\x2a\xe1\xa1\xce\xa7\xd1\x68\x74\x38\x1a\xf5\x1b\x9b\xfc\x9a\x11\xe1\x81\xe0\x5c\x35\x76\x36\x06\xa7\xdb\x3b\x3b\xb3\xb7\x9c\x7f\x95\x83\xd0\x98\x8f\x32\xc5\x1d\x41\x62\x8e\x42\x22\xfe\xa4\x23\x5a\x7c\x1c\x2d\xa1\xed\x1a\x25\xe8\x66\x43\x84\x3c\x49\xb9\x54\x83\xcc\x54\x5a\x8b\x28\x45\x6a\x7b\x52\x36\xac\x41\xbb\x12\x06\x45\x52\x0f\xf6\x66\x73\x8b\x29\xc2\x7a\xf3\x64\xc8\x53\x35\x44\xd7\xd2\xe4\x9b\xd6\x9a\x32\xaa\xc0\xb9\x02\xc7\x31\x61\x83\x6a\xd8\x34\xda\x7d\x07\xc7\x11\xb9\x2e\x1d\x45\x69\x76\x75\xe8\xe0\xce\x40\x02\x88\x8c\x21\x79\xd2\x08\x89\xb4\x60\xd2\xc8\x4e\x79\x2b\xaf\x68\xad\x22\xf3\x28\xd8\x5c\x6d\x2e\x03\x10\x86\x82\x98\x84\x15\xf4\x68\xee\xcb\x4c\x90\xf3\x8c\x31\x0d\x15\xfb\xa8\x3a\xea\x04\xec\x70\xd7\x5d\x2d\x77\x52\xfe\x41\x82\xed\x19\x02\x16\x09\xda\x90\x85\xc6\x89\x97\x94\x85\x0b\xb6\x42\x29\x5c\x36\xa6\xc4\xa7\xb6\x41\xf4\x2a\xde\xee\x3d\xb5\x33\x0f\x14\x09\xe7\x13\x9c\x09\xaa\x6e\xf3\x9b\x25\x5c\xda\x33\xaf\xb9\x54\xfe\xab\x92\xaa\x76\x81\xb2\x14\x1d\xf7\xc4\x05\x4a\x8a\xd5\xb5\xe0\xda\x49\x39\xed\x7c\xe6\x36\x36\x1a\x17\x2b\x78\xb4\x88\xe0\xb2\x72\x79\xc8\x55\xaf\xff\xea\x55\x3b\x6f\xaf\xd0\xed\x9d\x24\xe2\xac\x02\xdb\x60\x5a\xfb\x29\x92\xe4\xd9\x61\x35\x46\x1d\x05\x59\x01\x53\x70\x6e\xea\xe5\x75\x9b\x25\xf6\xae\x13\xc7\xe0\xdc\x02\xba\x96\x8e\x8e\x50\xc0\xb9\x92\x4a\xa0\xb4\x46\xfc\xb7\xd4\x4a\x4b\xa8\x34\xad\x11\x1c\x02\x8f\x7f\xbd\x9f\xe4\x8e\xa1\xf5\x0e\xd1\xed\x30\xb6\x1a\xed\x62\xba\xd2\xa8\xd2\x8e\x75\x3b\x83\xd7\x48\x6d\x3d\xe8\x0d\x8b\xea\x38\xe7\x95\xa2\x72\xca\xc4\xd1\xcb\x56\xb6\xfe\xd6\x2d\x30\xa7\xe9\x92\x32\x95\x32\x4b\x88\x26\xb0\xc3\xcc\x19\xc7\x59\xa2\x01\xba\x74\xa5\xaf\x90\x22\xf5\x25\x07\xe6\x51\x44\xb0\xf2\xa0\xfa\x74\xc3\x0a\xa0\x0c\xd3\x14\xc5\xf5\xea\x2f\x46\x9d\x83\x7a\x91\x13\xec\x0e\x50\x82\x7e\xe7\x0c\x5d\xeb\x76\x9b\x54\xf6\xa7\x06\x65\xeb\x8f\x39\xa4\x92\xde\x4e\xe1\x3d\x7e\x32\x76\xd0\xaa\xab\xac\x65\xb6\x90\x08\x96\x4e\x8e\x95\xbb\xc9\x6a\x8f\xe5\x9d\xb6\xdf\x65\x7d\x97\xd6\xd6\x4e\xe9\x99\x91\x93\xec\xee\x43\xcd\x7d\x9d\x46\x7a\xab\x95\xec\x1d\xb4\x67\x44\x3c\x84\x9a\x4a\xcc\xaf\x88\x58\xf3\x38\x9e\xb3\x30\xe5\x94\xa9\x0e\x32\x3f\x0b\x12\xaa\x7e\x6e\xed\x08\xaf\xbd\x26\x3d\xcd\xac\xb6\x5c\x74\x59\x0f\x7a\x3f\xeb\x50\x58\x84\xec\xb8\x15\xba\x9e\x57\x03\xd5\x7d\xb7\xb8\xdd\x13\x5b\xc8\x31\xab\xeb\x3a\x62\xc8\x8a\xba\x37\xfc\x6a\xcf\x22\xe7\x33\xdf\x68\x52\xe2\x38\xec\x64\x36\xc0\x7d\xc1\x36\x82\xc8\x4a\xda\x2c\xd2\xb5\xe0\x8a\x63\x1e\x7b\xa0\xf0\x0e\xd0\x5e\x0a\x9e\xac\xb9\x30\x2f\x1a\xdc\x5d\xf3\xbb\xe0\x1d\x8b\x33\x1a\x8a\x45\x9a\xc3\x7c\xe5\x69\xe0\x3c\x0e\xfe\x0b\x9c\xb3\x3c\xfd\x0f\xf9\xe5\x78\xd4\xe1\x97\xea\x62\xe1\x97\xea\xab\x83\xf9\xf2\xd4\xd5\xb1\x3a\xcf\x3a\x70\xac\xed\x9a\x5c\xaf\x7d\xfd\xbf\x53\xc9\x8a\x8a\xa5\x32\xa5\x7e\xcf\x8e\x8e\x26\x47\xc5\xaa\x6f\x2f\x53\x35\x81\x7a\x9a\x78\x45\xd4\x54\x29\x1b\xbf\x41\xbe\x5c\x75\x70\x95\xc8\x96\x40\x85\x4a\x2f\xb8\xf3\xe5\xe9\xff\x82\x85\x2d\xe5\x3b\x4d\x6c\xfa\x61\x8e\xe5\x3c\x0e\xda\xb6\xc5\x48\x2a\x8a\x97\x1c\x85\xa7\x28\x46\x0c\x53\xb6\x79\xef\x7a\xde\x6e\x21\xc7\xc4\xb6\x99\xf6\x49\x83\xfc\xff\x33\x9c\xbf\xfb\x19\x4e\x63\x12\x6e\x0c\x22\x3a\x0f\xca\xf8\x2f\x75\x6f\x62\x5d\xcf\x04\xf7\xe5\x41\x7e\x60\x4f\x0e\x54\xd3\x64\x2a\x58\x31\x35\x1b\x59\x39\x49\xfe\xfa\xc8\xf6\xdf\x8a\x76\x56\x78\xc4\xc5\x35\x12\xe1\x0e\x93\x90\xd8\x10\x65\x2c\x69\xf2\xcb\x19\x55\x28\xca\xb9\xa2\x81\x62\xbb\xf2\x7b\x7d\x71\xb1\x2e\x8d\x6f\x33\xb8\xb7\x1b\x9a\x42\x3b\x86\xc2\x42\x89\x3b\xd4\x80\x07\x37\x88\xb7\x99\x4a\x33\x5b\x63\xfa\x5e\xf0\x4e\xe4\xe3\x5b\x95\x78\xab\x54\xea\x0d\x87\xe6\x91\xc9\x3c\x0e\x06\x67\x6f\x7c\x33\x2e\x0f\x0f\xa0\xf9\x02\x50\xf7\x95\x77\xe7\xcb\x56\x3a\x68\x57\xd7\xf8\xee\xbc\x5e\x4b\x80\x1a\xb3\xa9\x60\xc5\xcb\x44\xcd\xb7\x20\xb4\xaf\xae\xe7\x37\xda\xa6\xc2\xce\xfc\xaa\x64\x4d\x6b\x4c\xf5\x4e\x4b\x95\x8e\xa7\xd7\x0d\xbd\xaa\xc3\xda\xde\x37\x9c\x95\x77\x3f\x7f\x46\xa7\x42\xc6\xc1\xbf\x03\x00\x00\xff\xff\x75\x16\x3e\x1e\xeb\x21\x00\x00") func assetsEnvironmentTemplateYmlBytes() ([]byte, error) { return bindataRead( @@ -84,7 +84,7 @@ func assetsEnvironmentTemplateYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/environment-template.yml", size: 8226, mode: os.FileMode(420), modTime: time.Unix(1484088447, 0)} + info := bindataFileInfo{name: "assets/environment-template.yml", size: 8683, mode: os.FileMode(420), modTime: time.Unix(1484180215, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/common/assets/environment-template.yml b/common/assets/environment-template.yml index 3d81d657..8a06e8e9 100644 --- a/common/assets/environment-template.yml +++ b/common/assets/environment-template.yml @@ -17,6 +17,14 @@ Parameters: Type: String Description: KeyName to associate with worker instances. Leave blank to disable SSH access. Default: '' + SshAllow: + Description: Subnet from which to allow SSH access. + Type: String + MinLength: '9' + MaxLength: '18' + Default: 0.0.0.0/0 + AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" + ConstraintDescription: 'must be a valid CIDR block: x.x.x.x/x.' VpcId: Type: String Description: Name of the value to import for the VpcId @@ -39,7 +47,7 @@ Metadata: Parameters: - InstanceType - KeyName - - SSHAllow + - SshAllow - Label: default: "Auto Scaling Configuration" Parameters: @@ -50,7 +58,7 @@ Metadata: default: "Instance type to launch?" KeyName: default: "Key to grant SSH access (blank for none)?" - SSHAllow: + SshAllow: default: "CIDR block to grant SSH access?" DesiredCapacity: default: "Desired ECS cluster instance count?" @@ -97,15 +105,15 @@ Resources: VPCZoneIdentifier: - Fn::If: - HasPublicSubnetAZ1 - - !ImportValue [!Ref PublicSubnetAZ1] + - Fn::ImportValue: !Sub ${PublicSubnetAZ1Id} - !Ref AWS::NoValue - Fn::If: - HasPublicSubnetAZ2 - - !ImportValue []!Ref PublicSubnetAZ2] + - Fn::ImportValue: !Sub ${PublicSubnetAZ2Id} - !Ref AWS::NoValue - Fn::If: - HasPublicSubnetAZ3 - - !ImportValue [!Ref PublicSubnetAZ3] + - Fn::ImportValue: !Sub ${PublicSubnetAZ3Id} - !Ref AWS::NoValue LaunchConfigurationName: !Ref ContainerInstances MinSize: '1' @@ -202,17 +210,19 @@ Resources: HostSG: Type: AWS::EC2::SecurityGroup Properties: - VpcId: !Ref VPC + VpcId: + Fn::ImportValue: !Sub ${VpcId} GroupDescription: ECS Host Security Group SecurityGroupIngress: - IpProtocol: tcp FromPort: '22' ToPort: '22' - CidrIp: !Ref SSHAllow + CidrIp: !Ref SshAllow ElbSG: Type: AWS::EC2::SecurityGroup Properties: - VpcId: !ImportValue [!Ref VpcId] + VpcId: + Fn::ImportValue: !Sub ${VpcId} GroupDescription: ELB Security Group SecurityGroupIngress: - IpProtocol: tcp @@ -241,15 +251,15 @@ Resources: Subnets: - Fn::If: - HasPublicSubnetAZ1 - - !Ref PublicSubnetAZ1 + - Fn::ImportValue: !Sub ${PublicSubnetAZ1Id} - !Ref AWS::NoValue - Fn::If: - HasPublicSubnetAZ2 - - !Ref PublicSubnetAZ2 + - Fn::ImportValue: !Sub ${PublicSubnetAZ2Id} - !Ref AWS::NoValue - Fn::If: - HasPublicSubnetAZ3 - - !Ref PublicSubnetAZ3 + - Fn::ImportValue: !Sub ${PublicSubnetAZ3Id} - !Ref AWS::NoValue SecurityGroups: - !Ref ElbSG @@ -267,7 +277,8 @@ Resources: Properties: Port: '8080' Protocol: HTTP - VpcId: !Ref VPC + VpcId: + Fn::ImportValue: !Sub ${VpcId} Outputs: BaseUrl: Value: !Sub http://${EcsElb.DNSName}/ diff --git a/common/stack.go b/common/stack.go index 04058c19..ecc79499 100644 --- a/common/stack.go +++ b/common/stack.go @@ -9,11 +9,13 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/aws/awserr" ) // Stack contains the data about a CloudFormation stack type Stack struct { Name string + Parameters map[string]string TemplatePath string } @@ -21,6 +23,7 @@ type Stack struct { func NewStack(name string) *Stack { return &Stack{ Name: name, + Parameters: make(map[string]string), TemplatePath: fmt.Sprintf("%s/%s.yml",os.TempDir(), name), } } @@ -32,6 +35,12 @@ func newCloudFormation(region string) (cloudformationiface.CloudFormationAPI, er return cloudformation.New(sess, &aws.Config{Region: aws.String(region)}), nil } +// WithParameter apply a parameter to the stack +func (stack *Stack) WithParameter(key string, value string) (*Stack) { + stack.Parameters[key] = value + return stack +} + // WriteTemplate will create a temp file with the template for a CFN stack //go:generate go-bindata -pkg $GOPACKAGE -o assets.go assets/ func (stack *Stack) WriteTemplate(assetName string, data interface{}) (error) { @@ -60,6 +69,18 @@ func (stack *Stack) WriteTemplate(assetName string, data interface{}) (error) { return nil } +func (stack *Stack) buildParameters() ([]*cloudformation.Parameter) { + parameters := make([]*cloudformation.Parameter, 0, len(stack.Parameters)) + for key, value := range stack.Parameters { + parameters = append(parameters, + &cloudformation.Parameter { + ParameterKey: aws.String(key), + ParameterValue: aws.String(value), + }) + } + return parameters +} + // UpsertStack will create/update the cloudformation stack func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) (error) { stackStatus := stack.AwaitFinalStatus(cfn) @@ -70,6 +91,7 @@ func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) (erro Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, + Parameters: stack.buildParameters(), TemplateBody: aws.String(stack.readTemplatePath()), } _, err := cfn.CreateStack(params) @@ -89,11 +111,19 @@ func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) (erro Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, + Parameters: stack.buildParameters(), TemplateBody: aws.String(stack.readTemplatePath()), } _, err := cfn.UpdateStack(params) if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + if awsErr.Code() == "ValidationError" && awsErr.Message() == "No updates are to be performed." { + fmt.Printf("No changes for stack: %s\n", stack.Name) + return nil + } + } + fmt.Println(err) return err } diff --git a/common/stack_test.go b/common/stack_test.go index 83353775..2edcb5eb 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -196,3 +196,20 @@ func TestStack_UpsertStack_Update(t *testing.T) { assert.Equal(1, cfn.waitUntilStackUpdateComplete) assert.Equal(3, cfn.responseIndex) } + + +func TestStack_WithParameter(t *testing.T) { + assert := assert.New(t) + stack := NewStack("foo") + + parameters := stack.buildParameters() + assert.Equal(0, len(parameters)) + + stack.WithParameter("p1","value 1").WithParameter("p2", "value 2") + parameters = stack.buildParameters() + assert.Equal(2, len(parameters)) + assert.Contains(*parameters[0].ParameterKey, "p") + assert.Contains(*parameters[0].ParameterValue, "value") + assert.Contains(*parameters[1].ParameterKey, "p") + assert.Contains(*parameters[1].ParameterValue, "value") +} \ No newline at end of file diff --git a/common/types.go b/common/types.go index 244e41a8..cefc9d47 100644 --- a/common/types.go +++ b/common/types.go @@ -21,12 +21,16 @@ type Config struct { type Environment struct { Name string Loadbalancer struct { - Hostname string + Hostname string } Cluster struct { - DesiredCapacity int `yaml:"desiredCapacity"` - MaxSize int `yaml:"maxSize"` + DesiredCapacity int `yaml:"desiredCapacity"` + MaxSize int `yaml:"maxSize"` } + VpcTarget struct { + VpcID string `yaml:"vpcId"` + PublicSubnetIds []string `yaml:"publicSubnetIds"` + } `yaml:"vpcTarget,omitempty"` } // Service defines the structure of the yml file for a service diff --git a/resources/environment.go b/resources/environment.go index 0c14b633..bec1cd2f 100644 --- a/resources/environment.go +++ b/resources/environment.go @@ -27,9 +27,12 @@ func (environmentManager *environmentManagerImpl) UpsertEnvironment(environmentN return err } - err = environmentManager.upsertVpc(env) - if err != nil { - return err + if env.VpcTarget.VpcID == "" { + // no target VPC, we manage it + err = environmentManager.upsertVpc(env) + if err != nil { + return err + } } err = environmentManager.upsertEcsCluster(env) @@ -55,11 +58,17 @@ func (environmentManager *environmentManagerImpl) getEnvironment(environmentName return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml",environmentName) } +func buildVpcStackName(env *common.Environment) string { + return fmt.Sprintf("mu-vpc-%s", env.Name) +} +func buildEnvironmentStackName(env *common.Environment) string { + return fmt.Sprintf("mu-env-%s", env.Name) +} + + func (environmentManager *environmentManagerImpl) upsertVpc(env *common.Environment) (error) { - cfn := environmentManager.context.CloudFormation - stackName := fmt.Sprintf("mu-vpc-%s", env.Name) // generate the CFN template - stack := common.NewStack(stackName) + stack := common.NewStack(buildVpcStackName(env)) err := stack.WriteTemplate("vpc-template.yml", env) if err != nil { @@ -68,7 +77,7 @@ func (environmentManager *environmentManagerImpl) upsertVpc(env *common.Environm // create/update the stack fmt.Printf("upserting VPC environment:%s stack:%s path:%s\n",env.Name, stack.Name, stack.TemplatePath) - err = stack.UpsertStack(cfn) + err = stack.UpsertStack(environmentManager.context.CloudFormation) if err != nil { return err } @@ -77,10 +86,23 @@ func (environmentManager *environmentManagerImpl) upsertVpc(env *common.Environm } func (environmentManager *environmentManagerImpl) upsertEcsCluster(env *common.Environment) (error) { - cfn := environmentManager.context.CloudFormation - stackName := fmt.Sprintf("mu-env-%s", env.Name) // generate the CFN template - stack := common.NewStack(stackName) + stack := common.NewStack(buildEnvironmentStackName(env)) + + if env.VpcTarget.VpcID == "" { + // apply default parameters since we manage the VPC + vpcStackName := buildVpcStackName(env) + stack.WithParameter("VpcId", fmt.Sprintf("%s-VpcId", vpcStackName)) + stack.WithParameter("PublicSubnetAZ1Id", fmt.Sprintf("%s-PublicSubnetAZ1Id", vpcStackName)) + stack.WithParameter("PublicSubnetAZ2Id", fmt.Sprintf("%s-PublicSubnetAZ2Id", vpcStackName)) + stack.WithParameter("PublicSubnetAZ3Id", fmt.Sprintf("%s-PublicSubnetAZ3Id", vpcStackName)) + } else { + // target VPC referenced from config + stack.WithParameter("VpcId", env.VpcTarget.VpcID) + for index, subnet := range env.VpcTarget.PublicSubnetIds { + stack.WithParameter(fmt.Sprintf("PublicSubnetAZ%dId",index+1), subnet) + } + } err := stack.WriteTemplate("environment-template.yml", env) if err != nil { @@ -89,7 +111,7 @@ func (environmentManager *environmentManagerImpl) upsertEcsCluster(env *common.E // create/update the stack fmt.Printf("upserting environment:%s stack:%s path:%s\n",env.Name, stack.Name, stack.TemplatePath) - err = stack.UpsertStack(cfn) + err = stack.UpsertStack(environmentManager.context.CloudFormation) if err != nil { return err } From fbdfb201e0b15e799c55003cd0c2edf76cac0ae7 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Wed, 11 Jan 2017 16:58:49 -0800 Subject: [PATCH 20/31] go fmt --- cli/app.go | 5 ++- cli/app_test.go | 3 +- cli/environments.go | 46 +++++++++++------------- cli/environments_test.go | 22 ++++++------ cli/pipelines.go | 24 ++++++------- cli/pipelines_test.go | 5 ++- cli/services.go | 58 +++++++++++++++--------------- cli/services_test.go | 5 ++- common/assets.go | 6 ++-- common/config.go | 2 +- common/config_test.go | 30 ++++++++-------- common/context.go | 6 ++-- common/stack.go | 68 +++++++++++++++++------------------ common/stack_test.go | 31 ++++++++-------- common/types.go | 16 ++++----- main.go | 6 ++-- resources/environment.go | 25 ++++++------- resources/environment_test.go | 2 +- 18 files changed, 169 insertions(+), 191 deletions(-) diff --git a/cli/app.go b/cli/app.go index 0211d701..c7ef0285 100644 --- a/cli/app.go +++ b/cli/app.go @@ -1,8 +1,8 @@ package cli import ( - "github.com/urfave/cli" "github.com/stelligent/mu/common" + "github.com/urfave/cli" ) // NewApp creates a new CLI app @@ -28,7 +28,7 @@ func NewApp(version string) *cli.App { app.Flags = []cli.Flag{ cli.StringFlag{ - Name: "config, c", + Name: "config, c", Usage: "path to config file", Value: "mu.yml", }, @@ -36,4 +36,3 @@ func NewApp(version string) *cli.App { return app } - diff --git a/cli/app_test.go b/cli/app_test.go index 23c1d9fc..30d43d7b 100644 --- a/cli/app_test.go +++ b/cli/app_test.go @@ -1,8 +1,8 @@ package cli import ( - "testing" "github.com/stretchr/testify/assert" + "testing" ) func TestNewApp(t *testing.T) { @@ -21,4 +21,3 @@ func TestNewApp(t *testing.T) { assert.Equal("service", app.Commands[1].Name, "Command[1].name should match") assert.Equal("pipeline", app.Commands[2].Name, "Command[2].name should match") } - diff --git a/cli/environments.go b/cli/environments.go index dc586f2f..cb1816f5 100644 --- a/cli/environments.go +++ b/cli/environments.go @@ -1,22 +1,19 @@ package cli -import( - "github.com/urfave/cli" - "fmt" +import ( "errors" + "fmt" "github.com/stelligent/mu/common" "github.com/stelligent/mu/resources" + "github.com/urfave/cli" ) - - - func newEnvironmentsCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "environment", + cmd := &cli.Command{ + Name: "environment", Aliases: []string{"env"}, - Usage: "options for managing environments", + Usage: "options for managing environments", Subcommands: []cli.Command{ *newEnvironmentsListCommand(ctx), *newEnvironmentsShowCommand(ctx), @@ -37,7 +34,7 @@ func newEnvironmentsUpsertCommand(ctx *common.Context) *cli.Command { ArgsUsage: "", Action: func(c *cli.Context) error { environmentName := c.Args().First() - if(len(environmentName) == 0) { + if len(environmentName) == 0 { cli.ShowCommandHelp(c, "upsert") return errors.New("environment must be provided") } @@ -50,10 +47,10 @@ func newEnvironmentsUpsertCommand(ctx *common.Context) *cli.Command { } func newEnvironmentsListCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "list", + cmd := &cli.Command{ + Name: "list", Aliases: []string{"ls"}, - Usage: "list environments", + Usage: "list environments", Action: func(c *cli.Context) error { fmt.Println("listing environments") return nil @@ -64,17 +61,17 @@ func newEnvironmentsListCommand(ctx *common.Context) *cli.Command { } func newEnvironmentsShowCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "show", - Usage: "show environment details", + cmd := &cli.Command{ + Name: "show", + Usage: "show environment details", ArgsUsage: "", Action: func(c *cli.Context) error { environmentName := c.Args().First() - if(len(environmentName) == 0) { + if len(environmentName) == 0 { cli.ShowCommandHelp(c, "show") return errors.New("environment must be provided") } - fmt.Printf("showing environment: %s\n",environmentName) + fmt.Printf("showing environment: %s\n", environmentName) return nil }, } @@ -82,22 +79,21 @@ func newEnvironmentsShowCommand(ctx *common.Context) *cli.Command { return cmd } func newEnvironmentsTerminateCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "terminate", - Aliases: []string{"term"}, - Usage: "terminate an environment", + cmd := &cli.Command{ + Name: "terminate", + Aliases: []string{"term"}, + Usage: "terminate an environment", ArgsUsage: "", Action: func(c *cli.Context) error { environmentName := c.Args().First() - if(len(environmentName) == 0) { + if len(environmentName) == 0 { cli.ShowCommandHelp(c, "terminate") return errors.New("environment must be provided") } - fmt.Printf("terminating environment: %s\n",environmentName) + fmt.Printf("terminating environment: %s\n", environmentName) return nil }, } return cmd } - diff --git a/cli/environments_test.go b/cli/environments_test.go index a539da0e..c319997b 100644 --- a/cli/environments_test.go +++ b/cli/environments_test.go @@ -1,14 +1,14 @@ package cli import ( - "testing" - "github.com/stretchr/testify/assert" + "bytes" + "flag" + "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" "github.com/urfave/cli" "io/ioutil" - "flag" - "bytes" - "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" + "testing" ) func TestNewEnvironmentsCommand(t *testing.T) { @@ -25,12 +25,11 @@ func TestNewEnvironmentsCommand(t *testing.T) { assert.Equal("options for managing environments", command.Usage, "Usage should match") assert.Equal(4, len(command.Subcommands), "Subcommands len should match") - args := []string { "environment","help" } + args := []string{"environment", "help"} err := runCommand(command, args) assert.Nil(err) } - func TestNewEnvironmentsUpsertCommand(t *testing.T) { assert := assert.New(t) ctx := common.NewContext() @@ -44,15 +43,15 @@ func TestNewEnvironmentsUpsertCommand(t *testing.T) { assert.Equal("", command.ArgsUsage, "ArgsUsage should match") assert.NotNil(command.Action) - args := []string { "upsert" } + args := []string{"upsert"} err := runCommand(command, args) assert.NotNil(err) - assert.Equal(1,lastExitCode) + assert.Equal(1, lastExitCode) - args = []string { "upsert","fooenv" } + args = []string{"upsert", "fooenv"} err = runCommand(command, args) assert.NotNil(err) - assert.Equal(1,lastExitCode) + assert.Equal(1, lastExitCode) } func TestNewEnvironmentsListCommand(t *testing.T) { @@ -111,6 +110,7 @@ func init() { cli.OsExiter = fakeOsExiter cli.ErrWriter = fakeErrWriter } + type mockedCloudFormation struct { cloudformationiface.CloudFormationAPI } diff --git a/cli/pipelines.go b/cli/pipelines.go index a04baaa0..7697a7e1 100644 --- a/cli/pipelines.go +++ b/cli/pipelines.go @@ -1,14 +1,14 @@ package cli -import( - "github.com/urfave/cli" +import ( "fmt" "github.com/stelligent/mu/common" + "github.com/urfave/cli" ) func newPipelinesCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "pipeline", + cmd := &cli.Command{ + Name: "pipeline", Usage: "options for managing pipelines", Subcommands: []cli.Command{ *newPipelinesListCommand(ctx), @@ -20,10 +20,10 @@ func newPipelinesCommand(ctx *common.Context) *cli.Command { } func newPipelinesListCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "list", + cmd := &cli.Command{ + Name: "list", Aliases: []string{"ls"}, - Usage: "list pipelines", + Usage: "list pipelines", Action: func(c *cli.Context) error { fmt.Println("listing pipelines") return nil @@ -34,18 +34,18 @@ func newPipelinesListCommand(ctx *common.Context) *cli.Command { } func newPipelinesShowCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "show", + cmd := &cli.Command{ + Name: "show", Usage: "show pipeline details", - Flags: []cli.Flag { + Flags: []cli.Flag{ cli.StringFlag{ - Name: "service, s", + Name: "service, s", Usage: "service to show", }, }, Action: func(c *cli.Context) error { service := c.String("service") - fmt.Printf("showing pipeline: %s\n",service) + fmt.Printf("showing pipeline: %s\n", service) return nil }, } diff --git a/cli/pipelines_test.go b/cli/pipelines_test.go index 0b310882..ddb0f4bf 100644 --- a/cli/pipelines_test.go +++ b/cli/pipelines_test.go @@ -1,9 +1,9 @@ package cli import ( - "testing" - "github.com/stretchr/testify/assert" "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" + "testing" ) func TestNewPipelinesCommand(t *testing.T) { @@ -45,4 +45,3 @@ func TestNewPipelinesShowCommand(t *testing.T) { assert.Equal("service, s", command.Flags[0].GetName(), "Flag should match") assert.NotNil(command.Action) } - diff --git a/cli/services.go b/cli/services.go index b01bc8dd..41bb49aa 100644 --- a/cli/services.go +++ b/cli/services.go @@ -1,16 +1,16 @@ package cli -import( - "github.com/urfave/cli" +import ( "fmt" "github.com/stelligent/mu/common" + "github.com/urfave/cli" ) func newServicesCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "service", + cmd := &cli.Command{ + Name: "service", Aliases: []string{"svc"}, - Usage: "options for managing services", + Usage: "options for managing services", Subcommands: []cli.Command{ *newServicesShowCommand(ctx), *newServicesDeployCommand(ctx), @@ -23,18 +23,18 @@ func newServicesCommand(ctx *common.Context) *cli.Command { } func newServicesShowCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "show", + cmd := &cli.Command{ + Name: "show", Usage: "show service details", - Flags: []cli.Flag { + Flags: []cli.Flag{ cli.StringFlag{ - Name: "service, s", + Name: "service, s", Usage: "service to show", }, }, Action: func(c *cli.Context) error { service := c.String("service") - fmt.Printf("showing service: %s\n",service) + fmt.Printf("showing service: %s\n", service) return nil }, } @@ -43,20 +43,20 @@ func newServicesShowCommand(ctx *common.Context) *cli.Command { } func newServicesDeployCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "deploy", - Usage: "deploy service to environment", + cmd := &cli.Command{ + Name: "deploy", + Usage: "deploy service to environment", ArgsUsage: "", - Flags: []cli.Flag { + Flags: []cli.Flag{ cli.StringFlag{ - Name: "service, s", + Name: "service, s", Usage: "service to deploy", }, }, Action: func(c *cli.Context) error { environmentName := c.Args().First() serviceName := c.String("service") - fmt.Printf("deploying service: %s to environment: %s\n",serviceName, environmentName) + fmt.Printf("deploying service: %s to environment: %s\n", serviceName, environmentName) return nil }, } @@ -65,13 +65,13 @@ func newServicesDeployCommand(ctx *common.Context) *cli.Command { } func newServicesSetenvCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "setenv", - Usage: "set environment variable", + cmd := &cli.Command{ + Name: "setenv", + Usage: "set environment variable", ArgsUsage: " =...", - Flags: []cli.Flag { + Flags: []cli.Flag{ cli.StringFlag{ - Name: "service, s", + Name: "service, s", Usage: "service to deploy", }, }, @@ -79,7 +79,7 @@ func newServicesSetenvCommand(ctx *common.Context) *cli.Command { environmentName := c.Args().First() serviceName := c.String("service") keyvals := c.Args().Tail() - fmt.Printf("setenv service: %s to environment: %s with vals: %s\n",serviceName, environmentName, keyvals) + fmt.Printf("setenv service: %s to environment: %s with vals: %s\n", serviceName, environmentName, keyvals) return nil }, } @@ -88,25 +88,23 @@ func newServicesSetenvCommand(ctx *common.Context) *cli.Command { } func newServicesUndeployCommand(ctx *common.Context) *cli.Command { - cmd := &cli.Command { - Name: "undeploy", - Usage: "undeploy service from environment", + cmd := &cli.Command{ + Name: "undeploy", + Usage: "undeploy service from environment", ArgsUsage: "", - Flags: []cli.Flag { + Flags: []cli.Flag{ cli.StringFlag{ - Name: "service, s", + Name: "service, s", Usage: "service to undeploy", }, }, Action: func(c *cli.Context) error { environmentName := c.Args().First() serviceName := c.String("service") - fmt.Printf("undeploying service: %s to environment: %s\n",serviceName, environmentName) + fmt.Printf("undeploying service: %s to environment: %s\n", serviceName, environmentName) return nil }, } return cmd } - - diff --git a/cli/services_test.go b/cli/services_test.go index a1a74609..6fe3b146 100644 --- a/cli/services_test.go +++ b/cli/services_test.go @@ -1,9 +1,9 @@ package cli import ( - "testing" - "github.com/stretchr/testify/assert" "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" + "testing" ) func TestNewServicesCommand(t *testing.T) { @@ -79,4 +79,3 @@ func TestNewUndeployCommand(t *testing.T) { assert.Equal("service, s", command.Flags[0].GetName(), "Flags Name") assert.NotNil(command.Action) } - diff --git a/common/assets.go b/common/assets.go index ebd3689a..ab62c6d2 100644 --- a/common/assets.go +++ b/common/assets.go @@ -162,7 +162,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "assets/environment-template.yml": assetsEnvironmentTemplateYml, - "assets/vpc-template.yml": assetsVpcTemplateYml, + "assets/vpc-template.yml": assetsVpcTemplateYml, } // AssetDir returns the file names below a certain @@ -204,10 +204,11 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "assets": &bintree{nil, map[string]*bintree{ "environment-template.yml": &bintree{assetsEnvironmentTemplateYml, map[string]*bintree{}}, - "vpc-template.yml": &bintree{assetsVpcTemplateYml, map[string]*bintree{}}, + "vpc-template.yml": &bintree{assetsVpcTemplateYml, map[string]*bintree{}}, }}, }} @@ -257,4 +258,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/common/config.go b/common/config.go index 62fa353c..204befae 100644 --- a/common/config.go +++ b/common/config.go @@ -9,7 +9,7 @@ func newConfig() *Config { return new(Config) } -func (config *Config) loadFromYaml(yamlConfig []byte) *Config { +func (config *Config) loadFromYaml(yamlConfig []byte) *Config { err := yaml.Unmarshal(yamlConfig, config) if err != nil { log.Panicf("Invalid config file: %v", err) diff --git a/common/config_test.go b/common/config_test.go index 94b05544..acaac2a3 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -1,9 +1,9 @@ package common import ( - "testing" - "github.com/stretchr/testify/assert" "fmt" + "github.com/stretchr/testify/assert" + "testing" ) func TestNewConfig(t *testing.T) { @@ -18,7 +18,7 @@ func TestLoadYamlConfig(t *testing.T) { assert := assert.New(t) yamlConfig := -` + ` --- environments: - name: dev @@ -37,24 +37,23 @@ service: desiredCount: 2 ` - config := newConfig() config.loadFromYaml([]byte(yamlConfig)) fmt.Println(config) assert.NotNil(config) - assert.Equal(2,len(config.Environments)) - assert.Equal("dev",config.Environments[0].Name) - assert.Equal("api-dev.example.com",config.Environments[0].Loadbalancer.Hostname) - assert.Equal(1,config.Environments[0].Cluster.DesiredCapacity) - assert.Equal(1,config.Environments[0].Cluster.MaxSize) - assert.Equal("production",config.Environments[1].Name) - assert.Equal("api.example.com",config.Environments[1].Loadbalancer.Hostname) - assert.Equal(2,config.Environments[1].Cluster.DesiredCapacity) - assert.Equal(5,config.Environments[1].Cluster.MaxSize) - - assert.Equal(2,config.Service.DesiredCount) + assert.Equal(2, len(config.Environments)) + assert.Equal("dev", config.Environments[0].Name) + assert.Equal("api-dev.example.com", config.Environments[0].Loadbalancer.Hostname) + assert.Equal(1, config.Environments[0].Cluster.DesiredCapacity) + assert.Equal(1, config.Environments[0].Cluster.MaxSize) + assert.Equal("production", config.Environments[1].Name) + assert.Equal("api.example.com", config.Environments[1].Loadbalancer.Hostname) + assert.Equal(2, config.Environments[1].Cluster.DesiredCapacity) + assert.Equal(5, config.Environments[1].Cluster.MaxSize) + + assert.Equal(2, config.Service.DesiredCount) } @@ -70,4 +69,3 @@ func TestLoadBadYamlConfig(t *testing.T) { config := newConfig() config.loadFromYaml([]byte(yamlConfig)) } - diff --git a/common/context.go b/common/context.go index 600db513..e1aecb49 100644 --- a/common/context.go +++ b/common/context.go @@ -1,8 +1,8 @@ package common import ( - "io/ioutil" "fmt" + "io/ioutil" ) // NewContext create a new context object @@ -11,11 +11,9 @@ func NewContext() *Context { return ctx } - - // InitializeFromFile loads config object from local file func (ctx *Context) InitializeFromFile(configFile string) { - yamlConfig, err := ioutil.ReadFile( configFile ) + yamlConfig, err := ioutil.ReadFile(configFile) if err != nil { fmt.Printf("WARN: Unable to find config file: %v\n", err) } else { diff --git a/common/stack.go b/common/stack.go index ecc79499..006f5277 100644 --- a/common/stack.go +++ b/common/stack.go @@ -2,29 +2,29 @@ package common import ( "fmt" - "os" - "text/template" "github.com/aws/aws-sdk-go/aws" - "io/ioutil" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" "github.com/aws/aws-sdk-go/service/cloudformation" - "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" + "io/ioutil" + "os" + "text/template" ) // Stack contains the data about a CloudFormation stack type Stack struct { - Name string - Parameters map[string]string + Name string + Parameters map[string]string TemplatePath string } // NewStack will create a new stack instance func NewStack(name string) *Stack { return &Stack{ - Name: name, - Parameters: make(map[string]string), - TemplatePath: fmt.Sprintf("%s/%s.yml",os.TempDir(), name), + Name: name, + Parameters: make(map[string]string), + TemplatePath: fmt.Sprintf("%s/%s.yml", os.TempDir(), name), } } func newCloudFormation(region string) (cloudformationiface.CloudFormationAPI, error) { @@ -36,15 +36,15 @@ func newCloudFormation(region string) (cloudformationiface.CloudFormationAPI, er } // WithParameter apply a parameter to the stack -func (stack *Stack) WithParameter(key string, value string) (*Stack) { +func (stack *Stack) WithParameter(key string, value string) *Stack { stack.Parameters[key] = value return stack } // WriteTemplate will create a temp file with the template for a CFN stack //go:generate go-bindata -pkg $GOPACKAGE -o assets.go assets/ -func (stack *Stack) WriteTemplate(assetName string, data interface{}) (error) { - asset, err := Asset(fmt.Sprintf("assets/%s",assetName)) +func (stack *Stack) WriteTemplate(assetName string, data interface{}) error { + asset, err := Asset(fmt.Sprintf("assets/%s", assetName)) if err != nil { return err } @@ -69,12 +69,12 @@ func (stack *Stack) WriteTemplate(assetName string, data interface{}) (error) { return nil } -func (stack *Stack) buildParameters() ([]*cloudformation.Parameter) { +func (stack *Stack) buildParameters() []*cloudformation.Parameter { parameters := make([]*cloudformation.Parameter, 0, len(stack.Parameters)) for key, value := range stack.Parameters { parameters = append(parameters, - &cloudformation.Parameter { - ParameterKey: aws.String(key), + &cloudformation.Parameter{ + ParameterKey: aws.String(key), ParameterValue: aws.String(value), }) } @@ -82,7 +82,7 @@ func (stack *Stack) buildParameters() ([]*cloudformation.Parameter) { } // UpsertStack will create/update the cloudformation stack -func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) (error) { +func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) error { stackStatus := stack.AwaitFinalStatus(cfn) if stackStatus == "" { fmt.Printf("creating stack: %s\n", stack.Name) @@ -91,7 +91,7 @@ func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) (erro Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, - Parameters: stack.buildParameters(), + Parameters: stack.buildParameters(), TemplateBody: aws.String(stack.readTemplatePath()), } _, err := cfn.CreateStack(params) @@ -111,7 +111,7 @@ func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) (erro Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, - Parameters: stack.buildParameters(), + Parameters: stack.buildParameters(), TemplateBody: aws.String(stack.readTemplatePath()), } @@ -132,7 +132,7 @@ func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) (erro return nil } -func (stack *Stack) readTemplatePath() (string) { +func (stack *Stack) readTemplatePath() string { templateBytes, err := ioutil.ReadFile(stack.TemplatePath) if err != nil { return "" @@ -141,7 +141,7 @@ func (stack *Stack) readTemplatePath() (string) { } // AwaitFinalStatus waits for the stack to arrive in a final status -func (stack *Stack) AwaitFinalStatus(cfn cloudformationiface.CloudFormationAPI) (string) { +func (stack *Stack) AwaitFinalStatus(cfn cloudformationiface.CloudFormationAPI) string { params := &cloudformation.DescribeStacksInput{ StackName: aws.String(stack.Name), } @@ -150,8 +150,8 @@ func (stack *Stack) AwaitFinalStatus(cfn cloudformationiface.CloudFormationAPI) if err == nil && resp != nil && len(resp.Stacks) == 1 { switch *resp.Stacks[0].StackStatus { case cloudformation.StackStatusReviewInProgress, - cloudformation.StackStatusCreateInProgress, - cloudformation.StackStatusRollbackInProgress: + cloudformation.StackStatusCreateInProgress, + cloudformation.StackStatusRollbackInProgress: // wait for create cfn.WaitUntilStackCreateComplete(params) resp, err = cfn.DescribeStacks(params) @@ -160,21 +160,21 @@ func (stack *Stack) AwaitFinalStatus(cfn cloudformationiface.CloudFormationAPI) cfn.WaitUntilStackDeleteComplete(params) resp, err = cfn.DescribeStacks(params) case cloudformation.StackStatusUpdateInProgress, - cloudformation.StackStatusUpdateRollbackInProgress, - cloudformation.StackStatusUpdateCompleteCleanupInProgress, - cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress: + cloudformation.StackStatusUpdateRollbackInProgress, + cloudformation.StackStatusUpdateCompleteCleanupInProgress, + cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress: // wait for update cfn.WaitUntilStackUpdateComplete(params) resp, err = cfn.DescribeStacks(params) case cloudformation.StackStatusCreateFailed, - cloudformation.StackStatusCreateComplete, - cloudformation.StackStatusRollbackFailed, - cloudformation.StackStatusRollbackComplete, - cloudformation.StackStatusDeleteFailed, - cloudformation.StackStatusDeleteComplete, - cloudformation.StackStatusUpdateComplete, - cloudformation.StackStatusUpdateRollbackFailed, - cloudformation.StackStatusUpdateRollbackComplete: + cloudformation.StackStatusCreateComplete, + cloudformation.StackStatusRollbackFailed, + cloudformation.StackStatusRollbackComplete, + cloudformation.StackStatusDeleteFailed, + cloudformation.StackStatusDeleteComplete, + cloudformation.StackStatusUpdateComplete, + cloudformation.StackStatusUpdateRollbackFailed, + cloudformation.StackStatusUpdateRollbackComplete: // no op } diff --git a/common/stack_test.go b/common/stack_test.go index 2edcb5eb..45517908 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -1,27 +1,27 @@ package common import ( - "testing" - "github.com/stretchr/testify/assert" - "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" - "github.com/aws/aws-sdk-go/service/cloudformation" - "github.com/aws/aws-sdk-go/aws" "errors" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" + "github.com/stretchr/testify/assert" + "testing" ) type mockedCloudFormation struct { cloudformationiface.CloudFormationAPI - responses []*cloudformation.DescribeStacksOutput - responseIndex int - createIndex int - updateIndex int - waitUntilStackExists int + responses []*cloudformation.DescribeStacksOutput + responseIndex int + createIndex int + updateIndex int + waitUntilStackExists int waitUntilStackCreateComplete int waitUntilStackUpdateComplete int } func (c *mockedCloudFormation) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) { - if(c.responseIndex >= len(c.responses)) { + if c.responseIndex >= len(c.responses) { return nil, errors.New("stack not found") } @@ -59,7 +59,7 @@ func TestNewStack(t *testing.T) { stack := NewStack("foo") assert.NotNil(stack) - assert.Equal("foo",stack.Name) + assert.Equal("foo", stack.Name) } func TestStack_WriteTemplate(t *testing.T) { @@ -78,7 +78,6 @@ func TestStack_AwaitFinalStatus_CreateComplete(t *testing.T) { assert := assert.New(t) stack := NewStack("foo") - cfn := new(mockedCloudFormation) cfn.responses = []*cloudformation.DescribeStacksOutput{ &cloudformation.DescribeStacksOutput{ @@ -100,7 +99,6 @@ func TestStack_AwaitFinalStatus_CreateInProgress(t *testing.T) { assert := assert.New(t) stack := NewStack("foo") - cfn := new(mockedCloudFormation) cfn.responses = []*cloudformation.DescribeStacksOutput{ &cloudformation.DescribeStacksOutput{ @@ -197,7 +195,6 @@ func TestStack_UpsertStack_Update(t *testing.T) { assert.Equal(3, cfn.responseIndex) } - func TestStack_WithParameter(t *testing.T) { assert := assert.New(t) stack := NewStack("foo") @@ -205,11 +202,11 @@ func TestStack_WithParameter(t *testing.T) { parameters := stack.buildParameters() assert.Equal(0, len(parameters)) - stack.WithParameter("p1","value 1").WithParameter("p2", "value 2") + stack.WithParameter("p1", "value 1").WithParameter("p2", "value 2") parameters = stack.buildParameters() assert.Equal(2, len(parameters)) assert.Contains(*parameters[0].ParameterKey, "p") assert.Contains(*parameters[0].ParameterValue, "value") assert.Contains(*parameters[1].ParameterKey, "p") assert.Contains(*parameters[1].ParameterValue, "value") -} \ No newline at end of file +} diff --git a/common/types.go b/common/types.go index cefc9d47..48bbad91 100644 --- a/common/types.go +++ b/common/types.go @@ -6,36 +6,36 @@ import ( // Context defines the context object passed around type Context struct { - Config Config + Config Config CloudFormation cloudformationiface.CloudFormationAPI } // Config defines the structure of the yml file for the mu config type Config struct { - Region string + Region string Environments []Environment - Service Service + Service Service } // Environment defines the structure of the yml file for an environment type Environment struct { - Name string + Name string Loadbalancer struct { Hostname string } Cluster struct { DesiredCapacity int `yaml:"desiredCapacity"` - MaxSize int `yaml:"maxSize"` + MaxSize int `yaml:"maxSize"` } VpcTarget struct { - VpcID string `yaml:"vpcId"` + VpcID string `yaml:"vpcId"` PublicSubnetIds []string `yaml:"publicSubnetIds"` } `yaml:"vpcTarget,omitempty"` } // Service defines the structure of the yml file for a service type Service struct { - DesiredCount int`yaml:"desiredCount"` - Pipeline struct { + DesiredCount int `yaml:"desiredCount"` + Pipeline struct { } } diff --git a/main.go b/main.go index f1fdcc38..a5fb6f44 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,12 @@ package main import ( - "os" - "github.com/stelligent/mu/cli" + "github.com/stelligent/mu/cli" + "os" ) var version string func main() { - cli.NewApp(version).Run(os.Args) + cli.NewApp(version).Run(os.Args) } diff --git a/resources/environment.go b/resources/environment.go index bec1cd2f..ee5e0264 100644 --- a/resources/environment.go +++ b/resources/environment.go @@ -6,22 +6,20 @@ import ( "strings" ) - // EnvironmentManager defines the env that will be created type EnvironmentManager interface { - UpsertEnvironment(environmentName string) (error) + UpsertEnvironment(environmentName string) error } - // NewEnvironmentManager will construct a manager for environments -func NewEnvironmentManager(ctx *common.Context) (EnvironmentManager) { +func NewEnvironmentManager(ctx *common.Context) EnvironmentManager { environmentManager := new(environmentManagerImpl) environmentManager.context = ctx return environmentManager } // UpsertEnvironment will create a new stack instance and write the template for the stack -func (environmentManager *environmentManagerImpl) UpsertEnvironment(environmentName string) (error) { +func (environmentManager *environmentManagerImpl) UpsertEnvironment(environmentName string) error { env, err := environmentManager.getEnvironment(environmentName) if err != nil { return err @@ -50,12 +48,12 @@ type environmentManagerImpl struct { func (environmentManager *environmentManagerImpl) getEnvironment(environmentName string) (*common.Environment, error) { ctx := environmentManager.context for _, e := range ctx.Config.Environments { - if(strings.EqualFold(environmentName, e.Name)) { + if strings.EqualFold(environmentName, e.Name) { return &e, nil } } - return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml",environmentName) + return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml", environmentName) } func buildVpcStackName(env *common.Environment) string { @@ -65,8 +63,7 @@ func buildEnvironmentStackName(env *common.Environment) string { return fmt.Sprintf("mu-env-%s", env.Name) } - -func (environmentManager *environmentManagerImpl) upsertVpc(env *common.Environment) (error) { +func (environmentManager *environmentManagerImpl) upsertVpc(env *common.Environment) error { // generate the CFN template stack := common.NewStack(buildVpcStackName(env)) @@ -76,7 +73,7 @@ func (environmentManager *environmentManagerImpl) upsertVpc(env *common.Environm } // create/update the stack - fmt.Printf("upserting VPC environment:%s stack:%s path:%s\n",env.Name, stack.Name, stack.TemplatePath) + fmt.Printf("upserting VPC environment:%s stack:%s path:%s\n", env.Name, stack.Name, stack.TemplatePath) err = stack.UpsertStack(environmentManager.context.CloudFormation) if err != nil { return err @@ -85,7 +82,7 @@ func (environmentManager *environmentManagerImpl) upsertVpc(env *common.Environm return nil } -func (environmentManager *environmentManagerImpl) upsertEcsCluster(env *common.Environment) (error) { +func (environmentManager *environmentManagerImpl) upsertEcsCluster(env *common.Environment) error { // generate the CFN template stack := common.NewStack(buildEnvironmentStackName(env)) @@ -100,7 +97,7 @@ func (environmentManager *environmentManagerImpl) upsertEcsCluster(env *common.E // target VPC referenced from config stack.WithParameter("VpcId", env.VpcTarget.VpcID) for index, subnet := range env.VpcTarget.PublicSubnetIds { - stack.WithParameter(fmt.Sprintf("PublicSubnetAZ%dId",index+1), subnet) + stack.WithParameter(fmt.Sprintf("PublicSubnetAZ%dId", index+1), subnet) } } @@ -110,7 +107,7 @@ func (environmentManager *environmentManagerImpl) upsertEcsCluster(env *common.E } // create/update the stack - fmt.Printf("upserting environment:%s stack:%s path:%s\n",env.Name, stack.Name, stack.TemplatePath) + fmt.Printf("upserting environment:%s stack:%s path:%s\n", env.Name, stack.Name, stack.TemplatePath) err = stack.UpsertStack(environmentManager.context.CloudFormation) if err != nil { return err @@ -118,5 +115,3 @@ func (environmentManager *environmentManagerImpl) upsertEcsCluster(env *common.E return nil } - - diff --git a/resources/environment_test.go b/resources/environment_test.go index 4190783f..4c53ea37 100644 --- a/resources/environment_test.go +++ b/resources/environment_test.go @@ -1,8 +1,8 @@ package resources import ( - "testing" "github.com/stretchr/testify/assert" + "testing" "github.com/stelligent/mu/common" ) From 2c7b4b2ce5b3b5c94f5a5f49804a3c7d4b3a998f Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 12 Jan 2017 12:52:59 -0800 Subject: [PATCH 21/31] Develop workflow to enable composibility of future plugins into the commands --- cli/app.go | 11 +- cli/environments.go | 6 +- cli/environments_test.go | 1 - common/assets.go | 260 ------------------ common/config.go | 19 -- common/context.go | 31 ++- common/{config_test.go => context_test.go} | 27 +- common/stack.go | 145 ++++------ common/stack_test.go | 120 +++----- common/types.go | 8 +- resources/environment.go | 117 -------- resources/environment_test.go | 44 --- .../assets/cluster.yml | 0 .../assets/vpc.yml | 0 templates/template.go | 33 +++ templates/template_test.go | 40 +++ workflows/environment.go | 90 ++++++ workflows/environment_test.go | 132 +++++++++ workflows/executor.go | 16 ++ workflows/executor_test.go | 35 +++ 20 files changed, 493 insertions(+), 642 deletions(-) delete mode 100644 common/assets.go delete mode 100644 common/config.go rename common/{config_test.go => context_test.go} (75%) delete mode 100644 resources/environment.go delete mode 100644 resources/environment_test.go rename common/assets/environment-template.yml => templates/assets/cluster.yml (100%) rename common/assets/vpc-template.yml => templates/assets/vpc.yml (100%) create mode 100644 templates/template.go create mode 100644 templates/template_test.go create mode 100644 workflows/environment.go create mode 100644 workflows/environment_test.go create mode 100644 workflows/executor.go create mode 100644 workflows/executor_test.go diff --git a/cli/app.go b/cli/app.go index c7ef0285..8f931d4a 100644 --- a/cli/app.go +++ b/cli/app.go @@ -1,8 +1,10 @@ package cli import ( + "bufio" "github.com/stelligent/mu/common" "github.com/urfave/cli" + "os" ) // NewApp creates a new CLI app @@ -22,7 +24,14 @@ func NewApp(version string) *cli.App { } app.Before = func(c *cli.Context) error { - context.InitializeFromFile(c.String("config")) + yamlFile, err := os.Open(c.String("config")) + if err != nil { + return err + } + defer func() { + yamlFile.Close() + }() + context.Initialize(bufio.NewReader(yamlFile)) return nil } diff --git a/cli/environments.go b/cli/environments.go index cb1816f5..b6d0b0ae 100644 --- a/cli/environments.go +++ b/cli/environments.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" "github.com/stelligent/mu/common" - "github.com/stelligent/mu/resources" + "github.com/stelligent/mu/workflows" "github.com/urfave/cli" ) @@ -26,7 +26,6 @@ func newEnvironmentsCommand(ctx *common.Context) *cli.Command { } func newEnvironmentsUpsertCommand(ctx *common.Context) *cli.Command { - environmentManager := resources.NewEnvironmentManager(ctx) cmd := &cli.Command{ Name: "upsert", Aliases: []string{"up"}, @@ -39,7 +38,8 @@ func newEnvironmentsUpsertCommand(ctx *common.Context) *cli.Command { return errors.New("environment must be provided") } - return environmentManager.UpsertEnvironment(environmentName) + workflow := workflows.NewEnvironmentUpserter(ctx, environmentName) + return workflow() }, } diff --git a/cli/environments_test.go b/cli/environments_test.go index c319997b..d433301c 100644 --- a/cli/environments_test.go +++ b/cli/environments_test.go @@ -34,7 +34,6 @@ func TestNewEnvironmentsUpsertCommand(t *testing.T) { assert := assert.New(t) ctx := common.NewContext() command := newEnvironmentsUpsertCommand(ctx) - ctx.CloudFormation = new(mockedCloudFormation) assert.NotNil(command) assert.Equal("upsert", command.Name, "Name should match") diff --git a/common/assets.go b/common/assets.go deleted file mode 100644 index ab62c6d2..00000000 --- a/common/assets.go +++ /dev/null @@ -1,260 +0,0 @@ -// Code generated by go-bindata. -// sources: -// assets/environment-template.yml -// assets/vpc-template.yml -// DO NOT EDIT! - -package common - -import ( - "bytes" - "compress/gzip" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -func (fi bindataFileInfo) Name() string { - return fi.name -} -func (fi bindataFileInfo) Size() int64 { - return fi.size -} -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} -func (fi bindataFileInfo) IsDir() bool { - return false -} -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -var _assetsEnvironmentTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x59\x7b\x6f\xdb\x38\x12\xff\x3f\x9f\x62\xea\x2b\xe0\xed\xa2\xf2\x43\x4e\xda\x54\x40\x76\xe1\x38\x6e\x6b\xac\xdd\x1a\x51\xda\x02\x4d\x8b\x82\xa2\x28\x9b\xa8\x44\xea\x48\x2a\x8f\xed\xf5\xbb\x1f\x48\x4a\xb2\x5e\xce\x26\xbb\x3d\xec\x1d\x70\x2e\x82\xda\xe4\x70\xde\xf3\x9b\xa1\xe4\x38\xce\xc1\xf4\x83\x7f\x41\x92\x34\x46\x8a\xbc\xe4\x22\x41\xea\x3d\x11\x92\x72\xe6\x41\xdf\x1d\x8d\x47\xce\xe8\x85\x33\x7a\xd1\x3f\x58\x23\x81\x12\xa2\x88\x90\xde\x01\xc0\x82\x49\x85\x18\x26\x17\xb7\x29\xd1\xbf\x01\xcc\x37\xf0\x95\xa0\x6c\x63\x16\xce\x88\xc4\x82\xa6\xca\xb0\x2a\xe8\x41\xdd\xa6\x04\x14\x87\x4c\x92\x41\x4e\x16\xa1\x2c\x56\x1e\x28\x77\x90\x50\x2c\xf8\x81\x39\x4a\x05\x09\x67\x28\x45\x98\xaa\xdb\xaa\x80\x37\x59\x12\x10\x51\x3f\xd9\x1f\xf7\xdb\x12\x2d\x21\xf0\x08\x68\x2e\x5b\x6a\xb9\x31\xca\x18\xde\x02\x65\x70\xcb\x33\x01\xf3\x99\x0f\x38\xce\xa4\x32\x3c\x57\xe8\xc6\xa7\xbf\x93\x3f\x94\xe7\x76\xc8\x5b\xa1\x1b\x9a\x64\x09\xb0\x2e\xb9\x5b\xa4\x00\x23\x06\x01\xc9\x15\x20\xe1\x1e\x15\x7e\x23\xb7\x6f\x50\x72\x2f\x9f\xe6\xa4\xda\x2a\x24\x25\xc7\x14\x29\x02\xd7\x54\x6d\xe1\x9a\x8b\xaf\x44\xec\x14\x18\x00\x2c\x09\xba\x22\x10\xc4\x88\x7d\xd5\x07\x42\x2a\x51\x10\x13\xf0\xfd\xd7\x80\x30\x26\x52\x36\xa2\xd1\xd7\x26\xfa\x72\x3b\x8d\x63\x7e\xed\xb5\x85\xfb\x59\xc0\x88\x82\x48\xf0\x04\xae\xb7\x14\x6f\x8d\x1a\x9a\xb8\xc5\xb3\x65\xc5\x8a\xb2\x25\x61\x1b\xb5\xf5\xa0\xff\xc2\xba\x72\x85\x6e\xca\xa5\xf1\x71\xbf\xae\xcb\x68\x60\xfe\x0d\x47\x66\xd9\x68\x44\xc2\x35\x52\x8a\x08\xe6\x41\xef\xa7\x4f\x9f\xc2\x6f\xe3\xa7\x93\xef\x4f\x3e\x7d\x1a\xdc\xe7\xc7\x30\xff\xea\x7e\x7f\xd2\x33\x2c\x67\x9c\x49\x25\x10\x65\xaa\x66\x63\x3f\xc9\xa4\xd2\x31\x43\x70\x85\x62\x1a\xc2\x6c\x71\x76\x0e\x41\xcc\xf1\x57\x0f\x6e\x06\xe6\xdf\xf0\x66\xa0\xb5\x7d\x9f\xe2\x45\x78\x9f\xa0\x99\x88\xf1\x08\xd4\x96\x68\xa6\x99\x09\x1f\x4d\x52\x2e\x14\x44\x5c\x98\x75\xc3\xec\x00\x60\x9d\x05\x31\xc5\xd6\xd3\xd3\x8f\xe3\x1f\x27\x60\xfa\x71\x0c\xd2\x06\x90\xb6\x05\xb9\x3f\x52\x90\x5b\x13\xd4\x4c\xb0\xba\xe0\xc9\x8f\x14\x3c\xb9\x43\xf0\x8a\x28\x14\x22\x85\xb4\xb4\xe9\x07\xdf\xf3\x66\x31\xcf\x42\x8b\x7e\x5a\x84\xb7\x60\x8a\x88\x08\xe1\xbc\x0e\x4b\xec\x7b\x25\x78\x96\x4a\xbb\x08\xe0\xc0\x12\x05\x24\x2e\x7e\xea\x4f\x58\x48\xe9\x95\x88\x37\xe3\x2c\xa2\x9b\x4c\x18\xd6\xbd\x92\xb6\x8e\xa7\xc5\xc7\xa9\x21\x6b\x6d\x23\x2f\xf7\xda\x5a\x51\xa0\xf7\x51\x68\x9a\x29\x0e\x3e\x46\x31\x65\x9b\x87\x2a\xd5\x00\xe4\xda\x5e\x0e\x9a\x75\x47\x19\x3d\x4a\x26\xed\x6e\xb1\xc7\x57\x45\x77\xb0\x20\xf9\x6b\xa1\x58\x0d\x14\xeb\x47\x7f\x23\xb7\xfa\xc0\x46\x20\xa6\x2a\xc8\x03\x3f\x59\xa8\xd3\xf9\xc0\x38\x23\x4f\x4a\x5e\x75\x4c\xab\x33\xdb\xd5\x77\x17\xcf\x92\x45\x67\x7b\xaa\x73\xca\x49\xaa\xe0\x5e\xc2\x31\x60\x9e\x31\x55\x72\xab\x35\x9d\x3a\x97\xa2\xa7\xdc\xc9\x65\xc6\x59\x48\x75\x18\x8d\xbb\x5f\x23\x59\xf3\x56\xef\x25\xf3\xbc\x37\x5c\xf5\x76\x49\x6b\x96\xe6\xff\xcc\x50\x2c\x7b\x1e\x5c\x3e\x3a\x27\x51\xe1\xe1\xa7\xd0\xef\x7f\xb6\x5c\x1a\xe0\xf3\x20\x6e\x2d\xe0\xda\xcb\xd7\xfd\x0b\x7c\xdd\x3b\xf8\x4e\xfe\x02\xdf\x49\xc1\x77\x85\xd2\x94\xb2\x8d\xcc\x61\xe2\x9c\x6c\x28\x67\x17\x7c\xba\x5a\x58\x76\x99\x74\x08\x92\xca\x19\x17\xdc\xa7\xab\xc5\xe2\xcc\x03\x94\x50\xc7\x0d\x26\xc1\xb3\xd1\xe1\xb8\x20\xbc\x26\x52\x39\x6e\x07\x21\xc2\xcf\x8e\x9f\xbb\xd8\x82\x14\xc9\x2c\x61\x17\xc7\xd1\xc4\x9d\x1c\x07\xcf\x6d\x13\x44\xa9\xc3\xb8\x50\xdb\xbd\xf2\xa3\xc0\x8d\xc6\xee\x8b\xa3\x82\x5a\xf2\x2c\xa7\xee\x52\xe2\x70\x72\x74\xf8\x7c\xec\x8e\x6a\xda\x76\xb1\x0d\x22\x32\x7a\x71\x14\x46\x6d\xb6\x5d\xd4\xf8\xf9\x71\x74\x38\x41\x87\x85\x6d\x98\x30\x25\x50\xdc\x49\x4b\xc6\xe4\x59\x74\x7c\x1c\x1e\x9c\x13\xc9\x33\x81\x89\x71\xfb\x1c\xcb\x99\x4d\xfc\x6a\x67\x30\x98\x3d\x9f\x19\xe0\x2e\x06\xa7\xf9\xcc\xd7\x08\x97\x03\x9c\x01\xea\xd6\x91\x0a\x41\xed\x87\xa1\xce\xbb\x44\x4a\x58\x28\xdf\x32\x0f\x2e\x3f\x5b\x48\x13\x3c\x25\x42\x51\x52\xa2\xd9\xfb\xf5\xec\x23\x67\x64\x11\x12\xa6\x68\x44\x0b\xd5\x74\x72\xe9\xdc\x5a\x44\xbb\x52\x76\x3a\x2a\xa9\xb2\x69\xc8\x4d\xe3\x7a\xaf\xfb\x98\x07\x8f\xfc\x2c\x80\xc7\xdf\x5a\xf5\xf3\xbd\x72\xc8\x64\xac\x31\xe7\x0d\x37\xc7\x1e\x22\xdd\x7d\xb0\x74\xf7\x07\x4a\x9f\x3c\x58\xfa\xe4\x7e\xd2\x97\xa6\x5f\xd4\x9a\x9a\x81\x40\x7b\x60\xc6\x99\x42\x94\x11\x51\xf4\x19\x59\x40\x2f\x65\x06\x7a\xcb\x1b\xc4\x0e\x8d\xed\xc9\x6a\x6f\x6b\xe3\xbe\xa5\xe9\xea\x8d\x33\x41\x8c\x12\x6b\x1e\x53\x5c\x36\x88\x22\xb3\x7d\xba\x61\xa8\xd2\xa5\x2f\x68\x42\x78\xa6\x3c\x58\x5f\x8c\x8f\x56\x66\xf9\x5d\x1a\x22\x45\xea\xc7\x2b\x09\x7b\xce\x63\xfd\x9f\xa5\xda\x31\x5a\x51\x56\x9a\xb8\x60\x3e\x11\x57\x14\xd7\xac\x33\xf6\x9d\x22\x85\xb7\x4d\xbb\x75\xef\xce\x24\xd1\xaa\x54\xf5\xd0\x9f\x0f\x88\xaa\xb7\xac\xae\xbc\xf4\xa0\xaf\x44\x46\xf4\xf1\xb6\x7b\xef\x2e\xbc\x8e\x60\xd9\x3b\x40\x65\x2a\x33\xf6\x76\x4f\x66\x54\xed\x0c\xc6\x86\x49\x75\x56\xc1\x3c\x49\x10\x0b\x6b\xf3\x0b\xc0\x68\xfc\x05\x85\xe1\x97\xa2\x77\x7e\x51\xfc\x0b\xae\xc2\x4a\xeb\x7c\x9e\x8e\xff\x6a\xec\x02\xfc\xe3\xd1\x30\xa0\x6c\x18\x20\xb9\x6d\xed\x11\xbc\xe5\x1a\x87\xbe\xcc\x96\xef\xfc\x8b\xf9\xf9\xc9\xe3\x6f\x3b\xfc\xfa\x0e\xf0\xcb\x2f\x30\x24\x0a\x0f\x09\x96\xfa\x6f\x60\xb5\xaf\xb0\x89\x68\x4c\x1a\x9a\xf7\xcc\x09\x1c\x31\xfd\xe7\x6c\xb3\xd4\x9c\xea\xb5\xd5\x66\x8a\x30\xb5\x57\xed\xcb\x04\x51\xf6\xb9\xb5\x2c\x15\xc2\x5f\x4f\x1e\x7f\x33\xae\xf6\xf5\x8f\x6a\xbd\x15\x1f\x61\x1a\x5f\x41\x66\xdb\x60\x93\x2a\xe1\xa1\xce\xa7\xd1\x68\x74\x38\x1a\xf5\x1b\x9b\xfc\x9a\x11\xe1\x81\xe0\x5c\x35\x76\x36\x06\xa7\xdb\x3b\x3b\xb3\xb7\x9c\x7f\x95\x83\xd0\x98\x8f\x32\xc5\x1d\x41\x62\x8e\x42\x22\xfe\xa4\x23\x5a\x7c\x1c\x2d\xa1\xed\x1a\x25\xe8\x66\x43\x84\x3c\x49\xb9\x54\x83\xcc\x54\x5a\x8b\x28\x45\x6a\x7b\x52\x36\xac\x41\xbb\x12\x06\x45\x52\x0f\xf6\x66\x73\x8b\x29\xc2\x7a\xf3\x64\xc8\x53\x35\x44\xd7\xd2\xe4\x9b\xd6\x9a\x32\xaa\xc0\xb9\x02\xc7\x31\x61\x83\x6a\xd8\x34\xda\x7d\x07\xc7\x11\xb9\x2e\x1d\x45\x69\x76\x75\xe8\xe0\xce\x40\x02\x88\x8c\x21\x79\xd2\x08\x89\xb4\x60\xd2\xc8\x4e\x79\x2b\xaf\x68\xad\x22\xf3\x28\xd8\x5c\x6d\x2e\x03\x10\x86\x82\x98\x84\x15\xf4\x68\xee\xcb\x4c\x90\xf3\x8c\x31\x0d\x15\xfb\xa8\x3a\xea\x04\xec\x70\xd7\x5d\x2d\x77\x52\xfe\x41\x82\xed\x19\x02\x16\x09\xda\x90\x85\xc6\x89\x97\x94\x85\x0b\xb6\x42\x29\x5c\x36\xa6\xc4\xa7\xb6\x41\xf4\x2a\xde\xee\x3d\xb5\x33\x0f\x14\x09\xe7\x13\x9c\x09\xaa\x6e\xf3\x9b\x25\x5c\xda\x33\xaf\xb9\x54\xfe\xab\x92\xaa\x76\x81\xb2\x14\x1d\xf7\xc4\x05\x4a\x8a\xd5\xb5\xe0\xda\x49\x39\xed\x7c\xe6\x36\x36\x1a\x17\x2b\x78\xb4\x88\xe0\xb2\x72\x79\xc8\x55\xaf\xff\xea\x55\x3b\x6f\xaf\xd0\xed\x9d\x24\xe2\xac\x02\xdb\x60\x5a\xfb\x29\x92\xe4\xd9\x61\x35\x46\x1d\x05\x59\x01\x53\x70\x6e\xea\xe5\x75\x9b\x25\xf6\xae\x13\xc7\xe0\xdc\x02\xba\x96\x8e\x8e\x50\xc0\xb9\x92\x4a\xa0\xb4\x46\xfc\xb7\xd4\x4a\x4b\xa8\x34\xad\x11\x1c\x02\x8f\x7f\xbd\x9f\xe4\x8e\xa1\xf5\x0e\xd1\xed\x30\xb6\x1a\xed\x62\xba\xd2\xa8\xd2\x8e\x75\x3b\x83\xd7\x48\x6d\x3d\xe8\x0d\x8b\xea\x38\xe7\x95\xa2\x72\xca\xc4\xd1\xcb\x56\xb6\xfe\xd6\x2d\x30\xa7\xe9\x92\x32\x95\x32\x4b\x88\x26\xb0\xc3\xcc\x19\xc7\x59\xa2\x01\xba\x74\xa5\xaf\x90\x22\xf5\x25\x07\xe6\x51\x44\xb0\xf2\xa0\xfa\x74\xc3\x0a\xa0\x0c\xd3\x14\xc5\xf5\xea\x2f\x46\x9d\x83\x7a\x91\x13\xec\x0e\x50\x82\x7e\xe7\x0c\x5d\xeb\x76\x9b\x54\xf6\xa7\x06\x65\xeb\x8f\x39\xa4\x92\xde\x4e\xe1\x3d\x7e\x32\x76\xd0\xaa\xab\xac\x65\xb6\x90\x08\x96\x4e\x8e\x95\xbb\xc9\x6a\x8f\xe5\x9d\xb6\xdf\x65\x7d\x97\xd6\xd6\x4e\xe9\x99\x91\x93\xec\xee\x43\xcd\x7d\x9d\x46\x7a\xab\x95\xec\x1d\xb4\x67\x44\x3c\x84\x9a\x4a\xcc\xaf\x88\x58\xf3\x38\x9e\xb3\x30\xe5\x94\xa9\x0e\x32\x3f\x0b\x12\xaa\x7e\x6e\xed\x08\xaf\xbd\x26\x3d\xcd\xac\xb6\x5c\x74\x59\x0f\x7a\x3f\xeb\x50\x58\x84\xec\xb8\x15\xba\x9e\x57\x03\xd5\x7d\xb7\xb8\xdd\x13\x5b\xc8\x31\xab\xeb\x3a\x62\xc8\x8a\xba\x37\xfc\x6a\xcf\x22\xe7\x33\xdf\x68\x52\xe2\x38\xec\x64\x36\xc0\x7d\xc1\x36\x82\xc8\x4a\xda\x2c\xd2\xb5\xe0\x8a\x63\x1e\x7b\xa0\xf0\x0e\xd0\x5e\x0a\x9e\xac\xb9\x30\x2f\x1a\xdc\x5d\xf3\xbb\xe0\x1d\x8b\x33\x1a\x8a\x45\x9a\xc3\x7c\xe5\x69\xe0\x3c\x0e\xfe\x0b\x9c\xb3\x3c\xfd\x0f\xf9\xe5\x78\xd4\xe1\x97\xea\x62\xe1\x97\xea\xab\x83\xf9\xf2\xd4\xd5\xb1\x3a\xcf\x3a\x70\xac\xed\x9a\x5c\xaf\x7d\xfd\xbf\x53\xc9\x8a\x8a\xa5\x32\xa5\x7e\xcf\x8e\x8e\x26\x47\xc5\xaa\x6f\x2f\x53\x35\x81\x7a\x9a\x78\x45\xd4\x54\x29\x1b\xbf\x41\xbe\x5c\x75\x70\x95\xc8\x96\x40\x85\x4a\x2f\xb8\xf3\xe5\xe9\xff\x82\x85\x2d\xe5\x3b\x4d\x6c\xfa\x61\x8e\xe5\x3c\x0e\xda\xb6\xc5\x48\x2a\x8a\x97\x1c\x85\xa7\x28\x46\x0c\x53\xb6\x79\xef\x7a\xde\x6e\x21\xc7\xc4\xb6\x99\xf6\x49\x83\xfc\xff\x33\x9c\xbf\xfb\x19\x4e\x63\x12\x6e\x0c\x22\x3a\x0f\xca\xf8\x2f\x75\x6f\x62\x5d\xcf\x04\xf7\xe5\x41\x7e\x60\x4f\x0e\x54\xd3\x64\x2a\x58\x31\x35\x1b\x59\x39\x49\xfe\xfa\xc8\xf6\xdf\x8a\x76\x56\x78\xc4\xc5\x35\x12\xe1\x0e\x93\x90\xd8\x10\x65\x2c\x69\xf2\xcb\x19\x55\x28\xca\xb9\xa2\x81\x62\xbb\xf2\x7b\x7d\x71\xb1\x2e\x8d\x6f\x33\xb8\xb7\x1b\x9a\x42\x3b\x86\xc2\x42\x89\x3b\xd4\x80\x07\x37\x88\xb7\x99\x4a\x33\x5b\x63\xfa\x5e\xf0\x4e\xe4\xe3\x5b\x95\x78\xab\x54\xea\x0d\x87\xe6\x91\xc9\x3c\x0e\x06\x67\x6f\x7c\x33\x2e\x0f\x0f\xa0\xf9\x02\x50\xf7\x95\x77\xe7\xcb\x56\x3a\x68\x57\xd7\xf8\xee\xbc\x5e\x4b\x80\x1a\xb3\xa9\x60\xc5\xcb\x44\xcd\xb7\x20\xb4\xaf\xae\xe7\x37\xda\xa6\xc2\xce\xfc\xaa\x64\x4d\x6b\x4c\xf5\x4e\x4b\x95\x8e\xa7\xd7\x0d\xbd\xaa\xc3\xda\xde\x37\x9c\x95\x77\x3f\x7f\x46\xa7\x42\xc6\xc1\xbf\x03\x00\x00\xff\xff\x75\x16\x3e\x1e\xeb\x21\x00\x00") - -func assetsEnvironmentTemplateYmlBytes() ([]byte, error) { - return bindataRead( - _assetsEnvironmentTemplateYml, - "assets/environment-template.yml", - ) -} - -func assetsEnvironmentTemplateYml() (*asset, error) { - bytes, err := assetsEnvironmentTemplateYmlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "assets/environment-template.yml", size: 8683, mode: os.FileMode(420), modTime: time.Unix(1484180215, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -var _assetsVpcTemplateYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x98\x4b\x6f\xdb\x38\x10\xc7\xef\xf9\x14\x93\x60\x01\x6d\x81\x38\xb1\xa5\xa4\x68\x78\xf3\x3a\x4e\x63\x6c\x9b\x18\x96\xe1\x02\x69\xf6\x40\x4b\xe3\x58\xa8\x44\x6a\x49\x2a\x0f\x14\xf9\xee\x0b\x51\x92\x1f\xb4\xe4\xfa\x99\x4d\xe1\x8b\x45\x0e\x39\xf3\xff\x69\xc8\xa1\x58\xab\xd5\x0e\x9a\xdf\xdc\x3e\x46\x71\x48\x15\x5e\x71\x11\x51\x35\x40\x21\x03\xce\x08\x58\x76\xbd\x51\xaf\xd5\x2f\x6a\xf5\x0b\xeb\xa0\x4b\x05\x8d\x50\xa1\x90\xe4\x00\xa0\xc3\xa4\xa2\xcc\xc3\x3e\x32\xca\xbc\x97\xb4\x09\xe0\x12\xa5\x27\x82\x58\xe9\xc1\x85\x05\xa8\xcc\x04\x14\x87\x44\x22\x8c\xb8\x80\x41\xb7\xa5\x07\xf4\x5f\x62\x24\xe0\x2a\x11\xb0\x07\xdd\xd0\x0c\x43\xfe\x84\xfe\x80\x86\x09\xca\x6c\xd2\x1a\xf8\x38\xa2\x49\xa8\x26\x4f\x7e\xe0\x51\x85\x7e\xee\x52\xf7\x91\x19\x23\x57\x8e\xf5\x34\x25\x31\xb9\xc9\x90\xa1\x82\x91\xe0\x11\x3c\x8d\x03\x6f\x9c\x06\x45\x53\x63\x70\xdd\x6b\xa0\x9e\x87\x52\x9e\x94\x87\xf6\x35\x60\x5f\x90\x3d\xa8\x31\x01\xeb\xc2\xca\x9a\xe8\xf3\xa4\xa9\xf1\xc9\x9a\x0f\xa8\x7e\xa2\x7f\xa7\xf5\x59\x61\x5d\xaa\x14\x0a\x46\xe0\xe8\xcf\xfb\x7b\xff\x67\xe3\xd8\x79\xfd\x70\x7f\x7f\xb2\xca\xc3\x69\xfe\xd7\x7e\xfd\x70\xa4\xa7\x6c\x71\x26\x95\xa0\x01\x53\x73\x1a\xad\x28\x91\x0a\x86\x08\x14\x1e\x69\x18\xf8\xd0\xea\x5c\xf6\x60\x18\x72\xef\x07\x81\xe7\x13\xfd\x3b\x7d\x3e\x49\xa3\x1d\xc4\x5e\x2b\xf0\xc5\x5f\xba\xaf\x92\x96\x1e\xba\xfc\xb5\xad\xcb\xa6\x51\xc0\x69\x7c\x7c\xbf\x74\xba\xc9\x30\x0c\xbc\x0c\x42\xf3\xae\xb1\x16\xa9\xe6\x5d\x63\xc7\xa4\xec\xb3\xdf\x85\x94\xbd\x26\x29\x7b\x87\xa4\x1a\xbf\x15\x29\x67\x4d\x52\xce\x0e\x49\xd9\xef\x9a\x54\x8b\x33\x3f\x48\xc7\xe8\x22\x70\x4d\xa5\xb1\x18\x33\x5e\x47\x57\x8c\x90\x1b\xae\x8e\xb2\xc7\xb4\x3a\xe8\xa6\xf6\xbf\x09\x0d\xe5\x11\x81\xef\x87\x3d\x1c\x55\x2e\xe4\x63\xb0\xac\x7f\xca\xa6\xb7\xb7\x98\xde\xfe\xf5\xf4\xce\x16\xd3\x3b\xc6\xf4\x3d\x94\x3c\x11\x5e\x56\x2c\x07\xdd\x16\x99\x49\x91\xe6\x37\x97\x90\x76\xcb\x26\xa4\xd8\xb8\xbb\x82\xc7\x28\x54\x50\xd4\x56\x80\x69\x06\x82\xf6\x36\x5b\x12\x72\x93\x36\xa3\xc3\x10\x2f\x99\x74\x93\x38\xe6\x42\x11\xb0\x94\x48\xd0\x32\xbb\xaf\xb9\x54\x8c\x46\x28\x0d\x03\xf3\xa8\x90\x39\x32\x5a\x17\xf7\xdb\x72\x25\x59\x77\x91\x63\x59\x8e\x90\x92\x04\xa9\x90\x3b\x88\xbd\x8e\x5f\x48\xcd\xa1\x2c\x42\xa8\x4a\x98\xdc\xfc\x2b\x8d\x33\x8b\x4e\x7c\xcb\xbe\xd0\x84\x79\x63\x02\xa9\xe2\xbc\xbf\xf9\x48\x83\x90\x0e\x83\x30\x50\x2f\x77\x9c\x21\x81\x43\x17\x43\xf4\x14\x7c\x87\xfa\x31\x1c\x7e\x4e\x67\x95\x79\x76\x68\x91\xf4\x41\x4e\x93\xe0\x6f\x7c\x21\x70\x83\xea\x89\x8b\xc2\x23\x80\x3e\x11\x91\x3c\xb2\xc5\x2d\x77\x2b\x58\xf6\x0e\x61\xd9\xbb\x84\xd5\xd8\x0b\x2c\x67\x2b\x58\xce\x0e\x61\x39\xbb\x84\x65\xef\x08\x56\x87\xa5\x55\x00\xd5\x67\xaa\xf0\x89\xbe\x94\xc3\x32\x8c\x2a\x98\x6c\xe0\x7d\xd0\x6d\xad\x14\xc0\xa0\xdb\xca\xfb\x9b\x4a\x51\x6f\x1c\x21\x53\x6b\xbd\x19\xc3\xcb\xc4\x62\x51\x59\x16\x5b\x8f\x27\x0a\xfb\xe9\x56\x57\x1e\xd0\xb4\x7f\xad\x30\x36\xce\x66\xed\x6f\x49\x28\x79\xc9\x8f\x91\xf9\xf2\x96\x91\x12\xb0\x15\x71\x4e\x85\x4c\xc2\x35\x09\xe4\x96\x97\x28\x55\xc0\x68\xba\x50\x66\xf2\x7c\xfe\xbb\x07\x60\x55\xc0\x93\xed\x76\xea\xa7\x29\x25\xf7\x02\xed\x60\xd9\x9a\x2d\x1d\xb0\x71\x89\xc8\xfa\x0d\xed\xf3\x83\x56\x85\x64\x6c\x8d\x6f\x24\xac\x6a\x3b\x5f\x2a\xcc\xde\x42\x98\xf3\x46\xc2\xaa\xb6\xde\xa5\xc2\x9c\x0d\x84\xe5\x2b\xb0\xe9\x85\xe5\x22\xa6\xfd\xfb\x5e\xeb\x1d\x36\xe4\x09\xf3\xdb\xf1\x18\x23\x14\x34\xec\x72\xa1\xcc\x18\xdb\x4c\x89\x8a\x5d\xd2\x30\xaa\x88\x76\x6a\x65\xa0\x31\x74\x02\xf4\x92\x10\x6f\x92\x68\x88\x22\xfd\xae\xa8\x3b\xc5\x11\xaf\x2b\xb8\xe2\x1e\x0f\x09\x58\x1f\xad\x19\xdb\xa6\x97\xbd\x4a\x7d\xc5\x52\x9c\x17\x1f\x04\xca\xf4\x8c\x38\xa2\xa1\x9c\x1c\x12\x97\x6c\x20\xa9\xe6\x1e\x65\x0f\x48\x26\x98\xae\x04\x8f\x74\x04\xf6\x99\x35\x69\xec\xf3\xd4\xfd\xf9\xb9\x73\x6e\x4d\xc9\xb9\xee\xf5\xfb\xe1\x75\xb6\x17\x5e\x3a\x80\xe2\xd2\xeb\x97\xcc\x6c\xdb\x20\x96\x35\xe4\xb8\xae\x95\x8a\xdf\x0f\xaf\xf3\xff\x39\xbf\x3e\xd5\x0d\x56\x59\xc3\x6d\xa2\x34\xac\xf7\x03\xaa\xbe\x15\xa8\xd9\x8f\xb5\x8d\x38\x99\x98\x26\x8b\xd0\x28\x9e\xa6\x98\x15\xab\x45\xe9\x80\xfd\xd6\xf7\xd5\xde\x84\x51\x42\xdf\x54\xde\x56\x55\x7e\x13\x79\xce\x9b\xca\xdb\xaa\xd6\xaf\x22\xef\x36\x51\x71\xa2\xb2\x6b\x13\x5d\xad\xf3\x03\xf3\xcc\x6d\x55\x7f\x8c\x10\xf8\xc0\x47\xa0\xc6\x08\x8f\xb1\xa7\x4d\xf2\x12\x3d\x57\xdb\xdb\xcf\xfa\x62\xa4\x70\x4f\x23\xfd\x65\x96\x0c\xe1\x8f\x9f\x1a\x87\xab\xa8\xf7\x23\x6d\x7e\xad\x69\x67\x8b\x4b\xa3\x32\x80\x58\xdb\x81\xd4\x86\x81\x3f\x77\xbb\x9c\x85\x92\x7b\xbd\x62\x84\x74\x46\xd3\xf3\x45\xc5\x82\x48\xbb\x96\x24\x7e\xde\xa9\xa3\xbe\xe1\xda\xc1\xba\x0a\x17\x94\x2d\xae\x94\x35\xd5\xda\x1b\xa8\xb5\x97\xa9\xb5\xf7\xa5\xd6\x2e\x51\xeb\xac\xa9\xd6\xd9\x40\xad\xb3\x4c\xad\xb3\x2f\xb5\x4e\xc7\x3f\xf8\x2f\x00\x00\xff\xff\x28\xeb\x52\x31\x42\x1c\x00\x00") - -func assetsVpcTemplateYmlBytes() ([]byte, error) { - return bindataRead( - _assetsVpcTemplateYml, - "assets/vpc-template.yml", - ) -} - -func assetsVpcTemplateYml() (*asset, error) { - bytes, err := assetsVpcTemplateYmlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "assets/vpc-template.yml", size: 7234, mode: os.FileMode(420), modTime: time.Unix(1484088279, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "assets/environment-template.yml": assetsEnvironmentTemplateYml, - "assets/vpc-template.yml": assetsVpcTemplateYml, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png -// then AssetDir("data") would return []string{"foo.txt", "img"} -// AssetDir("data/img") would return []string{"a.png", "b.png"} -// AssetDir("foo.txt") and AssetDir("notexist") would return an error -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - cannonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(cannonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} - -var _bintree = &bintree{nil, map[string]*bintree{ - "assets": &bintree{nil, map[string]*bintree{ - "environment-template.yml": &bintree{assetsEnvironmentTemplateYml, map[string]*bintree{}}, - "vpc-template.yml": &bintree{assetsVpcTemplateYml, map[string]*bintree{}}, - }}, -}} - -// RestoreAsset restores an asset under the given directory -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) - if err != nil { - return err - } - return nil -} - -// RestoreAssets restores an asset under the given directory recursively -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - cannonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) -} diff --git a/common/config.go b/common/config.go deleted file mode 100644 index 204befae..00000000 --- a/common/config.go +++ /dev/null @@ -1,19 +0,0 @@ -package common - -import ( - "gopkg.in/yaml.v2" - "log" -) - -func newConfig() *Config { - return new(Config) -} - -func (config *Config) loadFromYaml(yamlConfig []byte) *Config { - err := yaml.Unmarshal(yamlConfig, config) - if err != nil { - log.Panicf("Invalid config file: %v", err) - } - - return config -} diff --git a/common/context.go b/common/context.go index e1aecb49..fede3b91 100644 --- a/common/context.go +++ b/common/context.go @@ -1,8 +1,9 @@ package common import ( - "fmt" - "io/ioutil" + "bytes" + "gopkg.in/yaml.v2" + "io" ) // NewContext create a new context object @@ -11,25 +12,25 @@ func NewContext() *Context { return ctx } -// InitializeFromFile loads config object from local file -func (ctx *Context) InitializeFromFile(configFile string) { - yamlConfig, err := ioutil.ReadFile(configFile) +// Initialize loads config object +func (ctx *Context) Initialize(configReader io.Reader) error { + // load the configuration + err := loadYamlConfig(&ctx.Config, configReader) if err != nil { - fmt.Printf("WARN: Unable to find config file: %v\n", err) - } else { - ctx.Config.loadFromYaml(yamlConfig) + return err } - ctx.Initialize() -} - -// Initialize will create AWS services -func (ctx *Context) Initialize() error { - cfn, err := newCloudFormation(ctx.Config.Region) + // initialize StackManager + ctx.StackManager, err = newStackManager(ctx.Config.Region) if err != nil { return err } - ctx.CloudFormation = cfn return nil } + +func loadYamlConfig(config *Config, yamlReader io.Reader) error { + yamlBuffer := new(bytes.Buffer) + yamlBuffer.ReadFrom(yamlReader) + return yaml.Unmarshal(yamlBuffer.Bytes(), config) +} diff --git a/common/config_test.go b/common/context_test.go similarity index 75% rename from common/config_test.go rename to common/context_test.go index acaac2a3..3c32676c 100644 --- a/common/config_test.go +++ b/common/context_test.go @@ -1,17 +1,17 @@ package common import ( - "fmt" "github.com/stretchr/testify/assert" + "strings" "testing" ) -func TestNewConfig(t *testing.T) { +func TestNewContext(t *testing.T) { assert := assert.New(t) - config := newConfig() + context := NewContext() - assert.NotNil(config) + assert.NotNil(context) } func TestLoadYamlConfig(t *testing.T) { @@ -37,10 +37,11 @@ service: desiredCount: 2 ` - config := newConfig() - config.loadFromYaml([]byte(yamlConfig)) + context := NewContext() + config := &context.Config + err := loadYamlConfig(config, strings.NewReader(yamlConfig)) - fmt.Println(config) + assert.Nil(err) assert.NotNil(config) assert.Equal(2, len(config.Environments)) @@ -58,14 +59,12 @@ service: } func TestLoadBadYamlConfig(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Errorf("The code did not panic") - } - }() + assert := assert.New(t) yamlConfig := ` blah blah blah ` - config := newConfig() - config.loadFromYaml([]byte(yamlConfig)) + context := NewContext() + config := &context.Config + err := loadYamlConfig(config, strings.NewReader(yamlConfig)) + assert.NotNil(err) } diff --git a/common/stack.go b/common/stack.go index 006f5277..c0bab21f 100644 --- a/common/stack.go +++ b/common/stack.go @@ -1,77 +1,54 @@ package common import ( + "bytes" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" - "io/ioutil" - "os" - "text/template" + "io" ) -// Stack contains the data about a CloudFormation stack -type Stack struct { - Name string - Parameters map[string]string - TemplatePath string +// StackWaiter for waiting on stack status to be final +type StackWaiter interface { + AwaitFinalStatus(stackName string) string } -// NewStack will create a new stack instance -func NewStack(name string) *Stack { - return &Stack{ - Name: name, - Parameters: make(map[string]string), - TemplatePath: fmt.Sprintf("%s/%s.yml", os.TempDir(), name), - } -} -func newCloudFormation(region string) (cloudformationiface.CloudFormationAPI, error) { - sess, err := session.NewSession() - if err != nil { - return nil, err - } - return cloudformation.New(sess, &aws.Config{Region: aws.String(region)}), nil +// StackUpserter for applying changes to a stack +type StackUpserter interface { + UpsertStack(stackName string, templateBodyReader io.Reader, stackParameters map[string]string) error } -// WithParameter apply a parameter to the stack -func (stack *Stack) WithParameter(key string, value string) *Stack { - stack.Parameters[key] = value - return stack +// StackManager composite of all stack capabilities +type StackManager interface { + StackUpserter + StackWaiter } -// WriteTemplate will create a temp file with the template for a CFN stack -//go:generate go-bindata -pkg $GOPACKAGE -o assets.go assets/ -func (stack *Stack) WriteTemplate(assetName string, data interface{}) error { - asset, err := Asset(fmt.Sprintf("assets/%s", assetName)) - if err != nil { - return err - } - - tmpl, err := template.New(assetName).Parse(string(asset[:])) - if err != nil { - return err - } +type cloudformationStackManager struct { + cfnAPI cloudformationiface.CloudFormationAPI +} - templateOut, err := os.Create(stack.TemplatePath) - defer templateOut.Close() - if err != nil { - return err - } +// TODO: support "dry-run" and write the template to a file +// fmt.Sprintf("%s/%s.yml", os.TempDir(), name), - err = tmpl.Execute(templateOut, data) +// NewStackManager creates a new StackManager backed by cloudformation +func newStackManager(region string) (StackManager, error) { + sess, err := session.NewSession() if err != nil { - return err + return nil, err } - - templateOut.Sync() - return nil + cfn := cloudformation.New(sess, &aws.Config{Region: aws.String(region)}) + return &cloudformationStackManager{ + cfnAPI: cfn, + }, nil } -func (stack *Stack) buildParameters() []*cloudformation.Parameter { - parameters := make([]*cloudformation.Parameter, 0, len(stack.Parameters)) - for key, value := range stack.Parameters { +func buildStackParameters(stackParameters map[string]string) []*cloudformation.Parameter { + parameters := make([]*cloudformation.Parameter, 0, len(stackParameters)) + for key, value := range stackParameters { parameters = append(parameters, &cloudformation.Parameter{ ParameterKey: aws.String(key), @@ -82,44 +59,51 @@ func (stack *Stack) buildParameters() []*cloudformation.Parameter { } // UpsertStack will create/update the cloudformation stack -func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) error { - stackStatus := stack.AwaitFinalStatus(cfn) +func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, templateBodyReader io.Reader, stackParameters map[string]string) error { + stackStatus := cfnMgr.AwaitFinalStatus(stackName) + + // load the template + templateBodyBytes := new(bytes.Buffer) + templateBodyBytes.ReadFrom(templateBodyReader) + templateBody := aws.String(templateBodyBytes.String()) + + cfnAPI := cfnMgr.cfnAPI if stackStatus == "" { - fmt.Printf("creating stack: %s\n", stack.Name) + fmt.Printf("creating stack: %s\n", stackName) params := &cloudformation.CreateStackInput{ - StackName: aws.String(stack.Name), + StackName: aws.String(stackName), Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, - Parameters: stack.buildParameters(), - TemplateBody: aws.String(stack.readTemplatePath()), + Parameters: buildStackParameters(stackParameters), + TemplateBody: templateBody, } - _, err := cfn.CreateStack(params) + _, err := cfnAPI.CreateStack(params) if err != nil { return err } waitParams := &cloudformation.DescribeStacksInput{ - StackName: aws.String(stack.Name), + StackName: aws.String(stackName), } - cfn.WaitUntilStackExists(waitParams) + cfnAPI.WaitUntilStackExists(waitParams) } else { - fmt.Printf("updating stack: %s\n", stack.Name) + fmt.Printf("updating stack: %s\n", stackName) params := &cloudformation.UpdateStackInput{ - StackName: aws.String(stack.Name), + StackName: aws.String(stackName), Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, - Parameters: stack.buildParameters(), - TemplateBody: aws.String(stack.readTemplatePath()), + Parameters: buildStackParameters(stackParameters), + TemplateBody: templateBody, } - _, err := cfn.UpdateStack(params) + _, err := cfnAPI.UpdateStack(params) if err != nil { if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "ValidationError" && awsErr.Message() == "No updates are to be performed." { - fmt.Printf("No changes for stack: %s\n", stack.Name) + fmt.Printf("No changes for stack: %s\n", stackName) return nil } } @@ -128,24 +112,17 @@ func (stack *Stack) UpsertStack(cfn cloudformationiface.CloudFormationAPI) error } } - stack.AwaitFinalStatus(cfn) return nil } -func (stack *Stack) readTemplatePath() string { - templateBytes, err := ioutil.ReadFile(stack.TemplatePath) - if err != nil { - return "" - } - return string(templateBytes) -} - // AwaitFinalStatus waits for the stack to arrive in a final status -func (stack *Stack) AwaitFinalStatus(cfn cloudformationiface.CloudFormationAPI) string { +// returns: final status, or empty string if stack doesn't exist +func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) string { + cfnAPI := cfnMgr.cfnAPI params := &cloudformation.DescribeStacksInput{ - StackName: aws.String(stack.Name), + StackName: aws.String(stackName), } - resp, err := cfn.DescribeStacks(params) + resp, err := cfnAPI.DescribeStacks(params) if err == nil && resp != nil && len(resp.Stacks) == 1 { switch *resp.Stacks[0].StackStatus { @@ -153,19 +130,19 @@ func (stack *Stack) AwaitFinalStatus(cfn cloudformationiface.CloudFormationAPI) cloudformation.StackStatusCreateInProgress, cloudformation.StackStatusRollbackInProgress: // wait for create - cfn.WaitUntilStackCreateComplete(params) - resp, err = cfn.DescribeStacks(params) + cfnAPI.WaitUntilStackCreateComplete(params) + resp, err = cfnAPI.DescribeStacks(params) case cloudformation.StackStatusDeleteInProgress: // wait for delete - cfn.WaitUntilStackDeleteComplete(params) - resp, err = cfn.DescribeStacks(params) + cfnAPI.WaitUntilStackDeleteComplete(params) + resp, err = cfnAPI.DescribeStacks(params) case cloudformation.StackStatusUpdateInProgress, cloudformation.StackStatusUpdateRollbackInProgress, cloudformation.StackStatusUpdateCompleteCleanupInProgress, cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress: // wait for update - cfn.WaitUntilStackUpdateComplete(params) - resp, err = cfn.DescribeStacks(params) + cfnAPI.WaitUntilStackUpdateComplete(params) + resp, err = cfnAPI.DescribeStacks(params) case cloudformation.StackStatusCreateFailed, cloudformation.StackStatusCreateComplete, cloudformation.StackStatusRollbackFailed, diff --git a/common/stack_test.go b/common/stack_test.go index 45517908..3a25d5b7 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -6,27 +6,28 @@ import ( "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" "github.com/stretchr/testify/assert" + "strings" "testing" ) type mockedCloudFormation struct { cloudformationiface.CloudFormationAPI responses []*cloudformation.DescribeStacksOutput - responseIndex int - createIndex int - updateIndex int + responseCount int + createCount int + updateCount int waitUntilStackExists int waitUntilStackCreateComplete int waitUntilStackUpdateComplete int } func (c *mockedCloudFormation) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) { - if c.responseIndex >= len(c.responses) { + if c.responseCount >= len(c.responses) { return nil, errors.New("stack not found") } - resp := c.responses[c.responseIndex] - c.responseIndex = c.responseIndex + 1 + resp := c.responses[c.responseCount] + c.responseCount = c.responseCount + 1 if resp == nil { return nil, errors.New("stack not found") } @@ -46,37 +47,16 @@ func (c *mockedCloudFormation) WaitUntilStackExists(*cloudformation.DescribeStac return nil } func (c *mockedCloudFormation) CreateStack(*cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) { - c.createIndex = c.createIndex + 1 + c.createCount = c.createCount + 1 return nil, nil } func (c *mockedCloudFormation) UpdateStack(*cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) { - c.updateIndex = c.updateIndex + 1 + c.updateCount = c.updateCount + 1 return nil, nil } -func TestNewStack(t *testing.T) { - assert := assert.New(t) - stack := NewStack("foo") - - assert.NotNil(stack) - assert.Equal("foo", stack.Name) -} - -func TestStack_WriteTemplate(t *testing.T) { - assert := assert.New(t) - - stack := NewStack("foo") - env := &Environment{} - err := stack.WriteTemplate("environment-template.yml", env) - assert.Nil(err) - - template := stack.readTemplatePath() - assert.NotNil(template) -} - func TestStack_AwaitFinalStatus_CreateComplete(t *testing.T) { assert := assert.New(t) - stack := NewStack("foo") cfn := new(mockedCloudFormation) cfn.responses = []*cloudformation.DescribeStacksOutput{ @@ -89,15 +69,18 @@ func TestStack_AwaitFinalStatus_CreateComplete(t *testing.T) { }, } - finalStatus := stack.AwaitFinalStatus(cfn) + stackManager := cloudformationStackManager{ + cfnAPI: cfn, + } + + finalStatus := stackManager.AwaitFinalStatus("foo") assert.Equal(cloudformation.StackStatusCreateComplete, finalStatus) - assert.Equal(1, cfn.responseIndex) + assert.Equal(1, cfn.responseCount) } func TestStack_AwaitFinalStatus_CreateInProgress(t *testing.T) { assert := assert.New(t) - stack := NewStack("foo") cfn := new(mockedCloudFormation) cfn.responses = []*cloudformation.DescribeStacksOutput{ @@ -117,48 +100,38 @@ func TestStack_AwaitFinalStatus_CreateInProgress(t *testing.T) { }, } - finalStatus := stack.AwaitFinalStatus(cfn) + stackManager := cloudformationStackManager{ + cfnAPI: cfn, + } + + finalStatus := stackManager.AwaitFinalStatus("foo") assert.Equal(cloudformation.StackStatusCreateComplete, finalStatus) - assert.Equal(2, cfn.responseIndex) + assert.Equal(2, cfn.responseCount) assert.Equal(1, cfn.waitUntilStackCreateComplete) } func TestStack_UpsertStack_Create(t *testing.T) { assert := assert.New(t) - stack := NewStack("foo") cfn := new(mockedCloudFormation) cfn.responses = []*cloudformation.DescribeStacksOutput{ nil, - &cloudformation.DescribeStacksOutput{ - Stacks: []*cloudformation.Stack{ - &cloudformation.Stack{ - StackStatus: aws.String(cloudformation.StackStatusCreateInProgress), - }, - }, - }, - &cloudformation.DescribeStacksOutput{ - Stacks: []*cloudformation.Stack{ - &cloudformation.Stack{ - StackStatus: aws.String(cloudformation.StackStatusCreateComplete), - }, - }, - }, } - err := stack.UpsertStack(cfn) + stackManager := cloudformationStackManager{ + cfnAPI: cfn, + } + err := stackManager.UpsertStack("foo", strings.NewReader(""), nil) assert.Nil(err) assert.Equal(1, cfn.waitUntilStackExists) - assert.Equal(1, cfn.createIndex) - assert.Equal(1, cfn.waitUntilStackCreateComplete) - assert.Equal(3, cfn.responseIndex) + assert.Equal(1, cfn.createCount) + assert.Equal(1, cfn.responseCount) } func TestStack_UpsertStack_Update(t *testing.T) { assert := assert.New(t) - stack := NewStack("foo") cfn := new(mockedCloudFormation) cfn.responses = []*cloudformation.DescribeStacksOutput{ @@ -169,41 +142,32 @@ func TestStack_UpsertStack_Update(t *testing.T) { }, }, }, - &cloudformation.DescribeStacksOutput{ - Stacks: []*cloudformation.Stack{ - &cloudformation.Stack{ - StackStatus: aws.String(cloudformation.StackStatusUpdateInProgress), - }, - }, - }, - &cloudformation.DescribeStacksOutput{ - Stacks: []*cloudformation.Stack{ - &cloudformation.Stack{ - StackStatus: aws.String(cloudformation.StackStatusUpdateComplete), - }, - }, - }, } - err := stack.UpsertStack(cfn) + stackManager := cloudformationStackManager{ + cfnAPI: cfn, + } + err := stackManager.UpsertStack("foo", strings.NewReader(""), nil) assert.Nil(err) assert.Equal(0, cfn.waitUntilStackExists) - assert.Equal(0, cfn.createIndex) - assert.Equal(1, cfn.updateIndex) - assert.Equal(1, cfn.waitUntilStackUpdateComplete) - assert.Equal(3, cfn.responseIndex) + assert.Equal(0, cfn.createCount) + assert.Equal(1, cfn.updateCount) + assert.Equal(0, cfn.waitUntilStackUpdateComplete) + assert.Equal(1, cfn.responseCount) } -func TestStack_WithParameter(t *testing.T) { +func TestBuildParameters(t *testing.T) { assert := assert.New(t) - stack := NewStack("foo") - parameters := stack.buildParameters() + paramMap := make(map[string]string) + + parameters := buildStackParameters(paramMap) assert.Equal(0, len(parameters)) - stack.WithParameter("p1", "value 1").WithParameter("p2", "value 2") - parameters = stack.buildParameters() + paramMap["p1"] = "value 1" + paramMap["p2"] = "value 2" + parameters = buildStackParameters(paramMap) assert.Equal(2, len(parameters)) assert.Contains(*parameters[0].ParameterKey, "p") assert.Contains(*parameters[0].ParameterValue, "value") diff --git a/common/types.go b/common/types.go index 48bbad91..1f5e2ef2 100644 --- a/common/types.go +++ b/common/types.go @@ -1,13 +1,9 @@ package common -import ( - "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" -) - // Context defines the context object passed around type Context struct { - Config Config - CloudFormation cloudformationiface.CloudFormationAPI + Config Config + StackManager StackManager } // Config defines the structure of the yml file for the mu config diff --git a/resources/environment.go b/resources/environment.go deleted file mode 100644 index ee5e0264..00000000 --- a/resources/environment.go +++ /dev/null @@ -1,117 +0,0 @@ -package resources - -import ( - "fmt" - "github.com/stelligent/mu/common" - "strings" -) - -// EnvironmentManager defines the env that will be created -type EnvironmentManager interface { - UpsertEnvironment(environmentName string) error -} - -// NewEnvironmentManager will construct a manager for environments -func NewEnvironmentManager(ctx *common.Context) EnvironmentManager { - environmentManager := new(environmentManagerImpl) - environmentManager.context = ctx - return environmentManager -} - -// UpsertEnvironment will create a new stack instance and write the template for the stack -func (environmentManager *environmentManagerImpl) UpsertEnvironment(environmentName string) error { - env, err := environmentManager.getEnvironment(environmentName) - if err != nil { - return err - } - - if env.VpcTarget.VpcID == "" { - // no target VPC, we manage it - err = environmentManager.upsertVpc(env) - if err != nil { - return err - } - } - - err = environmentManager.upsertEcsCluster(env) - if err != nil { - return err - } - - return nil -} - -type environmentManagerImpl struct { - context *common.Context -} - -func (environmentManager *environmentManagerImpl) getEnvironment(environmentName string) (*common.Environment, error) { - ctx := environmentManager.context - for _, e := range ctx.Config.Environments { - if strings.EqualFold(environmentName, e.Name) { - return &e, nil - } - } - - return nil, fmt.Errorf("Unable to find environment named '%s' in mu.yml", environmentName) -} - -func buildVpcStackName(env *common.Environment) string { - return fmt.Sprintf("mu-vpc-%s", env.Name) -} -func buildEnvironmentStackName(env *common.Environment) string { - return fmt.Sprintf("mu-env-%s", env.Name) -} - -func (environmentManager *environmentManagerImpl) upsertVpc(env *common.Environment) error { - // generate the CFN template - stack := common.NewStack(buildVpcStackName(env)) - - err := stack.WriteTemplate("vpc-template.yml", env) - if err != nil { - return err - } - - // create/update the stack - fmt.Printf("upserting VPC environment:%s stack:%s path:%s\n", env.Name, stack.Name, stack.TemplatePath) - err = stack.UpsertStack(environmentManager.context.CloudFormation) - if err != nil { - return err - } - - return nil -} - -func (environmentManager *environmentManagerImpl) upsertEcsCluster(env *common.Environment) error { - // generate the CFN template - stack := common.NewStack(buildEnvironmentStackName(env)) - - if env.VpcTarget.VpcID == "" { - // apply default parameters since we manage the VPC - vpcStackName := buildVpcStackName(env) - stack.WithParameter("VpcId", fmt.Sprintf("%s-VpcId", vpcStackName)) - stack.WithParameter("PublicSubnetAZ1Id", fmt.Sprintf("%s-PublicSubnetAZ1Id", vpcStackName)) - stack.WithParameter("PublicSubnetAZ2Id", fmt.Sprintf("%s-PublicSubnetAZ2Id", vpcStackName)) - stack.WithParameter("PublicSubnetAZ3Id", fmt.Sprintf("%s-PublicSubnetAZ3Id", vpcStackName)) - } else { - // target VPC referenced from config - stack.WithParameter("VpcId", env.VpcTarget.VpcID) - for index, subnet := range env.VpcTarget.PublicSubnetIds { - stack.WithParameter(fmt.Sprintf("PublicSubnetAZ%dId", index+1), subnet) - } - } - - err := stack.WriteTemplate("environment-template.yml", env) - if err != nil { - return err - } - - // create/update the stack - fmt.Printf("upserting environment:%s stack:%s path:%s\n", env.Name, stack.Name, stack.TemplatePath) - err = stack.UpsertStack(environmentManager.context.CloudFormation) - if err != nil { - return err - } - - return nil -} diff --git a/resources/environment_test.go b/resources/environment_test.go deleted file mode 100644 index 4c53ea37..00000000 --- a/resources/environment_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package resources - -import ( - "github.com/stretchr/testify/assert" - "testing" - - "github.com/stelligent/mu/common" -) - -func TestEnvironment_UpsertEnvironmentUnknown(t *testing.T) { - assert := assert.New(t) - ctx := common.NewContext() - envMgr := NewEnvironmentManager(ctx) - - err := envMgr.UpsertEnvironment("test-stack-that-is-fake") - - assert.NotNil(err) -} - -func TestGetEnvironment(t *testing.T) { - assert := assert.New(t) - envMgr := new(environmentManagerImpl) - envMgr.context = common.NewContext() - - env1 := common.Environment{ - Name: "foo", - } - env2 := common.Environment{ - Name: "bar", - } - envMgr.context.Config.Environments = []common.Environment{env1, env2} - - fooEnv, fooErr := envMgr.getEnvironment("foo") - assert.Equal("foo", fooEnv.Name) - assert.Nil(fooErr) - - barEnv, barErr := envMgr.getEnvironment("BAR") - assert.Equal("bar", barEnv.Name) - assert.Nil(barErr) - - bazEnv, bazErr := envMgr.getEnvironment("baz") - assert.Nil(bazEnv) - assert.NotNil(bazErr) -} diff --git a/common/assets/environment-template.yml b/templates/assets/cluster.yml similarity index 100% rename from common/assets/environment-template.yml rename to templates/assets/cluster.yml diff --git a/common/assets/vpc-template.yml b/templates/assets/vpc.yml similarity index 100% rename from common/assets/vpc-template.yml rename to templates/assets/vpc.yml diff --git a/templates/template.go b/templates/template.go new file mode 100644 index 00000000..b4e71f86 --- /dev/null +++ b/templates/template.go @@ -0,0 +1,33 @@ +package templates + +import ( + "bufio" + "bytes" + "fmt" + "io" + "text/template" +) + +// NewTemplate will create a temp file with the template for a CFN stack +//go:generate go-bindata -pkg $GOPACKAGE -o assets.go assets/ +func NewTemplate(assetName string, data interface{}) (io.Reader, error) { + asset, err := Asset(fmt.Sprintf("assets/%s", assetName)) + if err != nil { + return nil, err + } + + tmpl, err := template.New(assetName).Parse(string(asset[:])) + if err != nil { + return nil, err + } + + buf := new(bytes.Buffer) + bufWriter := bufio.NewWriter(buf) + + err = tmpl.Execute(bufWriter, data) + if err != nil { + return nil, err + } + + return buf, nil +} diff --git a/templates/template_test.go b/templates/template_test.go new file mode 100644 index 00000000..5ba9f75d --- /dev/null +++ b/templates/template_test.go @@ -0,0 +1,40 @@ +package templates + +import ( + "bytes" + "github.com/aws/aws-sdk-go/aws" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewTemplate(t *testing.T) { + assert := assert.New(t) + + environment := new(common.Environment) + + templates := []string{"cluster.yml", "vpc.yml"} + for _, templateName := range templates { + templateBodyReader, err := NewTemplate(templateName, environment) + + assert.Nil(err) + assert.NotNil(templateBodyReader) + + templateBodyBytes := new(bytes.Buffer) + templateBodyBytes.ReadFrom(templateBodyReader) + templateBody := aws.String(templateBodyBytes.String()) + + assert.NotNil(templateBody) + assert.NotEmpty(templateBody) + } +} + +func TestNewTemplate_invalid(t *testing.T) { + assert := assert.New(t) + + environment := new(common.Environment) + + templateBodyReader, err := NewTemplate("invalid-template-name.yml", environment) + assert.Nil(templateBodyReader) + assert.NotNil(err) +} diff --git a/workflows/environment.go b/workflows/environment.go new file mode 100644 index 00000000..22344659 --- /dev/null +++ b/workflows/environment.go @@ -0,0 +1,90 @@ +package workflows + +import ( + "fmt" + "github.com/stelligent/mu/common" + "github.com/stelligent/mu/templates" + "strings" +) + +// NewEnvironmentUpserter create a new workflow for upserting an environment +func NewEnvironmentUpserter(ctx *common.Context, environmentName string) Executor { + + var environment *common.Environment + var vpcImportParams = make(map[string]string) + + return newWorkflow( + environmentFinder(&environment, &ctx.Config, environmentName), + environmentVpcUpserter(environment, vpcImportParams, ctx.StackManager, ctx.StackManager), + environmentEcsUpserter(environment, vpcImportParams, ctx.StackManager, ctx.StackManager), + ) +} + +// Find an environment in config, by name and set the reference +func environmentFinder(environment **common.Environment, config *common.Config, environmentName string) Executor { + + return func() error { + for _, e := range config.Environments { + if strings.EqualFold(e.Name, environmentName) { + *environment = &e + return nil + } + } + return fmt.Errorf("Unable to find environment named '%s' in configuration", environmentName) + } +} + +func environmentVpcUpserter(environment *common.Environment, vpcImportParams map[string]string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { + return func() error { + if environment.VpcTarget.VpcID == "" { + vpcStackName := fmt.Sprintf("mu-vpc-%s", environment.Name) + + // no target VPC, we need to create/update the VPC stack + fmt.Printf("upserting VPC environment:%s stack:%s\n", environment.Name, vpcStackName) + template, err := templates.NewTemplate("cluster.yml", environment) + if err != nil { + return err + } + err = stackUpserter.UpsertStack(vpcStackName, template, nil) + if err != nil { + return err + } + + stackWaiter.AwaitFinalStatus(vpcStackName) + + // apply default parameters since we manage the VPC + vpcImportParams["VpcId"] = fmt.Sprintf("%s-VpcId", vpcStackName) + vpcImportParams["PublicSubnetAZ1Id"] = fmt.Sprintf("%s-PublicSubnetAZ1Id", vpcStackName) + vpcImportParams["PublicSubnetAZ2Id"] = fmt.Sprintf("%s-PublicSubnetAZ2Id", vpcStackName) + vpcImportParams["PublicSubnetAZ3Id"] = fmt.Sprintf("%s-PublicSubnetAZ3Id", vpcStackName) + } else { + // target VPC referenced from config + vpcImportParams["VpcId"] = environment.VpcTarget.VpcID + for index, subnet := range environment.VpcTarget.PublicSubnetIds { + vpcImportParams[fmt.Sprintf("PublicSubnetAZ%dId", index+1)] = subnet + } + } + + return nil + } +} + +func environmentEcsUpserter(environment *common.Environment, vpcImportParams map[string]string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { + return func() error { + envStackName := fmt.Sprintf("mu-env-%s", environment.Name) + + fmt.Printf("upserting ECS environment:%s stack:%s\n", environment.Name, envStackName) + template, err := templates.NewTemplate("cluster.yml", environment) + if err != nil { + return err + } + + err = stackUpserter.UpsertStack(envStackName, template, vpcImportParams) + if err != nil { + return err + } + stackWaiter.AwaitFinalStatus(envStackName) + + return nil + } +} diff --git a/workflows/environment_test.go b/workflows/environment_test.go new file mode 100644 index 00000000..16e18a0c --- /dev/null +++ b/workflows/environment_test.go @@ -0,0 +1,132 @@ +package workflows + +import ( + "bytes" + "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "gopkg.in/yaml.v2" + "io" + "testing" +) + +func TestEnvironmentFinder(t *testing.T) { + assert := assert.New(t) + + env1 := common.Environment{ + Name: "foo", + } + env2 := common.Environment{ + Name: "bar", + } + config := new(common.Config) + config.Environments = []common.Environment{env1, env2} + + var environment *common.Environment + + environment = nil + fooErr := environmentFinder(&environment, config, "foo")() + assert.NotNil(environment) + assert.Equal("foo", environment.Name) + assert.Nil(fooErr) + + environment = nil + barErr := environmentFinder(&environment, config, "bar")() + assert.NotNil(environment) + assert.Equal("bar", environment.Name) + assert.Nil(barErr) + + environment = nil + bazErr := environmentFinder(&environment, config, "baz")() + assert.Nil(environment) + assert.NotNil(bazErr) +} + +func TestNewEnvironmentUpserter(t *testing.T) { + assert := assert.New(t) + ctx := common.NewContext() + upserter := NewEnvironmentUpserter(ctx, "foo") + assert.NotNil(upserter) +} + +type mockedStackManager struct { + mock.Mock +} + +func (m *mockedStackManager) AwaitFinalStatus(stackName string) string { + args := m.Called(stackName) + return args.String(0) +} +func (m *mockedStackManager) UpsertStack(stackName string, templateBodyReader io.Reader, stackParameters map[string]string) error { + args := m.Called(stackName) + return args.Error(0) +} + +func TestEnvironmentVpcUpserter(t *testing.T) { + assert := assert.New(t) + + environment := &common.Environment{ + Name: "foo", + } + + vpcInputParams := make(map[string]string) + + stackManager := new(mockedStackManager) + stackManager.On("AwaitFinalStatus", "mu-vpc-foo").Return(cloudformation.StackStatusCreateComplete) + stackManager.On("UpsertStack", "mu-vpc-foo").Return(nil) + + err := environmentVpcUpserter(environment, vpcInputParams, stackManager, stackManager)() + assert.Nil(err) + assert.Equal("mu-vpc-foo-VpcId", vpcInputParams["VpcId"]) + assert.Equal("mu-vpc-foo-PublicSubnetAZ1Id", vpcInputParams["PublicSubnetAZ1Id"]) + assert.Equal("mu-vpc-foo-PublicSubnetAZ2Id", vpcInputParams["PublicSubnetAZ2Id"]) + assert.Equal("mu-vpc-foo-PublicSubnetAZ3Id", vpcInputParams["PublicSubnetAZ3Id"]) + + stackManager.AssertExpectations(t) + stackManager.AssertNumberOfCalls(t, "AwaitFinalStatus", 1) + stackManager.AssertNumberOfCalls(t, "UpsertStack", 1) +} + +func TestEnvironmentVpcUpserter_Unmanaged(t *testing.T) { + assert := assert.New(t) + yamlConfig := + ` +--- +environments: + - name: dev + vpcTarget: + vpcId: myVpcId + publicSubnetIds: + - mySubnetId1 + - mySubnetId2 +` + config, err := loadYamlConfig(yamlConfig) + assert.Nil(err) + + vpcInputParams := make(map[string]string) + + stackManager := new(mockedStackManager) + + err = environmentVpcUpserter(&config.Environments[0], vpcInputParams, stackManager, stackManager)() + assert.Nil(err) + assert.Equal("myVpcId", vpcInputParams["VpcId"]) + assert.Equal("mySubnetId1", vpcInputParams["PublicSubnetAZ1Id"]) + assert.Equal("mySubnetId2", vpcInputParams["PublicSubnetAZ2Id"]) + + stackManager.AssertExpectations(t) + stackManager.AssertNumberOfCalls(t, "AwaitFinalStatus", 0) + stackManager.AssertNumberOfCalls(t, "UpsertStack", 0) +} + +func loadYamlConfig(yamlString string) (*common.Config, error) { + config := new(common.Config) + yamlBuffer := new(bytes.Buffer) + yamlBuffer.ReadFrom(bytes.NewBufferString(yamlString)) + err := yaml.Unmarshal(yamlBuffer.Bytes(), config) + if err != nil { + return nil, err + } + + return config, nil +} diff --git a/workflows/executor.go b/workflows/executor.go new file mode 100644 index 00000000..d9f6b5d4 --- /dev/null +++ b/workflows/executor.go @@ -0,0 +1,16 @@ +package workflows + +// Executor define contract for the steps of a workflow +type Executor func() error + +func newWorkflow(exectors ...Executor) Executor { + return func() error { + for _, executor := range exectors { + err := executor() + if err != nil { + return err + } + } + return nil + } +} diff --git a/workflows/executor_test.go b/workflows/executor_test.go new file mode 100644 index 00000000..5a6539ab --- /dev/null +++ b/workflows/executor_test.go @@ -0,0 +1,35 @@ +package workflows + +import ( + "errors" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewWorkflow(t *testing.T) { + assert := assert.New(t) + + // empty + emptyWorkflow := newWorkflow() + assert.Nil(emptyWorkflow()) + + // error case + errorWorkflow := newWorkflow(func() error { + return errors.New("error occured") + }) + assert.NotNil(errorWorkflow()) + + // multiple success case + runcount := 0 + successWorkflow := newWorkflow( + func() error { + runcount = runcount + 1 + return nil + }, + func() error { + runcount = runcount + 1 + return nil + }) + assert.Nil(successWorkflow()) + assert.Equal(2, runcount) +} From 7e5adb999ec40c6936a93a735482efae39c1a334 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 12 Jan 2017 13:28:52 -0800 Subject: [PATCH 22/31] Complete unit testing of workflow for environment creation --- Makefile | 2 +- common/stack.go | 1 - workflows/environment.go | 26 +++++++++------- workflows/environment_test.go | 56 +++++++++++++++++++++++++---------- workflows/executor.go | 4 +-- 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index ed72c5fc..ee5dd3ef 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ lint: setup test: lint @echo "=== testing ===" - go test ./... + go test -cover ./... build: test $(BUILD_FILES) diff --git a/common/stack.go b/common/stack.go index c0bab21f..feb38ba8 100644 --- a/common/stack.go +++ b/common/stack.go @@ -107,7 +107,6 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template return nil } } - fmt.Println(err) return err } diff --git a/workflows/environment.go b/workflows/environment.go index 22344659..332a1801 100644 --- a/workflows/environment.go +++ b/workflows/environment.go @@ -10,23 +10,27 @@ import ( // NewEnvironmentUpserter create a new workflow for upserting an environment func NewEnvironmentUpserter(ctx *common.Context, environmentName string) Executor { - var environment *common.Environment - var vpcImportParams = make(map[string]string) + workflow := new(environmentWorkflow) + vpcImportParams := make(map[string]string) return newWorkflow( - environmentFinder(&environment, &ctx.Config, environmentName), - environmentVpcUpserter(environment, vpcImportParams, ctx.StackManager, ctx.StackManager), - environmentEcsUpserter(environment, vpcImportParams, ctx.StackManager, ctx.StackManager), + workflow.environmentFinder(&ctx.Config, environmentName), + workflow.environmentVpcUpserter(vpcImportParams, ctx.StackManager, ctx.StackManager), + workflow.environmentEcsUpserter(vpcImportParams, ctx.StackManager, ctx.StackManager), ) } +type environmentWorkflow struct { + environment *common.Environment +} + // Find an environment in config, by name and set the reference -func environmentFinder(environment **common.Environment, config *common.Config, environmentName string) Executor { +func (workflow *environmentWorkflow) environmentFinder(config *common.Config, environmentName string) Executor { return func() error { for _, e := range config.Environments { if strings.EqualFold(e.Name, environmentName) { - *environment = &e + workflow.environment = &e return nil } } @@ -34,14 +38,15 @@ func environmentFinder(environment **common.Environment, config *common.Config, } } -func environmentVpcUpserter(environment *common.Environment, vpcImportParams map[string]string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { +func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[string]string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { return func() error { + environment := workflow.environment if environment.VpcTarget.VpcID == "" { vpcStackName := fmt.Sprintf("mu-vpc-%s", environment.Name) // no target VPC, we need to create/update the VPC stack fmt.Printf("upserting VPC environment:%s stack:%s\n", environment.Name, vpcStackName) - template, err := templates.NewTemplate("cluster.yml", environment) + template, err := templates.NewTemplate("vpc.yml", environment) if err != nil { return err } @@ -69,8 +74,9 @@ func environmentVpcUpserter(environment *common.Environment, vpcImportParams map } } -func environmentEcsUpserter(environment *common.Environment, vpcImportParams map[string]string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { +func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[string]string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { return func() error { + environment := workflow.environment envStackName := fmt.Sprintf("mu-env-%s", environment.Name) fmt.Printf("upserting ECS environment:%s stack:%s\n", environment.Name, envStackName) diff --git a/workflows/environment_test.go b/workflows/environment_test.go index 16e18a0c..9d2aa7c4 100644 --- a/workflows/environment_test.go +++ b/workflows/environment_test.go @@ -23,23 +23,23 @@ func TestEnvironmentFinder(t *testing.T) { config := new(common.Config) config.Environments = []common.Environment{env1, env2} - var environment *common.Environment + workflow := new(environmentWorkflow) - environment = nil - fooErr := environmentFinder(&environment, config, "foo")() - assert.NotNil(environment) - assert.Equal("foo", environment.Name) + workflow.environment = nil + fooErr := workflow.environmentFinder(config, "foo")() + assert.NotNil(workflow.environment) + assert.Equal("foo", workflow.environment.Name) assert.Nil(fooErr) - environment = nil - barErr := environmentFinder(&environment, config, "bar")() - assert.NotNil(environment) - assert.Equal("bar", environment.Name) + workflow.environment = nil + barErr := workflow.environmentFinder(config, "bar")() + assert.NotNil(workflow.environment) + assert.Equal("bar", workflow.environment.Name) assert.Nil(barErr) - environment = nil - bazErr := environmentFinder(&environment, config, "baz")() - assert.Nil(environment) + workflow.environment = nil + bazErr := workflow.environmentFinder(config, "baz")() + assert.Nil(workflow.environment) assert.NotNil(bazErr) } @@ -63,10 +63,33 @@ func (m *mockedStackManager) UpsertStack(stackName string, templateBodyReader io return args.Error(0) } +func TestEnvironmentEcsUpserter(t *testing.T) { + assert := assert.New(t) + + workflow := new(environmentWorkflow) + workflow.environment = &common.Environment{ + Name: "foo", + } + + vpcInputParams := make(map[string]string) + + stackManager := new(mockedStackManager) + stackManager.On("AwaitFinalStatus", "mu-env-foo").Return(cloudformation.StackStatusCreateComplete) + stackManager.On("UpsertStack", "mu-env-foo").Return(nil) + + err := workflow.environmentEcsUpserter(vpcInputParams, stackManager, stackManager)() + assert.Nil(err) + + stackManager.AssertExpectations(t) + stackManager.AssertNumberOfCalls(t, "AwaitFinalStatus", 1) + stackManager.AssertNumberOfCalls(t, "UpsertStack", 1) +} + func TestEnvironmentVpcUpserter(t *testing.T) { assert := assert.New(t) - environment := &common.Environment{ + workflow := new(environmentWorkflow) + workflow.environment = &common.Environment{ Name: "foo", } @@ -76,7 +99,7 @@ func TestEnvironmentVpcUpserter(t *testing.T) { stackManager.On("AwaitFinalStatus", "mu-vpc-foo").Return(cloudformation.StackStatusCreateComplete) stackManager.On("UpsertStack", "mu-vpc-foo").Return(nil) - err := environmentVpcUpserter(environment, vpcInputParams, stackManager, stackManager)() + err := workflow.environmentVpcUpserter(vpcInputParams, stackManager, stackManager)() assert.Nil(err) assert.Equal("mu-vpc-foo-VpcId", vpcInputParams["VpcId"]) assert.Equal("mu-vpc-foo-PublicSubnetAZ1Id", vpcInputParams["PublicSubnetAZ1Id"]) @@ -108,7 +131,10 @@ environments: stackManager := new(mockedStackManager) - err = environmentVpcUpserter(&config.Environments[0], vpcInputParams, stackManager, stackManager)() + workflow := new(environmentWorkflow) + workflow.environment = &config.Environments[0] + + err = workflow.environmentVpcUpserter(vpcInputParams, stackManager, stackManager)() assert.Nil(err) assert.Equal("myVpcId", vpcInputParams["VpcId"]) assert.Equal("mySubnetId1", vpcInputParams["PublicSubnetAZ1Id"]) diff --git a/workflows/executor.go b/workflows/executor.go index d9f6b5d4..63285206 100644 --- a/workflows/executor.go +++ b/workflows/executor.go @@ -3,9 +3,9 @@ package workflows // Executor define contract for the steps of a workflow type Executor func() error -func newWorkflow(exectors ...Executor) Executor { +func newWorkflow(executors ...Executor) Executor { return func() error { - for _, executor := range exectors { + for _, executor := range executors { err := executor() if err != nil { return err From 231806641fabeae649a7db6480637fd1d8b5cedf Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Thu, 12 Jan 2017 13:49:48 -0800 Subject: [PATCH 23/31] missing file --- templates/assets.go | 260 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 templates/assets.go diff --git a/templates/assets.go b/templates/assets.go new file mode 100644 index 00000000..7b445a09 --- /dev/null +++ b/templates/assets.go @@ -0,0 +1,260 @@ +// Code generated by go-bindata. +// sources: +// assets/cluster.yml +// assets/vpc.yml +// DO NOT EDIT! + +package templates + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _assetsClusterYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x59\x7b\x6f\xdb\x38\x12\xff\x3f\x9f\x62\xea\x2b\xe0\xed\xa2\xf2\x43\x4e\xda\x54\x40\x76\xe1\x38\x6e\x6b\xac\xdd\x1a\x51\xda\x02\x4d\x8b\x82\xa2\x28\x9b\xa8\x44\xea\x48\x2a\x8f\xed\xf5\xbb\x1f\x48\x4a\xb2\x5e\xce\x26\xbb\x3d\xec\x1d\x70\x2e\x82\xda\xe4\x70\xde\xf3\x9b\xa1\xe4\x38\xce\xc1\xf4\x83\x7f\x41\x92\x34\x46\x8a\xbc\xe4\x22\x41\xea\x3d\x11\x92\x72\xe6\x41\xdf\x1d\x8d\x47\xce\xe8\x85\x33\x7a\xd1\x3f\x58\x23\x81\x12\xa2\x88\x90\xde\x01\xc0\x82\x49\x85\x18\x26\x17\xb7\x29\xd1\xbf\x01\xcc\x37\xf0\x95\xa0\x6c\x63\x16\xce\x88\xc4\x82\xa6\xca\xb0\x2a\xe8\x41\xdd\xa6\x04\x14\x87\x4c\x92\x41\x4e\x16\xa1\x2c\x56\x1e\x28\x77\x90\x50\x2c\xf8\x81\x39\x4a\x05\x09\x67\x28\x45\x98\xaa\xdb\xaa\x80\x37\x59\x12\x10\x51\x3f\xd9\x1f\xf7\xdb\x12\x2d\x21\xf0\x08\x68\x2e\x5b\x6a\xb9\x31\xca\x18\xde\x02\x65\x70\xcb\x33\x01\xf3\x99\x0f\x38\xce\xa4\x32\x3c\x57\xe8\xc6\xa7\xbf\x93\x3f\x94\xe7\x76\xc8\x5b\xa1\x1b\x9a\x64\x09\xb0\x2e\xb9\x5b\xa4\x00\x23\x06\x01\xc9\x15\x20\xe1\x1e\x15\x7e\x23\xb7\x6f\x50\x72\x2f\x9f\xe6\xa4\xda\x2a\x24\x25\xc7\x14\x29\x02\xd7\x54\x6d\xe1\x9a\x8b\xaf\x44\xec\x14\x18\x00\x2c\x09\xba\x22\x10\xc4\x88\x7d\xd5\x07\x42\x2a\x51\x10\x13\xf0\xfd\xd7\x80\x30\x26\x52\x36\xa2\xd1\xd7\x26\xfa\x72\x3b\x8d\x63\x7e\xed\xb5\x85\xfb\x59\xc0\x88\x82\x48\xf0\x04\xae\xb7\x14\x6f\x8d\x1a\x9a\xb8\xc5\xb3\x65\xc5\x8a\xb2\x25\x61\x1b\xb5\xf5\xa0\xff\xc2\xba\x72\x85\x6e\xca\xa5\xf1\x71\xbf\xae\xcb\x68\x60\xfe\x0d\x47\x66\xd9\x68\x44\xc2\x35\x52\x8a\x08\xe6\x41\xef\xa7\x4f\x9f\xc2\x6f\xe3\xa7\x93\xef\x4f\x3e\x7d\x1a\xdc\xe7\xc7\x30\xff\xea\x7e\x7f\xd2\x33\x2c\x67\x9c\x49\x25\x10\x65\xaa\x66\x63\x3f\xc9\xa4\xd2\x31\x43\x70\x85\x62\x1a\xc2\x6c\x71\x76\x0e\x41\xcc\xf1\x57\x0f\x6e\x06\xe6\xdf\xf0\x66\xa0\xb5\x7d\x9f\xe2\x45\x78\x9f\xa0\x99\x88\xf1\x08\xd4\x96\x68\xa6\x99\x09\x1f\x4d\x52\x2e\x14\x44\x5c\x98\x75\xc3\xec\x00\x60\x9d\x05\x31\xc5\xd6\xd3\xd3\x8f\xe3\x1f\x27\x60\xfa\x71\x0c\xd2\x06\x90\xb6\x05\xb9\x3f\x52\x90\x5b\x13\xd4\x4c\xb0\xba\xe0\xc9\x8f\x14\x3c\xb9\x43\xf0\x8a\x28\x14\x22\x85\xb4\xb4\xe9\x07\xdf\xf3\x66\x31\xcf\x42\x8b\x7e\x5a\x84\xb7\x60\x8a\x88\x08\xe1\xbc\x0e\x4b\xec\x7b\x25\x78\x96\x4a\xbb\x08\xe0\xc0\x12\x05\x24\x2e\x7e\xea\x4f\x58\x48\xe9\x95\x88\x37\xe3\x2c\xa2\x9b\x4c\x18\xd6\xbd\x92\xb6\x8e\xa7\xc5\xc7\xa9\x21\x6b\x6d\x23\x2f\xf7\xda\x5a\x51\xa0\xf7\x51\x68\x9a\x29\x0e\x3e\x46\x31\x65\x9b\x87\x2a\xd5\x00\xe4\xda\x5e\x0e\x9a\x75\x47\x19\x3d\x4a\x26\xed\x6e\xb1\xc7\x57\x45\x77\xb0\x20\xf9\x6b\xa1\x58\x0d\x14\xeb\x47\x7f\x23\xb7\xfa\xc0\x46\x20\xa6\x2a\xc8\x03\x3f\x59\xa8\xd3\xf9\xc0\x38\x23\x4f\x4a\x5e\x75\x4c\xab\x33\xdb\xd5\x77\x17\xcf\x92\x45\x67\x7b\xaa\x73\xca\x49\xaa\xe0\x5e\xc2\x31\x60\x9e\x31\x55\x72\xab\x35\x9d\x3a\x97\xa2\xa7\xdc\xc9\x65\xc6\x59\x48\x75\x18\x8d\xbb\x5f\x23\x59\xf3\x56\xef\x25\xf3\xbc\x37\x5c\xf5\x76\x49\x6b\x96\xe6\xff\xcc\x50\x2c\x7b\x1e\x5c\x3e\x3a\x27\x51\xe1\xe1\xa7\xd0\xef\x7f\xb6\x5c\x1a\xe0\xf3\x20\x6e\x2d\xe0\xda\xcb\xd7\xfd\x0b\x7c\xdd\x3b\xf8\x4e\xfe\x02\xdf\x49\xc1\x77\x85\xd2\x94\xb2\x8d\xcc\x61\xe2\x9c\x6c\x28\x67\x17\x7c\xba\x5a\x58\x76\x99\x74\x08\x92\xca\x19\x17\xdc\xa7\xab\xc5\xe2\xcc\x03\x94\x50\xc7\x0d\x26\xc1\xb3\xd1\xe1\xb8\x20\xbc\x26\x52\x39\x6e\x07\x21\xc2\xcf\x8e\x9f\xbb\xd8\x82\x14\xc9\x2c\x61\x17\xc7\xd1\xc4\x9d\x1c\x07\xcf\x6d\x13\x44\xa9\xc3\xb8\x50\xdb\xbd\xf2\xa3\xc0\x8d\xc6\xee\x8b\xa3\x82\x5a\xf2\x2c\xa7\xee\x52\xe2\x70\x72\x74\xf8\x7c\xec\x8e\x6a\xda\x76\xb1\x0d\x22\x32\x7a\x71\x14\x46\x6d\xb6\x5d\xd4\xf8\xf9\x71\x74\x38\x41\x87\x85\x6d\x98\x30\x25\x50\xdc\x49\x4b\xc6\xe4\x59\x74\x7c\x1c\x1e\x9c\x13\xc9\x33\x81\x89\x71\xfb\x1c\xcb\x99\x4d\xfc\x6a\x67\x30\x98\x3d\x9f\x19\xe0\x2e\x06\xa7\xf9\xcc\xd7\x08\x97\x03\x9c\x01\xea\xd6\x91\x0a\x41\xed\x87\xa1\xce\xbb\x44\x4a\x58\x28\xdf\x32\x0f\x2e\x3f\x5b\x48\x13\x3c\x25\x42\x51\x52\xa2\xd9\xfb\xf5\xec\x23\x67\x64\x11\x12\xa6\x68\x44\x0b\xd5\x74\x72\xe9\xdc\x5a\x44\xbb\x52\x76\x3a\x2a\xa9\xb2\x69\xc8\x4d\xe3\x7a\xaf\xfb\x98\x07\x8f\xfc\x2c\x80\xc7\xdf\x5a\xf5\xf3\xbd\x72\xc8\x64\xac\x31\xe7\x0d\x37\xc7\x1e\x22\xdd\x7d\xb0\x74\xf7\x07\x4a\x9f\x3c\x58\xfa\xe4\x7e\xd2\x97\xa6\x5f\xd4\x9a\x9a\x81\x40\x7b\x60\xc6\x99\x42\x94\x11\x51\xf4\x19\x59\x40\x2f\x65\x06\x7a\xcb\x1b\xc4\x0e\x8d\xed\xc9\x6a\x6f\x6b\xe3\xbe\xa5\xe9\xea\x8d\x33\x41\x8c\x12\x6b\x1e\x53\x5c\x36\x88\x22\xb3\x7d\xba\x61\xa8\xd2\xa5\x2f\x68\x42\x78\xa6\x3c\x58\x5f\x8c\x8f\x56\x66\xf9\x5d\x1a\x22\x45\xea\xc7\x2b\x09\x7b\xce\x63\xfd\x9f\xa5\xda\x31\x5a\x51\x56\x9a\xb8\x60\x3e\x11\x57\x14\xd7\xac\x33\xf6\x9d\x22\x85\xb7\x4d\xbb\x75\xef\xce\x24\xd1\xaa\x54\xf5\xd0\x9f\x0f\x88\xaa\xb7\xac\xae\xbc\xf4\xa0\xaf\x44\x46\xf4\xf1\xb6\x7b\xef\x2e\xbc\x8e\x60\xd9\x3b\x40\x65\x2a\x33\xf6\x76\x4f\x66\x54\xed\x0c\xc6\x86\x49\x75\x56\xc1\x3c\x49\x10\x0b\x6b\xf3\x0b\xc0\x68\xfc\x05\x85\xe1\x97\xa2\x77\x7e\x51\xfc\x0b\xae\xc2\x4a\xeb\x7c\x9e\x8e\xff\x6a\xec\x02\xfc\xe3\xd1\x30\xa0\x6c\x18\x20\xb9\x6d\xed\x11\xbc\xe5\x1a\x87\xbe\xcc\x96\xef\xfc\x8b\xf9\xf9\xc9\xe3\x6f\x3b\xfc\xfa\x0e\xf0\xcb\x2f\x30\x24\x0a\x0f\x09\x96\xfa\x6f\x60\xb5\xaf\xb0\x89\x68\x4c\x1a\x9a\xf7\xcc\x09\x1c\x31\xfd\xe7\x6c\xb3\xd4\x9c\xea\xb5\xd5\x66\x8a\x30\xb5\x57\xed\xcb\x04\x51\xf6\xb9\xb5\x2c\x15\xc2\x5f\x4f\x1e\x7f\x33\xae\xf6\xf5\x8f\x6a\xbd\x15\x1f\x61\x1a\x5f\x41\x66\xdb\x60\x93\x2a\xe1\xa1\xce\xa7\xd1\x68\x74\x38\x1a\xf5\x1b\x9b\xfc\x9a\x11\xe1\x81\xe0\x5c\x35\x76\x36\x06\xa7\xdb\x3b\x3b\xb3\xb7\x9c\x7f\x95\x83\xd0\x98\x8f\x32\xc5\x1d\x41\x62\x8e\x42\x22\xfe\xa4\x23\x5a\x7c\x1c\x2d\xa1\xed\x1a\x25\xe8\x66\x43\x84\x3c\x49\xb9\x54\x83\xcc\x54\x5a\x8b\x28\x45\x6a\x7b\x52\x36\xac\x41\xbb\x12\x06\x45\x52\x0f\xf6\x66\x73\x8b\x29\xc2\x7a\xf3\x64\xc8\x53\x35\x44\xd7\xd2\xe4\x9b\xd6\x9a\x32\xaa\xc0\xb9\x02\xc7\x31\x61\x83\x6a\xd8\x34\xda\x7d\x07\xc7\x11\xb9\x2e\x1d\x45\x69\x76\x75\xe8\xe0\xce\x40\x02\x88\x8c\x21\x79\xd2\x08\x89\xb4\x60\xd2\xc8\x4e\x79\x2b\xaf\x68\xad\x22\xf3\x28\xd8\x5c\x6d\x2e\x03\x10\x86\x82\x98\x84\x15\xf4\x68\xee\xcb\x4c\x90\xf3\x8c\x31\x0d\x15\xfb\xa8\x3a\xea\x04\xec\x70\xd7\x5d\x2d\x77\x52\xfe\x41\x82\xed\x19\x02\x16\x09\xda\x90\x85\xc6\x89\x97\x94\x85\x0b\xb6\x42\x29\x5c\x36\xa6\xc4\xa7\xb6\x41\xf4\x2a\xde\xee\x3d\xb5\x33\x0f\x14\x09\xe7\x13\x9c\x09\xaa\x6e\xf3\x9b\x25\x5c\xda\x33\xaf\xb9\x54\xfe\xab\x92\xaa\x76\x81\xb2\x14\x1d\xf7\xc4\x05\x4a\x8a\xd5\xb5\xe0\xda\x49\x39\xed\x7c\xe6\x36\x36\x1a\x17\x2b\x78\xb4\x88\xe0\xb2\x72\x79\xc8\x55\xaf\xff\xea\x55\x3b\x6f\xaf\xd0\xed\x9d\x24\xe2\xac\x02\xdb\x60\x5a\xfb\x29\x92\xe4\xd9\x61\x35\x46\x1d\x05\x59\x01\x53\x70\x6e\xea\xe5\x75\x9b\x25\xf6\xae\x13\xc7\xe0\xdc\x02\xba\x96\x8e\x8e\x50\xc0\xb9\x92\x4a\xa0\xb4\x46\xfc\xb7\xd4\x4a\x4b\xa8\x34\xad\x11\x1c\x02\x8f\x7f\xbd\x9f\xe4\x8e\xa1\xf5\x0e\xd1\xed\x30\xb6\x1a\xed\x62\xba\xd2\xa8\xd2\x8e\x75\x3b\x83\xd7\x48\x6d\x3d\xe8\x0d\x8b\xea\x38\xe7\x95\xa2\x72\xca\xc4\xd1\xcb\x56\xb6\xfe\xd6\x2d\x30\xa7\xe9\x92\x32\x95\x32\x4b\x88\x26\xb0\xc3\xcc\x19\xc7\x59\xa2\x01\xba\x74\xa5\xaf\x90\x22\xf5\x25\x07\xe6\x51\x44\xb0\xf2\xa0\xfa\x74\xc3\x0a\xa0\x0c\xd3\x14\xc5\xf5\xea\x2f\x46\x9d\x83\x7a\x91\x13\xec\x0e\x50\x82\x7e\xe7\x0c\x5d\xeb\x76\x9b\x54\xf6\xa7\x06\x65\xeb\x8f\x39\xa4\x92\xde\x4e\xe1\x3d\x7e\x32\x76\xd0\xaa\xab\xac\x65\xb6\x90\x08\x96\x4e\x8e\x95\xbb\xc9\x6a\x8f\xe5\x9d\xb6\xdf\x65\x7d\x97\xd6\xd6\x4e\xe9\x99\x91\x93\xec\xee\x43\xcd\x7d\x9d\x46\x7a\xab\x95\xec\x1d\xb4\x67\x44\x3c\x84\x9a\x4a\xcc\xaf\x88\x58\xf3\x38\x9e\xb3\x30\xe5\x94\xa9\x0e\x32\x3f\x0b\x12\xaa\x7e\x6e\xed\x08\xaf\xbd\x26\x3d\xcd\xac\xb6\x5c\x74\x59\x0f\x7a\x3f\xeb\x50\x58\x84\xec\xb8\x15\xba\x9e\x57\x03\xd5\x7d\xb7\xb8\xdd\x13\x5b\xc8\x31\xab\xeb\x3a\x62\xc8\x8a\xba\x37\xfc\x6a\xcf\x22\xe7\x33\xdf\x68\x52\xe2\x38\xec\x64\x36\xc0\x7d\xc1\x36\x82\xc8\x4a\xda\x2c\xd2\xb5\xe0\x8a\x63\x1e\x7b\xa0\xf0\x0e\xd0\x5e\x0a\x9e\xac\xb9\x30\x2f\x1a\xdc\x5d\xf3\xbb\xe0\x1d\x8b\x33\x1a\x8a\x45\x9a\xc3\x7c\xe5\x69\xe0\x3c\x0e\xfe\x0b\x9c\xb3\x3c\xfd\x0f\xf9\xe5\x78\xd4\xe1\x97\xea\x62\xe1\x97\xea\xab\x83\xf9\xf2\xd4\xd5\xb1\x3a\xcf\x3a\x70\xac\xed\x9a\x5c\xaf\x7d\xfd\xbf\x53\xc9\x8a\x8a\xa5\x32\xa5\x7e\xcf\x8e\x8e\x26\x47\xc5\xaa\x6f\x2f\x53\x35\x81\x7a\x9a\x78\x45\xd4\x54\x29\x1b\xbf\x41\xbe\x5c\x75\x70\x95\xc8\x96\x40\x85\x4a\x2f\xb8\xf3\xe5\xe9\xff\x82\x85\x2d\xe5\x3b\x4d\x6c\xfa\x61\x8e\xe5\x3c\x0e\xda\xb6\xc5\x48\x2a\x8a\x97\x1c\x85\xa7\x28\x46\x0c\x53\xb6\x79\xef\x7a\xde\x6e\x21\xc7\xc4\xb6\x99\xf6\x49\x83\xfc\xff\x33\x9c\xbf\xfb\x19\x4e\x63\x12\x6e\x0c\x22\x3a\x0f\xca\xf8\x2f\x75\x6f\x62\x5d\xcf\x04\xf7\xe5\x41\x7e\x60\x4f\x0e\x54\xd3\x64\x2a\x58\x31\x35\x1b\x59\x39\x49\xfe\xfa\xc8\xf6\xdf\x8a\x76\x56\x78\xc4\xc5\x35\x12\xe1\x0e\x93\x90\xd8\x10\x65\x2c\x69\xf2\xcb\x19\x55\x28\xca\xb9\xa2\x81\x62\xbb\xf2\x7b\x7d\x71\xb1\x2e\x8d\x6f\x33\xb8\xb7\x1b\x9a\x42\x3b\x86\xc2\x42\x89\x3b\xd4\x80\x07\x37\x88\xb7\x99\x4a\x33\x5b\x63\xfa\x5e\xf0\x4e\xe4\xe3\x5b\x95\x78\xab\x54\xea\x0d\x87\xe6\x91\xc9\x3c\x0e\x06\x67\x6f\x7c\x33\x2e\x0f\x0f\xa0\xf9\x02\x50\xf7\x95\x77\xe7\xcb\x56\x3a\x68\x57\xd7\xf8\xee\xbc\x5e\x4b\x80\x1a\xb3\xa9\x60\xc5\xcb\x44\xcd\xb7\x20\xb4\xaf\xae\xe7\x37\xda\xa6\xc2\xce\xfc\xaa\x64\x4d\x6b\x4c\xf5\x4e\x4b\x95\x8e\xa7\xd7\x0d\xbd\xaa\xc3\xda\xde\x37\x9c\x95\x77\x3f\x7f\x46\xa7\x42\xc6\xc1\xbf\x03\x00\x00\xff\xff\x75\x16\x3e\x1e\xeb\x21\x00\x00") + +func assetsClusterYmlBytes() ([]byte, error) { + return bindataRead( + _assetsClusterYml, + "assets/cluster.yml", + ) +} + +func assetsClusterYml() (*asset, error) { + bytes, err := assetsClusterYmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/cluster.yml", size: 8683, mode: os.FileMode(420), modTime: time.Unix(1484180215, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _assetsVpcYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x98\x4b\x6f\xdb\x38\x10\xc7\xef\xf9\x14\x93\x60\x01\x6d\x81\x38\xb1\xa5\xa4\x68\x78\xf3\x3a\x4e\x63\x6c\x9b\x18\x96\xe1\x02\x69\xf6\x40\x4b\xe3\x58\xa8\x44\x6a\x49\x2a\x0f\x14\xf9\xee\x0b\x51\x92\x1f\xb4\xe4\xfa\x99\x4d\xe1\x8b\x45\x0e\x39\xf3\xff\x69\xc8\xa1\x58\xab\xd5\x0e\x9a\xdf\xdc\x3e\x46\x71\x48\x15\x5e\x71\x11\x51\x35\x40\x21\x03\xce\x08\x58\x76\xbd\x51\xaf\xd5\x2f\x6a\xf5\x0b\xeb\xa0\x4b\x05\x8d\x50\xa1\x90\xe4\x00\xa0\xc3\xa4\xa2\xcc\xc3\x3e\x32\xca\xbc\x97\xb4\x09\xe0\x12\xa5\x27\x82\x58\xe9\xc1\x85\x05\xa8\xcc\x04\x14\x87\x44\x22\x8c\xb8\x80\x41\xb7\xa5\x07\xf4\x5f\x62\x24\xe0\x2a\x11\xb0\x07\xdd\xd0\x0c\x43\xfe\x84\xfe\x80\x86\x09\xca\x6c\xd2\x1a\xf8\x38\xa2\x49\xa8\x26\x4f\x7e\xe0\x51\x85\x7e\xee\x52\xf7\x91\x19\x23\x57\x8e\xf5\x34\x25\x31\xb9\xc9\x90\xa1\x82\x91\xe0\x11\x3c\x8d\x03\x6f\x9c\x06\x45\x53\x63\x70\xdd\x6b\xa0\x9e\x87\x52\x9e\x94\x87\xf6\x35\x60\x5f\x90\x3d\xa8\x31\x01\xeb\xc2\xca\x9a\xe8\xf3\xa4\xa9\xf1\xc9\x9a\x0f\xa8\x7e\xa2\x7f\xa7\xf5\x59\x61\x5d\xaa\x14\x0a\x46\xe0\xe8\xcf\xfb\x7b\xff\x67\xe3\xd8\x79\xfd\x70\x7f\x7f\xb2\xca\xc3\x69\xfe\xd7\x7e\xfd\x70\xa4\xa7\x6c\x71\x26\x95\xa0\x01\x53\x73\x1a\xad\x28\x91\x0a\x86\x08\x14\x1e\x69\x18\xf8\xd0\xea\x5c\xf6\x60\x18\x72\xef\x07\x81\xe7\x13\xfd\x3b\x7d\x3e\x49\xa3\x1d\xc4\x5e\x2b\xf0\xc5\x5f\xba\xaf\x92\x96\x1e\xba\xfc\xb5\xad\xcb\xa6\x51\xc0\x69\x7c\x7c\xbf\x74\xba\xc9\x30\x0c\xbc\x0c\x42\xf3\xae\xb1\x16\xa9\xe6\x5d\x63\xc7\xa4\xec\xb3\xdf\x85\x94\xbd\x26\x29\x7b\x87\xa4\x1a\xbf\x15\x29\x67\x4d\x52\xce\x0e\x49\xd9\xef\x9a\x54\x8b\x33\x3f\x48\xc7\xe8\x22\x70\x4d\xa5\xb1\x18\x33\x5e\x47\x57\x8c\x90\x1b\xae\x8e\xb2\xc7\xb4\x3a\xe8\xa6\xf6\xbf\x09\x0d\xe5\x11\x81\xef\x87\x3d\x1c\x55\x2e\xe4\x63\xb0\xac\x7f\xca\xa6\xb7\xb7\x98\xde\xfe\xf5\xf4\xce\x16\xd3\x3b\xc6\xf4\x3d\x94\x3c\x11\x5e\x56\x2c\x07\xdd\x16\x99\x49\x91\xe6\x37\x97\x90\x76\xcb\x26\xa4\xd8\xb8\xbb\x82\xc7\x28\x54\x50\xd4\x56\x80\x69\x06\x82\xf6\x36\x5b\x12\x72\x93\x36\xa3\xc3\x10\x2f\x99\x74\x93\x38\xe6\x42\x11\xb0\x94\x48\xd0\x32\xbb\xaf\xb9\x54\x8c\x46\x28\x0d\x03\xf3\xa8\x90\x39\x32\x5a\x17\xf7\xdb\x72\x25\x59\x77\x91\x63\x59\x8e\x90\x92\x04\xa9\x90\x3b\x88\xbd\x8e\x5f\x48\xcd\xa1\x2c\x42\xa8\x4a\x98\xdc\xfc\x2b\x8d\x33\x8b\x4e\x7c\xcb\xbe\xd0\x84\x79\x63\x02\xa9\xe2\xbc\xbf\xf9\x48\x83\x90\x0e\x83\x30\x50\x2f\x77\x9c\x21\x81\x43\x17\x43\xf4\x14\x7c\x87\xfa\x31\x1c\x7e\x4e\x67\x95\x79\x76\x68\x91\xf4\x41\x4e\x93\xe0\x6f\x7c\x21\x70\x83\xea\x89\x8b\xc2\x23\x80\x3e\x11\x91\x3c\xb2\xc5\x2d\x77\x2b\x58\xf6\x0e\x61\xd9\xbb\x84\xd5\xd8\x0b\x2c\x67\x2b\x58\xce\x0e\x61\x39\xbb\x84\x65\xef\x08\x56\x87\xa5\x55\x00\xd5\x67\xaa\xf0\x89\xbe\x94\xc3\x32\x8c\x2a\x98\x6c\xe0\x7d\xd0\x6d\xad\x14\xc0\xa0\xdb\xca\xfb\x9b\x4a\x51\x6f\x1c\x21\x53\x6b\xbd\x19\xc3\xcb\xc4\x62\x51\x59\x16\x5b\x8f\x27\x0a\xfb\xe9\x56\x57\x1e\xd0\xb4\x7f\xad\x30\x36\xce\x66\xed\x6f\x49\x28\x79\xc9\x8f\x91\xf9\xf2\x96\x91\x12\xb0\x15\x71\x4e\x85\x4c\xc2\x35\x09\xe4\x96\x97\x28\x55\xc0\x68\xba\x50\x66\xf2\x7c\xfe\xbb\x07\x60\x55\xc0\x93\xed\x76\xea\xa7\x29\x25\xf7\x02\xed\x60\xd9\x9a\x2d\x1d\xb0\x71\x89\xc8\xfa\x0d\xed\xf3\x83\x56\x85\x64\x6c\x8d\x6f\x24\xac\x6a\x3b\x5f\x2a\xcc\xde\x42\x98\xf3\x46\xc2\xaa\xb6\xde\xa5\xc2\x9c\x0d\x84\xe5\x2b\xb0\xe9\x85\xe5\x22\xa6\xfd\xfb\x5e\xeb\x1d\x36\xe4\x09\xf3\xdb\xf1\x18\x23\x14\x34\xec\x72\xa1\xcc\x18\xdb\x4c\x89\x8a\x5d\xd2\x30\xaa\x88\x76\x6a\x65\xa0\x31\x74\x02\xf4\x92\x10\x6f\x92\x68\x88\x22\xfd\xae\xa8\x3b\xc5\x11\xaf\x2b\xb8\xe2\x1e\x0f\x09\x58\x1f\xad\x19\xdb\xa6\x97\xbd\x4a\x7d\xc5\x52\x9c\x17\x1f\x04\xca\xf4\x8c\x38\xa2\xa1\x9c\x1c\x12\x97\x6c\x20\xa9\xe6\x1e\x65\x0f\x48\x26\x98\xae\x04\x8f\x74\x04\xf6\x99\x35\x69\xec\xf3\xd4\xfd\xf9\xb9\x73\x6e\x4d\xc9\xb9\xee\xf5\xfb\xe1\x75\xb6\x17\x5e\x3a\x80\xe2\xd2\xeb\x97\xcc\x6c\xdb\x20\x96\x35\xe4\xb8\xae\x95\x8a\xdf\x0f\xaf\xf3\xff\x39\xbf\x3e\xd5\x0d\x56\x59\xc3\x6d\xa2\x34\xac\xf7\x03\xaa\xbe\x15\xa8\xd9\x8f\xb5\x8d\x38\x99\x98\x26\x8b\xd0\x28\x9e\xa6\x98\x15\xab\x45\xe9\x80\xfd\xd6\xf7\xd5\xde\x84\x51\x42\xdf\x54\xde\x56\x55\x7e\x13\x79\xce\x9b\xca\xdb\xaa\xd6\xaf\x22\xef\x36\x51\x71\xa2\xb2\x6b\x13\x5d\xad\xf3\x03\xf3\xcc\x6d\x55\x7f\x8c\x10\xf8\xc0\x47\xa0\xc6\x08\x8f\xb1\xa7\x4d\xf2\x12\x3d\x57\xdb\xdb\xcf\xfa\x62\xa4\x70\x4f\x23\xfd\x65\x96\x0c\xe1\x8f\x9f\x1a\x87\xab\xa8\xf7\x23\x6d\x7e\xad\x69\x67\x8b\x4b\xa3\x32\x80\x58\xdb\x81\xd4\x86\x81\x3f\x77\xbb\x9c\x85\x92\x7b\xbd\x62\x84\x74\x46\xd3\xf3\x45\xc5\x82\x48\xbb\x96\x24\x7e\xde\xa9\xa3\xbe\xe1\xda\xc1\xba\x0a\x17\x94\x2d\xae\x94\x35\xd5\xda\x1b\xa8\xb5\x97\xa9\xb5\xf7\xa5\xd6\x2e\x51\xeb\xac\xa9\xd6\xd9\x40\xad\xb3\x4c\xad\xb3\x2f\xb5\x4e\xc7\x3f\xf8\x2f\x00\x00\xff\xff\x28\xeb\x52\x31\x42\x1c\x00\x00") + +func assetsVpcYmlBytes() ([]byte, error) { + return bindataRead( + _assetsVpcYml, + "assets/vpc.yml", + ) +} + +func assetsVpcYml() (*asset, error) { + bytes, err := assetsVpcYmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/vpc.yml", size: 7234, mode: os.FileMode(420), modTime: time.Unix(1484088279, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "assets/cluster.yml": assetsClusterYml, + "assets/vpc.yml": assetsVpcYml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "assets": &bintree{nil, map[string]*bintree{ + "cluster.yml": &bintree{assetsClusterYml, map[string]*bintree{}}, + "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + From 084dbb3ce169bd45200667949495cc992f1f4205 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Fri, 13 Jan 2017 00:18:19 -0800 Subject: [PATCH 24/31] Added logging --- cli/app.go | 21 ++++++++++++++ cli/app_test.go | 4 ++- common/logging.go | 63 ++++++++++++++++++++++++++++++++++++++++ common/stack.go | 30 +++++++++++++++---- templates/assets.go | 6 ++-- workflows/environment.go | 11 +++++-- workflows/executor.go | 9 +++++- 7 files changed, 131 insertions(+), 13 deletions(-) create mode 100644 common/logging.go diff --git a/cli/app.go b/cli/app.go index 8f931d4a..248bab2d 100644 --- a/cli/app.go +++ b/cli/app.go @@ -24,6 +24,17 @@ func NewApp(version string) *cli.App { } app.Before = func(c *cli.Context) error { + // setup logging + if c.Bool("verbose") { + common.SetupLogging(2) + } else if c.Bool("silent") { + common.SetupLogging(0) + } else { + common.SetupLogging(1) + + } + + // load yaml config yamlFile, err := os.Open(c.String("config")) if err != nil { return err @@ -31,6 +42,8 @@ func NewApp(version string) *cli.App { defer func() { yamlFile.Close() }() + + // initialize context context.Initialize(bufio.NewReader(yamlFile)) return nil } @@ -41,6 +54,14 @@ func NewApp(version string) *cli.App { Usage: "path to config file", Value: "mu.yml", }, + cli.BoolFlag{ + Name: "silent, s", + Usage: "silent mode, errors only", + }, + cli.BoolFlag{ + Name: "verbose, V", + Usage: "increase level of log verbosity", + }, } return app diff --git a/cli/app_test.go b/cli/app_test.go index 30d43d7b..db489ccf 100644 --- a/cli/app_test.go +++ b/cli/app_test.go @@ -14,8 +14,10 @@ func TestNewApp(t *testing.T) { assert.Equal("1.2.3", app.Version, "Version should match") assert.Equal("Microservice Platform on AWS", app.Usage, "usage should match") assert.Equal(true, app.EnableBashCompletion, "bash completion should match") - assert.Equal(1, len(app.Flags), "Flags len should match") + assert.Equal(3, len(app.Flags), "Flags len should match") assert.Equal("config, c", app.Flags[0].GetName(), "Flags name should match") + assert.Equal("silent, s", app.Flags[1].GetName(), "Flags name should match") + assert.Equal("verbose, V", app.Flags[2].GetName(), "Flags name should match") assert.Equal(3, len(app.Commands), "Commands len should match") assert.Equal("environment", app.Commands[0].Name, "Command[0].name should match") assert.Equal("service", app.Commands[1].Name, "Command[1].name should match") diff --git a/common/logging.go b/common/logging.go new file mode 100644 index 00000000..6cb235f2 --- /dev/null +++ b/common/logging.go @@ -0,0 +1,63 @@ +package common + +import ( + "github.com/op/go-logging" + "os" +) + +// SetupLogging - verbosity 0=error, 1=info, 2=debug +func SetupLogging(verbosity int) { + errBackend := logging.NewLogBackend(os.Stderr, "", 0) + errFormat := logging.MustStringFormatter( + `%{color}%{shortfunc} â–¶ %{level:.5s} %{color:reset} %{message}`, + ) + errFormatter := logging.NewBackendFormatter(errBackend, errFormat) + errLeveled := logging.AddModuleLevel(errFormatter) + errLeveled.SetLevel(logging.ERROR, "") + + if verbosity >= 1 { + infoBackend := logging.NewLogBackend(os.Stdout, "", 0) + infoFormat := logging.MustStringFormatter( + `%{color}%{message}%{color:reset}`, + ) + infoFormatter := logging.NewBackendFormatter(infoBackend, infoFormat) + infoLeveled := logging.AddModuleLevel(notToExceedLevel(logging.WARNING, infoFormatter)) + infoLeveled.SetLevel(logging.INFO, "") + + if verbosity >= 2 { + debugBackend := logging.NewLogBackend(os.Stdout, "", 0) + debugFormat := logging.MustStringFormatter( + `%{color}%{time:15:04:05.000} %{module} â–¶ %{id:03x}%{color:reset} %{message}`, + ) + debugFormatter := logging.NewBackendFormatter(debugBackend, debugFormat) + debugLeveled := logging.AddModuleLevel(notToExceedLevel(logging.DEBUG, debugFormatter)) + debugLeveled.SetLevel(logging.DEBUG, "") + + logging.SetBackend(debugLeveled, infoLeveled, errLeveled) + } else { + logging.SetBackend(infoLeveled, errLeveled) + } + } else { + logging.SetBackend(errLeveled) + } + +} + +func notToExceedLevel(notToExceed logging.Level, delegate logging.Backend) logging.Backend { + return ¬ToExceedLevelBackend{ + delegate: delegate, + notToExceedLevel: notToExceed, + } +} + +type notToExceedLevelBackend struct { + delegate logging.Backend + notToExceedLevel logging.Level +} + +func (b *notToExceedLevelBackend) Log(l logging.Level, i int, r *logging.Record) error { + if l < b.notToExceedLevel { + return nil + } + return b.delegate.Log(l, i, r) +} diff --git a/common/stack.go b/common/stack.go index feb38ba8..c092edd8 100644 --- a/common/stack.go +++ b/common/stack.go @@ -2,15 +2,17 @@ package common import ( "bytes" - "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" + "github.com/op/go-logging" "io" ) +var log = logging.MustGetLogger("stack") + // StackWaiter for waiting on stack status to be final type StackWaiter interface { AwaitFinalStatus(stackName string) string @@ -40,6 +42,7 @@ func newStackManager(region string) (StackManager, error) { if err != nil { return nil, err } + log.Debugf("Connecting to CloudFormation service in region:%s", region) cfn := cloudformation.New(sess, &aws.Config{Region: aws.String(region)}) return &cloudformationStackManager{ cfnAPI: cfn, @@ -67,18 +70,23 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template templateBodyBytes.ReadFrom(templateBodyReader) templateBody := aws.String(templateBodyBytes.String()) + parameters := buildStackParameters(stackParameters) + cfnAPI := cfnMgr.cfnAPI if stackStatus == "" { - fmt.Printf("creating stack: %s\n", stackName) + + log.Debugf(" Creating stack named '%s'", stackName) + log.Debugf(" Stack parameters:\n\t%s", parameters) params := &cloudformation.CreateStackInput{ StackName: aws.String(stackName), Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, - Parameters: buildStackParameters(stackParameters), + Parameters: parameters, TemplateBody: templateBody, } _, err := cfnAPI.CreateStack(params) + log.Debug(" Create stack complete err=%s", err) if err != nil { return err } @@ -86,24 +94,29 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template waitParams := &cloudformation.DescribeStacksInput{ StackName: aws.String(stackName), } + log.Debug(" Waiting for stack to exist...") cfnAPI.WaitUntilStackExists(waitParams) + log.Debug(" Stack exists.") } else { - fmt.Printf("updating stack: %s\n", stackName) + log.Debugf(" Updating stack named '%s'", stackName) + log.Debugf(" Prior state: %s", stackStatus) + log.Debugf(" Stack parameters:\n\t%s", parameters) params := &cloudformation.UpdateStackInput{ StackName: aws.String(stackName), Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, - Parameters: buildStackParameters(stackParameters), + Parameters: parameters, TemplateBody: templateBody, } _, err := cfnAPI.UpdateStack(params) + log.Debug(" Update stack complete err=%s", err) if err != nil { if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "ValidationError" && awsErr.Message() == "No updates are to be performed." { - fmt.Printf("No changes for stack: %s\n", stackName) + log.Noticef(" No changes for stack '%s'", stackName) return nil } } @@ -129,10 +142,12 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) str cloudformation.StackStatusCreateInProgress, cloudformation.StackStatusRollbackInProgress: // wait for create + log.Debugf(" Waiting for stack:%s to complete...current status=%s", stackName, *resp.Stacks[0].StackStatus) cfnAPI.WaitUntilStackCreateComplete(params) resp, err = cfnAPI.DescribeStacks(params) case cloudformation.StackStatusDeleteInProgress: // wait for delete + log.Debugf(" Waiting for stack:%s to delete...current status=%s", stackName, *resp.Stacks[0].StackStatus) cfnAPI.WaitUntilStackDeleteComplete(params) resp, err = cfnAPI.DescribeStacks(params) case cloudformation.StackStatusUpdateInProgress, @@ -140,6 +155,7 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) str cloudformation.StackStatusUpdateCompleteCleanupInProgress, cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress: // wait for update + log.Debugf(" Waiting for stack:%s to update...current status=%s", stackName, *resp.Stacks[0].StackStatus) cfnAPI.WaitUntilStackUpdateComplete(params) resp, err = cfnAPI.DescribeStacks(params) case cloudformation.StackStatusCreateFailed, @@ -154,8 +170,10 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) str // no op } + log.Debugf(" Returning final status for stack:%s ... status=%s", stackName, *resp.Stacks[0].StackStatus) return *resp.Stacks[0].StackStatus } + log.Debugf(" Stack doesn't exist ... stack=%s", stackName) return "" } diff --git a/templates/assets.go b/templates/assets.go index 7b445a09..c2ecac7e 100644 --- a/templates/assets.go +++ b/templates/assets.go @@ -162,7 +162,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "assets/cluster.yml": assetsClusterYml, - "assets/vpc.yml": assetsVpcYml, + "assets/vpc.yml": assetsVpcYml, } // AssetDir returns the file names below a certain @@ -204,10 +204,11 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "assets": &bintree{nil, map[string]*bintree{ "cluster.yml": &bintree{assetsClusterYml, map[string]*bintree{}}, - "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, + "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, }}, }} @@ -257,4 +258,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/workflows/environment.go b/workflows/environment.go index 332a1801..e4dfdeaf 100644 --- a/workflows/environment.go +++ b/workflows/environment.go @@ -2,11 +2,14 @@ package workflows import ( "fmt" + "github.com/op/go-logging" "github.com/stelligent/mu/common" "github.com/stelligent/mu/templates" "strings" ) +var log = logging.MustGetLogger("environment") + // NewEnvironmentUpserter create a new workflow for upserting an environment func NewEnvironmentUpserter(ctx *common.Context, environmentName string) Executor { @@ -42,10 +45,11 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ return func() error { environment := workflow.environment if environment.VpcTarget.VpcID == "" { + log.Debugf("No VpcTarget, so we will upsert the VPC stack") vpcStackName := fmt.Sprintf("mu-vpc-%s", environment.Name) // no target VPC, we need to create/update the VPC stack - fmt.Printf("upserting VPC environment:%s stack:%s\n", environment.Name, vpcStackName) + log.Infof("Upserting VPC environment '%s' ...", environment.Name) template, err := templates.NewTemplate("vpc.yml", environment) if err != nil { return err @@ -55,6 +59,7 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ return err } + log.Debugf("Waiting for stack '%s' to complete", vpcStackName) stackWaiter.AwaitFinalStatus(vpcStackName) // apply default parameters since we manage the VPC @@ -63,6 +68,7 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ vpcImportParams["PublicSubnetAZ2Id"] = fmt.Sprintf("%s-PublicSubnetAZ2Id", vpcStackName) vpcImportParams["PublicSubnetAZ3Id"] = fmt.Sprintf("%s-PublicSubnetAZ3Id", vpcStackName) } else { + log.Debugf("VpcTarget exists, so we will reference the VPC stack") // target VPC referenced from config vpcImportParams["VpcId"] = environment.VpcTarget.VpcID for index, subnet := range environment.VpcTarget.PublicSubnetIds { @@ -79,7 +85,7 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ environment := workflow.environment envStackName := fmt.Sprintf("mu-env-%s", environment.Name) - fmt.Printf("upserting ECS environment:%s stack:%s\n", environment.Name, envStackName) + log.Infof("Upserting ECS environment '%s' ...", environment.Name) template, err := templates.NewTemplate("cluster.yml", environment) if err != nil { return err @@ -89,6 +95,7 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ if err != nil { return err } + log.Debugf("Waiting for stack '%s' to complete", envStackName) stackWaiter.AwaitFinalStatus(envStackName) return nil diff --git a/workflows/executor.go b/workflows/executor.go index 63285206..919ba10e 100644 --- a/workflows/executor.go +++ b/workflows/executor.go @@ -1,14 +1,21 @@ package workflows +import ( + "errors" + "github.com/op/go-logging" +) + // Executor define contract for the steps of a workflow type Executor func() error func newWorkflow(executors ...Executor) Executor { + var log = logging.MustGetLogger("workflow") return func() error { for _, executor := range executors { err := executor() if err != nil { - return err + log.Error(err) + return errors.New("") } } return nil From 0103c64fa8a492c91518715bdf019435dd51352b Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Fri, 13 Jan 2017 11:45:00 -0800 Subject: [PATCH 25/31] added tags to stacks --- Makefile | 2 +- cli/app.go | 4 +-- cli/app_test.go | 4 +-- common/context.go | 7 +++++ common/stack.go | 57 +++++++++++++++++++++++++++-------- common/stack_test.go | 22 ++++++++++++-- main.go | 4 +-- workflows/environment.go | 11 +++++-- workflows/environment_test.go | 2 +- 9 files changed, 88 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index ee5dd3ef..d0e2f5cb 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ VERSION := $(shell cat VERSION)$(if $(IS_MASTER),,-$(BRANCH)) ARCH := $(shell go env GOARCH) BUILD_FILES = $(foreach os, $(TARGET_OS), .release/$(PACKAGE)-$(os)-$(ARCH)) UPLOAD_FILES = $(foreach os, $(TARGET_OS), $(PACKAGE)-$(os)-$(ARCH)) -GOLDFLAGS = "-X main.version=$(VERSION)" +GOLDFLAGS = "-X common.version=$(VERSION)" TAG_VERSION = v$(VERSION) default: build diff --git a/cli/app.go b/cli/app.go index 248bab2d..0bc80b78 100644 --- a/cli/app.go +++ b/cli/app.go @@ -8,13 +8,13 @@ import ( ) // NewApp creates a new CLI app -func NewApp(version string) *cli.App { +func NewApp() *cli.App { context := common.NewContext() app := cli.NewApp() app.Name = "mu" app.Usage = "Microservice Platform on AWS" - app.Version = version + app.Version = common.GetVersion() app.EnableBashCompletion = true app.Commands = []cli.Command{ diff --git a/cli/app_test.go b/cli/app_test.go index db489ccf..8f0fa069 100644 --- a/cli/app_test.go +++ b/cli/app_test.go @@ -7,11 +7,11 @@ import ( func TestNewApp(t *testing.T) { assert := assert.New(t) - app := NewApp("1.2.3") + app := NewApp() assert.NotNil(app) assert.Equal("mu", app.Name, "Name should match") - assert.Equal("1.2.3", app.Version, "Version should match") + assert.Equal("0.0.0-local", app.Version, "Version should match") assert.Equal("Microservice Platform on AWS", app.Usage, "usage should match") assert.Equal(true, app.EnableBashCompletion, "bash completion should match") assert.Equal(3, len(app.Flags), "Flags len should match") diff --git a/common/context.go b/common/context.go index fede3b91..b80835ae 100644 --- a/common/context.go +++ b/common/context.go @@ -6,6 +6,13 @@ import ( "io" ) +var version = "0.0.0-local" + +// GetVersion returns the current version of the app +func GetVersion() string { + return version +} + // NewContext create a new context object func NewContext() *Context { ctx := new(Context) diff --git a/common/stack.go b/common/stack.go index c092edd8..392a0fcd 100644 --- a/common/stack.go +++ b/common/stack.go @@ -2,6 +2,7 @@ package common import ( "bytes" + "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" @@ -9,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" "github.com/op/go-logging" "io" + "time" ) var log = logging.MustGetLogger("stack") @@ -20,7 +22,7 @@ type StackWaiter interface { // StackUpserter for applying changes to a stack type StackUpserter interface { - UpsertStack(stackName string, templateBodyReader io.Reader, stackParameters map[string]string) error + UpsertStack(stackName string, templateBodyReader io.Reader, parameters map[string]string, tags map[string]string) error } // StackManager composite of all stack capabilities @@ -49,20 +51,43 @@ func newStackManager(region string) (StackManager, error) { }, nil } -func buildStackParameters(stackParameters map[string]string) []*cloudformation.Parameter { - parameters := make([]*cloudformation.Parameter, 0, len(stackParameters)) - for key, value := range stackParameters { - parameters = append(parameters, +func buildStackParameters(parameters map[string]string) []*cloudformation.Parameter { + stackParameters := make([]*cloudformation.Parameter, 0, len(parameters)) + for key, value := range parameters { + stackParameters = append(stackParameters, &cloudformation.Parameter{ ParameterKey: aws.String(key), ParameterValue: aws.String(value), }) } - return parameters + return stackParameters +} + +func buildStackTags(tags map[string]string) []*cloudformation.Tag { + stackTags := make([]*cloudformation.Tag, 0, len(tags)+2) + + stackTags = append(stackTags, + &cloudformation.Tag{ + Key: aws.String("mu:version"), + Value: aws.String(GetVersion()), + }, + &cloudformation.Tag{ + Key: aws.String("mu:lastupdate"), + Value: aws.String(fmt.Sprintf("%v", time.Now().Unix())), + }) + + for key, value := range tags { + stackTags = append(stackTags, + &cloudformation.Tag{ + Key: aws.String(fmt.Sprintf("mu:%s", key)), + Value: aws.String(value), + }) + } + return stackTags } // UpsertStack will create/update the cloudformation stack -func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, templateBodyReader io.Reader, stackParameters map[string]string) error { +func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, templateBodyReader io.Reader, parameters map[string]string, tags map[string]string) error { stackStatus := cfnMgr.AwaitFinalStatus(stackName) // load the template @@ -70,20 +95,26 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template templateBodyBytes.ReadFrom(templateBodyReader) templateBody := aws.String(templateBodyBytes.String()) - parameters := buildStackParameters(stackParameters) + // stack parameters + stackParameters := buildStackParameters(parameters) + + // stack tags + stackTags := buildStackTags(tags) cfnAPI := cfnMgr.cfnAPI if stackStatus == "" { log.Debugf(" Creating stack named '%s'", stackName) - log.Debugf(" Stack parameters:\n\t%s", parameters) + log.Debugf(" Stack parameters:\n\t%s", stackParameters) + log.Debugf(" Stack tags:\n\t%s", stackTags) params := &cloudformation.CreateStackInput{ StackName: aws.String(stackName), Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, - Parameters: parameters, + Parameters: stackParameters, TemplateBody: templateBody, + Tags: stackTags, } _, err := cfnAPI.CreateStack(params) log.Debug(" Create stack complete err=%s", err) @@ -101,14 +132,16 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template } else { log.Debugf(" Updating stack named '%s'", stackName) log.Debugf(" Prior state: %s", stackStatus) - log.Debugf(" Stack parameters:\n\t%s", parameters) + log.Debugf(" Stack parameters:\n\t%s", stackParameters) + log.Debugf(" Stack tags:\n\t%s", stackTags) params := &cloudformation.UpdateStackInput{ StackName: aws.String(stackName), Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), }, - Parameters: parameters, + Parameters: stackParameters, TemplateBody: templateBody, + Tags: stackTags, } _, err := cfnAPI.UpdateStack(params) diff --git a/common/stack_test.go b/common/stack_test.go index 3a25d5b7..1b22195a 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -122,7 +122,7 @@ func TestStack_UpsertStack_Create(t *testing.T) { stackManager := cloudformationStackManager{ cfnAPI: cfn, } - err := stackManager.UpsertStack("foo", strings.NewReader(""), nil) + err := stackManager.UpsertStack("foo", strings.NewReader(""), nil, nil) assert.Nil(err) assert.Equal(1, cfn.waitUntilStackExists) @@ -147,7 +147,7 @@ func TestStack_UpsertStack_Update(t *testing.T) { stackManager := cloudformationStackManager{ cfnAPI: cfn, } - err := stackManager.UpsertStack("foo", strings.NewReader(""), nil) + err := stackManager.UpsertStack("foo", strings.NewReader(""), nil, nil) assert.Nil(err) assert.Equal(0, cfn.waitUntilStackExists) @@ -174,3 +174,21 @@ func TestBuildParameters(t *testing.T) { assert.Contains(*parameters[1].ParameterKey, "p") assert.Contains(*parameters[1].ParameterValue, "value") } + +func TestTagParameters(t *testing.T) { + assert := assert.New(t) + + paramMap := make(map[string]string) + + parameters := buildStackTags(paramMap) + assert.Equal(2, len(parameters)) + + paramMap["p1"] = "value 1" + paramMap["p2"] = "value 2" + parameters = buildStackTags(paramMap) + assert.Equal(4, len(parameters)) + assert.Contains(*parameters[0].Key, "mu:") + assert.Contains(*parameters[1].Key, "mu:") + assert.Contains(*parameters[2].Key, "mu:") + assert.Contains(*parameters[3].Key, "mu:") +} diff --git a/main.go b/main.go index a5fb6f44..03f7e2bd 100644 --- a/main.go +++ b/main.go @@ -5,8 +5,6 @@ import ( "os" ) -var version string - func main() { - cli.NewApp(version).Run(os.Args) + cli.NewApp().Run(os.Args) } diff --git a/workflows/environment.go b/workflows/environment.go index e4dfdeaf..16949051 100644 --- a/workflows/environment.go +++ b/workflows/environment.go @@ -54,7 +54,7 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ if err != nil { return err } - err = stackUpserter.UpsertStack(vpcStackName, template, nil) + err = stackUpserter.UpsertStack(vpcStackName, template, nil, buildEnvironmentTags(environment.Name, "vpc")) if err != nil { return err } @@ -91,7 +91,7 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ return err } - err = stackUpserter.UpsertStack(envStackName, template, vpcImportParams) + err = stackUpserter.UpsertStack(envStackName, template, vpcImportParams, buildEnvironmentTags(environment.Name, "cluster")) if err != nil { return err } @@ -101,3 +101,10 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ return nil } } + +func buildEnvironmentTags(environmentName string, stackType string) map[string]string { + return map[string]string{ + "type": stackType, + "environment": environmentName, + } +} diff --git a/workflows/environment_test.go b/workflows/environment_test.go index 9d2aa7c4..a1a7e64d 100644 --- a/workflows/environment_test.go +++ b/workflows/environment_test.go @@ -58,7 +58,7 @@ func (m *mockedStackManager) AwaitFinalStatus(stackName string) string { args := m.Called(stackName) return args.String(0) } -func (m *mockedStackManager) UpsertStack(stackName string, templateBodyReader io.Reader, stackParameters map[string]string) error { +func (m *mockedStackManager) UpsertStack(stackName string, templateBodyReader io.Reader, stackParameters map[string]string, stackTags map[string]string) error { args := m.Called(stackName) return args.Error(0) } From f7aed44e75e72f68e0c81d57e468e8cf97e5dd32 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Fri, 13 Jan 2017 12:17:39 -0800 Subject: [PATCH 26/31] cfn tags --- templates/assets.go | 12 ++++++------ templates/assets/vpc.yml | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/templates/assets.go b/templates/assets.go index c2ecac7e..d9cfdca1 100644 --- a/templates/assets.go +++ b/templates/assets.go @@ -84,12 +84,12 @@ func assetsClusterYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/cluster.yml", size: 8683, mode: os.FileMode(420), modTime: time.Unix(1484180215, 0)} + info := bindataFileInfo{name: "assets/cluster.yml", size: 8683, mode: os.FileMode(420), modTime: time.Unix(1484338456, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsVpcYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x98\x4b\x6f\xdb\x38\x10\xc7\xef\xf9\x14\x93\x60\x01\x6d\x81\x38\xb1\xa5\xa4\x68\x78\xf3\x3a\x4e\x63\x6c\x9b\x18\x96\xe1\x02\x69\xf6\x40\x4b\xe3\x58\xa8\x44\x6a\x49\x2a\x0f\x14\xf9\xee\x0b\x51\x92\x1f\xb4\xe4\xfa\x99\x4d\xe1\x8b\x45\x0e\x39\xf3\xff\x69\xc8\xa1\x58\xab\xd5\x0e\x9a\xdf\xdc\x3e\x46\x71\x48\x15\x5e\x71\x11\x51\x35\x40\x21\x03\xce\x08\x58\x76\xbd\x51\xaf\xd5\x2f\x6a\xf5\x0b\xeb\xa0\x4b\x05\x8d\x50\xa1\x90\xe4\x00\xa0\xc3\xa4\xa2\xcc\xc3\x3e\x32\xca\xbc\x97\xb4\x09\xe0\x12\xa5\x27\x82\x58\xe9\xc1\x85\x05\xa8\xcc\x04\x14\x87\x44\x22\x8c\xb8\x80\x41\xb7\xa5\x07\xf4\x5f\x62\x24\xe0\x2a\x11\xb0\x07\xdd\xd0\x0c\x43\xfe\x84\xfe\x80\x86\x09\xca\x6c\xd2\x1a\xf8\x38\xa2\x49\xa8\x26\x4f\x7e\xe0\x51\x85\x7e\xee\x52\xf7\x91\x19\x23\x57\x8e\xf5\x34\x25\x31\xb9\xc9\x90\xa1\x82\x91\xe0\x11\x3c\x8d\x03\x6f\x9c\x06\x45\x53\x63\x70\xdd\x6b\xa0\x9e\x87\x52\x9e\x94\x87\xf6\x35\x60\x5f\x90\x3d\xa8\x31\x01\xeb\xc2\xca\x9a\xe8\xf3\xa4\xa9\xf1\xc9\x9a\x0f\xa8\x7e\xa2\x7f\xa7\xf5\x59\x61\x5d\xaa\x14\x0a\x46\xe0\xe8\xcf\xfb\x7b\xff\x67\xe3\xd8\x79\xfd\x70\x7f\x7f\xb2\xca\xc3\x69\xfe\xd7\x7e\xfd\x70\xa4\xa7\x6c\x71\x26\x95\xa0\x01\x53\x73\x1a\xad\x28\x91\x0a\x86\x08\x14\x1e\x69\x18\xf8\xd0\xea\x5c\xf6\x60\x18\x72\xef\x07\x81\xe7\x13\xfd\x3b\x7d\x3e\x49\xa3\x1d\xc4\x5e\x2b\xf0\xc5\x5f\xba\xaf\x92\x96\x1e\xba\xfc\xb5\xad\xcb\xa6\x51\xc0\x69\x7c\x7c\xbf\x74\xba\xc9\x30\x0c\xbc\x0c\x42\xf3\xae\xb1\x16\xa9\xe6\x5d\x63\xc7\xa4\xec\xb3\xdf\x85\x94\xbd\x26\x29\x7b\x87\xa4\x1a\xbf\x15\x29\x67\x4d\x52\xce\x0e\x49\xd9\xef\x9a\x54\x8b\x33\x3f\x48\xc7\xe8\x22\x70\x4d\xa5\xb1\x18\x33\x5e\x47\x57\x8c\x90\x1b\xae\x8e\xb2\xc7\xb4\x3a\xe8\xa6\xf6\xbf\x09\x0d\xe5\x11\x81\xef\x87\x3d\x1c\x55\x2e\xe4\x63\xb0\xac\x7f\xca\xa6\xb7\xb7\x98\xde\xfe\xf5\xf4\xce\x16\xd3\x3b\xc6\xf4\x3d\x94\x3c\x11\x5e\x56\x2c\x07\xdd\x16\x99\x49\x91\xe6\x37\x97\x90\x76\xcb\x26\xa4\xd8\xb8\xbb\x82\xc7\x28\x54\x50\xd4\x56\x80\x69\x06\x82\xf6\x36\x5b\x12\x72\x93\x36\xa3\xc3\x10\x2f\x99\x74\x93\x38\xe6\x42\x11\xb0\x94\x48\xd0\x32\xbb\xaf\xb9\x54\x8c\x46\x28\x0d\x03\xf3\xa8\x90\x39\x32\x5a\x17\xf7\xdb\x72\x25\x59\x77\x91\x63\x59\x8e\x90\x92\x04\xa9\x90\x3b\x88\xbd\x8e\x5f\x48\xcd\xa1\x2c\x42\xa8\x4a\x98\xdc\xfc\x2b\x8d\x33\x8b\x4e\x7c\xcb\xbe\xd0\x84\x79\x63\x02\xa9\xe2\xbc\xbf\xf9\x48\x83\x90\x0e\x83\x30\x50\x2f\x77\x9c\x21\x81\x43\x17\x43\xf4\x14\x7c\x87\xfa\x31\x1c\x7e\x4e\x67\x95\x79\x76\x68\x91\xf4\x41\x4e\x93\xe0\x6f\x7c\x21\x70\x83\xea\x89\x8b\xc2\x23\x80\x3e\x11\x91\x3c\xb2\xc5\x2d\x77\x2b\x58\xf6\x0e\x61\xd9\xbb\x84\xd5\xd8\x0b\x2c\x67\x2b\x58\xce\x0e\x61\x39\xbb\x84\x65\xef\x08\x56\x87\xa5\x55\x00\xd5\x67\xaa\xf0\x89\xbe\x94\xc3\x32\x8c\x2a\x98\x6c\xe0\x7d\xd0\x6d\xad\x14\xc0\xa0\xdb\xca\xfb\x9b\x4a\x51\x6f\x1c\x21\x53\x6b\xbd\x19\xc3\xcb\xc4\x62\x51\x59\x16\x5b\x8f\x27\x0a\xfb\xe9\x56\x57\x1e\xd0\xb4\x7f\xad\x30\x36\xce\x66\xed\x6f\x49\x28\x79\xc9\x8f\x91\xf9\xf2\x96\x91\x12\xb0\x15\x71\x4e\x85\x4c\xc2\x35\x09\xe4\x96\x97\x28\x55\xc0\x68\xba\x50\x66\xf2\x7c\xfe\xbb\x07\x60\x55\xc0\x93\xed\x76\xea\xa7\x29\x25\xf7\x02\xed\x60\xd9\x9a\x2d\x1d\xb0\x71\x89\xc8\xfa\x0d\xed\xf3\x83\x56\x85\x64\x6c\x8d\x6f\x24\xac\x6a\x3b\x5f\x2a\xcc\xde\x42\x98\xf3\x46\xc2\xaa\xb6\xde\xa5\xc2\x9c\x0d\x84\xe5\x2b\xb0\xe9\x85\xe5\x22\xa6\xfd\xfb\x5e\xeb\x1d\x36\xe4\x09\xf3\xdb\xf1\x18\x23\x14\x34\xec\x72\xa1\xcc\x18\xdb\x4c\x89\x8a\x5d\xd2\x30\xaa\x88\x76\x6a\x65\xa0\x31\x74\x02\xf4\x92\x10\x6f\x92\x68\x88\x22\xfd\xae\xa8\x3b\xc5\x11\xaf\x2b\xb8\xe2\x1e\x0f\x09\x58\x1f\xad\x19\xdb\xa6\x97\xbd\x4a\x7d\xc5\x52\x9c\x17\x1f\x04\xca\xf4\x8c\x38\xa2\xa1\x9c\x1c\x12\x97\x6c\x20\xa9\xe6\x1e\x65\x0f\x48\x26\x98\xae\x04\x8f\x74\x04\xf6\x99\x35\x69\xec\xf3\xd4\xfd\xf9\xb9\x73\x6e\x4d\xc9\xb9\xee\xf5\xfb\xe1\x75\xb6\x17\x5e\x3a\x80\xe2\xd2\xeb\x97\xcc\x6c\xdb\x20\x96\x35\xe4\xb8\xae\x95\x8a\xdf\x0f\xaf\xf3\xff\x39\xbf\x3e\xd5\x0d\x56\x59\xc3\x6d\xa2\x34\xac\xf7\x03\xaa\xbe\x15\xa8\xd9\x8f\xb5\x8d\x38\x99\x98\x26\x8b\xd0\x28\x9e\xa6\x98\x15\xab\x45\xe9\x80\xfd\xd6\xf7\xd5\xde\x84\x51\x42\xdf\x54\xde\x56\x55\x7e\x13\x79\xce\x9b\xca\xdb\xaa\xd6\xaf\x22\xef\x36\x51\x71\xa2\xb2\x6b\x13\x5d\xad\xf3\x03\xf3\xcc\x6d\x55\x7f\x8c\x10\xf8\xc0\x47\xa0\xc6\x08\x8f\xb1\xa7\x4d\xf2\x12\x3d\x57\xdb\xdb\xcf\xfa\x62\xa4\x70\x4f\x23\xfd\x65\x96\x0c\xe1\x8f\x9f\x1a\x87\xab\xa8\xf7\x23\x6d\x7e\xad\x69\x67\x8b\x4b\xa3\x32\x80\x58\xdb\x81\xd4\x86\x81\x3f\x77\xbb\x9c\x85\x92\x7b\xbd\x62\x84\x74\x46\xd3\xf3\x45\xc5\x82\x48\xbb\x96\x24\x7e\xde\xa9\xa3\xbe\xe1\xda\xc1\xba\x0a\x17\x94\x2d\xae\x94\x35\xd5\xda\x1b\xa8\xb5\x97\xa9\xb5\xf7\xa5\xd6\x2e\x51\xeb\xac\xa9\xd6\xd9\x40\xad\xb3\x4c\xad\xb3\x2f\xb5\x4e\xc7\x3f\xf8\x2f\x00\x00\xff\xff\x28\xeb\x52\x31\x42\x1c\x00\x00") +var _assetsVpcYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x99\x4b\x6f\xdb\x38\x10\xc7\xef\xf9\x14\xd3\x60\x01\x6d\x81\x3a\xb5\xa5\xa4\x68\x78\xf3\x3a\x4e\x63\x6c\x9b\x18\x96\xe1\x02\x69\xf6\x40\x4b\xe3\x58\x88\x44\x6a\x49\x2a\x0f\x14\xf9\xee\x0b\x51\x92\x1f\xb4\xe4\xfa\x15\xaf\x0b\x5f\x62\x72\xc8\x99\xff\x4f\x43\xce\x58\xa9\xd5\x6a\x47\xcd\xef\x6e\x1f\xa3\x38\xa4\x0a\x2f\xb9\x88\xa8\x1a\xa0\x90\x01\x67\x04\x2c\xbb\xde\xa8\xd7\xea\xe7\xb5\xfa\xb9\x75\xd4\xa5\x82\x46\xa8\x50\x48\x72\x04\xd0\x61\x52\x51\xe6\x61\x1f\x19\x65\xde\x4b\x3a\x04\x70\x81\xd2\x13\x41\xac\xf4\xe2\xc2\x02\x54\x66\x02\x8a\x43\x22\x11\x46\x5c\xc0\xa0\xdb\xd2\x0b\xfa\x2f\x31\x12\x70\x95\x08\xd8\xbd\x1e\x68\x86\x21\x7f\x42\x7f\x40\xc3\x04\x65\xb6\x69\x0d\x7c\x1c\xd1\x24\x54\x93\x6f\x7e\xe0\x51\x85\x7e\xee\x52\xcf\x91\x19\x23\x57\x8e\xf5\x36\x25\x31\xb9\xc9\x90\xa1\x82\x91\xe0\x11\x3c\x8d\x03\x6f\x9c\x06\x45\x53\x63\x70\xdd\x2b\xa0\x9e\x87\x52\x9e\x94\x87\xf6\x2d\x60\x5f\x91\xdd\xab\x31\x01\xeb\xdc\xca\x86\xe8\xf3\x64\xa8\xf1\xd9\x9a\x0f\xa8\x7e\xa2\x3f\x1f\xeb\xb3\xc2\xba\x54\x29\x14\x8c\xc0\xf1\x9f\x77\x77\xfe\xcf\xc6\x07\xe7\xf5\xfd\xdd\xdd\xc9\x2a\x5f\x3e\xe6\x7f\xda\xaf\xef\x8f\xf5\x96\x2d\xce\xa4\x12\x34\x60\x6a\x4e\xa3\x15\x25\x52\xc1\x10\x81\xc2\x23\x0d\x03\x1f\x5a\x9d\x8b\x1e\x0c\x43\xee\x3d\x10\x78\x3e\xd1\x9f\x8f\xcf\x27\x69\xb4\x83\xd8\x6b\x05\xbe\xf8\x4b\xcf\x55\xd2\xd2\x4b\x97\x3f\xb6\x75\xd9\x34\x0a\x38\x8d\x4f\x87\x4b\xa7\x9b\x0c\xc3\xc0\xcb\x20\x34\x6f\x1b\x6b\x91\x6a\xde\x36\x76\x4c\xca\x3e\xfd\x5d\x48\xd9\x6b\x92\xb2\x77\x48\xaa\xf1\x5b\x91\x72\xd6\x24\xe5\xec\x90\x94\x7d\xd0\xa4\x5a\x9c\xf9\x41\xba\x46\x17\x81\x2b\x2a\x8d\xc3\x98\xf1\x3a\xbe\x64\x84\x5c\x73\x75\x9c\x7d\x4d\xab\x83\x1e\x6a\xff\x9b\xd0\x50\x1e\x13\xf8\xf1\xae\x87\xa3\xca\x83\xfc\x01\x2c\xeb\x9f\xb2\xed\xed\x2d\xb6\xb7\x7f\xbd\xbd\xb3\xc5\xf6\x8e\xb1\x7d\x0f\x25\x4f\x84\x97\x15\xcb\x41\xb7\x45\x66\x52\xa4\xf9\xdd\x25\xa4\xdd\xb2\x09\x29\x2e\xee\xae\xe0\x31\x0a\x15\x14\xb5\x15\x60\x9a\x81\xa0\xbd\xcd\x96\x84\xdc\xa4\xcd\xe8\x30\xc4\x0b\x26\xdd\x24\x8e\xb9\x50\x04\x2c\x25\x12\xb4\xcc\xe9\x2b\x2e\x15\xa3\x11\x4a\xc3\xc0\x6c\x15\x32\x47\xc6\x68\x6e\xdb\xa7\xf7\x72\x8a\xe3\x6f\x7c\x21\x70\x4d\x23\xcc\x47\x00\x74\x63\x40\xe0\x9d\x9b\x0c\xe1\x8f\x9f\x5a\xa0\xab\xa8\xf7\x90\x1a\xbd\x2e\xde\xd9\xe5\x34\xb2\xe9\x22\x4f\xb3\x3c\x23\x25\x49\x56\x81\x6c\x10\x7b\x1d\xbf\xc0\x95\x83\x5d\x04\x59\x95\x74\xb9\xf9\x37\x1a\x67\x16\x9d\xf8\x86\x7d\xa5\x09\xf3\xc6\x04\x52\x6a\xf9\x7c\xf3\x91\x06\x21\x1d\x06\x61\xa0\x5e\x6e\x39\xd3\x9a\x31\x44\x4f\xc1\x0f\xa8\x7f\x80\x77\x5f\xd2\x5d\x65\x9e\x61\x55\xe4\x50\x3d\x71\xf1\x60\xc2\xcb\xfc\x6e\x0a\xb9\x16\xeb\xe5\xb5\xc6\xe2\xbd\xbf\x15\x6d\x7b\x87\xb4\xed\x5d\xd2\x6e\x1c\x26\x6d\x67\x2b\xda\xce\x0e\x69\x3b\xbb\xa4\x6d\x1f\x0a\xed\x0e\x4b\x8b\x21\xaa\x2f\x54\xe1\x13\x7d\x29\xa7\x6d\x18\x55\x40\xdd\x57\xf8\x59\x05\x58\x29\xf0\x41\xb7\x95\xcf\x37\x95\xa2\xde\x38\x42\xa6\xd6\x4a\x09\xc3\xcb\xc4\x62\x91\x48\xa6\xa9\xc7\x13\x85\xfd\xb4\x52\x94\x07\x34\x9d\x5f\x2b\x8c\x3d\x67\xc6\xbc\x9c\x25\x4a\xf2\x86\x2b\x46\xe6\xcb\x1b\x46\x4a\x9e\x4b\x85\xcc\x29\x87\x89\x5a\x13\x60\x6e\x79\x81\x52\x05\x8c\xa6\x07\x7c\xe6\x7c\xce\xff\xea\x04\x58\xf5\xf9\x4c\x0a\xd5\xd4\x4f\x53\x4a\xee\x05\xda\xc1\xb2\xbb\xa6\x74\xc1\xc6\xc5\x35\x9b\x37\xb4\xcf\x2f\x5a\x15\x92\x51\x13\xf6\x24\xac\xaa\x8e\x2d\x15\x66\x6f\x21\xcc\xd9\x93\xb0\xaa\x92\xb1\x54\x98\xb3\x81\xb0\xfc\x00\x37\xbd\xb0\x5c\xc4\x74\xfe\xc0\xaf\x8a\x0e\x1b\xf2\x84\xf9\xed\x78\x8c\x11\x0a\x1a\x76\xb9\x50\xa6\xc4\x36\x53\xa2\xe2\x8e\x36\x8c\x2a\xc4\x4e\xad\x0c\xb2\x06\x26\x80\x5e\x12\xe2\x75\x12\x0d\x51\xa4\x3f\x0a\xeb\x4e\xd1\x9f\x77\x05\x57\xdc\xe3\x21\x01\xeb\x93\x35\x63\xdb\xf4\xb2\x4c\xd0\xef\xc7\x8a\x66\xff\x5e\xa0\x4c\x1b\xfc\x11\x0d\xe5\xa4\xc3\x5f\x72\xff\xa4\x9a\x7b\x94\xdd\x23\x99\xd0\xbb\x14\x3c\xd2\x11\xd8\xa7\xd6\x64\xb0\xcf\x53\xf7\x67\x67\xce\x99\x35\x25\xe7\xba\x57\x87\xc3\xeb\xf4\x4d\x78\xe9\x00\x8a\x37\x96\xbf\x64\x66\xdb\x06\xb1\x6c\x20\xc7\x75\xa5\x54\x7c\x38\xbc\xce\xfe\xe7\xfc\xfa\x5c\x37\x58\x65\x03\x37\x89\xd2\xb0\x0e\x07\x54\x7d\x2b\x50\xb3\xbf\xb4\x37\xe2\x64\x62\x9a\x1c\x42\xa3\xf6\x9a\x62\x56\x2c\x36\xa5\x0b\xde\xb6\x3d\x58\xed\x49\x18\x15\x78\xaf\xf2\xb6\x6a\x12\x36\x91\xe7\xec\x55\xde\x56\xad\xc2\x2a\xf2\x6e\x12\x15\x27\x2a\x7b\xe7\xa5\x8b\x7d\xde\x6f\xcf\xbc\x6a\xec\x8f\x11\x02\x1f\xf8\x08\xd4\x18\xe1\x31\xce\xea\x79\x51\xb9\x67\x5b\x83\xf6\xb3\x7e\xab\x55\xb8\xa7\x51\x75\x69\xd7\xce\x16\x8f\x46\x65\x00\x59\x2b\x00\x52\x1b\x06\xfe\xdc\xbf\x06\xb2\x50\x72\xaf\x97\x8c\x90\xce\x68\xda\x9e\x54\x1c\x88\x74\x6a\x49\xe2\xe7\x93\x3a\xea\x6b\xae\x1d\xac\xab\x70\x41\xd9\xe2\x49\x59\x53\xad\xbd\x81\x5a\x7b\x99\x5a\xfb\xad\xd4\xda\x25\x6a\x9d\x35\xd5\x3a\x1b\xa8\x75\x96\xa9\x75\xde\x4a\xad\xd3\xf1\x8f\xfe\x0b\x00\x00\xff\xff\x66\x12\x61\x06\xff\x1d\x00\x00") func assetsVpcYmlBytes() ([]byte, error) { return bindataRead( @@ -104,7 +104,7 @@ func assetsVpcYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/vpc.yml", size: 7234, mode: os.FileMode(420), modTime: time.Unix(1484088279, 0)} + info := bindataFileInfo{name: "assets/vpc.yml", size: 7679, mode: os.FileMode(420), modTime: time.Unix(1484338356, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -162,7 +162,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "assets/cluster.yml": assetsClusterYml, - "assets/vpc.yml": assetsVpcYml, + "assets/vpc.yml": assetsVpcYml, } // AssetDir returns the file names below a certain @@ -204,11 +204,10 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } - var _bintree = &bintree{nil, map[string]*bintree{ "assets": &bintree{nil, map[string]*bintree{ "cluster.yml": &bintree{assetsClusterYml, map[string]*bintree{}}, - "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, + "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, }}, }} @@ -258,3 +257,4 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } + diff --git a/templates/assets/vpc.yml b/templates/assets/vpc.yml index 10ccc091..b107ee65 100644 --- a/templates/assets/vpc.yml +++ b/templates/assets/vpc.yml @@ -66,6 +66,9 @@ Resources: EnableDnsSupport: 'true' EnableDnsHostnames: 'true' InstanceTenancy: !Ref InstanceTenancy + Tags: + - Key: Name + Value: !Sub ${AWS::StackName} PublicSubnetAZ1: Type: AWS::EC2::Subnet Condition: HasPublicSubnetAZ1 @@ -77,6 +80,8 @@ Resources: Tags: - Key: Network Value: Public + - Key: Name + Value: !Sub ${AWS::StackName}-public-1 PublicSubnetAZ2: Type: AWS::EC2::Subnet Condition: HasPublicSubnetAZ2 @@ -88,6 +93,8 @@ Resources: Tags: - Key: Network Value: Public + - Key: Name + Value: !Sub ${AWS::StackName}-public-2 PublicSubnetAZ3: Type: AWS::EC2::Subnet Condition: HasPublicSubnetAZ3 @@ -99,12 +106,16 @@ Resources: Tags: - Key: Network Value: Public + - Key: Name + Value: !Sub ${AWS::StackName}-public-3 InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Network Value: Public + - Key: Name + Value: !Sub ${AWS::StackName} VPCInternetGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: @@ -117,6 +128,8 @@ Resources: Tags: - Key: Network Value: Public + - Key: Name + Value: !Sub ${AWS::StackName}-public PublicRoute: Type: AWS::EC2::Route DependsOn: VPCInternetGateway @@ -149,6 +162,8 @@ Resources: Tags: - Key: Network Value: Public + - Key: Name + Value: !Sub ${AWS::StackName}-public InboundEphemeralPortPublicNetworkAclEntry: Type: AWS::EC2::NetworkAclEntry Properties: From 4caad60a9af8dbcf2582178fd78c1377894327a8 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Fri, 13 Jan 2017 17:01:48 -0800 Subject: [PATCH 27/31] use mocks for stack testing. add support for list environments --- cli/environments.go | 4 +- common/stack.go | 71 +++++++++++++++++++++ common/stack_test.go | 112 ++++++++++++++++------------------ common/types.go | 20 ++++++ templates/assets.go | 10 +-- workflows/environment.go | 60 +++++++++++++++--- workflows/environment_test.go | 4 +- 7 files changed, 204 insertions(+), 77 deletions(-) diff --git a/cli/environments.go b/cli/environments.go index b6d0b0ae..79c2a8a7 100644 --- a/cli/environments.go +++ b/cli/environments.go @@ -52,8 +52,8 @@ func newEnvironmentsListCommand(ctx *common.Context) *cli.Command { Aliases: []string{"ls"}, Usage: "list environments", Action: func(c *cli.Context) error { - fmt.Println("listing environments") - return nil + workflow := workflows.NewEnvironmentLister(ctx) + return workflow() }, } diff --git a/common/stack.go b/common/stack.go index 392a0fcd..e37fc3e7 100644 --- a/common/stack.go +++ b/common/stack.go @@ -10,11 +10,17 @@ import ( "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" "github.com/op/go-logging" "io" + "strings" "time" ) var log = logging.MustGetLogger("stack") +// CreateStackName will create a name for a stack +func CreateStackName(stackType StackType, name string) string { + return fmt.Sprintf("mu-%s-%s", stackType, name) +} + // StackWaiter for waiting on stack status to be final type StackWaiter interface { AwaitFinalStatus(stackName string) string @@ -25,10 +31,16 @@ type StackUpserter interface { UpsertStack(stackName string, templateBodyReader io.Reader, parameters map[string]string, tags map[string]string) error } +// StackLister for listing stacks +type StackLister interface { + ListStacks(stackType StackType) ([]*Stack, error) +} + // StackManager composite of all stack capabilities type StackManager interface { StackUpserter StackWaiter + StackLister } type cloudformationStackManager struct { @@ -210,3 +222,62 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) str log.Debugf(" Stack doesn't exist ... stack=%s", stackName) return "" } + +// ListStacks will find mu stacks +func (cfnMgr *cloudformationStackManager) ListStacks(stackType StackType) ([]*Stack, error) { + cfnAPI := cfnMgr.cfnAPI + + params := &cloudformation.ListStacksInput{ + StackStatusFilter: []*string{ + aws.String(cloudformation.StackStatusReviewInProgress), + aws.String(cloudformation.StackStatusCreateInProgress), + aws.String(cloudformation.StackStatusRollbackInProgress), + aws.String(cloudformation.StackStatusDeleteInProgress), + aws.String(cloudformation.StackStatusUpdateInProgress), + aws.String(cloudformation.StackStatusUpdateRollbackInProgress), + aws.String(cloudformation.StackStatusUpdateCompleteCleanupInProgress), + aws.String(cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress), + aws.String(cloudformation.StackStatusCreateFailed), + aws.String(cloudformation.StackStatusCreateComplete), + aws.String(cloudformation.StackStatusRollbackFailed), + aws.String(cloudformation.StackStatusRollbackComplete), + aws.String(cloudformation.StackStatusDeleteFailed), + aws.String(cloudformation.StackStatusUpdateComplete), + aws.String(cloudformation.StackStatusUpdateRollbackFailed), + aws.String(cloudformation.StackStatusUpdateRollbackComplete), + }, + } + + var stacks []*Stack + + stackNamePrefix := "mu-" + if stackType != "" { + stackNamePrefix = fmt.Sprintf("%s%s-", stackNamePrefix, stackType) + } + log.Debugf("Searching for stacks with prefix '%s'", stackNamePrefix) + + err := cfnAPI.ListStacksPages(params, + func(page *cloudformation.ListStacksOutput, lastPage bool) bool { + for _, stackSummary := range page.StackSummaries { + if !strings.HasPrefix(aws.StringValue(stackSummary.StackName), stackNamePrefix) { + continue + } + if strings.HasPrefix(aws.StringValue(stackSummary.StackStatus), "DELETE_") { + continue + } + stack := new(Stack) + stack.ID = aws.StringValue(stackSummary.StackId) + stack.Name = aws.StringValue(stackSummary.StackName) + stack.Status = aws.StringValue(stackSummary.StackStatus) + stack.StatusReason = aws.StringValue(stackSummary.StackStatusReason) + + stacks = append(stacks, stack) + } + return true + }) + + if err != nil { + return nil, err + } + return stacks, nil +} diff --git a/common/stack_test.go b/common/stack_test.go index 1b22195a..26dac38f 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -6,68 +6,58 @@ import ( "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "strings" "testing" ) type mockedCloudFormation struct { + mock.Mock cloudformationiface.CloudFormationAPI - responses []*cloudformation.DescribeStacksOutput - responseCount int - createCount int - updateCount int - waitUntilStackExists int - waitUntilStackCreateComplete int - waitUntilStackUpdateComplete int } -func (c *mockedCloudFormation) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) { - if c.responseCount >= len(c.responses) { - return nil, errors.New("stack not found") - } - - resp := c.responses[c.responseCount] - c.responseCount = c.responseCount + 1 - if resp == nil { - return nil, errors.New("stack not found") - } - return resp, nil +func (m *mockedCloudFormation) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) { + args := m.Called() + return args.Get(0).(*cloudformation.DescribeStacksOutput), args.Error(1) +} +func (m *mockedCloudFormation) ListStacksPages(input *cloudformation.ListStacksInput, callback func(page *cloudformation.ListStacksOutput, lastPage bool) bool) error { + args := m.Called() + return args.Error(0) } -func (c *mockedCloudFormation) WaitUntilStackCreateComplete(*cloudformation.DescribeStacksInput) error { - c.waitUntilStackCreateComplete = c.waitUntilStackCreateComplete + 1 +func (m *mockedCloudFormation) WaitUntilStackCreateComplete(*cloudformation.DescribeStacksInput) error { + m.Called() return nil } -func (c *mockedCloudFormation) WaitUntilStackUpdateComplete(*cloudformation.DescribeStacksInput) error { - c.waitUntilStackUpdateComplete = c.waitUntilStackUpdateComplete + 1 +func (m *mockedCloudFormation) WaitUntilStackUpdateComplete(*cloudformation.DescribeStacksInput) error { + m.Called() return nil } -func (c *mockedCloudFormation) WaitUntilStackExists(*cloudformation.DescribeStacksInput) error { - c.waitUntilStackExists = c.waitUntilStackExists + 1 +func (m *mockedCloudFormation) WaitUntilStackExists(*cloudformation.DescribeStacksInput) error { + m.Called() return nil } -func (c *mockedCloudFormation) CreateStack(*cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) { - c.createCount = c.createCount + 1 - return nil, nil +func (m *mockedCloudFormation) CreateStack(*cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) { + args := m.Called() + return args.Get(0).(*cloudformation.CreateStackOutput), args.Error(1) } -func (c *mockedCloudFormation) UpdateStack(*cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) { - c.updateCount = c.updateCount + 1 - return nil, nil +func (m *mockedCloudFormation) UpdateStack(*cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) { + args := m.Called() + return args.Get(0).(*cloudformation.UpdateStackOutput), args.Error(1) } func TestStack_AwaitFinalStatus_CreateComplete(t *testing.T) { assert := assert.New(t) cfn := new(mockedCloudFormation) - cfn.responses = []*cloudformation.DescribeStacksOutput{ + cfn.On("DescribeStacks").Return( &cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ - &cloudformation.Stack{ + { StackStatus: aws.String(cloudformation.StackStatusCreateComplete), }, }, - }, - } + }, nil) stackManager := cloudformationStackManager{ cfnAPI: cfn, @@ -76,29 +66,32 @@ func TestStack_AwaitFinalStatus_CreateComplete(t *testing.T) { finalStatus := stackManager.AwaitFinalStatus("foo") assert.Equal(cloudformation.StackStatusCreateComplete, finalStatus) - assert.Equal(1, cfn.responseCount) + cfn.AssertExpectations(t) + cfn.AssertNumberOfCalls(t, "DescribeStacks", 1) } func TestStack_AwaitFinalStatus_CreateInProgress(t *testing.T) { assert := assert.New(t) cfn := new(mockedCloudFormation) - cfn.responses = []*cloudformation.DescribeStacksOutput{ + cfn.On("DescribeStacks").Return( &cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ - &cloudformation.Stack{ + { StackStatus: aws.String(cloudformation.StackStatusCreateInProgress), }, }, - }, + }, nil).Once() + cfn.On("DescribeStacks").Return( &cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ - &cloudformation.Stack{ + { StackStatus: aws.String(cloudformation.StackStatusCreateComplete), }, }, - }, - } + }, nil) + + cfn.On("WaitUntilStackCreateComplete").Return(nil) stackManager := cloudformationStackManager{ cfnAPI: cfn, @@ -107,17 +100,18 @@ func TestStack_AwaitFinalStatus_CreateInProgress(t *testing.T) { finalStatus := stackManager.AwaitFinalStatus("foo") assert.Equal(cloudformation.StackStatusCreateComplete, finalStatus) - assert.Equal(2, cfn.responseCount) - assert.Equal(1, cfn.waitUntilStackCreateComplete) + cfn.AssertExpectations(t) + cfn.AssertNumberOfCalls(t, "DescribeStacks", 2) + cfn.AssertNumberOfCalls(t, "WaitUntilStackCreateComplete", 1) } func TestStack_UpsertStack_Create(t *testing.T) { assert := assert.New(t) cfn := new(mockedCloudFormation) - cfn.responses = []*cloudformation.DescribeStacksOutput{ - nil, - } + cfn.On("DescribeStacks").Return(&cloudformation.DescribeStacksOutput{}, errors.New("stack not found")) + cfn.On("CreateStack").Return(&cloudformation.CreateStackOutput{}, nil) + cfn.On("WaitUntilStackExists").Return(nil) stackManager := cloudformationStackManager{ cfnAPI: cfn, @@ -125,24 +119,25 @@ func TestStack_UpsertStack_Create(t *testing.T) { err := stackManager.UpsertStack("foo", strings.NewReader(""), nil, nil) assert.Nil(err) - assert.Equal(1, cfn.waitUntilStackExists) - assert.Equal(1, cfn.createCount) - assert.Equal(1, cfn.responseCount) + cfn.AssertExpectations(t) + cfn.AssertNumberOfCalls(t, "DescribeStacks", 1) + cfn.AssertNumberOfCalls(t, "CreateStack", 1) + cfn.AssertNumberOfCalls(t, "WaitUntilStackExists", 1) } func TestStack_UpsertStack_Update(t *testing.T) { assert := assert.New(t) cfn := new(mockedCloudFormation) - cfn.responses = []*cloudformation.DescribeStacksOutput{ + cfn.On("DescribeStacks").Return( &cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ - &cloudformation.Stack{ + { StackStatus: aws.String(cloudformation.StackStatusCreateComplete), }, }, - }, - } + }, nil) + cfn.On("UpdateStack").Return(&cloudformation.UpdateStackOutput{}, nil) stackManager := cloudformationStackManager{ cfnAPI: cfn, @@ -150,11 +145,12 @@ func TestStack_UpsertStack_Update(t *testing.T) { err := stackManager.UpsertStack("foo", strings.NewReader(""), nil, nil) assert.Nil(err) - assert.Equal(0, cfn.waitUntilStackExists) - assert.Equal(0, cfn.createCount) - assert.Equal(1, cfn.updateCount) - assert.Equal(0, cfn.waitUntilStackUpdateComplete) - assert.Equal(1, cfn.responseCount) + cfn.AssertExpectations(t) + cfn.AssertNumberOfCalls(t, "DescribeStacks", 1) + cfn.AssertNumberOfCalls(t, "CreateStack", 0) + cfn.AssertNumberOfCalls(t, "UpdateStack", 1) + cfn.AssertNumberOfCalls(t, "WaitUntilStackUpdateComplete", 0) + cfn.AssertNumberOfCalls(t, "WaitUntilStackExists", 0) } func TestBuildParameters(t *testing.T) { diff --git a/common/types.go b/common/types.go index 1f5e2ef2..602d517d 100644 --- a/common/types.go +++ b/common/types.go @@ -35,3 +35,23 @@ type Service struct { Pipeline struct { } } + +// Stack summary +type Stack struct { + ID string + Name string + Status string + StatusReason string + Tags map[string]string +} + +// StackType describes supported stack types +type StackType string + +// List of valid stack types +const ( + StackTypeVpc StackType = "vpc" + StackTypeCluster = "cluster" + StackTypeService = "service" + StackTypePipeline = "pipeline" +) diff --git a/templates/assets.go b/templates/assets.go index d9cfdca1..dc8ef7a9 100644 --- a/templates/assets.go +++ b/templates/assets.go @@ -89,7 +89,7 @@ func assetsClusterYml() (*asset, error) { return a, nil } -var _assetsVpcYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x99\x4b\x6f\xdb\x38\x10\xc7\xef\xf9\x14\xd3\x60\x01\x6d\x81\x3a\xb5\xa5\xa4\x68\x78\xf3\x3a\x4e\x63\x6c\x9b\x18\x96\xe1\x02\x69\xf6\x40\x4b\xe3\x58\x88\x44\x6a\x49\x2a\x0f\x14\xf9\xee\x0b\x51\x92\x1f\xb4\xe4\xfa\x15\xaf\x0b\x5f\x62\x72\xc8\x99\xff\x4f\x43\xce\x58\xa9\xd5\x6a\x47\xcd\xef\x6e\x1f\xa3\x38\xa4\x0a\x2f\xb9\x88\xa8\x1a\xa0\x90\x01\x67\x04\x2c\xbb\xde\xa8\xd7\xea\xe7\xb5\xfa\xb9\x75\xd4\xa5\x82\x46\xa8\x50\x48\x72\x04\xd0\x61\x52\x51\xe6\x61\x1f\x19\x65\xde\x4b\x3a\x04\x70\x81\xd2\x13\x41\xac\xf4\xe2\xc2\x02\x54\x66\x02\x8a\x43\x22\x11\x46\x5c\xc0\xa0\xdb\xd2\x0b\xfa\x2f\x31\x12\x70\x95\x08\xd8\xbd\x1e\x68\x86\x21\x7f\x42\x7f\x40\xc3\x04\x65\xb6\x69\x0d\x7c\x1c\xd1\x24\x54\x93\x6f\x7e\xe0\x51\x85\x7e\xee\x52\xcf\x91\x19\x23\x57\x8e\xf5\x36\x25\x31\xb9\xc9\x90\xa1\x82\x91\xe0\x11\x3c\x8d\x03\x6f\x9c\x06\x45\x53\x63\x70\xdd\x2b\xa0\x9e\x87\x52\x9e\x94\x87\xf6\x2d\x60\x5f\x91\xdd\xab\x31\x01\xeb\xdc\xca\x86\xe8\xf3\x64\xa8\xf1\xd9\x9a\x0f\xa8\x7e\xa2\x3f\x1f\xeb\xb3\xc2\xba\x54\x29\x14\x8c\xc0\xf1\x9f\x77\x77\xfe\xcf\xc6\x07\xe7\xf5\xfd\xdd\xdd\xc9\x2a\x5f\x3e\xe6\x7f\xda\xaf\xef\x8f\xf5\x96\x2d\xce\xa4\x12\x34\x60\x6a\x4e\xa3\x15\x25\x52\xc1\x10\x81\xc2\x23\x0d\x03\x1f\x5a\x9d\x8b\x1e\x0c\x43\xee\x3d\x10\x78\x3e\xd1\x9f\x8f\xcf\x27\x69\xb4\x83\xd8\x6b\x05\xbe\xf8\x4b\xcf\x55\xd2\xd2\x4b\x97\x3f\xb6\x75\xd9\x34\x0a\x38\x8d\x4f\x87\x4b\xa7\x9b\x0c\xc3\xc0\xcb\x20\x34\x6f\x1b\x6b\x91\x6a\xde\x36\x76\x4c\xca\x3e\xfd\x5d\x48\xd9\x6b\x92\xb2\x77\x48\xaa\xf1\x5b\x91\x72\xd6\x24\xe5\xec\x90\x94\x7d\xd0\xa4\x5a\x9c\xf9\x41\xba\x46\x17\x81\x2b\x2a\x8d\xc3\x98\xf1\x3a\xbe\x64\x84\x5c\x73\x75\x9c\x7d\x4d\xab\x83\x1e\x6a\xff\x9b\xd0\x50\x1e\x13\xf8\xf1\xae\x87\xa3\xca\x83\xfc\x01\x2c\xeb\x9f\xb2\xed\xed\x2d\xb6\xb7\x7f\xbd\xbd\xb3\xc5\xf6\x8e\xb1\x7d\x0f\x25\x4f\x84\x97\x15\xcb\x41\xb7\x45\x66\x52\xa4\xf9\xdd\x25\xa4\xdd\xb2\x09\x29\x2e\xee\xae\xe0\x31\x0a\x15\x14\xb5\x15\x60\x9a\x81\xa0\xbd\xcd\x96\x84\xdc\xa4\xcd\xe8\x30\xc4\x0b\x26\xdd\x24\x8e\xb9\x50\x04\x2c\x25\x12\xb4\xcc\xe9\x2b\x2e\x15\xa3\x11\x4a\xc3\xc0\x6c\x15\x32\x47\xc6\x68\x6e\xdb\xa7\xf7\x72\x8a\xe3\x6f\x7c\x21\x70\x4d\x23\xcc\x47\x00\x74\x63\x40\xe0\x9d\x9b\x0c\xe1\x8f\x9f\x5a\xa0\xab\xa8\xf7\x90\x1a\xbd\x2e\xde\xd9\xe5\x34\xb2\xe9\x22\x4f\xb3\x3c\x23\x25\x49\x56\x81\x6c\x10\x7b\x1d\xbf\xc0\x95\x83\x5d\x04\x59\x95\x74\xb9\xf9\x37\x1a\x67\x16\x9d\xf8\x86\x7d\xa5\x09\xf3\xc6\x04\x52\x6a\xf9\x7c\xf3\x91\x06\x21\x1d\x06\x61\xa0\x5e\x6e\x39\xd3\x9a\x31\x44\x4f\xc1\x0f\xa8\x7f\x80\x77\x5f\xd2\x5d\x65\x9e\x61\x55\xe4\x50\x3d\x71\xf1\x60\xc2\xcb\xfc\x6e\x0a\xb9\x16\xeb\xe5\xb5\xc6\xe2\xbd\xbf\x15\x6d\x7b\x87\xb4\xed\x5d\xd2\x6e\x1c\x26\x6d\x67\x2b\xda\xce\x0e\x69\x3b\xbb\xa4\x6d\x1f\x0a\xed\x0e\x4b\x8b\x21\xaa\x2f\x54\xe1\x13\x7d\x29\xa7\x6d\x18\x55\x40\xdd\x57\xf8\x59\x05\x58\x29\xf0\x41\xb7\x95\xcf\x37\x95\xa2\xde\x38\x42\xa6\xd6\x4a\x09\xc3\xcb\xc4\x62\x91\x48\xa6\xa9\xc7\x13\x85\xfd\xb4\x52\x94\x07\x34\x9d\x5f\x2b\x8c\x3d\x67\xc6\xbc\x9c\x25\x4a\xf2\x86\x2b\x46\xe6\xcb\x1b\x46\x4a\x9e\x4b\x85\xcc\x29\x87\x89\x5a\x13\x60\x6e\x79\x81\x52\x05\x8c\xa6\x07\x7c\xe6\x7c\xce\xff\xea\x04\x58\xf5\xf9\x4c\x0a\xd5\xd4\x4f\x53\x4a\xee\x05\xda\xc1\xb2\xbb\xa6\x74\xc1\xc6\xc5\x35\x9b\x37\xb4\xcf\x2f\x5a\x15\x92\x51\x13\xf6\x24\xac\xaa\x8e\x2d\x15\x66\x6f\x21\xcc\xd9\x93\xb0\xaa\x92\xb1\x54\x98\xb3\x81\xb0\xfc\x00\x37\xbd\xb0\x5c\xc4\x74\xfe\xc0\xaf\x8a\x0e\x1b\xf2\x84\xf9\xed\x78\x8c\x11\x0a\x1a\x76\xb9\x50\xa6\xc4\x36\x53\xa2\xe2\x8e\x36\x8c\x2a\xc4\x4e\xad\x0c\xb2\x06\x26\x80\x5e\x12\xe2\x75\x12\x0d\x51\xa4\x3f\x0a\xeb\x4e\xd1\x9f\x77\x05\x57\xdc\xe3\x21\x01\xeb\x93\x35\x63\xdb\xf4\xb2\x4c\xd0\xef\xc7\x8a\x66\xff\x5e\xa0\x4c\x1b\xfc\x11\x0d\xe5\xa4\xc3\x5f\x72\xff\xa4\x9a\x7b\x94\xdd\x23\x99\xd0\xbb\x14\x3c\xd2\x11\xd8\xa7\xd6\x64\xb0\xcf\x53\xf7\x67\x67\xce\x99\x35\x25\xe7\xba\x57\x87\xc3\xeb\xf4\x4d\x78\xe9\x00\x8a\x37\x96\xbf\x64\x66\xdb\x06\xb1\x6c\x20\xc7\x75\xa5\x54\x7c\x38\xbc\xce\xfe\xe7\xfc\xfa\x5c\x37\x58\x65\x03\x37\x89\xd2\xb0\x0e\x07\x54\x7d\x2b\x50\xb3\xbf\xb4\x37\xe2\x64\x62\x9a\x1c\x42\xa3\xf6\x9a\x62\x56\x2c\x36\xa5\x0b\xde\xb6\x3d\x58\xed\x49\x18\x15\x78\xaf\xf2\xb6\x6a\x12\x36\x91\xe7\xec\x55\xde\x56\xad\xc2\x2a\xf2\x6e\x12\x15\x27\x2a\x7b\xe7\xa5\x8b\x7d\xde\x6f\xcf\xbc\x6a\xec\x8f\x11\x02\x1f\xf8\x08\xd4\x18\xe1\x31\xce\xea\x79\x51\xb9\x67\x5b\x83\xf6\xb3\x7e\xab\x55\xb8\xa7\x51\x75\x69\xd7\xce\x16\x8f\x46\x65\x00\x59\x2b\x00\x52\x1b\x06\xfe\xdc\xbf\x06\xb2\x50\x72\xaf\x97\x8c\x90\xce\x68\xda\x9e\x54\x1c\x88\x74\x6a\x49\xe2\xe7\x93\x3a\xea\x6b\xae\x1d\xac\xab\x70\x41\xd9\xe2\x49\x59\x53\xad\xbd\x81\x5a\x7b\x99\x5a\xfb\xad\xd4\xda\x25\x6a\x9d\x35\xd5\x3a\x1b\xa8\x75\x96\xa9\x75\xde\x4a\xad\xd3\xf1\x8f\xfe\x0b\x00\x00\xff\xff\x66\x12\x61\x06\xff\x1d\x00\x00") +var _assetsVpcYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x99\x4b\x6f\xdb\x38\x10\xc7\xef\xf9\x14\xd3\x60\x01\x6d\x81\x3a\xb5\xa5\xa4\x68\x78\xf3\x3a\x4e\x63\x6c\x9b\x18\x96\xe1\x02\x69\xf6\x40\x4b\xe3\x58\x88\x44\x6a\x49\x2a\x0f\x14\xf9\xee\x0b\x51\x92\x1f\xb4\xe4\xfa\x15\xaf\x8b\x5c\x6a\x72\xc8\x99\xff\x4f\x43\xce\x48\xad\xd5\x6a\x47\xcd\xef\x6e\x1f\xa3\x38\xa4\x0a\x2f\xb9\x88\xa8\x1a\xa0\x90\x01\x67\x04\x2c\xbb\xde\xa8\xd7\xea\xe7\xb5\xfa\xb9\x75\xd4\xa5\x82\x46\xa8\x50\x48\x72\x04\xd0\x61\x52\x51\xe6\x61\x1f\x19\x65\xde\x4b\x3a\x04\x70\x81\xd2\x13\x41\xac\xf4\xe2\xc2\x02\x54\x66\x02\x8a\x43\x22\x11\x46\x5c\xc0\xa0\xdb\xd2\x0b\xfa\x2f\x31\x12\x70\x95\x08\xd8\xbd\x1e\x68\x86\x21\x7f\x42\x7f\x40\xc3\x04\x65\xb6\x69\x0d\x7c\x1c\xd1\x24\x54\x93\x5f\x7e\xe0\x51\x85\x7e\xee\x52\xcf\x91\x19\x23\x57\x8e\xf5\x36\x25\x31\xb9\xc9\x90\xa1\x82\x91\xe0\x11\x3c\x8d\x03\x6f\x9c\x06\x45\x53\x63\x70\xdd\x2b\xa0\x9e\x87\x52\x9e\x94\x87\xf6\x2d\x60\x5f\x91\xdd\xab\x31\x01\xeb\xdc\xca\x86\xe8\xf3\x64\xa8\xf1\xd9\x9a\x0f\xa8\x7e\xa2\xff\x3e\xd6\x67\x85\x75\xa9\x52\x28\x18\x81\xe3\x3f\xef\xee\xfc\x9f\x8d\x0f\xce\xeb\xfb\xbb\xbb\x93\x55\x7e\x7c\xcc\xff\x69\xbf\xbe\x3f\xd6\x5b\xb6\x38\x93\x4a\xd0\x80\xa9\x39\x8d\x56\x94\x48\x05\x43\x04\x0a\x8f\x34\x0c\x7c\x68\x75\x2e\x7a\x30\x0c\xb9\xf7\x40\xe0\xf9\x44\xff\x7d\x7c\x3e\x49\xa3\x1d\xc4\x5e\x2b\xf0\xc5\x5f\x7a\xae\x92\x96\x5e\xba\xfc\xb1\xad\xcb\xa6\x51\xc0\x69\x7c\x3a\x5c\x3a\xdd\x64\x18\x06\x5e\x06\xa1\x79\xdb\x58\x8b\x54\xf3\xb6\xb1\x63\x52\xf6\xe9\xef\x42\xca\x5e\x93\x94\xbd\x43\x52\x8d\xdf\x8a\x94\xb3\x26\x29\x67\x87\xa4\xec\x83\x26\xd5\xe2\xcc\x0f\xd2\x35\xba\x08\x5c\x51\x69\x1c\xc6\x8c\xd7\xf1\x25\x23\xe4\x9a\xab\xe3\xec\x67\x5a\x1d\xf4\x50\xfb\xdf\x84\x86\xf2\x98\xc0\x8f\x77\x3d\x1c\x55\x1e\xe4\x0f\x60\x59\xff\x94\x6d\x6f\x6f\xb1\xbd\xfd\xeb\xed\x9d\x2d\xb6\x77\x8c\xed\x7b\x28\x79\x22\xbc\xac\x58\x0e\xba\x2d\x32\x93\x22\xcd\xef\x2e\x21\xed\x96\x4d\x48\x71\x71\x77\x05\x8f\x51\xa8\xa0\xa8\xad\x00\xd3\x0c\x04\xed\x6d\xb6\x24\xe4\x26\x6d\x46\x87\x21\x5e\x30\xe9\x26\x71\xcc\x85\x22\x60\x29\x91\xa0\x65\x4e\x5f\x71\xa9\x18\x8d\x50\x1a\x06\x66\xab\x90\x39\x32\x46\x73\xdb\x3e\xbd\x97\x53\x1c\x7f\xe3\x0b\x81\x6b\x1a\x61\x3e\x02\xa0\x1b\x03\x02\xef\xdc\x64\x08\x7f\xfc\xd4\x02\x5d\x45\xbd\x87\xd4\xe8\x75\xf1\xce\x2e\xa7\x91\x4d\x17\x79\x9a\xe5\x19\x29\x49\xb2\x0a\x64\x83\xd8\xeb\xf8\x05\xae\x1c\xec\x22\xc8\xaa\xa4\xcb\xcd\xbf\xd1\x38\xb3\xe8\xc4\x37\xec\x2b\x4d\x98\x37\x26\x90\x52\xcb\xe7\x9b\x8f\x34\x08\xe9\x30\x08\x03\xf5\x72\xcb\x99\xd6\x8c\x21\x7a\x0a\x7e\x40\xfd\x03\xbc\xfb\x92\xee\x2a\xf3\x0c\xab\x22\x87\xea\x89\x8b\x07\x13\x5e\xe6\x77\x53\xc8\xb5\x58\x2f\xaf\x35\x16\xef\xfd\xad\x68\xdb\x3b\xa4\x6d\xef\x92\x76\xe3\x10\x68\xdb\x8b\xb5\x63\x2b\xda\xce\x0e\x69\x3b\xbb\xa4\x6d\x1f\x02\x6d\x47\xbf\xe0\xa4\xc5\x10\xd5\x17\xaa\xf0\x89\xbe\x94\xd3\x36\x8c\x2a\xa0\xee\x2b\xfc\xac\x02\xac\x14\xf8\xa0\xdb\xca\xe7\x9b\x4a\x51\x6f\x1c\x21\x53\x6b\xa5\x84\xe1\x65\x62\xb1\x48\x24\xd3\xd4\xe3\x89\xc2\x7e\x5a\x29\xca\x03\x9a\xce\xaf\x15\xc6\x9e\x33\x63\x5e\xce\x12\x25\x79\xc3\x15\x23\xf3\xe5\x0d\x23\x25\xcf\xa5\x42\xe6\x94\xc3\x44\xad\x09\x30\xb7\xbc\x40\xa9\x02\x46\xd3\x03\x3e\x73\x3e\xe7\xdf\x3a\x01\x56\x7d\x3e\x93\x42\x35\xf5\xd3\x94\x92\x7b\x81\x76\xb0\xec\xae\x29\x5d\xb0\x71\x71\xcd\xe6\x0d\xed\xf3\x8b\x56\x85\x64\xd4\x84\x3d\x09\xab\xaa\x63\x4b\x85\xd9\x5b\x08\x73\xf6\x24\xac\xaa\x64\x2c\x15\xe6\x6c\x20\x2c\x3f\xc0\x4d\x2f\x2c\x17\x31\x9d\x3f\xf0\xab\xa2\xc3\x86\x3c\x61\x7e\x3b\x1e\x63\x84\x82\x86\x5d\x2e\x94\x29\xb1\xcd\x94\xa8\xb8\xa3\x0d\xa3\x0a\xb1\x53\x2b\x83\xac\x81\x09\xa0\x97\x84\x78\x9d\x44\x43\x14\xe9\x4b\x61\xdd\x29\xfa\xf3\xae\xe0\x8a\x7b\x3c\x24\x60\x7d\xb2\x66\x6c\x9b\x5e\x96\x09\xfa\xfb\x58\xd1\xec\xdf\x0b\x94\x69\x83\x3f\xa2\xa1\x9c\x74\xf8\x4b\xee\x9f\x54\x73\x8f\xb2\x7b\x24\x13\x7a\x97\x82\x47\x3a\x02\xfb\xd4\x9a\x0c\xf6\x79\xea\xfe\xec\xcc\x39\xb3\xa6\xe4\x5c\xf7\xea\x70\x78\x9d\xbe\x09\x2f\x1d\x40\xf1\xc5\xf2\x97\xcc\x6c\xdb\x20\x96\x0d\xe4\xb8\xae\x94\x8a\x0f\x87\xd7\xd9\xff\x9c\x5f\x9f\xeb\x06\xab\x6c\xe0\x26\x51\x1a\xd6\xe1\x80\xaa\x6f\x05\x6a\xf6\x4d\x7b\x23\x4e\x26\xa6\xc9\x21\x34\x6a\xaf\x29\x66\xc5\x62\x53\xba\xe0\x6d\xdb\x83\xd5\x9e\x84\x51\x81\xf7\x2a\x6f\xab\x26\x61\x13\x79\xce\x5e\xe5\x6d\xd5\x2a\xac\x22\xef\x26\x51\x71\xa2\xb2\x6f\x5e\xba\xd8\xe7\xfd\xf6\xcc\xa7\xc6\xfe\x18\x21\xf0\x81\x8f\x40\x8d\x11\x1e\xe3\xac\x9e\x17\x95\x7b\xb6\x35\x68\x3f\xeb\xaf\x5a\x85\x7b\x1a\x55\x97\x76\xed\x6c\xf1\x68\x54\x06\x90\xb5\x02\x20\xb5\x61\xe0\xcf\xfd\xd7\x40\x16\x4a\xee\xf5\x92\x11\xd2\x19\x4d\xdb\x93\x8a\x03\x91\x4e\x2d\x49\xfc\x7c\x52\x47\x7d\xcd\xb5\x83\x75\x15\x2e\x28\x5b\x3c\x29\x6b\xaa\xb5\x37\x50\x6b\x2f\x53\x6b\xbf\x95\x5a\xbb\x44\xad\xb3\xa6\x5a\x67\x03\xb5\xce\x32\xb5\xce\x5b\xa9\x75\x3a\xfe\xd1\x7f\x01\x00\x00\xff\xff\x7b\x24\x36\xe9\xff\x1d\x00\x00") func assetsVpcYmlBytes() ([]byte, error) { return bindataRead( @@ -104,7 +104,7 @@ func assetsVpcYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/vpc.yml", size: 7679, mode: os.FileMode(420), modTime: time.Unix(1484338356, 0)} + info := bindataFileInfo{name: "assets/vpc.yml", size: 7679, mode: os.FileMode(420), modTime: time.Unix(1484338624, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -162,7 +162,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "assets/cluster.yml": assetsClusterYml, - "assets/vpc.yml": assetsVpcYml, + "assets/vpc.yml": assetsVpcYml, } // AssetDir returns the file names below a certain @@ -204,10 +204,11 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "assets": &bintree{nil, map[string]*bintree{ "cluster.yml": &bintree{assetsClusterYml, map[string]*bintree{}}, - "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, + "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, }}, }} @@ -257,4 +258,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/workflows/environment.go b/workflows/environment.go index 16949051..532b170e 100644 --- a/workflows/environment.go +++ b/workflows/environment.go @@ -2,6 +2,7 @@ package workflows import ( "fmt" + "github.com/fatih/color" "github.com/op/go-logging" "github.com/stelligent/mu/common" "github.com/stelligent/mu/templates" @@ -10,6 +11,10 @@ import ( var log = logging.MustGetLogger("environment") +type environmentWorkflow struct { + environment *common.Environment +} + // NewEnvironmentUpserter create a new workflow for upserting an environment func NewEnvironmentUpserter(ctx *common.Context, environmentName string) Executor { @@ -23,10 +28,6 @@ func NewEnvironmentUpserter(ctx *common.Context, environmentName string) Executo ) } -type environmentWorkflow struct { - environment *common.Environment -} - // Find an environment in config, by name and set the reference func (workflow *environmentWorkflow) environmentFinder(config *common.Config, environmentName string) Executor { @@ -46,7 +47,7 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ environment := workflow.environment if environment.VpcTarget.VpcID == "" { log.Debugf("No VpcTarget, so we will upsert the VPC stack") - vpcStackName := fmt.Sprintf("mu-vpc-%s", environment.Name) + vpcStackName := common.CreateStackName(common.StackTypeVpc, environment.Name) // no target VPC, we need to create/update the VPC stack log.Infof("Upserting VPC environment '%s' ...", environment.Name) @@ -54,7 +55,7 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ if err != nil { return err } - err = stackUpserter.UpsertStack(vpcStackName, template, nil, buildEnvironmentTags(environment.Name, "vpc")) + err = stackUpserter.UpsertStack(vpcStackName, template, nil, buildEnvironmentTags(environment.Name, common.StackTypeVpc)) if err != nil { return err } @@ -83,7 +84,7 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[string]string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { return func() error { environment := workflow.environment - envStackName := fmt.Sprintf("mu-env-%s", environment.Name) + envStackName := common.CreateStackName(common.StackTypeCluster, environment.Name) log.Infof("Upserting ECS environment '%s' ...", environment.Name) template, err := templates.NewTemplate("cluster.yml", environment) @@ -91,7 +92,7 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ return err } - err = stackUpserter.UpsertStack(envStackName, template, vpcImportParams, buildEnvironmentTags(environment.Name, "cluster")) + err = stackUpserter.UpsertStack(envStackName, template, vpcImportParams, buildEnvironmentTags(environment.Name, common.StackTypeCluster)) if err != nil { return err } @@ -102,9 +103,48 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ } } -func buildEnvironmentTags(environmentName string, stackType string) map[string]string { +func buildEnvironmentTags(environmentName string, stackType common.StackType) map[string]string { return map[string]string{ - "type": stackType, + "type": string(stackType), "environment": environmentName, } } + +// NewEnvironmentLister create a new workflow for listing environments +func NewEnvironmentLister(ctx *common.Context) Executor { + + workflow := new(environmentWorkflow) + + return newWorkflow( + workflow.environmentLister(ctx.StackManager), + ) +} + +func (workflow *environmentWorkflow) environmentLister(stackLister common.StackLister) Executor { + bold := color.New(color.Bold).SprintFunc() + red := color.New(color.FgRed).SprintFunc() + green := color.New(color.FgGreen).SprintFunc() + blue := color.New(color.FgBlue).SprintFunc() + return func() error { + stacks, err := stackLister.ListStacks(common.StackTypeCluster) + + if err != nil { + return err + } + + for _, stack := range stacks { + var color func(a ...interface{}) string + if strings.HasSuffix(stack.Status, "_FAILED") { + color = red + } else if strings.HasSuffix(stack.Status, "_COMPLETE") { + color = green + } else { + color = blue + } + log.Infof("%8s - %8s %s", bold(stack.Name), color(stack.Status), stack.StatusReason) + + } + + return nil + } +} diff --git a/workflows/environment_test.go b/workflows/environment_test.go index a1a7e64d..9bae2c52 100644 --- a/workflows/environment_test.go +++ b/workflows/environment_test.go @@ -74,8 +74,8 @@ func TestEnvironmentEcsUpserter(t *testing.T) { vpcInputParams := make(map[string]string) stackManager := new(mockedStackManager) - stackManager.On("AwaitFinalStatus", "mu-env-foo").Return(cloudformation.StackStatusCreateComplete) - stackManager.On("UpsertStack", "mu-env-foo").Return(nil) + stackManager.On("AwaitFinalStatus", "mu-cluster-foo").Return(cloudformation.StackStatusCreateComplete) + stackManager.On("UpsertStack", "mu-cluster-foo").Return(nil) err := workflow.environmentEcsUpserter(vpcInputParams, stackManager, stackManager)() assert.Nil(err) From d66c6a0d2953d680ed648dbe04555ac7fa21bd8e Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 16 Jan 2017 13:39:57 -0800 Subject: [PATCH 28/31] testing for list stacks --- common/stack.go | 59 ++++++++++++++---------------------- common/stack_test.go | 65 ++++++++++++++++++++++++++++++++++++++-- workflows/environment.go | 26 ++++++++++++++-- 3 files changed, 109 insertions(+), 41 deletions(-) diff --git a/common/stack.go b/common/stack.go index e37fc3e7..59189dc5 100644 --- a/common/stack.go +++ b/common/stack.go @@ -227,51 +227,36 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) str func (cfnMgr *cloudformationStackManager) ListStacks(stackType StackType) ([]*Stack, error) { cfnAPI := cfnMgr.cfnAPI - params := &cloudformation.ListStacksInput{ - StackStatusFilter: []*string{ - aws.String(cloudformation.StackStatusReviewInProgress), - aws.String(cloudformation.StackStatusCreateInProgress), - aws.String(cloudformation.StackStatusRollbackInProgress), - aws.String(cloudformation.StackStatusDeleteInProgress), - aws.String(cloudformation.StackStatusUpdateInProgress), - aws.String(cloudformation.StackStatusUpdateRollbackInProgress), - aws.String(cloudformation.StackStatusUpdateCompleteCleanupInProgress), - aws.String(cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress), - aws.String(cloudformation.StackStatusCreateFailed), - aws.String(cloudformation.StackStatusCreateComplete), - aws.String(cloudformation.StackStatusRollbackFailed), - aws.String(cloudformation.StackStatusRollbackComplete), - aws.String(cloudformation.StackStatusDeleteFailed), - aws.String(cloudformation.StackStatusUpdateComplete), - aws.String(cloudformation.StackStatusUpdateRollbackFailed), - aws.String(cloudformation.StackStatusUpdateRollbackComplete), - }, - } + params := &cloudformation.DescribeStacksInput{ } var stacks []*Stack - stackNamePrefix := "mu-" - if stackType != "" { - stackNamePrefix = fmt.Sprintf("%s%s-", stackNamePrefix, stackType) - } - log.Debugf("Searching for stacks with prefix '%s'", stackNamePrefix) + log.Debugf("Searching for stacks of type '%s'", stackType) - err := cfnAPI.ListStacksPages(params, - func(page *cloudformation.ListStacksOutput, lastPage bool) bool { - for _, stackSummary := range page.StackSummaries { - if !strings.HasPrefix(aws.StringValue(stackSummary.StackName), stackNamePrefix) { - continue - } - if strings.HasPrefix(aws.StringValue(stackSummary.StackStatus), "DELETE_") { + err := cfnAPI.DescribeStacksPages(params, + func(page *cloudformation.DescribeStacksOutput, lastPage bool) bool { + for _, stackDetails := range page.Stacks { + if strings.HasPrefix(aws.StringValue(stackDetails.StackStatus), "DELETE_") { continue } + stack := new(Stack) - stack.ID = aws.StringValue(stackSummary.StackId) - stack.Name = aws.StringValue(stackSummary.StackName) - stack.Status = aws.StringValue(stackSummary.StackStatus) - stack.StatusReason = aws.StringValue(stackSummary.StackStatusReason) + stack.ID = aws.StringValue(stackDetails.StackId) + stack.Name = aws.StringValue(stackDetails.StackName) + stack.Status = aws.StringValue(stackDetails.StackStatus) + stack.StatusReason = aws.StringValue(stackDetails.StackStatusReason) + stack.Tags = make(map[string]string) + + for _, tag := range stackDetails.Tags { + key := aws.StringValue(tag.Key) + if strings.HasPrefix(key, "mu:") { + stack.Tags[key[3:]] = aws.StringValue(tag.Value) + } + } - stacks = append(stacks, stack) + if stack.Tags["type"] == string(stackType) { + stacks = append(stacks, stack) + } } return true }) diff --git a/common/stack_test.go b/common/stack_test.go index 26dac38f..1d413c45 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/mock" "strings" "testing" + "fmt" ) type mockedCloudFormation struct { @@ -20,8 +21,8 @@ func (m *mockedCloudFormation) DescribeStacks(input *cloudformation.DescribeStac args := m.Called() return args.Get(0).(*cloudformation.DescribeStacksOutput), args.Error(1) } -func (m *mockedCloudFormation) ListStacksPages(input *cloudformation.ListStacksInput, callback func(page *cloudformation.ListStacksOutput, lastPage bool) bool) error { - args := m.Called() +func (m *mockedCloudFormation) DescribeStacksPages(input *cloudformation.DescribeStacksInput, cb func(*cloudformation.DescribeStacksOutput, bool) bool) error { + args := m.Called(input, cb) return args.Error(0) } @@ -153,6 +154,66 @@ func TestStack_UpsertStack_Update(t *testing.T) { cfn.AssertNumberOfCalls(t, "WaitUntilStackExists", 0) } +func TestCloudformationStackManager_ListStacks(t *testing.T) { + assert := assert.New(t) + + cfn := new(mockedCloudFormation) + cfn.On("DescribeStacksPages", mock.AnythingOfType("*cloudformation.DescribeStacksInput"), mock.AnythingOfType("func(*cloudformation.DescribeStacksOutput, bool) bool")). + Return(nil). + Run(func (args mock.Arguments) { + fmt.Println(args) + cb := args.Get(1).(func(*cloudformation.DescribeStacksOutput, bool) bool) + cb(&cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + { + StackName: aws.String("mu-cluster-dev"), + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("mu:type"), + Value: aws.String("cluster"), + }, + }, + }, + { + StackName: aws.String("mu-vpc-dev"), + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("mu:type"), + Value: aws.String("vpc"), + }, + }, + }, + { + StackName: aws.String("deleted-stack"), + StackStatus: aws.String(cloudformation.StackStatusDeleteComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("mu:type"), + Value: aws.String("cluster"), + }, + }, + }, + }, + },true) + }) + + stackManager := cloudformationStackManager{ + cfnAPI: cfn, + } + stacks, err := stackManager.ListStacks(StackTypeCluster) + + assert.Nil(err) + assert.NotNil(stacks) + assert.Equal(1,len(stacks)) + assert.Equal("mu-cluster-dev", stacks[0].Name) + assert.Equal("cluster", stacks[0].Tags["type"]) + assert.Equal(cloudformation.StackStatusCreateComplete, stacks[0].Status) + cfn.AssertExpectations(t) + cfn.AssertNumberOfCalls(t, "DescribeStacksPages", 1) +} + func TestBuildParameters(t *testing.T) { assert := assert.New(t) diff --git a/workflows/environment.go b/workflows/environment.go index 532b170e..f738dbdd 100644 --- a/workflows/environment.go +++ b/workflows/environment.go @@ -2,11 +2,15 @@ package workflows import ( "fmt" - "github.com/fatih/color" "github.com/op/go-logging" "github.com/stelligent/mu/common" "github.com/stelligent/mu/templates" "strings" + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" + "os" + "strconv" + "time" ) var log = logging.MustGetLogger("environment") @@ -125,6 +129,7 @@ func (workflow *environmentWorkflow) environmentLister(stackLister common.StackL red := color.New(color.FgRed).SprintFunc() green := color.New(color.FgGreen).SprintFunc() blue := color.New(color.FgBlue).SprintFunc() + return func() error { stacks, err := stackLister.ListStacks(common.StackTypeCluster) @@ -132,6 +137,10 @@ func (workflow *environmentWorkflow) environmentLister(stackLister common.StackL return err } + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Environment","Stack","Status","Last Update","Mu Version"}) + table.SetBorder(false) + for _, stack := range stacks { var color func(a ...interface{}) string if strings.HasSuffix(stack.Status, "_FAILED") { @@ -141,10 +150,23 @@ func (workflow *environmentWorkflow) environmentLister(stackLister common.StackL } else { color = blue } - log.Infof("%8s - %8s %s", bold(stack.Name), color(stack.Status), stack.StatusReason) + + lastUpdate,_ := strconv.ParseInt(stack.Tags["lastupdate"], 10, 64) + tm := time.Unix(lastUpdate, 0) + + table.Append([]string{ + bold(stack.Tags["environment"]), + stack.Name, + fmt.Sprintf("%s %s",color(stack.Status),stack.StatusReason), + tm.String(), + stack.Tags["version"], + }) } + table.Render() + + return nil } } From 43f739d9efc4bc327aae65d115dbb21843766b36 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 16 Jan 2017 15:54:13 -0800 Subject: [PATCH 29/31] Completed ability to show show environment including container instances --- cli/environments.go | 7 +- common/cluster.go | 64 ++++++++++++++++ common/cluster_test.go | 56 ++++++++++++++ common/context.go | 6 ++ common/log.go | 5 ++ common/stack.go | 68 ++++++++++++----- common/stack_test.go | 111 +++++++++++++++++++-------- common/types.go | 6 +- workflows/environment.go | 157 +++++++++++++++++++++++++++++++++------ 9 files changed, 402 insertions(+), 78 deletions(-) create mode 100644 common/cluster.go create mode 100644 common/cluster_test.go create mode 100644 common/log.go diff --git a/cli/environments.go b/cli/environments.go index 79c2a8a7..98bc9372 100644 --- a/cli/environments.go +++ b/cli/environments.go @@ -6,6 +6,7 @@ import ( "github.com/stelligent/mu/common" "github.com/stelligent/mu/workflows" "github.com/urfave/cli" + "os" ) func newEnvironmentsCommand(ctx *common.Context) *cli.Command { @@ -52,7 +53,7 @@ func newEnvironmentsListCommand(ctx *common.Context) *cli.Command { Aliases: []string{"ls"}, Usage: "list environments", Action: func(c *cli.Context) error { - workflow := workflows.NewEnvironmentLister(ctx) + workflow := workflows.NewEnvironmentLister(ctx, os.Stdout) return workflow() }, } @@ -71,8 +72,8 @@ func newEnvironmentsShowCommand(ctx *common.Context) *cli.Command { cli.ShowCommandHelp(c, "show") return errors.New("environment must be provided") } - fmt.Printf("showing environment: %s\n", environmentName) - return nil + workflow := workflows.NewEnvironmentViewer(ctx, environmentName, os.Stdout) + return workflow() }, } diff --git a/common/cluster.go b/common/cluster.go new file mode 100644 index 00000000..5d972019 --- /dev/null +++ b/common/cluster.go @@ -0,0 +1,64 @@ +package common + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/ecs/ecsiface" +) + +// ClusterInstanceLister for getting cluster instances +type ClusterInstanceLister interface { + ListInstances(clusterName string) ([]*ecs.ContainerInstance, error) +} + +// ClusterManager composite of all cluster capabilities +type ClusterManager interface { + ClusterInstanceLister +} + +type ecsClusterManager struct { + ecsAPI ecsiface.ECSAPI +} + +func newClusterManager(region string) (ClusterManager, error) { + sess, err := session.NewSession() + if err != nil { + return nil, err + } + log.Debugf("Connecting to ECS service in region:%s", region) + ecs := ecs.New(sess, &aws.Config{Region: aws.String(region)}) + return &ecsClusterManager{ + ecsAPI: ecs, + }, nil +} + +// ListInstances get the instances for a specific cluster +func (ecsMgr *ecsClusterManager) ListInstances(clusterName string) ([]*ecs.ContainerInstance, error) { + ecsAPI := ecsMgr.ecsAPI + + params := &ecs.ListContainerInstancesInput{ + Cluster: aws.String(clusterName), + } + + log.Debugf("Searching for container instances for cluster named '%s'", clusterName) + + var instanceIds []*string + err := ecsAPI.ListContainerInstancesPages(params, func(page *ecs.ListContainerInstancesOutput, lastPage bool) bool { + for _, instanceID := range page.ContainerInstanceArns { + instanceIds = append(instanceIds, instanceID) + } + return true + }) + if err != nil { + return nil, err + } + + describeParams := &ecs.DescribeContainerInstancesInput{ + Cluster: aws.String(clusterName), + ContainerInstances: instanceIds, + } + describeOut, err := ecsAPI.DescribeContainerInstances(describeParams) + + return describeOut.ContainerInstances, nil +} diff --git a/common/cluster_test.go b/common/cluster_test.go new file mode 100644 index 00000000..54317176 --- /dev/null +++ b/common/cluster_test.go @@ -0,0 +1,56 @@ +package common + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/ecs/ecsiface" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type mockedECS struct { + mock.Mock + ecsiface.ECSAPI +} + +func (m *mockedECS) DescribeContainerInstances(input *ecs.DescribeContainerInstancesInput) (*ecs.DescribeContainerInstancesOutput, error) { + args := m.Called() + return args.Get(0).(*ecs.DescribeContainerInstancesOutput), args.Error(1) +} +func (m *mockedECS) ListContainerInstancesPages(input *ecs.ListContainerInstancesInput, cb func(*ecs.ListContainerInstancesOutput, bool) bool) error { + args := m.Called(input, cb) + return args.Error(0) +} + +func TestEcsClusterManager_ListInstances(t *testing.T) { + assert := assert.New(t) + + m := new(mockedECS) + m.On("DescribeContainerInstances").Return( + &ecs.DescribeContainerInstancesOutput{ + ContainerInstances: []*ecs.ContainerInstance{}, + }, nil) + m.On("ListContainerInstancesPages", mock.AnythingOfType("*ecs.ListContainerInstancesInput"), mock.AnythingOfType("func(*ecs.ListContainerInstancesOutput, bool) bool")). + Return(nil). + Run(func(args mock.Arguments) { + cb := args.Get(1).(func(*ecs.ListContainerInstancesOutput, bool) bool) + cb(&ecs.ListContainerInstancesOutput{ + ContainerInstanceArns: []*string{ + aws.String("foobarbaz"), + }, + }, true) + }) + + clusterManager := ecsClusterManager{ + ecsAPI: m, + } + + instances, err := clusterManager.ListInstances("foo") + assert.Nil(err) + assert.NotNil(instances) + + m.AssertExpectations(t) + m.AssertNumberOfCalls(t, "DescribeContainerInstances", 1) + m.AssertNumberOfCalls(t, "ListContainerInstancesPages", 1) +} diff --git a/common/context.go b/common/context.go index b80835ae..bb48a834 100644 --- a/common/context.go +++ b/common/context.go @@ -33,6 +33,12 @@ func (ctx *Context) Initialize(configReader io.Reader) error { return err } + // initialize ClusterManager + ctx.ClusterManager, err = newClusterManager(ctx.Config.Region) + if err != nil { + return err + } + return nil } diff --git a/common/log.go b/common/log.go new file mode 100644 index 00000000..20024509 --- /dev/null +++ b/common/log.go @@ -0,0 +1,5 @@ +package common + +import "github.com/op/go-logging" + +var log = logging.MustGetLogger("common") diff --git a/common/stack.go b/common/stack.go index 59189dc5..fa0f8b71 100644 --- a/common/stack.go +++ b/common/stack.go @@ -8,14 +8,11 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" - "github.com/op/go-logging" "io" "strings" "time" ) -var log = logging.MustGetLogger("stack") - // CreateStackName will create a name for a stack func CreateStackName(stackType StackType, name string) string { return fmt.Sprintf("mu-%s-%s", stackType, name) @@ -36,11 +33,17 @@ type StackLister interface { ListStacks(stackType StackType) ([]*Stack, error) } +// StackGetter for getting stacks +type StackGetter interface { + GetStack(stackName string) (*Stack, error) +} + // StackManager composite of all stack capabilities type StackManager interface { StackUpserter StackWaiter StackLister + StackGetter } type cloudformationStackManager struct { @@ -223,11 +226,34 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) str return "" } +func buildStack(stackDetails *cloudformation.Stack) *Stack { + stack := new(Stack) + stack.ID = aws.StringValue(stackDetails.StackId) + stack.Name = aws.StringValue(stackDetails.StackName) + stack.Status = aws.StringValue(stackDetails.StackStatus) + stack.StatusReason = aws.StringValue(stackDetails.StackStatusReason) + stack.Tags = make(map[string]string) + stack.Outputs = make(map[string]string) + + for _, tag := range stackDetails.Tags { + key := aws.StringValue(tag.Key) + if strings.HasPrefix(key, "mu:") { + stack.Tags[key[3:]] = aws.StringValue(tag.Value) + } + } + + for _, output := range stackDetails.Outputs { + stack.Outputs[aws.StringValue(output.OutputKey)] = aws.StringValue(output.OutputValue) + } + + return stack +} + // ListStacks will find mu stacks func (cfnMgr *cloudformationStackManager) ListStacks(stackType StackType) ([]*Stack, error) { cfnAPI := cfnMgr.cfnAPI - params := &cloudformation.DescribeStacksInput{ } + params := &cloudformation.DescribeStacksInput{} var stacks []*Stack @@ -236,28 +262,17 @@ func (cfnMgr *cloudformationStackManager) ListStacks(stackType StackType) ([]*St err := cfnAPI.DescribeStacksPages(params, func(page *cloudformation.DescribeStacksOutput, lastPage bool) bool { for _, stackDetails := range page.Stacks { - if strings.HasPrefix(aws.StringValue(stackDetails.StackStatus), "DELETE_") { + if cloudformation.StackStatusDeleteComplete == aws.StringValue(stackDetails.StackStatus) { continue } - stack := new(Stack) - stack.ID = aws.StringValue(stackDetails.StackId) - stack.Name = aws.StringValue(stackDetails.StackName) - stack.Status = aws.StringValue(stackDetails.StackStatus) - stack.StatusReason = aws.StringValue(stackDetails.StackStatusReason) - stack.Tags = make(map[string]string) - - for _, tag := range stackDetails.Tags { - key := aws.StringValue(tag.Key) - if strings.HasPrefix(key, "mu:") { - stack.Tags[key[3:]] = aws.StringValue(tag.Value) - } - } + stack := buildStack(stackDetails) if stack.Tags["type"] == string(stackType) { stacks = append(stacks, stack) } } + return true }) @@ -266,3 +281,20 @@ func (cfnMgr *cloudformationStackManager) ListStacks(stackType StackType) ([]*St } return stacks, nil } + +// GetStack get a specific stack +func (cfnMgr *cloudformationStackManager) GetStack(stackName string) (*Stack, error) { + cfnAPI := cfnMgr.cfnAPI + + params := &cloudformation.DescribeStacksInput{StackName: aws.String(stackName)} + + log.Debugf("Searching for stack named '%s'", stackName) + + resp, err := cfnAPI.DescribeStacks(params) + if err != nil { + return nil, err + } + stack := buildStack(resp.Stacks[0]) + + return stack, nil +} diff --git a/common/stack_test.go b/common/stack_test.go index 1d413c45..8d7a1895 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/mock" "strings" "testing" - "fmt" ) type mockedCloudFormation struct { @@ -160,44 +159,43 @@ func TestCloudformationStackManager_ListStacks(t *testing.T) { cfn := new(mockedCloudFormation) cfn.On("DescribeStacksPages", mock.AnythingOfType("*cloudformation.DescribeStacksInput"), mock.AnythingOfType("func(*cloudformation.DescribeStacksOutput, bool) bool")). Return(nil). - Run(func (args mock.Arguments) { - fmt.Println(args) - cb := args.Get(1).(func(*cloudformation.DescribeStacksOutput, bool) bool) - cb(&cloudformation.DescribeStacksOutput{ - Stacks: []*cloudformation.Stack{ - { - StackName: aws.String("mu-cluster-dev"), - StackStatus: aws.String(cloudformation.StackStatusCreateComplete), - Tags: []*cloudformation.Tag{ - { - Key: aws.String("mu:type"), - Value: aws.String("cluster"), + Run(func(args mock.Arguments) { + cb := args.Get(1).(func(*cloudformation.DescribeStacksOutput, bool) bool) + cb(&cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + { + StackName: aws.String("mu-cluster-dev"), + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("mu:type"), + Value: aws.String("cluster"), + }, }, }, - }, - { - StackName: aws.String("mu-vpc-dev"), - StackStatus: aws.String(cloudformation.StackStatusCreateComplete), - Tags: []*cloudformation.Tag{ - { - Key: aws.String("mu:type"), - Value: aws.String("vpc"), + { + StackName: aws.String("mu-vpc-dev"), + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("mu:type"), + Value: aws.String("vpc"), + }, }, }, - }, - { - StackName: aws.String("deleted-stack"), - StackStatus: aws.String(cloudformation.StackStatusDeleteComplete), - Tags: []*cloudformation.Tag{ - { - Key: aws.String("mu:type"), - Value: aws.String("cluster"), + { + StackName: aws.String("deleted-stack"), + StackStatus: aws.String(cloudformation.StackStatusDeleteComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("mu:type"), + Value: aws.String("cluster"), + }, }, }, }, - }, - },true) - }) + }, true) + }) stackManager := cloudformationStackManager{ cfnAPI: cfn, @@ -206,7 +204,7 @@ func TestCloudformationStackManager_ListStacks(t *testing.T) { assert.Nil(err) assert.NotNil(stacks) - assert.Equal(1,len(stacks)) + assert.Equal(1, len(stacks)) assert.Equal("mu-cluster-dev", stacks[0].Name) assert.Equal("cluster", stacks[0].Tags["type"]) assert.Equal(cloudformation.StackStatusCreateComplete, stacks[0].Status) @@ -214,6 +212,53 @@ func TestCloudformationStackManager_ListStacks(t *testing.T) { cfn.AssertNumberOfCalls(t, "DescribeStacksPages", 1) } +func TestStack_GetStack(t *testing.T) { + assert := assert.New(t) + + cfn := new(mockedCloudFormation) + cfn.On("DescribeStacks").Return( + &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + { + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + }, + }, + }, nil) + + stackManager := cloudformationStackManager{ + cfnAPI: cfn, + } + + stack, err := stackManager.GetStack("foo") + + assert.Nil(err) + assert.Equal(cloudformation.StackStatusCreateComplete, stack.Status) + cfn.AssertExpectations(t) + cfn.AssertNumberOfCalls(t, "DescribeStacks", 1) +} + +func TestBuildStack(t *testing.T) { + assert := assert.New(t) + + stackDetails := cloudformation.Stack{ + StackName: aws.String("mu-cluster-dev"), + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("mu:type"), + Value: aws.String("cluster"), + }, + }, + } + + stack := buildStack(&stackDetails) + + assert.NotNil(stack) + assert.Equal("mu-cluster-dev", stack.Name) + assert.Equal("cluster", stack.Tags["type"]) + assert.Equal(cloudformation.StackStatusCreateComplete, stack.Status) +} + func TestBuildParameters(t *testing.T) { assert := assert.New(t) diff --git a/common/types.go b/common/types.go index 602d517d..8a1e31de 100644 --- a/common/types.go +++ b/common/types.go @@ -2,8 +2,9 @@ package common // Context defines the context object passed around type Context struct { - Config Config - StackManager StackManager + Config Config + StackManager StackManager + ClusterManager ClusterManager } // Config defines the structure of the yml file for the mu config @@ -43,6 +44,7 @@ type Stack struct { Status string StatusReason string Tags map[string]string + Outputs map[string]string } // StackType describes supported stack types diff --git a/workflows/environment.go b/workflows/environment.go index f738dbdd..8a13c75c 100644 --- a/workflows/environment.go +++ b/workflows/environment.go @@ -2,14 +2,16 @@ package workflows import ( "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" "github.com/op/go-logging" "github.com/stelligent/mu/common" "github.com/stelligent/mu/templates" - "strings" - "github.com/fatih/color" - "github.com/olekukonko/tablewriter" - "os" + "io" "strconv" + "strings" "time" ) @@ -114,21 +116,33 @@ func buildEnvironmentTags(environmentName string, stackType common.StackType) ma } } +func colorizeStatus(stackStatus string) string { + red := color.New(color.FgRed).SprintFunc() + green := color.New(color.FgGreen).SprintFunc() + blue := color.New(color.FgBlue).SprintFunc() + var color func(a ...interface{}) string + if strings.HasSuffix(stackStatus, "_FAILED") { + color = red + } else if strings.HasSuffix(stackStatus, "_COMPLETE") { + color = green + } else { + color = blue + } + return color(stackStatus) +} + // NewEnvironmentLister create a new workflow for listing environments -func NewEnvironmentLister(ctx *common.Context) Executor { +func NewEnvironmentLister(ctx *common.Context, writer io.Writer) Executor { workflow := new(environmentWorkflow) return newWorkflow( - workflow.environmentLister(ctx.StackManager), + workflow.environmentLister(ctx.StackManager, writer), ) } -func (workflow *environmentWorkflow) environmentLister(stackLister common.StackLister) Executor { +func (workflow *environmentWorkflow) environmentLister(stackLister common.StackLister, writer io.Writer) Executor { bold := color.New(color.Bold).SprintFunc() - red := color.New(color.FgRed).SprintFunc() - green := color.New(color.FgGreen).SprintFunc() - blue := color.New(color.FgBlue).SprintFunc() return func() error { stacks, err := stackLister.ListStacks(common.StackTypeCluster) @@ -137,27 +151,19 @@ func (workflow *environmentWorkflow) environmentLister(stackLister common.StackL return err } - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Environment","Stack","Status","Last Update","Mu Version"}) + table := tablewriter.NewWriter(writer) + table.SetHeader([]string{"Environment", "Stack", "Status", "Last Update", "Mu Version"}) table.SetBorder(false) for _, stack := range stacks { - var color func(a ...interface{}) string - if strings.HasSuffix(stack.Status, "_FAILED") { - color = red - } else if strings.HasSuffix(stack.Status, "_COMPLETE") { - color = green - } else { - color = blue - } - lastUpdate,_ := strconv.ParseInt(stack.Tags["lastupdate"], 10, 64) + lastUpdate, _ := strconv.ParseInt(stack.Tags["lastupdate"], 10, 64) tm := time.Unix(lastUpdate, 0) table.Append([]string{ bold(stack.Tags["environment"]), stack.Name, - fmt.Sprintf("%s %s",color(stack.Status),stack.StatusReason), + fmt.Sprintf("%s %s", colorizeStatus(stack.Status), stack.StatusReason), tm.String(), stack.Tags["version"], }) @@ -166,7 +172,114 @@ func (workflow *environmentWorkflow) environmentLister(stackLister common.StackL table.Render() + return nil + } +} + +// NewEnvironmentViewer create a new workflow for showing an environment +func NewEnvironmentViewer(ctx *common.Context, environmentName string, writer io.Writer) Executor { + + workflow := new(environmentWorkflow) + + return newWorkflow( + workflow.environmentFinder(&ctx.Config, environmentName), + workflow.environmentViewer(ctx.StackManager, ctx.ClusterManager, writer), + ) +} + +func (workflow *environmentWorkflow) environmentViewer(stackGetter common.StackGetter, instanceLister common.ClusterInstanceLister, writer io.Writer) Executor { + bold := color.New(color.Bold).SprintFunc() + return func() error { + environment := workflow.environment + + clusterStackName := common.CreateStackName(common.StackTypeCluster, environment.Name) + clusterStack, err := stackGetter.GetStack(clusterStackName) + if err != nil { + return err + } + + vpcStackName := common.CreateStackName(common.StackTypeVpc, environment.Name) + vpcStack, _ := stackGetter.GetStack(vpcStackName) + + fmt.Fprintf(writer, "%s:\t%s\n", bold("Environment"), environment.Name) + fmt.Fprintf(writer, "%s:\t%s (%s)\n", bold("Cluster Stack"), clusterStack.Name, colorizeStatus(clusterStack.Status)) + if vpcStack == nil { + fmt.Fprintf(writer, "%s:\tunmanaged\n", bold("VPC Stack")) + } else { + fmt.Fprintf(writer, "%s:\t%s (%s)\n", bold("VPC Stack"), vpcStack.Name, colorizeStatus(vpcStack.Status)) + } + + fmt.Fprintf(writer, "%s:\t%s\n", bold("Base URL"), clusterStack.Outputs["BaseUrl"]) + + fmt.Fprintf(writer, "%s:\n", bold("Container Instances")) + fmt.Fprint(writer, "\n") + + instances, err := instanceLister.ListInstances(clusterStack.Outputs["EcsCluster"]) + if err != nil { + return err + } + table := buildInstanceTable(writer, instances) + table.Render() + fmt.Fprint(writer, "\n") + + fmt.Fprintf(writer, "%s:\n", bold("Services")) + fmt.Fprint(writer, "\n") + table = buildServiceTable(writer) + table.Render() + + fmt.Fprint(writer, "\n") return nil } } + +func buildServiceTable(writer io.Writer) *tablewriter.Table { + table := tablewriter.NewWriter(writer) + table.SetHeader([]string{"Name", "Status"}) + table.SetBorder(false) + return table +} + +func buildInstanceTable(writer io.Writer, instances []*ecs.ContainerInstance) *tablewriter.Table { + table := tablewriter.NewWriter(writer) + table.SetHeader([]string{"EC2 Instance", "Type", "AMI", "AZ", "Connected", "Status", "# Tasks", "CPU Avail", "Mem Avail"}) + table.SetBorder(false) + for _, instance := range instances { + instanceType := "???" + availZone := "???" + amiID := "???" + for _, attr := range instance.Attributes { + switch aws.StringValue(attr.Name) { + case "ecs.availability-zone": + availZone = aws.StringValue(attr.Value) + case "ecs.instance-type": + instanceType = aws.StringValue(attr.Value) + case "ecs.ami-id": + amiID = aws.StringValue(attr.Value) + } + } + var cpuAvail int64 + var memAvail int64 + for _, resource := range instance.RemainingResources { + switch aws.StringValue(resource.Name) { + case "CPU": + cpuAvail = aws.Int64Value(resource.IntegerValue) + case "MEMORY": + memAvail = aws.Int64Value(resource.IntegerValue) + } + } + table.Append([]string{ + aws.StringValue(instance.Ec2InstanceId), + instanceType, + amiID, + availZone, + fmt.Sprintf("%v", aws.BoolValue(instance.AgentConnected)), + aws.StringValue(instance.Status), + fmt.Sprintf("%d", aws.Int64Value(instance.RunningTasksCount)), + fmt.Sprintf("%d", cpuAvail), + fmt.Sprintf("%d", memAvail), + }) + } + + return table +} From d1838301052e2320f0f11a7faa9a0f82db5b9027 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 16 Jan 2017 23:35:52 -0800 Subject: [PATCH 30/31] Documentation for environment config. Added lookup of latest ECS AMI --- README.md | 32 ++++++++++++++ common/cluster.go | 1 + common/stack.go | 60 ++++++++++++++++++++++++++- common/types.go | 6 +++ templates/assets.go | 12 +++--- templates/assets/cluster.yml | 78 ++++++++++++++++++++++++++--------- workflows/environment.go | 48 +++++++++++++++++++-- workflows/environment_test.go | 7 +++- 8 files changed, 212 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 2fc3f170..14b68969 100644 --- a/README.md +++ b/README.md @@ -57,5 +57,37 @@ curl -s https://raw.githubusercontent.com/stelligent/mu/master/install.sh | INST > mu pipeline terminate [-s ] ``` +## Configuration +The definition of your environments, services and pipelines is done via a YAML file (default `./mu.yml`). +``` +--- +### Region to utilize +region: us-west-2 + +### Define a list of environments +environments: + + # The unique name of the environment (required) + - name: dev + + ### Attributes for the ECS container instances + cluster: + imageId: ami-xxxxxx # The AMI to use for the ECS container instances (default: latest ECS optimized AMI) + instanceTenancy: default # Whether to use default or dedicated tenancy (default: default) + desiredCapacity: 1 # Desired number of ECS container instances (default 1) + maxSize: 2 # Max size to scale the ECS ASG to (default: 2) + keyName: my-keypair # name of EC2 keypair to associate with ECS container instances (default: none) + sshAllow: 0.0.0.0/0 # CIDR block to allow SSH access from (default: 0.0.0.0/0) + scaleOutThreshold: 80 # Threshold for % memory utilization to scale out ECS container instances (default: 80) + scaleInThreshold: 30 # Threshold for % memory utilization to scale in ECS container instances (default: 30) + + ### attributes for the VPC to target. If not defined, a VPC will be created. (default: none) + vpcTarget: + vpcId: vpc-xxxxx # The id of the VPC to launch ECS container instances into + publicSubnetIds: # The list of subnets to use for ECS container instances + - sg-xxxxx + - sg-xxxxy + - sg-xxxxz +``` diff --git a/common/cluster.go b/common/cluster.go index 5d972019..4cae12c9 100644 --- a/common/cluster.go +++ b/common/cluster.go @@ -33,6 +33,7 @@ func newClusterManager(region string) (ClusterManager, error) { }, nil } + // ListInstances get the instances for a specific cluster func (ecsMgr *ecsClusterManager) ListInstances(clusterName string) ([]*ecs.ContainerInstance, error) { ecsAPI := ecsMgr.ecsAPI diff --git a/common/stack.go b/common/stack.go index fa0f8b71..256cfd7e 100644 --- a/common/stack.go +++ b/common/stack.go @@ -11,6 +11,9 @@ import ( "io" "strings" "time" + "github.com/aws/aws-sdk-go/service/ec2" + "errors" + "github.com/aws/aws-sdk-go/service/ec2/ec2iface" ) // CreateStackName will create a name for a stack @@ -38,16 +41,23 @@ type StackGetter interface { GetStack(stackName string) (*Stack, error) } +// ImageFinder for finding latest image +type ImageFinder interface { + FindLatestImageID(namePattern string) (string, error) +} + // StackManager composite of all stack capabilities type StackManager interface { StackUpserter StackWaiter StackLister StackGetter + ImageFinder } type cloudformationStackManager struct { cfnAPI cloudformationiface.CloudFormationAPI + ec2API ec2iface.EC2API } // TODO: support "dry-run" and write the template to a file @@ -60,10 +70,16 @@ func newStackManager(region string) (StackManager, error) { return nil, err } log.Debugf("Connecting to CloudFormation service in region:%s", region) - cfn := cloudformation.New(sess, &aws.Config{Region: aws.String(region)}) + cfnAPI := cloudformation.New(sess, &aws.Config{Region: aws.String(region)}) + + log.Debugf("Connecting to EC2 service in region:%s", region) + ec2API := ec2.New(sess, &aws.Config{Region: aws.String(region)}) + return &cloudformationStackManager{ - cfnAPI: cfn, + cfnAPI: cfnAPI, + ec2API: ec2API, }, nil + } func buildStackParameters(parameters map[string]string) []*cloudformation.Parameter { @@ -298,3 +314,43 @@ func (cfnMgr *cloudformationStackManager) GetStack(stackName string) (*Stack, er return stack, nil } + +// FindLatestImageID for a given +func (cfnMgr *cloudformationStackManager) FindLatestImageID(namePattern string) (string, error) { + ec2Api := cfnMgr.ec2API + resp, err := ec2Api.DescribeImages(&ec2.DescribeImagesInput{ + Owners: []*string {aws.String("amazon")}, + Filters: []*ec2.Filter { + { + Name: aws.String("name"), + Values: []*string { + aws.String(namePattern), + }, + }, + }, + }) + + if(err != nil) { + return "", err + } + + var imageID string + var imageCreateDate time.Time + for _, image := range resp.Images { + createDate, err := time.Parse(time.RFC3339, aws.StringValue(image.CreationDate)) + if err != nil { + return "", err + } + if imageCreateDate.Before(createDate) { + imageCreateDate = createDate + imageID = aws.StringValue(image.ImageId) + } + } + + if imageID == "" { + return "", errors.New("Unable to find image") + } + log.Debugf("Found latest imageId %s for pattern %s", imageID, namePattern) + return imageID, nil +} + diff --git a/common/types.go b/common/types.go index 8a1e31de..2156eb88 100644 --- a/common/types.go +++ b/common/types.go @@ -21,8 +21,14 @@ type Environment struct { Hostname string } Cluster struct { + ImageID string `yaml:"imageId"` + InstanceTenancy string `yaml:"instanceTenancy"` DesiredCapacity int `yaml:"desiredCapacity"` MaxSize int `yaml:"maxSize"` + KeyName string `yaml:"keyName"` + SSHAllow string `yaml:"sshAllow"` + ScaleOutThreshold int `yaml:"scaleOutThreshold"` + ScaleInThreshold int `yaml:"scaleInThreshold"` } VpcTarget struct { VpcID string `yaml:"vpcId"` diff --git a/templates/assets.go b/templates/assets.go index dc8ef7a9..8f54a798 100644 --- a/templates/assets.go +++ b/templates/assets.go @@ -69,7 +69,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _assetsClusterYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x59\x7b\x6f\xdb\x38\x12\xff\x3f\x9f\x62\xea\x2b\xe0\xed\xa2\xf2\x43\x4e\xda\x54\x40\x76\xe1\x38\x6e\x6b\xac\xdd\x1a\x51\xda\x02\x4d\x8b\x82\xa2\x28\x9b\xa8\x44\xea\x48\x2a\x8f\xed\xf5\xbb\x1f\x48\x4a\xb2\x5e\xce\x26\xbb\x3d\xec\x1d\x70\x2e\x82\xda\xe4\x70\xde\xf3\x9b\xa1\xe4\x38\xce\xc1\xf4\x83\x7f\x41\x92\x34\x46\x8a\xbc\xe4\x22\x41\xea\x3d\x11\x92\x72\xe6\x41\xdf\x1d\x8d\x47\xce\xe8\x85\x33\x7a\xd1\x3f\x58\x23\x81\x12\xa2\x88\x90\xde\x01\xc0\x82\x49\x85\x18\x26\x17\xb7\x29\xd1\xbf\x01\xcc\x37\xf0\x95\xa0\x6c\x63\x16\xce\x88\xc4\x82\xa6\xca\xb0\x2a\xe8\x41\xdd\xa6\x04\x14\x87\x4c\x92\x41\x4e\x16\xa1\x2c\x56\x1e\x28\x77\x90\x50\x2c\xf8\x81\x39\x4a\x05\x09\x67\x28\x45\x98\xaa\xdb\xaa\x80\x37\x59\x12\x10\x51\x3f\xd9\x1f\xf7\xdb\x12\x2d\x21\xf0\x08\x68\x2e\x5b\x6a\xb9\x31\xca\x18\xde\x02\x65\x70\xcb\x33\x01\xf3\x99\x0f\x38\xce\xa4\x32\x3c\x57\xe8\xc6\xa7\xbf\x93\x3f\x94\xe7\x76\xc8\x5b\xa1\x1b\x9a\x64\x09\xb0\x2e\xb9\x5b\xa4\x00\x23\x06\x01\xc9\x15\x20\xe1\x1e\x15\x7e\x23\xb7\x6f\x50\x72\x2f\x9f\xe6\xa4\xda\x2a\x24\x25\xc7\x14\x29\x02\xd7\x54\x6d\xe1\x9a\x8b\xaf\x44\xec\x14\x18\x00\x2c\x09\xba\x22\x10\xc4\x88\x7d\xd5\x07\x42\x2a\x51\x10\x13\xf0\xfd\xd7\x80\x30\x26\x52\x36\xa2\xd1\xd7\x26\xfa\x72\x3b\x8d\x63\x7e\xed\xb5\x85\xfb\x59\xc0\x88\x82\x48\xf0\x04\xae\xb7\x14\x6f\x8d\x1a\x9a\xb8\xc5\xb3\x65\xc5\x8a\xb2\x25\x61\x1b\xb5\xf5\xa0\xff\xc2\xba\x72\x85\x6e\xca\xa5\xf1\x71\xbf\xae\xcb\x68\x60\xfe\x0d\x47\x66\xd9\x68\x44\xc2\x35\x52\x8a\x08\xe6\x41\xef\xa7\x4f\x9f\xc2\x6f\xe3\xa7\x93\xef\x4f\x3e\x7d\x1a\xdc\xe7\xc7\x30\xff\xea\x7e\x7f\xd2\x33\x2c\x67\x9c\x49\x25\x10\x65\xaa\x66\x63\x3f\xc9\xa4\xd2\x31\x43\x70\x85\x62\x1a\xc2\x6c\x71\x76\x0e\x41\xcc\xf1\x57\x0f\x6e\x06\xe6\xdf\xf0\x66\xa0\xb5\x7d\x9f\xe2\x45\x78\x9f\xa0\x99\x88\xf1\x08\xd4\x96\x68\xa6\x99\x09\x1f\x4d\x52\x2e\x14\x44\x5c\x98\x75\xc3\xec\x00\x60\x9d\x05\x31\xc5\xd6\xd3\xd3\x8f\xe3\x1f\x27\x60\xfa\x71\x0c\xd2\x06\x90\xb6\x05\xb9\x3f\x52\x90\x5b\x13\xd4\x4c\xb0\xba\xe0\xc9\x8f\x14\x3c\xb9\x43\xf0\x8a\x28\x14\x22\x85\xb4\xb4\xe9\x07\xdf\xf3\x66\x31\xcf\x42\x8b\x7e\x5a\x84\xb7\x60\x8a\x88\x08\xe1\xbc\x0e\x4b\xec\x7b\x25\x78\x96\x4a\xbb\x08\xe0\xc0\x12\x05\x24\x2e\x7e\xea\x4f\x58\x48\xe9\x95\x88\x37\xe3\x2c\xa2\x9b\x4c\x18\xd6\xbd\x92\xb6\x8e\xa7\xc5\xc7\xa9\x21\x6b\x6d\x23\x2f\xf7\xda\x5a\x51\xa0\xf7\x51\x68\x9a\x29\x0e\x3e\x46\x31\x65\x9b\x87\x2a\xd5\x00\xe4\xda\x5e\x0e\x9a\x75\x47\x19\x3d\x4a\x26\xed\x6e\xb1\xc7\x57\x45\x77\xb0\x20\xf9\x6b\xa1\x58\x0d\x14\xeb\x47\x7f\x23\xb7\xfa\xc0\x46\x20\xa6\x2a\xc8\x03\x3f\x59\xa8\xd3\xf9\xc0\x38\x23\x4f\x4a\x5e\x75\x4c\xab\x33\xdb\xd5\x77\x17\xcf\x92\x45\x67\x7b\xaa\x73\xca\x49\xaa\xe0\x5e\xc2\x31\x60\x9e\x31\x55\x72\xab\x35\x9d\x3a\x97\xa2\xa7\xdc\xc9\x65\xc6\x59\x48\x75\x18\x8d\xbb\x5f\x23\x59\xf3\x56\xef\x25\xf3\xbc\x37\x5c\xf5\x76\x49\x6b\x96\xe6\xff\xcc\x50\x2c\x7b\x1e\x5c\x3e\x3a\x27\x51\xe1\xe1\xa7\xd0\xef\x7f\xb6\x5c\x1a\xe0\xf3\x20\x6e\x2d\xe0\xda\xcb\xd7\xfd\x0b\x7c\xdd\x3b\xf8\x4e\xfe\x02\xdf\x49\xc1\x77\x85\xd2\x94\xb2\x8d\xcc\x61\xe2\x9c\x6c\x28\x67\x17\x7c\xba\x5a\x58\x76\x99\x74\x08\x92\xca\x19\x17\xdc\xa7\xab\xc5\xe2\xcc\x03\x94\x50\xc7\x0d\x26\xc1\xb3\xd1\xe1\xb8\x20\xbc\x26\x52\x39\x6e\x07\x21\xc2\xcf\x8e\x9f\xbb\xd8\x82\x14\xc9\x2c\x61\x17\xc7\xd1\xc4\x9d\x1c\x07\xcf\x6d\x13\x44\xa9\xc3\xb8\x50\xdb\xbd\xf2\xa3\xc0\x8d\xc6\xee\x8b\xa3\x82\x5a\xf2\x2c\xa7\xee\x52\xe2\x70\x72\x74\xf8\x7c\xec\x8e\x6a\xda\x76\xb1\x0d\x22\x32\x7a\x71\x14\x46\x6d\xb6\x5d\xd4\xf8\xf9\x71\x74\x38\x41\x87\x85\x6d\x98\x30\x25\x50\xdc\x49\x4b\xc6\xe4\x59\x74\x7c\x1c\x1e\x9c\x13\xc9\x33\x81\x89\x71\xfb\x1c\xcb\x99\x4d\xfc\x6a\x67\x30\x98\x3d\x9f\x19\xe0\x2e\x06\xa7\xf9\xcc\xd7\x08\x97\x03\x9c\x01\xea\xd6\x91\x0a\x41\xed\x87\xa1\xce\xbb\x44\x4a\x58\x28\xdf\x32\x0f\x2e\x3f\x5b\x48\x13\x3c\x25\x42\x51\x52\xa2\xd9\xfb\xf5\xec\x23\x67\x64\x11\x12\xa6\x68\x44\x0b\xd5\x74\x72\xe9\xdc\x5a\x44\xbb\x52\x76\x3a\x2a\xa9\xb2\x69\xc8\x4d\xe3\x7a\xaf\xfb\x98\x07\x8f\xfc\x2c\x80\xc7\xdf\x5a\xf5\xf3\xbd\x72\xc8\x64\xac\x31\xe7\x0d\x37\xc7\x1e\x22\xdd\x7d\xb0\x74\xf7\x07\x4a\x9f\x3c\x58\xfa\xe4\x7e\xd2\x97\xa6\x5f\xd4\x9a\x9a\x81\x40\x7b\x60\xc6\x99\x42\x94\x11\x51\xf4\x19\x59\x40\x2f\x65\x06\x7a\xcb\x1b\xc4\x0e\x8d\xed\xc9\x6a\x6f\x6b\xe3\xbe\xa5\xe9\xea\x8d\x33\x41\x8c\x12\x6b\x1e\x53\x5c\x36\x88\x22\xb3\x7d\xba\x61\xa8\xd2\xa5\x2f\x68\x42\x78\xa6\x3c\x58\x5f\x8c\x8f\x56\x66\xf9\x5d\x1a\x22\x45\xea\xc7\x2b\x09\x7b\xce\x63\xfd\x9f\xa5\xda\x31\x5a\x51\x56\x9a\xb8\x60\x3e\x11\x57\x14\xd7\xac\x33\xf6\x9d\x22\x85\xb7\x4d\xbb\x75\xef\xce\x24\xd1\xaa\x54\xf5\xd0\x9f\x0f\x88\xaa\xb7\xac\xae\xbc\xf4\xa0\xaf\x44\x46\xf4\xf1\xb6\x7b\xef\x2e\xbc\x8e\x60\xd9\x3b\x40\x65\x2a\x33\xf6\x76\x4f\x66\x54\xed\x0c\xc6\x86\x49\x75\x56\xc1\x3c\x49\x10\x0b\x6b\xf3\x0b\xc0\x68\xfc\x05\x85\xe1\x97\xa2\x77\x7e\x51\xfc\x0b\xae\xc2\x4a\xeb\x7c\x9e\x8e\xff\x6a\xec\x02\xfc\xe3\xd1\x30\xa0\x6c\x18\x20\xb9\x6d\xed\x11\xbc\xe5\x1a\x87\xbe\xcc\x96\xef\xfc\x8b\xf9\xf9\xc9\xe3\x6f\x3b\xfc\xfa\x0e\xf0\xcb\x2f\x30\x24\x0a\x0f\x09\x96\xfa\x6f\x60\xb5\xaf\xb0\x89\x68\x4c\x1a\x9a\xf7\xcc\x09\x1c\x31\xfd\xe7\x6c\xb3\xd4\x9c\xea\xb5\xd5\x66\x8a\x30\xb5\x57\xed\xcb\x04\x51\xf6\xb9\xb5\x2c\x15\xc2\x5f\x4f\x1e\x7f\x33\xae\xf6\xf5\x8f\x6a\xbd\x15\x1f\x61\x1a\x5f\x41\x66\xdb\x60\x93\x2a\xe1\xa1\xce\xa7\xd1\x68\x74\x38\x1a\xf5\x1b\x9b\xfc\x9a\x11\xe1\x81\xe0\x5c\x35\x76\x36\x06\xa7\xdb\x3b\x3b\xb3\xb7\x9c\x7f\x95\x83\xd0\x98\x8f\x32\xc5\x1d\x41\x62\x8e\x42\x22\xfe\xa4\x23\x5a\x7c\x1c\x2d\xa1\xed\x1a\x25\xe8\x66\x43\x84\x3c\x49\xb9\x54\x83\xcc\x54\x5a\x8b\x28\x45\x6a\x7b\x52\x36\xac\x41\xbb\x12\x06\x45\x52\x0f\xf6\x66\x73\x8b\x29\xc2\x7a\xf3\x64\xc8\x53\x35\x44\xd7\xd2\xe4\x9b\xd6\x9a\x32\xaa\xc0\xb9\x02\xc7\x31\x61\x83\x6a\xd8\x34\xda\x7d\x07\xc7\x11\xb9\x2e\x1d\x45\x69\x76\x75\xe8\xe0\xce\x40\x02\x88\x8c\x21\x79\xd2\x08\x89\xb4\x60\xd2\xc8\x4e\x79\x2b\xaf\x68\xad\x22\xf3\x28\xd8\x5c\x6d\x2e\x03\x10\x86\x82\x98\x84\x15\xf4\x68\xee\xcb\x4c\x90\xf3\x8c\x31\x0d\x15\xfb\xa8\x3a\xea\x04\xec\x70\xd7\x5d\x2d\x77\x52\xfe\x41\x82\xed\x19\x02\x16\x09\xda\x90\x85\xc6\x89\x97\x94\x85\x0b\xb6\x42\x29\x5c\x36\xa6\xc4\xa7\xb6\x41\xf4\x2a\xde\xee\x3d\xb5\x33\x0f\x14\x09\xe7\x13\x9c\x09\xaa\x6e\xf3\x9b\x25\x5c\xda\x33\xaf\xb9\x54\xfe\xab\x92\xaa\x76\x81\xb2\x14\x1d\xf7\xc4\x05\x4a\x8a\xd5\xb5\xe0\xda\x49\x39\xed\x7c\xe6\x36\x36\x1a\x17\x2b\x78\xb4\x88\xe0\xb2\x72\x79\xc8\x55\xaf\xff\xea\x55\x3b\x6f\xaf\xd0\xed\x9d\x24\xe2\xac\x02\xdb\x60\x5a\xfb\x29\x92\xe4\xd9\x61\x35\x46\x1d\x05\x59\x01\x53\x70\x6e\xea\xe5\x75\x9b\x25\xf6\xae\x13\xc7\xe0\xdc\x02\xba\x96\x8e\x8e\x50\xc0\xb9\x92\x4a\xa0\xb4\x46\xfc\xb7\xd4\x4a\x4b\xa8\x34\xad\x11\x1c\x02\x8f\x7f\xbd\x9f\xe4\x8e\xa1\xf5\x0e\xd1\xed\x30\xb6\x1a\xed\x62\xba\xd2\xa8\xd2\x8e\x75\x3b\x83\xd7\x48\x6d\x3d\xe8\x0d\x8b\xea\x38\xe7\x95\xa2\x72\xca\xc4\xd1\xcb\x56\xb6\xfe\xd6\x2d\x30\xa7\xe9\x92\x32\x95\x32\x4b\x88\x26\xb0\xc3\xcc\x19\xc7\x59\xa2\x01\xba\x74\xa5\xaf\x90\x22\xf5\x25\x07\xe6\x51\x44\xb0\xf2\xa0\xfa\x74\xc3\x0a\xa0\x0c\xd3\x14\xc5\xf5\xea\x2f\x46\x9d\x83\x7a\x91\x13\xec\x0e\x50\x82\x7e\xe7\x0c\x5d\xeb\x76\x9b\x54\xf6\xa7\x06\x65\xeb\x8f\x39\xa4\x92\xde\x4e\xe1\x3d\x7e\x32\x76\xd0\xaa\xab\xac\x65\xb6\x90\x08\x96\x4e\x8e\x95\xbb\xc9\x6a\x8f\xe5\x9d\xb6\xdf\x65\x7d\x97\xd6\xd6\x4e\xe9\x99\x91\x93\xec\xee\x43\xcd\x7d\x9d\x46\x7a\xab\x95\xec\x1d\xb4\x67\x44\x3c\x84\x9a\x4a\xcc\xaf\x88\x58\xf3\x38\x9e\xb3\x30\xe5\x94\xa9\x0e\x32\x3f\x0b\x12\xaa\x7e\x6e\xed\x08\xaf\xbd\x26\x3d\xcd\xac\xb6\x5c\x74\x59\x0f\x7a\x3f\xeb\x50\x58\x84\xec\xb8\x15\xba\x9e\x57\x03\xd5\x7d\xb7\xb8\xdd\x13\x5b\xc8\x31\xab\xeb\x3a\x62\xc8\x8a\xba\x37\xfc\x6a\xcf\x22\xe7\x33\xdf\x68\x52\xe2\x38\xec\x64\x36\xc0\x7d\xc1\x36\x82\xc8\x4a\xda\x2c\xd2\xb5\xe0\x8a\x63\x1e\x7b\xa0\xf0\x0e\xd0\x5e\x0a\x9e\xac\xb9\x30\x2f\x1a\xdc\x5d\xf3\xbb\xe0\x1d\x8b\x33\x1a\x8a\x45\x9a\xc3\x7c\xe5\x69\xe0\x3c\x0e\xfe\x0b\x9c\xb3\x3c\xfd\x0f\xf9\xe5\x78\xd4\xe1\x97\xea\x62\xe1\x97\xea\xab\x83\xf9\xf2\xd4\xd5\xb1\x3a\xcf\x3a\x70\xac\xed\x9a\x5c\xaf\x7d\xfd\xbf\x53\xc9\x8a\x8a\xa5\x32\xa5\x7e\xcf\x8e\x8e\x26\x47\xc5\xaa\x6f\x2f\x53\x35\x81\x7a\x9a\x78\x45\xd4\x54\x29\x1b\xbf\x41\xbe\x5c\x75\x70\x95\xc8\x96\x40\x85\x4a\x2f\xb8\xf3\xe5\xe9\xff\x82\x85\x2d\xe5\x3b\x4d\x6c\xfa\x61\x8e\xe5\x3c\x0e\xda\xb6\xc5\x48\x2a\x8a\x97\x1c\x85\xa7\x28\x46\x0c\x53\xb6\x79\xef\x7a\xde\x6e\x21\xc7\xc4\xb6\x99\xf6\x49\x83\xfc\xff\x33\x9c\xbf\xfb\x19\x4e\x63\x12\x6e\x0c\x22\x3a\x0f\xca\xf8\x2f\x75\x6f\x62\x5d\xcf\x04\xf7\xe5\x41\x7e\x60\x4f\x0e\x54\xd3\x64\x2a\x58\x31\x35\x1b\x59\x39\x49\xfe\xfa\xc8\xf6\xdf\x8a\x76\x56\x78\xc4\xc5\x35\x12\xe1\x0e\x93\x90\xd8\x10\x65\x2c\x69\xf2\xcb\x19\x55\x28\xca\xb9\xa2\x81\x62\xbb\xf2\x7b\x7d\x71\xb1\x2e\x8d\x6f\x33\xb8\xb7\x1b\x9a\x42\x3b\x86\xc2\x42\x89\x3b\xd4\x80\x07\x37\x88\xb7\x99\x4a\x33\x5b\x63\xfa\x5e\xf0\x4e\xe4\xe3\x5b\x95\x78\xab\x54\xea\x0d\x87\xe6\x91\xc9\x3c\x0e\x06\x67\x6f\x7c\x33\x2e\x0f\x0f\xa0\xf9\x02\x50\xf7\x95\x77\xe7\xcb\x56\x3a\x68\x57\xd7\xf8\xee\xbc\x5e\x4b\x80\x1a\xb3\xa9\x60\xc5\xcb\x44\xcd\xb7\x20\xb4\xaf\xae\xe7\x37\xda\xa6\xc2\xce\xfc\xaa\x64\x4d\x6b\x4c\xf5\x4e\x4b\x95\x8e\xa7\xd7\x0d\xbd\xaa\xc3\xda\xde\x37\x9c\x95\x77\x3f\x7f\x46\xa7\x42\xc6\xc1\xbf\x03\x00\x00\xff\xff\x75\x16\x3e\x1e\xeb\x21\x00\x00") +var _assetsClusterYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x1a\xfb\x6f\xdb\xb8\xf9\xf7\xfc\x15\x5f\xbd\x0e\xbe\x1e\x2a\xdb\x71\xae\x87\x56\x58\x7a\x70\x1c\xb7\x35\xce\x69\x8d\x38\x6d\x81\x3e\x50\xd0\xd2\x67\x9b\xab\x44\x6a\x24\x95\xc7\x75\xf9\xdf\x07\x92\x92\x4c\x4a\x72\x9a\xf4\xba\xdd\x6e\x98\x8b\xa0\x16\xf9\xf1\x7b\x3f\x29\x07\x41\xb0\x37\x7a\xbb\x38\xc3\x34\x4b\x88\xc2\x67\x5c\xa4\x44\xbd\x41\x21\x29\x67\x21\x74\x87\x83\xfd\x41\x30\x78\x12\x0c\x9e\x74\xf7\xe6\x44\x90\x14\x15\x0a\x19\xee\x01\x4c\x99\x54\x84\x45\x78\x76\x95\xa1\x7e\x06\x30\xdf\x60\xa1\x04\x65\x6b\xb3\x70\x8c\x32\x12\x34\x53\x06\x55\x09\x0f\xea\x2a\x43\x50\x1c\x72\x89\xbd\x02\x6c\x45\xf2\x44\x85\xa0\x86\xbd\x94\x46\x82\xef\x99\xa3\x54\x60\x3c\x26\x19\x89\xa8\xba\x72\x09\xbc\xcc\xd3\x25\x0a\xff\x64\x77\xbf\xdb\xa4\x68\x01\x81\xaf\x80\x16\xb4\xa5\xa6\x9b\x90\x9c\x45\x1b\xa0\x0c\xae\x78\x2e\x60\x32\x5e\x40\x94\xe4\x52\x19\x9c\x27\xe4\x72\x41\x7f\xc3\xaf\xd2\x1b\xb6\xd0\x3b\x21\x97\x34\xcd\x53\x60\x6d\x74\x37\x44\x41\x44\x18\x2c\xb1\x60\x00\xe3\x1d\x2c\xfc\x8a\x57\x2f\x49\x7a\x2b\x9d\x16\xa0\x5a\x2a\x22\x25\x8f\x28\x51\x08\x17\x54\x6d\xe0\x82\x8b\xcf\x28\xb6\x0c\xf4\x00\x66\x48\xce\x11\x96\x09\x61\x9f\xf5\x81\x98\x4a\xb2\x4c\x10\x16\x8b\x17\x40\xa2\x08\xa5\xac\x59\xa3\xab\x45\x5c\xc8\xcd\x28\x49\xf8\x45\xd8\x24\xbe\xc8\x97\x0c\x15\xac\x04\x4f\xe1\x62\x43\xa3\x8d\x61\x43\x03\x37\x70\x36\xa4\x38\xa1\x6c\x86\x6c\xad\x36\x21\x74\x9f\x58\x55\x9e\x90\xcb\x6a\x69\xff\x71\xd7\xe7\x65\xd0\x33\xff\xfa\x03\xb3\x6c\x38\xc2\x78\x4e\x94\x42\xc1\x42\xe8\xfc\xf0\xe1\x43\xfc\x65\xff\xe1\xc1\xf5\x83\x0f\x1f\x7a\xb7\x79\xe8\x17\x5f\x87\xd7\x0f\x3a\x06\xe5\x98\x33\xa9\x04\xa1\x4c\x79\x32\x76\xd3\x5c\x2a\x6d\x33\x02\xe7\x24\xa1\x31\x8c\xa7\xc7\xa7\xb0\x4c\x78\xf4\x39\x84\xcb\x9e\xf9\xd7\xbf\xec\x19\x4d\x45\x24\xc1\x57\xb9\x3a\xdb\x08\x94\x1b\x9e\xc4\x2d\x2a\xab\xf6\x80\xa8\x42\x67\x04\xa4\x3e\x08\x3c\x57\x80\xe7\xc8\x14\x5c\xd0\x24\xd1\x24\x29\xa3\x4a\x1b\x34\xfe\x9a\x2f\x3e\x1e\x54\xf4\xa7\xec\x1b\xc9\x53\xf6\xad\xd4\x0f\x0c\xf5\x69\x4a\xd6\x38\x6d\x23\xaa\x9d\x7b\x74\x32\xdd\x46\xde\x2e\xbf\x76\xdd\xee\x4d\x16\x95\xc8\x6e\x8e\x00\xe3\xfe\x7c\x05\x6a\x83\xda\x42\xb9\x89\x05\x9a\x66\x5c\x28\x58\x71\x61\xd6\x0d\xb2\x3d\x80\x79\xbe\x4c\x68\x64\xdd\x76\xf4\x6e\xff\xfb\x11\x18\xbd\xdb\x07\x69\xa3\x81\x36\x09\x0d\xbf\x27\xa1\xa1\x47\xa8\xae\x36\x9f\xf0\xc1\xf7\x24\x7c\x70\x03\xe1\x13\x54\x24\x26\x8a\x68\x6a\xa3\xb7\x8b\x30\x1c\x27\x3c\x8f\x6d\x29\xd1\x24\xc2\x29\x53\x28\x56\x24\x2a\x92\x5a\x55\x48\x9e\x0b\x9e\x67\xd2\x2e\x02\x04\x30\x23\x4b\x4c\xca\x47\xfd\x89\x4b\x2a\x9d\xaa\x7c\x8c\x39\x5b\xd1\x75\x2e\x0c\xea\x4e\x05\xeb\x17\xa7\xf2\x13\x78\x65\xca\xdb\x28\x72\xa7\xb7\x56\x66\xbb\xdb\x30\x34\xca\x15\x37\x41\x47\xd9\xfa\xae\x4c\xd5\xaa\x9b\xb7\x57\x54\x20\x5f\x51\x86\x8f\x0a\x49\xb3\xf4\xee\xd0\x55\x59\x6a\x6d\xe0\xfd\x52\x32\xe6\x55\x18\xff\xe8\xaf\x78\xa5\x0f\xac\x05\x61\xca\x49\xe3\xf0\x83\xad\x1b\xda\x1f\x18\x67\xf8\xa0\xc2\xe5\x17\x08\x1f\xd9\x36\x59\xb6\xe1\xac\x50\xb4\xd6\x7a\x1f\x53\x01\xe2\x56\xca\xaa\xb6\x41\xc4\x73\xa6\x2a\x6c\x5e\x05\xf7\xb1\x94\x05\xfa\x46\x2c\x63\xce\x62\xaa\xcd\x68\xd4\xfd\x82\x48\x4f\x5b\x9d\x67\x2c\x0c\x5f\x72\xd5\xd9\x3a\xad\x59\x9a\xfc\x23\x27\x89\xec\x84\xf0\xfe\xde\x29\xae\x4a\x0d\x3f\x84\x6e\xf7\xa3\xc5\x52\x4b\x3e\x77\xc2\xd6\x48\x5c\x3b\xf1\x0e\x7f\x07\xde\xe1\x0d\x78\x0f\x7e\x07\xde\x83\x12\xef\x29\x4a\x9e\x8b\x08\x8d\x62\x27\x91\x1c\x5b\x13\xb8\x39\xca\x64\x8f\xc9\xd8\xa4\x90\xb2\x1f\x9a\x8c\x17\x3a\xd6\x8a\x50\x33\x29\xa3\x71\xc4\x01\xf0\x1e\x0c\x74\x91\xaf\x32\x64\xb1\x7c\xc5\x42\x78\xff\xd1\x06\x97\xe0\x19\x0a\x45\xb1\x8a\xab\x37\xf3\xf1\x3b\xce\x70\x1a\x23\x53\x74\x45\x4b\xd6\xb4\x98\x5a\xca\xe9\x6a\xeb\x54\x41\x8b\x4d\x9d\x4d\x03\x6e\x52\xe8\x1b\x9d\x51\x43\xb8\xb7\xc8\x97\x70\xff\x4b\xc3\x92\xd7\xce\x21\xa3\x3b\x23\xce\x4b\x6e\x8e\xdd\x85\xfa\xf0\xce\xd4\x87\xdf\x91\xfa\xc1\x9d\xa9\x1f\xdc\x8e\xfa\xcc\x64\x2e\x2f\xbd\x9a\x60\xb4\x07\xc6\x9c\x29\x42\x19\x8a\x32\xe3\xc9\x32\x09\x50\x66\x92\x40\x35\x18\x6c\xf3\x82\x3d\xe9\x66\xd9\x66\x06\xb2\x30\x6d\x59\x7a\x2c\xd0\x30\x31\xe7\x09\x8d\xaa\x54\x55\x7a\xf6\x82\xae\x19\x71\xea\xc5\x19\x4d\x91\xe7\x2a\x84\xf9\xd9\xfe\xa3\x13\xb3\xfc\x3a\x8b\x89\x42\xff\xb8\xe3\xb0\xa7\x3c\xd1\xff\x59\xa8\x2d\xa2\x13\xca\x2a\x11\xa7\x6c\x81\xe2\x9c\x46\x9e\x74\x46\xbe\x23\xa2\xa2\x4d\x5d\x6e\x5d\x45\x72\x89\x9a\x15\x97\x0f\xfd\x79\x4b\xa8\x7a\xc5\x7c\xe6\x65\x08\x5d\x25\x72\x74\x5b\x5a\x97\xdb\x5d\x41\x57\x7c\xb1\xa0\x3b\xe2\x6b\x14\xff\x3d\x97\x2a\x45\xa6\x2c\x96\xf1\x86\xb0\x35\x4e\x59\xad\x0e\xd6\xe3\xd7\x31\x78\x4b\x2e\x28\x0e\x8d\x39\x4f\x62\x7e\xc1\x42\x38\x18\x0c\xca\xda\x64\xc1\xb6\x64\x43\xd8\xdf\x76\xca\xff\x43\x52\x05\x5a\xac\x13\x4c\xb9\xb8\x1a\x25\x44\xa4\x2f\xe8\x7a\xd3\x10\xcc\x34\x65\x6f\xb5\x8b\x84\xa1\x81\xda\x25\x8f\xde\xf3\x3a\x44\x13\xc0\x46\x6b\x81\x1e\x53\xe8\xaa\xa0\x05\x4f\xe1\xfe\x97\xc6\xdc\x73\xfd\x57\xd3\x26\x3c\x82\x94\xb2\x5c\x6d\x43\x12\x95\xa0\x91\x95\xda\x1e\x3f\x45\x89\xe2\xdc\x84\x53\x01\xa3\x77\x65\xa6\xfb\x44\xcd\x72\x7f\x32\x5e\x94\x32\x2b\xa2\xa8\x54\x34\x0a\x61\x74\x8e\x82\xac\xcb\xc8\x9d\xa3\xa0\x3c\x76\xd5\x33\xd1\x3d\xac\x8d\x51\xb3\x27\xad\xd1\x8d\x32\xaa\xd9\xc8\xea\xbd\xc1\xba\xab\x81\x51\x54\x35\x01\x4e\x8a\xf2\x43\xa2\x4c\x1f\x34\x45\x26\x7d\x68\x2b\x68\x51\xc5\xbc\x46\xb3\xcc\x8a\xc6\xf2\x55\x15\xac\x0c\x9e\x66\x44\x50\xc9\xd9\xab\x0c\x05\x51\x5c\x84\xf0\x5c\xe7\x1c\x14\x67\x1b\xc2\x5c\x4e\x1d\x7b\xcf\xca\xfe\xeb\x7b\x9b\x9b\x32\xc7\xda\x7f\x2b\xad\xed\x4c\x99\x7f\x32\x63\x4f\xd9\x5d\x6d\x5d\xe6\x89\xff\x8c\xa9\x67\x28\x65\xdd\xce\xcd\x22\x77\x73\xce\x6a\x29\x99\x7b\x85\x4d\xaa\x29\xcd\x48\xde\x3e\xa9\x51\xb5\x2d\x3b\x91\x41\xe2\xce\x2e\x11\x4f\x53\xc2\x62\x6f\x9e\x01\x18\xec\x7f\x22\x71\xfc\xa9\xec\xa5\x3f\x29\xfe\x29\x72\x9b\xbb\xc6\xf9\xc2\xc9\xfe\x59\xdb\x05\xf8\xcb\xbd\xfe\x92\xb2\xfe\x92\xc8\x4d\x63\x0f\xa3\x0d\xd7\xb9\xf2\xd3\x78\xf6\x7a\x71\x36\x39\x3d\xbc\xff\x65\xab\xd4\x6b\x80\xa7\x4f\xa1\x8f\x2a\xea\x63\x24\xf5\x5f\xcf\x72\xef\xa0\x59\xd1\x04\x6b\x9c\x77\xcc\x89\x68\xc5\xf4\x5f\xb0\xc9\x33\x73\xaa\xd3\x64\x9b\x29\x93\x69\x77\xb0\xfd\x3e\x25\x94\x7d\x6c\x2c\x4b\x45\xa2\xcf\x87\xf7\xbf\x18\x55\x2f\xf4\x83\xdb\xf5\x94\x1f\x81\x6b\xca\x59\x09\x76\x6a\x9e\xea\x50\x29\x8f\x75\x55\x1f\x0c\x06\x3f\x0d\x06\xdd\xda\x26\xbf\x60\x28\x42\x10\x9c\xab\xda\xce\xda\x74\xcb\xcd\x9d\xad\xd8\x1b\xce\x3f\xcb\x5e\x6c\xc4\x27\xb9\xe2\x81\xc0\x84\x93\x18\xc5\x37\x2a\xa2\x81\x27\xd0\x14\x9a\xaa\x51\x82\xae\xd7\x28\xe4\x61\xc6\xa5\xea\xe5\xa6\xdf\x69\x00\x65\x44\x6d\x0e\xab\xb1\xa1\xd7\x8c\x84\x5e\xe9\xd4\xbd\x9d\xde\xdc\x40\x4a\x4c\xb0\x1f\xf6\x79\xa6\xfa\xe4\x42\x1a\x7f\xd3\x5c\x53\x46\x15\x04\xe7\x10\x04\xc6\x6c\xe0\x9a\x4d\x47\xf5\x35\x04\x81\x28\x78\x69\x09\x4a\xb3\xab\x4d\x07\x37\x1a\x12\x40\xe4\x8c\xc8\xc3\x9a\x49\xa4\x6d\xe9\x6a\xde\x29\xaf\xe4\x39\xf5\x22\xb2\xb0\x82\xf5\xd5\xfa\x32\x00\x32\xb2\x4c\x30\x76\x7a\xb8\xfa\xbe\xcc\x05\x9e\xe6\x8c\xe9\x54\xb1\x0b\xaa\x25\x4e\xc0\x0e\x7b\xed\xd1\x72\x23\xe4\x57\x1c\x6c\x47\x55\x2a\xef\x13\x6d\xee\x2c\x9e\xca\xfa\x80\x51\x2e\xa8\xba\x2a\x6e\x8f\xe0\xbd\x05\x7a\xc1\xa5\x5a\x3c\x87\x8f\x6d\x97\x24\x05\x9a\xe6\x5d\xd0\x94\xa4\xe5\xea\x5c\x70\x2d\x78\xd5\x93\x0d\x6b\x1b\xb5\xcb\x13\xb8\x37\x5d\xc1\x7b\xe7\x82\xe0\x21\xf8\xa3\xbf\x79\xea\xb8\x33\x4d\xa7\xe4\xed\xb5\x44\x71\xec\xa4\x62\x30\x43\xd3\x11\x91\xf8\xf3\x4f\xae\xde\x5b\x82\xcc\x49\x90\x10\x5c\xfa\x21\x73\x95\xa7\xf6\x3e\x23\x49\x20\xb8\x02\x72\x21\x03\xad\xf5\x25\xe7\x4a\x2a\x41\x32\x0f\xf8\x0f\xf1\xff\x06\x51\x69\x86\x0e\x08\x10\xee\xff\x72\x3b\xca\x2d\xcd\xf2\x0d\xa4\x9b\x66\x6c\x14\xcf\xe9\xe8\x44\x67\x8a\xa6\xad\x9b\x5e\x39\x27\x6a\x13\x42\xa7\x5f\x7a\xfc\x29\x77\x02\x25\xa8\x1c\x47\x2f\x5b\xda\xfa\x5b\x3b\xc1\x02\xa6\xb5\x23\x93\x32\x4f\x51\x03\xd8\xd6\xe3\x98\x47\xb9\xe9\xf3\x2b\x55\xea\x0e\x09\xfd\xa5\x00\x26\xab\x15\x46\x2a\x04\xf7\x06\xd3\x12\xa0\x2c\xa2\x19\x49\xfc\x88\x2e\x87\xc8\x3d\x3f\x70\x31\x1a\xf6\x48\x4a\x7e\xe3\x8c\x5c\xe8\x12\x9a\x3a\xfb\xb6\x4d\xf2\xaf\x32\xa5\x92\xe1\x96\xe1\x1d\x7a\x32\x72\x50\x57\x55\x56\x32\x1b\x48\x18\xc9\xa0\xc8\x7f\xdb\x99\x75\x87\xe4\xad\xb2\xdf\x24\x7d\x1b\xd7\x56\x4e\x19\x9a\x61\x1e\xfd\xae\xcc\xdd\xd7\x6e\xa4\xb7\x1a\xce\xde\x02\x7b\x8c\xe2\x2e\xd0\x54\x46\xfc\x1c\xc5\x9c\x27\xc9\x84\xc5\x19\xa7\x4c\xb5\x80\x2d\xf2\x65\x4a\xd5\x8f\x8d\x1d\x11\x36\xd7\x64\xa8\x91\x79\xcb\x65\xe5\x0c\xa1\xf3\xa3\x36\x85\xcd\x90\x2d\xf7\x6d\xc3\x30\xf4\x92\xea\x0e\xc7\x74\xde\xca\x40\x91\xb3\xda\x2e\x7a\x0c\x58\x19\xf7\x06\x5f\xe3\x85\x90\xe6\xa4\xca\xe3\xe0\x4e\xbc\x1e\x1f\x53\xb6\x16\x28\x1d\xb7\x99\x66\x73\xc1\x15\x8f\x78\x12\x82\x8a\xb6\x09\xed\x99\xe0\xe9\x9c\x0b\xf3\x66\x76\xb8\x2d\x68\x67\xbc\x65\x71\x4c\x63\x31\xcd\xca\xa9\x60\x7b\xe3\x3f\x49\x96\xff\x05\xca\x99\x1d\xfd\x9b\xf4\xf2\x78\xd0\xa2\x17\x77\xb1\xd4\x8b\xfb\xae\x75\x32\x3b\x1a\x6a\x5b\x9d\xe6\x2d\x79\xac\xa9\x9a\x82\xaf\x5d\x35\xbd\x95\x49\x87\xc5\x8a\x99\x8a\xbf\x9f\x1f\x3d\x3a\x78\x54\xae\x2e\xec\x35\x95\x47\x50\x77\x08\xcf\x51\x8d\x94\xb2\xf6\xeb\x15\xcb\xae\x82\x5d\x20\x1b\x02\x0e\x94\x5e\x18\x4e\x66\x47\x7f\x06\x09\x1b\xcc\xb7\x8a\x58\xd7\xc3\x24\x92\x93\x64\xd9\x94\x2d\x21\x7a\xc6\x9e\x71\x12\x1f\x91\x84\xb0\x88\xb2\xf5\x9b\x61\x18\x6e\x17\x8a\x9c\xd8\x14\xd3\xde\xe1\xca\xff\xdf\x8e\xff\xd1\xb7\xe3\xb5\x4e\xb8\xd6\x88\x68\x3f\xa8\xec\x3f\xd3\xb5\x89\xb5\xbd\x6d\xd9\xe5\x07\xc5\x81\x1d\x3e\xe0\xba\xc9\x48\xb0\xed\x25\xc7\x24\x59\x16\x20\xc5\x2b\xe2\xc6\xe5\x8a\x25\xbe\xe2\xe2\x82\x88\x78\x9b\x93\x88\x58\xa3\x32\x92\xd4\xf1\x15\x88\x1c\x88\xaa\xaf\xa8\x65\xb1\x6d\xf8\xbd\x38\x3b\x9b\x57\xc2\x37\x11\xdc\x5a\x0d\x75\xa2\x2d\x4d\x61\xc9\xc4\x0d\x6c\xc0\x9d\x0b\xc4\xab\x5c\x65\xb9\x8d\x31\x3d\x17\xbc\x16\x45\xfb\xe6\x02\x6f\x94\xca\xc2\x7e\xdf\x5c\x83\x4c\x92\x65\xef\xf8\xe5\xc2\xb4\xcb\xfd\x3d\x68\xfc\x0a\x63\x76\x04\xaf\x4f\x67\x0d\x77\xd0\xaa\xf6\xf0\x6e\xb5\xee\x39\x80\x87\x6c\x24\x58\xf9\x83\x01\x8d\xb7\x04\xb4\xbf\xf5\x99\x5c\x6a\x99\x4a\x39\x8b\x51\xc9\x8a\x56\xeb\xea\x83\x06\x2b\x2d\xef\x05\x6f\xb8\x42\xdb\xf9\x2b\x06\xe7\xfd\xee\xb7\xf0\x54\xd2\xd8\xfb\x57\x00\x00\x00\xff\xff\xcd\x09\x1d\xe5\x1c\x27\x00\x00") func assetsClusterYmlBytes() ([]byte, error) { return bindataRead( @@ -84,7 +84,7 @@ func assetsClusterYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/cluster.yml", size: 8683, mode: os.FileMode(420), modTime: time.Unix(1484338456, 0)} + info := bindataFileInfo{name: "assets/cluster.yml", size: 10012, mode: os.FileMode(420), modTime: time.Unix(1484636245, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -104,7 +104,7 @@ func assetsVpcYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/vpc.yml", size: 7679, mode: os.FileMode(420), modTime: time.Unix(1484338624, 0)} + info := bindataFileInfo{name: "assets/vpc.yml", size: 7679, mode: os.FileMode(420), modTime: time.Unix(1484356037, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -162,7 +162,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "assets/cluster.yml": assetsClusterYml, - "assets/vpc.yml": assetsVpcYml, + "assets/vpc.yml": assetsVpcYml, } // AssetDir returns the file names below a certain @@ -204,11 +204,10 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } - var _bintree = &bintree{nil, map[string]*bintree{ "assets": &bintree{nil, map[string]*bintree{ "cluster.yml": &bintree{assetsClusterYml, map[string]*bintree{}}, - "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, + "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, }}, }} @@ -258,3 +257,4 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } + diff --git a/templates/assets/cluster.yml b/templates/assets/cluster.yml index 8a06e8e9..b286e594 100644 --- a/templates/assets/cluster.yml +++ b/templates/assets/cluster.yml @@ -25,6 +25,18 @@ Parameters: Default: 0.0.0.0/0 AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" ConstraintDescription: 'must be a valid CIDR block: x.x.x.x/x.' + ScaleOutThreshold: + Description: Threshold at which a scale out event will be initiated + Type: Number + Default: '80' + ScaleInThreshold: + Description: Threshold at which a scale in event will be initiated + Type: Number + Default: '30' + ImageId: + Description: ECS AMI to launch + Type: String + Default: '' VpcId: Type: String Description: Name of the value to import for the VpcId @@ -77,24 +89,6 @@ Conditions: HasPublicSubnetAZ3: "Fn::Not": - "Fn::Equals": [!Ref PublicSubnetAZ3Id, ''] -Mappings: - AWSRegionToAMI: - us-east-1: - AMIID: ami-2b3b6041 - us-west-2: - AMIID: ami-ac6872cd - eu-west-1: - AMIID: ami-03238b70 - ap-northeast-1: - AMIID: ami-fb2f1295 - ap-southeast-2: - AMIID: ami-43547120 - us-west-1: - AMIID: ami-bfe095df - ap-southeast-1: - AMIID: ami-c78f43a4 - eu-central-1: - AMIID: ami-e1e6f88d Resources: EcsCluster: Type: AWS::ECS::Cluster @@ -128,6 +122,52 @@ Resources: MaxBatchSize: '1' PauseTime: PT15M WaitOnResourceSignals: 'true' + ScaleOutPolicy: + Type: AWS::AutoScaling::ScalingPolicy + Properties: + AdjustmentType: ChangeInCapacity + AutoScalingGroupName: !Ref ECSAutoScalingGroup + Cooldown: 300 + ScalingAdjustment: 1 + ScaleInPolicy: + Type: AWS::AutoScaling::ScalingPolicy + Properties: + AdjustmentType: ChangeInCapacity + AutoScalingGroupName: !Ref ECSAutoScalingGroup + Cooldown: 300 + ScalingAdjustment: -1 + MemoryAlarmHigh: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmDescription: !Sub Scale-out if Memory > ${ScaleOutThreshold}% for 5 minutes + MetricName: MemoryReservation + Namespace: AWS/ECS + Statistic: Average + Period: 300 + EvaluationPeriods: 1 + Threshold: !Ref ScaleOutThreshold + AlarmActions: + - !Ref ScaleOutPolicy + Dimensions: + - Name: ClusterName + Value: !Ref EcsCluster + ComparisonOperator: GreaterThanThreshold + MemoryAlarmLow: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmDescription: !Sub Scale-in if Memory < ${ScaleInThreshold}% for 5 minutes + MetricName: MemoryReservation + Namespace: AWS/ECS + Statistic: Average + Period: 300 + EvaluationPeriods: 1 + Threshold: !Ref ScaleInThreshold + AlarmActions: + - !Ref ScaleInPolicy + Dimensions: + - Name: ClusterName + Value: !Ref EcsCluster + ComparisonOperator: LessThanThreshold ContainerInstances: Type: AWS::AutoScaling::LaunchConfiguration Metadata: @@ -163,7 +203,7 @@ Resources: - "/etc/cfn/cfn-hup.conf" - "/etc/cfn/hooks.d/cfn-auto-reloader.conf" Properties: - ImageId: !FindInMap [ AWSRegionToAMI, !Ref "AWS::Region", AMIID ] + ImageId: !Ref ImageId SecurityGroups: [ !Ref HostSG ] InstanceType: !Ref InstanceType IamInstanceProfile: !Ref EC2InstanceProfile diff --git a/workflows/environment.go b/workflows/environment.go index 8a13c75c..bc419e16 100644 --- a/workflows/environment.go +++ b/workflows/environment.go @@ -21,6 +21,8 @@ type environmentWorkflow struct { environment *common.Environment } +var ecsImagePattern = "amzn-ami-*-amazon-ecs-optimized" + // NewEnvironmentUpserter create a new workflow for upserting an environment func NewEnvironmentUpserter(ctx *common.Context, environmentName string) Executor { @@ -30,7 +32,7 @@ func NewEnvironmentUpserter(ctx *common.Context, environmentName string) Executo return newWorkflow( workflow.environmentFinder(&ctx.Config, environmentName), workflow.environmentVpcUpserter(vpcImportParams, ctx.StackManager, ctx.StackManager), - workflow.environmentEcsUpserter(vpcImportParams, ctx.StackManager, ctx.StackManager), + workflow.environmentEcsUpserter(vpcImportParams, ctx.StackManager, ctx.StackManager, ctx.StackManager), ) } @@ -61,7 +63,15 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ if err != nil { return err } - err = stackUpserter.UpsertStack(vpcStackName, template, nil, buildEnvironmentTags(environment.Name, common.StackTypeVpc)) + + vpcStackParams := make(map[string]string) + if environment.Cluster.InstanceTenancy != ""{ + vpcStackParams["InstanceTenancy"] = environment.Cluster.InstanceTenancy + } + if environment.Cluster.SSHAllow != ""{ + vpcStackParams["SshAllow"] = environment.Cluster.SSHAllow + } + err = stackUpserter.UpsertStack(vpcStackName, template, vpcStackParams, buildEnvironmentTags(environment.Name, common.StackTypeVpc)) if err != nil { return err } @@ -87,7 +97,7 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ } } -func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[string]string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { +func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[string]string, imageFinder common.ImageFinder, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { return func() error { environment := workflow.environment envStackName := common.CreateStackName(common.StackTypeCluster, environment.Name) @@ -98,7 +108,37 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ return err } - err = stackUpserter.UpsertStack(envStackName, template, vpcImportParams, buildEnvironmentTags(environment.Name, common.StackTypeCluster)) + stackParams := vpcImportParams + + if environment.Cluster.SSHAllow != ""{ + stackParams["SshAllow"] = environment.Cluster.SSHAllow + } + if environment.Cluster.ImageID != ""{ + stackParams["ImageId"] = environment.Cluster.ImageID + } else { + stackParams["ImageId"], err = imageFinder.FindLatestImageID(ecsImagePattern) + if(err != nil) { + return err + } + + } + if environment.Cluster.DesiredCapacity != 0{ + stackParams["DesiredCapacity"] = strconv.Itoa(environment.Cluster.DesiredCapacity) + } + if environment.Cluster.MaxSize != 0{ + stackParams["MaxSize"] = strconv.Itoa(environment.Cluster.MaxSize) + } + if environment.Cluster.KeyName != ""{ + stackParams["Keyname"] = environment.Cluster.KeyName + } + if environment.Cluster.ScaleInThreshold != 0{ + stackParams["ScaleInThreshold"] = strconv.Itoa(environment.Cluster.ScaleInThreshold) + } + if environment.Cluster.ScaleOutThreshold != 0{ + stackParams["ScaleOutThreshold"] = strconv.Itoa(environment.Cluster.ScaleOutThreshold) + } + + err = stackUpserter.UpsertStack(envStackName, template, stackParams, buildEnvironmentTags(environment.Name, common.StackTypeCluster)) if err != nil { return err } diff --git a/workflows/environment_test.go b/workflows/environment_test.go index 9bae2c52..983b86b8 100644 --- a/workflows/environment_test.go +++ b/workflows/environment_test.go @@ -62,6 +62,10 @@ func (m *mockedStackManager) UpsertStack(stackName string, templateBodyReader io args := m.Called(stackName) return args.Error(0) } +func (m *mockedStackManager) FindLatestImageID(pattern string) (string,error) { + args := m.Called() + return args.String(0), args.Error(1) +} func TestEnvironmentEcsUpserter(t *testing.T) { assert := assert.New(t) @@ -76,8 +80,9 @@ func TestEnvironmentEcsUpserter(t *testing.T) { stackManager := new(mockedStackManager) stackManager.On("AwaitFinalStatus", "mu-cluster-foo").Return(cloudformation.StackStatusCreateComplete) stackManager.On("UpsertStack", "mu-cluster-foo").Return(nil) + stackManager.On("FindLatestImageID").Return("ami-00000", nil) - err := workflow.environmentEcsUpserter(vpcInputParams, stackManager, stackManager)() + err := workflow.environmentEcsUpserter(vpcInputParams, stackManager, stackManager, stackManager)() assert.Nil(err) stackManager.AssertExpectations(t) From 3c149679897d57f16fbba12f21895767ef7f8034 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 17 Jan 2017 00:09:21 -0800 Subject: [PATCH 31/31] Added support to terminate environment to fix 21 --- cli/environments.go | 5 +-- common/cluster.go | 1 - common/stack.go | 38 ++++++++++++++----- common/stack_test.go | 20 ++++++++++ common/types.go | 16 ++++---- templates/assets.go | 6 +-- workflows/environment.go | 69 ++++++++++++++++++++++++++--------- workflows/environment_test.go | 67 +++++++++++++++++++++++++++++++++- 8 files changed, 179 insertions(+), 43 deletions(-) diff --git a/cli/environments.go b/cli/environments.go index 98bc9372..eab15953 100644 --- a/cli/environments.go +++ b/cli/environments.go @@ -2,7 +2,6 @@ package cli import ( "errors" - "fmt" "github.com/stelligent/mu/common" "github.com/stelligent/mu/workflows" "github.com/urfave/cli" @@ -91,8 +90,8 @@ func newEnvironmentsTerminateCommand(ctx *common.Context) *cli.Command { cli.ShowCommandHelp(c, "terminate") return errors.New("environment must be provided") } - fmt.Printf("terminating environment: %s\n", environmentName) - return nil + workflow := workflows.NewEnvironmentTerminator(ctx, environmentName) + return workflow() }, } diff --git a/common/cluster.go b/common/cluster.go index 4cae12c9..5d972019 100644 --- a/common/cluster.go +++ b/common/cluster.go @@ -33,7 +33,6 @@ func newClusterManager(region string) (ClusterManager, error) { }, nil } - // ListInstances get the instances for a specific cluster func (ecsMgr *ecsClusterManager) ListInstances(clusterName string) ([]*ecs.ContainerInstance, error) { ecsAPI := ecsMgr.ecsAPI diff --git a/common/stack.go b/common/stack.go index 256cfd7e..76c4df4c 100644 --- a/common/stack.go +++ b/common/stack.go @@ -2,18 +2,18 @@ package common import ( "bytes" + "errors" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ec2/ec2iface" "io" "strings" "time" - "github.com/aws/aws-sdk-go/service/ec2" - "errors" - "github.com/aws/aws-sdk-go/service/ec2/ec2iface" ) // CreateStackName will create a name for a stack @@ -41,6 +41,11 @@ type StackGetter interface { GetStack(stackName string) (*Stack, error) } +// StackDeleter for deleting stacks +type StackDeleter interface { + DeleteStack(stackName string) error +} + // ImageFinder for finding latest image type ImageFinder interface { FindLatestImageID(namePattern string) (string, error) @@ -52,6 +57,7 @@ type StackManager interface { StackWaiter StackLister StackGetter + StackDeleter ImageFinder } @@ -234,8 +240,11 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) str // no op } - log.Debugf(" Returning final status for stack:%s ... status=%s", stackName, *resp.Stacks[0].StackStatus) - return *resp.Stacks[0].StackStatus + + if len(resp.Stacks) > 0 { + log.Debugf(" Returning final status for stack:%s ... status=%s", stackName, *resp.Stacks[0].StackStatus) + return *resp.Stacks[0].StackStatus + } } log.Debugf(" Stack doesn't exist ... stack=%s", stackName) @@ -319,18 +328,18 @@ func (cfnMgr *cloudformationStackManager) GetStack(stackName string) (*Stack, er func (cfnMgr *cloudformationStackManager) FindLatestImageID(namePattern string) (string, error) { ec2Api := cfnMgr.ec2API resp, err := ec2Api.DescribeImages(&ec2.DescribeImagesInput{ - Owners: []*string {aws.String("amazon")}, - Filters: []*ec2.Filter { + Owners: []*string{aws.String("amazon")}, + Filters: []*ec2.Filter{ { Name: aws.String("name"), - Values: []*string { + Values: []*string{ aws.String(namePattern), }, }, }, }) - if(err != nil) { + if err != nil { return "", err } @@ -354,3 +363,14 @@ func (cfnMgr *cloudformationStackManager) FindLatestImageID(namePattern string) return imageID, nil } +// DeleteStack delete a specific stack +func (cfnMgr *cloudformationStackManager) DeleteStack(stackName string) error { + cfnAPI := cfnMgr.cfnAPI + + params := &cloudformation.DeleteStackInput{StackName: aws.String(stackName)} + + log.Debugf("Deleting stack named '%s'", stackName) + + _, err := cfnAPI.DeleteStack(params) + return err +} diff --git a/common/stack_test.go b/common/stack_test.go index 8d7a1895..34199d5f 100644 --- a/common/stack_test.go +++ b/common/stack_test.go @@ -16,6 +16,10 @@ type mockedCloudFormation struct { cloudformationiface.CloudFormationAPI } +func (m *mockedCloudFormation) DeleteStack(input *cloudformation.DeleteStackInput) (*cloudformation.DeleteStackOutput, error) { + args := m.Called() + return args.Get(0).(*cloudformation.DeleteStackOutput), args.Error(1) +} func (m *mockedCloudFormation) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) { args := m.Called() return args.Get(0).(*cloudformation.DescribeStacksOutput), args.Error(1) @@ -236,6 +240,22 @@ func TestStack_GetStack(t *testing.T) { cfn.AssertExpectations(t) cfn.AssertNumberOfCalls(t, "DescribeStacks", 1) } +func TestStack_DeleteStack(t *testing.T) { + assert := assert.New(t) + + cfn := new(mockedCloudFormation) + cfn.On("DeleteStack").Return(&cloudformation.DeleteStackOutput{}, nil) + + stackManager := cloudformationStackManager{ + cfnAPI: cfn, + } + + err := stackManager.DeleteStack("foo") + + assert.Nil(err) + cfn.AssertExpectations(t) + cfn.AssertNumberOfCalls(t, "DeleteStack", 1) +} func TestBuildStack(t *testing.T) { assert := assert.New(t) diff --git a/common/types.go b/common/types.go index 2156eb88..cd0de130 100644 --- a/common/types.go +++ b/common/types.go @@ -21,14 +21,14 @@ type Environment struct { Hostname string } Cluster struct { - ImageID string `yaml:"imageId"` - InstanceTenancy string `yaml:"instanceTenancy"` - DesiredCapacity int `yaml:"desiredCapacity"` - MaxSize int `yaml:"maxSize"` - KeyName string `yaml:"keyName"` - SSHAllow string `yaml:"sshAllow"` - ScaleOutThreshold int `yaml:"scaleOutThreshold"` - ScaleInThreshold int `yaml:"scaleInThreshold"` + ImageID string `yaml:"imageId"` + InstanceTenancy string `yaml:"instanceTenancy"` + DesiredCapacity int `yaml:"desiredCapacity"` + MaxSize int `yaml:"maxSize"` + KeyName string `yaml:"keyName"` + SSHAllow string `yaml:"sshAllow"` + ScaleOutThreshold int `yaml:"scaleOutThreshold"` + ScaleInThreshold int `yaml:"scaleInThreshold"` } VpcTarget struct { VpcID string `yaml:"vpcId"` diff --git a/templates/assets.go b/templates/assets.go index 8f54a798..436dd0dc 100644 --- a/templates/assets.go +++ b/templates/assets.go @@ -162,7 +162,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "assets/cluster.yml": assetsClusterYml, - "assets/vpc.yml": assetsVpcYml, + "assets/vpc.yml": assetsVpcYml, } // AssetDir returns the file names below a certain @@ -204,10 +204,11 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "assets": &bintree{nil, map[string]*bintree{ "cluster.yml": &bintree{assetsClusterYml, map[string]*bintree{}}, - "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, + "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, }}, }} @@ -257,4 +258,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/workflows/environment.go b/workflows/environment.go index bc419e16..c1f81145 100644 --- a/workflows/environment.go +++ b/workflows/environment.go @@ -65,10 +65,10 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ } vpcStackParams := make(map[string]string) - if environment.Cluster.InstanceTenancy != ""{ + if environment.Cluster.InstanceTenancy != "" { vpcStackParams["InstanceTenancy"] = environment.Cluster.InstanceTenancy } - if environment.Cluster.SSHAllow != ""{ + if environment.Cluster.SSHAllow != "" { vpcStackParams["SshAllow"] = environment.Cluster.SSHAllow } err = stackUpserter.UpsertStack(vpcStackName, template, vpcStackParams, buildEnvironmentTags(environment.Name, common.StackTypeVpc)) @@ -110,31 +110,31 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ stackParams := vpcImportParams - if environment.Cluster.SSHAllow != ""{ + if environment.Cluster.SSHAllow != "" { stackParams["SshAllow"] = environment.Cluster.SSHAllow } - if environment.Cluster.ImageID != ""{ + if environment.Cluster.ImageID != "" { stackParams["ImageId"] = environment.Cluster.ImageID } else { stackParams["ImageId"], err = imageFinder.FindLatestImageID(ecsImagePattern) - if(err != nil) { + if err != nil { return err } } - if environment.Cluster.DesiredCapacity != 0{ + if environment.Cluster.DesiredCapacity != 0 { stackParams["DesiredCapacity"] = strconv.Itoa(environment.Cluster.DesiredCapacity) } - if environment.Cluster.MaxSize != 0{ + if environment.Cluster.MaxSize != 0 { stackParams["MaxSize"] = strconv.Itoa(environment.Cluster.MaxSize) } - if environment.Cluster.KeyName != ""{ + if environment.Cluster.KeyName != "" { stackParams["Keyname"] = environment.Cluster.KeyName } - if environment.Cluster.ScaleInThreshold != 0{ + if environment.Cluster.ScaleInThreshold != 0 { stackParams["ScaleInThreshold"] = strconv.Itoa(environment.Cluster.ScaleInThreshold) } - if environment.Cluster.ScaleOutThreshold != 0{ + if environment.Cluster.ScaleOutThreshold != 0 { stackParams["ScaleOutThreshold"] = strconv.Itoa(environment.Cluster.ScaleOutThreshold) } @@ -222,26 +222,23 @@ func NewEnvironmentViewer(ctx *common.Context, environmentName string, writer io workflow := new(environmentWorkflow) return newWorkflow( - workflow.environmentFinder(&ctx.Config, environmentName), - workflow.environmentViewer(ctx.StackManager, ctx.ClusterManager, writer), + workflow.environmentViewer(environmentName, ctx.StackManager, ctx.ClusterManager, writer), ) } -func (workflow *environmentWorkflow) environmentViewer(stackGetter common.StackGetter, instanceLister common.ClusterInstanceLister, writer io.Writer) Executor { +func (workflow *environmentWorkflow) environmentViewer(environmentName string, stackGetter common.StackGetter, instanceLister common.ClusterInstanceLister, writer io.Writer) Executor { bold := color.New(color.Bold).SprintFunc() return func() error { - environment := workflow.environment - - clusterStackName := common.CreateStackName(common.StackTypeCluster, environment.Name) + clusterStackName := common.CreateStackName(common.StackTypeCluster, environmentName) clusterStack, err := stackGetter.GetStack(clusterStackName) if err != nil { return err } - vpcStackName := common.CreateStackName(common.StackTypeVpc, environment.Name) + vpcStackName := common.CreateStackName(common.StackTypeVpc, environmentName) vpcStack, _ := stackGetter.GetStack(vpcStackName) - fmt.Fprintf(writer, "%s:\t%s\n", bold("Environment"), environment.Name) + fmt.Fprintf(writer, "%s:\t%s\n", bold("Environment"), environmentName) fmt.Fprintf(writer, "%s:\t%s (%s)\n", bold("Cluster Stack"), clusterStack.Name, colorizeStatus(clusterStack.Status)) if vpcStack == nil { fmt.Fprintf(writer, "%s:\tunmanaged\n", bold("VPC Stack")) @@ -323,3 +320,39 @@ func buildInstanceTable(writer io.Writer, instances []*ecs.ContainerInstance) *t return table } + +// NewEnvironmentTerminator create a new workflow for terminating an environment +func NewEnvironmentTerminator(ctx *common.Context, environmentName string) Executor { + + workflow := new(environmentWorkflow) + + return newWorkflow( + workflow.environmentEcsTerminator(environmentName, ctx.StackManager, ctx.StackManager), + workflow.environmentVpcTerminator(environmentName, ctx.StackManager, ctx.StackManager), + ) +} + +func (workflow *environmentWorkflow) environmentEcsTerminator(environmentName string, stackDeleter common.StackDeleter, stackWaiter common.StackWaiter) Executor { + return func() error { + envStackName := common.CreateStackName(common.StackTypeCluster, environmentName) + err := stackDeleter.DeleteStack(envStackName) + if err != nil { + return err + } + + stackWaiter.AwaitFinalStatus(envStackName) + return nil + } +} +func (workflow *environmentWorkflow) environmentVpcTerminator(environmentName string, stackDeleter common.StackDeleter, stackWaiter common.StackWaiter) Executor { + return func() error { + vpcStackName := common.CreateStackName(common.StackTypeVpc, environmentName) + err := stackDeleter.DeleteStack(vpcStackName) + if err != nil { + log.Debugf("Unable to delete VPC, but ignoring error: %v", err) + } + + stackWaiter.AwaitFinalStatus(vpcStackName) + return nil + } +} diff --git a/workflows/environment_test.go b/workflows/environment_test.go index 983b86b8..abeb2619 100644 --- a/workflows/environment_test.go +++ b/workflows/environment_test.go @@ -50,6 +50,27 @@ func TestNewEnvironmentUpserter(t *testing.T) { assert.NotNil(upserter) } +func TestNewEnvironmentViewer(t *testing.T) { + assert := assert.New(t) + ctx := common.NewContext() + viewer := NewEnvironmentViewer(ctx, "foo", nil) + assert.NotNil(viewer) +} + +func TestNewEnvironmentLister(t *testing.T) { + assert := assert.New(t) + ctx := common.NewContext() + lister := NewEnvironmentLister(ctx, nil) + assert.NotNil(lister) +} + +func TestNewEnvironmentTerminator(t *testing.T) { + assert := assert.New(t) + ctx := common.NewContext() + terminator := NewEnvironmentTerminator(ctx, "foo") + assert.NotNil(terminator) +} + type mockedStackManager struct { mock.Mock } @@ -62,7 +83,11 @@ func (m *mockedStackManager) UpsertStack(stackName string, templateBodyReader io args := m.Called(stackName) return args.Error(0) } -func (m *mockedStackManager) FindLatestImageID(pattern string) (string,error) { +func (m *mockedStackManager) DeleteStack(stackName string) error { + args := m.Called(stackName) + return args.Error(0) +} +func (m *mockedStackManager) FindLatestImageID(pattern string) (string, error) { args := m.Called() return args.String(0), args.Error(1) } @@ -161,3 +186,43 @@ func loadYamlConfig(yamlString string) (*common.Config, error) { return config, nil } + +func TestNewEnvironmentEcsTerminator(t *testing.T) { + assert := assert.New(t) + + workflow := new(environmentWorkflow) + workflow.environment = &common.Environment{ + Name: "foo", + } + + stackManager := new(mockedStackManager) + stackManager.On("AwaitFinalStatus", "mu-cluster-foo").Return(cloudformation.StackStatusDeleteComplete) + stackManager.On("DeleteStack", "mu-cluster-foo").Return(nil) + + err := workflow.environmentEcsTerminator("foo", stackManager, stackManager)() + assert.Nil(err) + + stackManager.AssertExpectations(t) + stackManager.AssertNumberOfCalls(t, "AwaitFinalStatus", 1) + stackManager.AssertNumberOfCalls(t, "DeleteStack", 1) +} + +func TestNewEnvironmentVpcTerminator(t *testing.T) { + assert := assert.New(t) + + workflow := new(environmentWorkflow) + workflow.environment = &common.Environment{ + Name: "foo", + } + + stackManager := new(mockedStackManager) + stackManager.On("AwaitFinalStatus", "mu-vpc-foo").Return(cloudformation.StackStatusDeleteComplete) + stackManager.On("DeleteStack", "mu-vpc-foo").Return(nil) + + err := workflow.environmentVpcTerminator("foo", stackManager, stackManager)() + assert.Nil(err) + + stackManager.AssertExpectations(t) + stackManager.AssertNumberOfCalls(t, "AwaitFinalStatus", 1) + stackManager.AssertNumberOfCalls(t, "DeleteStack", 1) +}