diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..4e61e809 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +# Override for Makefile +[{Makefile, makefile, GNUmakefile}] +indent_style = tab +indent_size = 4 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..fa23be10 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "wiki"] + path = wiki + url = git@github.com:stelligent/mu.wiki.git + branch = master diff --git a/Makefile b/Makefile index 3a64d0dc..10ee794c 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ IS_MASTER := $(filter master, $(BRANCH)) VERSION := $(shell cat VERSION)$(if $(IS_MASTER),,-$(BRANCH)) SRC_FILES = $(shell glide nv) ARCH := $(shell go env GOARCH) +OS := $(shell go env GOOS) BUILD_DIR = $(if $(CIRCLE_ARTIFACTS),$(CIRCLE_ARTIFACTS),.release) BUILD_FILES = $(foreach os, $(TARGET_OS), $(BUILD_DIR)/$(PACKAGE)-$(os)-$(ARCH)) UPLOAD_FILES = $(foreach os, $(TARGET_OS), $(PACKAGE)-$(os)-$(ARCH)) @@ -24,6 +25,8 @@ deps: go get "github.com/aktau/github-release" #go get -t -d -v $(SRC_FILES) glide install + +gen: go generate $(SRC_FILES) lint: fmt @@ -31,7 +34,7 @@ lint: fmt go vet $(SRC_FILES) glide novendor | xargs -n1 golint -set_exit_status -test: lint +test: lint gen @echo "=== testing ===" ifneq ($(CIRCLE_TEST_REPORTS),) mkdir -p $(CIRCLE_TEST_REPORTS)/unit @@ -41,13 +44,18 @@ else endif -build: $(BUILD_FILES) +build: gen $(BUILD_FILES) $(BUILD_FILES): @echo "=== building $(VERSION) - $@ ===" mkdir -p $(BUILD_DIR) GOOS=$(word 2,$(subst -, ,$(notdir $@))) GOARCH=$(word 3,$(subst -, ,$(notdir $@))) go build -ldflags=$(GOLDFLAGS) -o '$@' +install: build + @echo "=== building $(VERSION) - $(PACKAGE)-$(OS)-$(ARCH) ===" + cp $(BUILD_DIR)/$(PACKAGE)-$(OS)-$(ARCH) /usr/local/bin/mu + chmod 755 /usr/local/bin/mu + release-clean: ifeq ($(IS_MASTER),) @echo "=== clearing old release $(VERSION) ===" @@ -89,4 +97,4 @@ fmt: go fmt $(SRC_FILES) -.PHONY: default all lint test build deps clean release-clean release-create dev-release release $(UPLOAD_FILES) $(TARGET_OS) +.PHONY: default all lint test build deps gen clean release-clean release-create dev-release release install $(UPLOAD_FILES) $(TARGET_OS) diff --git a/README.md b/README.md index 8bf6d7ea..e40d398d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Build Status](https://circleci.com/gh/stelligent/mu.svg?style=shield)](https://circleci.com/gh/stelligent/mu) [![Join the chat at https://gitter.im/stelligent/mu](https://badges.gitter.im/stelligent/mu.svg)](https://gitter.im/stelligent/mu?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Go Report Card](https://goreportcard.com/badge/github.com/stelligent/mu)](https://goreportcard.com/report/github.com/stelligent/mu) - # Why? Amazon ECS (EC2 Container Service) provides an excellent platform for deploying microservices as containers. The challenge however is that there is a significant learning curve for microservice developers to deploy their applications in an efficient manner. Specifically, they must learn to use CloudFormation to orchestrate the management of ECS, ECR, EC2, ELB, VPC, and IAM resources. Additionally, tools like CodeBuild and CodePipeline must be mastered to create a continuous delivery pipeline for their microservices. @@ -9,201 +8,40 @@ To address these challenges, this tool was created to simplify the declaration a The `mu` tool uses CloudFormation stacks to manage all resources it creates. Additionally, `mu` will not create any databases or other AWS resources to support itself. It will only create resources (via CloudFormation) necessary to run your microservices. This means at any point you can stop using `mu` and continue to manage the AWS resources that it created via AWS tools such as the CLI or the console. -![Architecture Diagram](docs/ms-architecture-3.png) - -# Installation - -```bash -# Install latest version to /usr/local/bin -curl -s https://raw.githubusercontent.com/stelligent/mu/master/install.sh | sh - -# Install v0.1.0 version to ~/bin -curl -s https://raw.githubusercontent.com/stelligent/mu/master/install.sh | INSTALL_VERSION=0.1.0 INSTALL_DIR=~/bin sh -``` - -# Environments -Environments are defined to become a target for deploying services to. Each environment is a CloudFormation stack consisting of the following resources: - -* **VPC** – To provide the network infrastructure to launch the ECS container instances into. Optionally, you can target an existing VPC. -* **ECS Cluster** – The cluster that the services will be deployed into. -* **Auto Scaling Group** – To manage the ECS container instances that contain the compute resources for running the containers. Auto scaling policies will be defined based on memory entitlements in the cluster. -* **Application Load Balancer** – To provide load balancing for the microservices running in containers. - -![Environment Diagram](docs/ms-architecture-1.png) - -## Configuration -``` ---- - -### 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) - httpProxy: 10.0.0.43:8080 # Host and port to use for HTTP proxy for yum, docker images, and ECS (default: none) - 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 ELB - loadBalancer: - internal: true # Whether to create an internal ELB or not (default: false) - hostedzone: mydomain.com # HostedZone in Route53 to create ELB DNS for. Leave blank to not create DNS (default: none) - name: api # Name to register in hostedzone for ELB DNS. (default: environment name) - certificate: arn:aws:acm:... # The ARN of a certificate in ACM. If defined, will create HTTPS listener in ELB. (default: none) - - ### 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 - ecsSubnetIds: # The list of subnets to use for ECS container instances - - subnet-xxxxx - - subnet-xxxxy - - subnet-xxxxz - elbSubnetIds: # The list of subnets to use for ELBs - - subnet-xxxxx - - subnet-xxxxy - - subnet-xxxxz -``` - -## Commands -``` -# List all environments -> mu env list - -# Show details about a specific environment (ECS container instances, Running services, etc) -> mu env show - -# Upsert an environment -> mu env up - -# Terminate an environment -> mu env terminate -``` - -# Services -Services are first pushed to an ECR repository and then deployed to a specific environment. Each service is a CloudFormation stack consisting of the following resources: - -* **Task Definition** – An ECS task definition referencing the image and tag in the ECR repo. -* **Service** - An ECS service referencing the Task Definition. -* **Target Group** - An ALB target group for the Service to reference and register containers in. -* **Listener Rule** - A rule in the ALB listener from the environment to route specific URLs to the target group. - -![Service Diagram](docs/ms-architecture-2.png) +![Architecture Diagram](https://github.com/stelligent/mu/wiki/img/ms-architecture-3.png) -## Configuration -``` ---- - -### Define the service for this repo -service: - name: my-service # The unique name of the service (default: the name of the directory that mu.yml was in) - desiredCount: 4 # The desired number of tasks to run for the service (default: 2) - dockerfile: ./Dockerfile # The relative path to the Dockerfile to build images (default: ./Dockerfile) - imageRepository: tutum/hello-world # The repository to push images to and deploy services from. Leave unset to have mu manage an ECR repository (default: none) - port: 80 # The port to expose from the container (default: 8080) - healthEndpoint: /health # The endpoint inside the container to determine if the task is healthy (default: /health) - cpu: 20 # The number of CPU units to allocate to each task (default: 10) - memory: 400 # The amount of memory in MiB to allocate to each task (default: 300) - - # The paths to match on in the ALB and route to this service. Leave blank to not create an ALB target group for this service (default: none) - pathPatterns: - - /bananas - - /apples - - # The priority for resolving the pathPatterns from the ALB (between 1 and 99999) - priority: 25 - -``` - -## Commands -``` -# Show details about a specific service (Which versions in which environments, pipeline status) -> mu service show [] - -# Build docker image and push to ECR -> mu service push - -# Deploy the service to an environment -> mu service deploy - -# Undeploy the service from an environment -> mu service undeploy [] -``` - -# Pipelines -A pipeline can be created for each service that consists of the following steps: - -* **Source** - Retrieve source from GitHub for a specific branch. Triggered on each commit. -* **Build Artifact** - Compile the source code via CodeBuild and a `buildspec.yml`. -* **Build Image** - Build the Docker image and push to ECR repository. -* **Acceptance** - Deploy to acceptance environment and run automated tests. -* **Production** - Wait for manual approval, then deploy to production environment. +# Demo +Watch the 90 second demo below to see mu in action! - +![Demo](https://github.com/stelligent/mu/wiki/quickstart/mu-quickstart.gif) +# Get Started! +Install latest version to /usr/local/bin (or for additional options, see [wiki](https://github.com/stelligent/mu/wiki/Installation)): -## Configuration -``` ---- -service: - - name: my-service - # ... service config goes here ... - - # Define the behavior of the pipeline - pipeline: - source: - repo: stelligent/microservice-exemplar # The GitHub repo slug to build (default: none) - branch: mu # The branch to build from (default: master) - build: - image: aws/codebuild/java:openjdk-8 # The image to use for CodeBuild job (default: aws/codebuild/ubuntu-base:latest) - type: linuxContainer - computeType: BUILD_GENERAL1_SMALL # The type of compute instance for builds (default: BUILD_GENERAL1_SMALL) - acceptance: - environment: dev # The environment name to deploy to for testing (default: dev) - production: - environment: production # The environment name to deploy to for production (default: production) -``` - -## Commands -``` -# List the pipelines -> mu pipeline list - -# Upsert the pipeline -> mu pipeline up [-t ] - -# Terminate the pipeline -> mu pipeline terminate [] -``` - -# Common flags +```bash +curl -s https://raw.githubusercontent.com/stelligent/mu/master/install.sh | sh ``` -# Path to mu config -> mu -c path/to/mu.yml ... -# AWS region -> mu -r us-west-2 ... +Assuming your project already has a Dockerfile, you can initialize your mu.yml file with: `mu init`. More details available in the [quickstart](https://github.com/stelligent/mu/wiki/Quickstart). -# or via environment variable -> AWS_REGION=us-west-2 mu ... +# What's next? +Check out the [examples](examples) to see common `mu.yml` configuration use cases: -# AWS profile -> mu -p my-profile ... +* **[Basic](examples/basic)** - Simple website with continuous delivery pipeline deploying to dev and prod environments +* **[Test Automation](examples/pipeline-newman)** - Example of automating end-to-end testing via [Newman](https://github.com/postmanlabs/newman) +* **[Env Variables](examples/service-env-vars)** - Defining environment variables for the service +* **[HTTPS](examples/elb-https)** - Enable HTTPS on the ALB for an environment +* **[DNS](examples/elb-dns)** - Associate Route53 resource record with ALB for an environment +* **[VPC Target](examples/vpc-target)** - Targeting an existing VPC for an environment +* **[Custom CloudFormation](examples/custom-cloudformation)** - Demonstration of adding custom AWS resources via CloudFormation -# or via environment variable -> AWS_PROFILE=my-profie mu ... +Refer to the wiki for complete details on the configuration of `mu.yml` and the cli usage: -``` +* **[Environments](https://github.com/stelligent/mu/wiki/Environments)** - managing VPCs, ECS clusters, container instances and ALBs +* **[Services](https://github.com/stelligent/mu/wiki/Services)** - managing ECS service configuration +* **[Pipelines](https://github.com/stelligent/mu/wiki/Pipelines)** - managing continuous delivery pipelines +* **[CLI](https://github.com/stelligent/mu/wiki/CLI-Usage)** - details about using the CLI +* **[Custom CloudFormation](https://github.com/stelligent/mu/wiki/Custom-CloudFormation)** - details about customizing the CloudFormation that is generated by mu. # Contributing diff --git a/VERSION b/VERSION index 11808190..699c6c6d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.7 +0.1.8 diff --git a/cli/app.go b/cli/app.go index af862d1f..5c3f6a0e 100644 --- a/cli/app.go +++ b/cli/app.go @@ -17,6 +17,7 @@ func NewApp() *cli.App { app.EnableBashCompletion = true app.Commands = []cli.Command{ + *newInitCommand(context), *newEnvironmentsCommand(context), *newServicesCommand(context), *newPipelinesCommand(context), @@ -34,7 +35,7 @@ func NewApp() *cli.App { } // initialize context - err := context.InitializeContext(c.String("profile"), c.String("region")) + err := context.InitializeContext(c.String("profile"), c.String("region"), c.Bool("dryrun")) if err != nil { return err } @@ -45,8 +46,12 @@ func NewApp() *cli.App { err = context.InitializeConfigFromFile(c.String("config")) if err != nil { - log.Warningf("Unable to load mu config: %v", err) + // ignore errors for init command + if c.Args().First() != "init" { + log.Warningf("Unable to load mu config: %v", err) + } } + return nil } @@ -73,6 +78,10 @@ func NewApp() *cli.App { Name: "verbose, V", Usage: "increase level of log verbosity", }, + cli.BoolFlag{ + Name: "dryrun, d", + Usage: "generate the cloudformation templates without upserting stacks", + }, } return app diff --git a/cli/app_test.go b/cli/app_test.go index 2469a651..f80e74de 100644 --- a/cli/app_test.go +++ b/cli/app_test.go @@ -14,14 +14,16 @@ func TestNewApp(t *testing.T) { 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(5, len(app.Flags), "Flags len should match") + assert.Equal(6, len(app.Flags), "Flags len should match") assert.Equal("config, c", app.Flags[0].GetName(), "Flags name should match") assert.Equal("region, r", app.Flags[1].GetName(), "Flags name should match") assert.Equal("profile, p", app.Flags[2].GetName(), "Flags name should match") assert.Equal("silent, s", app.Flags[3].GetName(), "Flags name should match") assert.Equal("verbose, V", app.Flags[4].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") - assert.Equal("pipeline", app.Commands[2].Name, "Command[2].name should match") + assert.Equal("dryrun, d", app.Flags[5].GetName(), "Flags name should match") + assert.Equal(4, len(app.Commands), "Commands len should match") + assert.Equal("init", app.Commands[0].Name, "Command[0].name should match") + assert.Equal("environment", app.Commands[1].Name, "Command[1].name should match") + assert.Equal("service", app.Commands[2].Name, "Command[2].name should match") + assert.Equal("pipeline", app.Commands[3].Name, "Command[3].name should match") } diff --git a/cli/environments.go b/cli/environments.go index eab15953..539117c6 100644 --- a/cli/environments.go +++ b/cli/environments.go @@ -62,8 +62,15 @@ func newEnvironmentsListCommand(ctx *common.Context) *cli.Command { func newEnvironmentsShowCommand(ctx *common.Context) *cli.Command { cmd := &cli.Command{ - Name: "show", - Usage: "show environment details", + Name: "show", + Usage: "show environment details", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format, f", + Usage: "output format, either 'json' or 'cli' (default: cli)", + Value: "cli", + }, + }, ArgsUsage: "", Action: func(c *cli.Context) error { environmentName := c.Args().First() @@ -71,7 +78,7 @@ func newEnvironmentsShowCommand(ctx *common.Context) *cli.Command { cli.ShowCommandHelp(c, "show") return errors.New("environment must be provided") } - workflow := workflows.NewEnvironmentViewer(ctx, environmentName, os.Stdout) + workflow := workflows.NewEnvironmentViewer(ctx, c.String("format"), environmentName, os.Stdout) return workflow() }, } diff --git a/cli/environments_test.go b/cli/environments_test.go index d433301c..094a83ab 100644 --- a/cli/environments_test.go +++ b/cli/environments_test.go @@ -73,6 +73,8 @@ func TestNewEnvironmentsShowCommand(t *testing.T) { assert.NotNil(command) assert.Equal("show", command.Name, "Name should match") assert.Equal("", command.ArgsUsage, "ArgsUsage should match") + assert.Equal(1, len(command.Flags), "Flag len should match") + assert.Equal("format, f", command.Flags[0].GetName(), "Flag should match") assert.NotNil(command.Action) } func TestNewEnvironmentsTerminateCommand(t *testing.T) { diff --git a/cli/init.go b/cli/init.go new file mode 100644 index 00000000..4bff8f76 --- /dev/null +++ b/cli/init.go @@ -0,0 +1,36 @@ +package cli + +import ( + "github.com/stelligent/mu/common" + "github.com/stelligent/mu/workflows" + "github.com/urfave/cli" +) + +func newInitCommand(ctx *common.Context) *cli.Command { + + cmd := &cli.Command{ + Name: "init", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "env, e", + Usage: "Initialize environments as well as service (default: false)", + }, + cli.IntFlag{ + Name: "port, P", + Usage: "Port the application listens on", + Value: 8080, + }, + cli.BoolFlag{ + Name: "force, f", + Usage: "Force overwrite of existing mu.yml (default: false)", + }, + }, + Usage: "initialize mu.yml file", + Action: func(c *cli.Context) error { + workflow := workflows.NewConfigInitializer(ctx, c.Bool("env"), c.Int("port"), c.Bool("force")) + return workflow() + }, + } + + return cmd +} diff --git a/cli/init_test.go b/cli/init_test.go new file mode 100644 index 00000000..56a961d9 --- /dev/null +++ b/cli/init_test.go @@ -0,0 +1,21 @@ +package cli + +import ( + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewInitCommand(t *testing.T) { + assert := assert.New(t) + + ctx := common.NewContext() + + command := newInitCommand(ctx) + + assert.NotNil(command) + assert.Equal("init", command.Name, "Name should match") + assert.Equal("initialize mu.yml file", command.Usage, "Usage should match") + assert.Equal(3, len(command.Flags), "Flags len should match") + +} diff --git a/cli/pipelines.go b/cli/pipelines.go index a46fecde..4702ccf2 100644 --- a/cli/pipelines.go +++ b/cli/pipelines.go @@ -74,6 +74,7 @@ func newPipelinesUpsertCommand(ctx *common.Context) *cli.Command { byteToken, err := terminal.ReadPassword(int(syscall.Stdin)) if err == nil { token = strings.TrimSpace(string(byteToken)) + fmt.Println("") } } diff --git a/common/context.go b/common/context.go index 9ccfc7f8..517fe967 100644 --- a/common/context.go +++ b/common/context.go @@ -37,9 +37,31 @@ func NewContext() *Context { // InitializeConfigFromFile loads config from file func (ctx *Context) InitializeConfigFromFile(muFile string) error { absMuFile, err := filepath.Abs(muFile) - if err != nil { - return err + + // set the basedir + ctx.Config.Basedir = path.Dir(absMuFile) + log.Debugf("Setting basedir=%s", ctx.Config.Basedir) + + ctx.Config.Repo.Name = path.Base(ctx.Config.Basedir) + log.Debugf("Setting repo name=%s", ctx.Config.Repo.Name) + + ctx.Config.Repo.Revision = time.Now().Format("20060102150405") + + gitRevision, err := findGitRevision(ctx.Config.Basedir) + if err == nil { + ctx.Config.Repo.Revision = gitRevision + } else { + log.Warningf("Unable to determine git revision: %s", err.Error()) } + log.Debugf("Setting repo revision=%s", ctx.Config.Repo.Revision) + + gitSlug, err := findGitSlug() + if err == nil { + ctx.Config.Repo.Slug = gitSlug + } else { + log.Warningf("Unable to determine git slug: %s", err.Error()) + } + log.Debugf("Setting repo slug=%s", ctx.Config.Repo.Slug) // load yaml config yamlFile, err := os.Open(absMuFile) @@ -50,15 +72,6 @@ func (ctx *Context) InitializeConfigFromFile(muFile string) error { yamlFile.Close() }() - // set the basedir - ctx.Config.Basedir = path.Dir(absMuFile) - ctx.Config.Repo.Name = path.Base(ctx.Config.Basedir) - ctx.Config.Repo.Revision = time.Now().Format("20060102150405") - gitRevision, err := findGitRevision(absMuFile) - if err == nil { - ctx.Config.Repo.Revision = gitRevision - } - return ctx.InitializeConfig(bufio.NewReader(yamlFile)) } @@ -70,11 +83,15 @@ func (ctx *Context) InitializeConfig(configReader io.Reader) error { if err != nil { return err } + + // register the stack overrides + registerStackOverrides(ctx.Config.Templates) + return nil } // InitializeContext loads manager objects -func (ctx *Context) InitializeContext(profile string, region string) error { +func (ctx *Context) InitializeContext(profile string, region string, dryrun bool) error { sessOptions := session.Options{SharedConfigState: session.SharedConfigEnable} if region != "" { sessOptions.Config = aws.Config{Region: aws.String(region)} @@ -89,7 +106,7 @@ func (ctx *Context) InitializeContext(profile string, region string) error { } // initialize StackManager - ctx.StackManager, err = newStackManager(sess) + ctx.StackManager, err = newStackManager(sess, dryrun) if err != nil { return err } diff --git a/common/git.go b/common/git.go index 46dde674..fb6951f9 100644 --- a/common/git.go +++ b/common/git.go @@ -2,10 +2,13 @@ package common import ( "errors" + "fmt" + "github.com/tcnksm/go-gitconfig" "gopkg.in/src-d/go-git.v3" "gopkg.in/src-d/go-git.v3/utils/fs" "os" "path" + "regexp" ) func findGitRevision(file string) (string, error) { @@ -25,6 +28,21 @@ func findGitRevision(file string) (string, error) { } return string(hash.String()[:7]), nil } +func findGitSlug() (string, error) { + url, err := gitconfig.OriginURL() + if err != nil { + return "", err + } + + httpRegex := regexp.MustCompile("^http(s?)://.*github.com.*/(.+)/(.+).git$") + sshRegex := regexp.MustCompile("github.com:(.+)/(.+).git$") + if matches := httpRegex.FindStringSubmatch(url); matches != nil { + return fmt.Sprintf("%s/%s", matches[2], matches[3]), nil + } else if matches := sshRegex.FindStringSubmatch(url); matches != nil { + return fmt.Sprintf("%s/%s", matches[1], matches[2]), nil + } + return url, nil +} func findGitDirectory(fromFile string) (string, error) { log.Debugf("Searching for git directory in %s", fromFile) diff --git a/common/map.go b/common/map.go new file mode 100644 index 00000000..be5b67b2 --- /dev/null +++ b/common/map.go @@ -0,0 +1,48 @@ +package common + +import ( + "reflect" +) + +// MapApply recursively applies map values from source to destination +func MapApply(dest, src interface{}) { + srcMap := reflect.ValueOf(src) + if srcMap.Kind() != reflect.Map { + return + } + + destMap := reflect.ValueOf(dest) + if destMap.Kind() != reflect.Map { + return + } + + for _, key := range srcMap.MapKeys() { + srcVal := valueOfInterface(srcMap.MapIndex(key)) + destVal := valueOfInterface(destMap.MapIndex(key)) + + switch srcVal.Kind() { + default: + destMap.SetMapIndex(key, srcVal) + case reflect.Slice, reflect.Array: + if destVal.IsValid() { + newAry := reflect.AppendSlice(destVal, srcVal) + destMap.SetMapIndex(key, newAry) + } else { + destMap.SetMapIndex(key, srcVal) + } + case reflect.Map: + if destVal.IsValid() { + MapApply(destVal.Interface(), srcVal.Interface()) + } else { + destMap.SetMapIndex(key, srcVal) + } + } + } +} + +func valueOfInterface(v reflect.Value) reflect.Value { + if !v.IsValid() { + return v + } + return reflect.ValueOf(v.Interface()) +} diff --git a/common/map_test.go b/common/map_test.go new file mode 100644 index 00000000..39e79a5e --- /dev/null +++ b/common/map_test.go @@ -0,0 +1,77 @@ +package common + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMapApply_SimpleMap(t *testing.T) { + assert := assert.New(t) + + origMap := make(map[interface{}]interface{}) + origMap["StringValue"] = "hello world" + origMap["IntValue"] = 42 + origMap["BoolValue"] = true + + newMap := make(map[interface{}]interface{}) + newMap["StringValue"] = "replaced value" + newMap["NewValue"] = "new value" + + MapApply(origMap, newMap) + + assert.Equal(42, origMap["IntValue"]) + assert.Equal(true, origMap["BoolValue"]) + assert.Equal("replaced value", origMap["StringValue"]) + assert.Equal("new value", origMap["NewValue"]) +} + +func TestMapApply_DeepMap(t *testing.T) { + assert := assert.New(t) + + origMap := make(map[interface{}]interface{}) + origMap["MapValue"] = make(map[string]string) + origMap["MapValue"].(map[string]string)["NestedVal"] = "nested value" + origMap["MapValue"].(map[string]string)["NestedVal2"] = "another nested value" + + newMap := make(map[interface{}]interface{}) + newMap["MapValue"] = make(map[string]string) + newMap["MapValue"].(map[string]string)["NestedVal"] = "override nested value" + newMap["MapValue"].(map[string]string)["NewNestedVal"] = "new nested value" + newMap["NewValue"] = make(map[string]string) + newMap["NewValue"].(map[string]string)["Val"] = "new map" + + MapApply(origMap, newMap) + + nestedMap := origMap["MapValue"].(map[string]string) + nestedMap2 := origMap["NewValue"].(map[string]string) + assert.Equal(3, len(nestedMap)) + assert.Equal("override nested value", nestedMap["NestedVal"]) + assert.Equal("another nested value", nestedMap["NestedVal2"]) + assert.Equal("new nested value", nestedMap["NewNestedVal"]) + assert.Equal(1, len(nestedMap2)) + assert.Equal("new map", nestedMap2["Val"]) +} + +func TestMapApply_MergeList(t *testing.T) { + assert := assert.New(t) + + origMap := make(map[interface{}]interface{}) + origMap["ListValue"] = []string{"one", "two"} + + newMap := make(map[interface{}]interface{}) + newMap["ListValue"] = []string{"three", "four"} + newMap["AnotherListValue"] = []string{"foo", "bar"} + + MapApply(origMap, newMap) + + aryMap := origMap["ListValue"].([]string) + aryMap2 := origMap["AnotherListValue"].([]string) + assert.Equal(4, len(aryMap)) + assert.Equal("one", aryMap[0]) + assert.Equal("two", aryMap[1]) + assert.Equal("three", aryMap[2]) + assert.Equal("four", aryMap[3]) + assert.Equal(2, len(aryMap2)) + assert.Equal("foo", aryMap2[0]) + assert.Equal("bar", aryMap2[1]) +} diff --git a/common/stack.go b/common/stack.go index dfcc6d05..dc668286 100644 --- a/common/stack.go +++ b/common/stack.go @@ -2,6 +2,7 @@ package common import ( "bytes" + "encoding/json" "errors" "fmt" "github.com/aws/aws-sdk-go/aws" @@ -12,8 +13,11 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2/ec2iface" "io" + "io/ioutil" + "os" "strings" "time" + "github.com/briandowns/spinner" ) // CreateStackName will create a name for a stack @@ -21,6 +25,21 @@ func CreateStackName(stackType StackType, names ...string) string { return fmt.Sprintf("mu-%s-%s", stackType, strings.Join(names, "-")) } +// GetStackOverrides will get the overrides from the config +func GetStackOverrides(stackName string) interface{} { + if stackOverrides == nil { + return nil + } + + return stackOverrides[stackName] +} + +var stackOverrides map[string]interface{} + +func registerStackOverrides(overrides map[string]interface{}) { + stackOverrides = overrides +} + // StackWaiter for waiting on stack status to be final type StackWaiter interface { AwaitFinalStatus(stackName string) *Stack @@ -62,12 +81,13 @@ type StackManager interface { } type cloudformationStackManager struct { + dryrun bool cfnAPI cloudformationiface.CloudFormationAPI ec2API ec2iface.EC2API } // NewStackManager creates a new StackManager backed by cloudformation -func newStackManager(sess *session.Session) (StackManager, error) { +func newStackManager(sess *session.Session, dryrun bool) (StackManager, error) { log.Debug("Connecting to CloudFormation service") cfnAPI := cloudformation.New(sess) @@ -75,6 +95,7 @@ func newStackManager(sess *session.Session) (StackManager, error) { ec2API := ec2.New(sess) return &cloudformationStackManager{ + dryrun: dryrun, cfnAPI: cfnAPI, ec2API: ec2API, }, nil @@ -117,6 +138,16 @@ func buildStackTags(tags map[string]string) []*cloudformation.Tag { func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, templateBodyReader io.Reader, parameters map[string]string, tags map[string]string) error { stack := cfnMgr.AwaitFinalStatus(stackName) + // delete stack if in rollback status + if stack != nil && stack.Status == cloudformation.StackStatusRollbackComplete { + log.Warningf(" Stack '%s' was in '%s' status, deleting...", stackName, stack.Status) + err := cfnMgr.DeleteStack(stackName) + if err != nil { + return err + } + stack = cfnMgr.AwaitFinalStatus(stackName) + } + // load the template templateBodyBytes := new(bytes.Buffer) templateBodyBytes.ReadFrom(templateBodyReader) @@ -128,6 +159,9 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template // stack tags stackTags := buildStackTags(tags) + // directory to write cfn to + cfnDirectory := fmt.Sprintf("%s/mu-cloudformation", os.TempDir()) + cfnAPI := cfnMgr.cfnAPI if stack == nil || stack.Status == "" { @@ -143,6 +177,13 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template TemplateBody: templateBody, Tags: stackTags, } + + if cfnMgr.dryrun { + writeTemplateAndConfig(cfnDirectory, stackName, templateBodyBytes, parameters) + log.Infof(" DRYRUN: Skipping create of stack named '%s'. Template and parameters written to '%s'", stackName, cfnDirectory) + return nil + } + _, err := cfnAPI.CreateStack(params) log.Debug(" Create stack complete err=%s", err) if err != nil { @@ -172,6 +213,12 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template Tags: stackTags, } + if cfnMgr.dryrun { + writeTemplateAndConfig(cfnDirectory, stackName, templateBodyBytes, parameters) + log.Infof(" DRYRUN: Skipping update of stack named '%s'. Template and parameters written to '%s'", stackName, cfnDirectory) + return nil + } + _, err := cfnAPI.UpdateStack(params) log.Debug(" Update stack complete err=%s", err) if err != nil { @@ -195,6 +242,12 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) *St params := &cloudformation.DescribeStacksInput{ StackName: aws.String(stackName), } + + // initialize Spinner + spinner := spinner.New(spinner.CharSets[9], 100*time.Millisecond) + spinner.Start() + defer spinner.Stop() + resp, err := cfnAPI.DescribeStacks(params) if err == nil && resp != nil && len(resp.Stacks) == 1 { @@ -370,8 +423,32 @@ func (cfnMgr *cloudformationStackManager) DeleteStack(stackName string) error { params := &cloudformation.DeleteStackInput{StackName: aws.String(stackName)} + if cfnMgr.dryrun { + log.Infof(" DRYRUN: Skipping delete of stack named '%s'", stackName) + return nil + } + log.Debugf("Deleting stack named '%s'", stackName) _, err := cfnAPI.DeleteStack(params) return err } + +func writeTemplateAndConfig(cfnDirectory string, stackName string, templateBodyBytes *bytes.Buffer, parameters map[string]string) error { + os.MkdirAll(cfnDirectory, 0700) + templateFile := fmt.Sprintf("%s/template-%s.yml", cfnDirectory, stackName) + err := ioutil.WriteFile(templateFile, templateBodyBytes.Bytes(), 0600) + if err != nil { + return err + } + + configMap := make(map[string]map[string]string) + configMap["Parameters"] = parameters + configBody, err := json.MarshalIndent(configMap, "", " ") + if err != nil { + return err + } + configFile := fmt.Sprintf("%s/config-%s.json", cfnDirectory, stackName) + err = ioutil.WriteFile(configFile, configBody, 0600) + return nil +} diff --git a/common/types.go b/common/types.go index 3376ea1c..b6be15aa 100644 --- a/common/types.go +++ b/common/types.go @@ -18,76 +18,83 @@ type Context struct { // Config defines the structure of the yml file for the mu config type Config struct { - Environments []Environment - Service Service - Basedir string + Environments []Environment `yaml:"environments,omitempty"` + Service Service `yaml:"service,omitempty"` + Basedir string `yaml:"-"` Repo struct { Name string + Slug string Revision string - } + } `yaml:"-"` + Templates map[string]interface{} `yaml:"templates,omitempty"` } // Environment defines the structure of the yml file for an environment type Environment struct { - Name string + Name string `yaml:"name,omitempty"` Loadbalancer struct { - HostedZone string `yaml:"hostedzone"` - Name string `yaml:"name"` - Certificate string `yaml:"certificate"` - Internal bool `yaml:"internal"` - } + HostedZone string `yaml:"hostedzone,omitempty"` + Name string `yaml:"name,omitempty"` + Certificate string `yaml:"certificate,omitempty"` + Internal bool `yaml:"internal,omitempty"` + } `yaml:"loadbalancer,omitempty"` 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"` - HTTPProxy string `yaml:"httpProxy"` - } + InstanceType string `yaml:"instanceType,omitempty"` + ImageID string `yaml:"imageId,omitempty"` + InstanceTenancy string `yaml:"instanceTenancy,omitempty"` + DesiredCapacity int `yaml:"desiredCapacity,omitempty"` + MaxSize int `yaml:"maxSize,omitempty"` + KeyName string `yaml:"keyName,omitempty"` + SSHAllow string `yaml:"sshAllow,omitempty"` + ScaleOutThreshold int `yaml:"scaleOutThreshold,omitempty"` + ScaleInThreshold int `yaml:"scaleInThreshold,omitempty"` + HTTPProxy string `yaml:"httpProxy,omitempty"` + } `yaml:"cluster,omitempty"` VpcTarget struct { - VpcID string `yaml:"vpcId"` - EcsSubnetIds []string `yaml:"ecsSubnetIds"` - ElbSubnetIds []string `yaml:"elbSubnetIds"` + VpcID string `yaml:"vpcId,omitempty"` + EcsSubnetIds []string `yaml:"ecsSubnetIds,omitempty"` + ElbSubnetIds []string `yaml:"elbSubnetIds,omitempty"` } `yaml:"vpcTarget,omitempty"` } // Service defines the structure of the yml file for a service type Service struct { - Name string `yaml:"name"` - DesiredCount int `yaml:"desiredCount"` - Dockerfile string `yaml:"dockerfile"` - ImageRepository string `yaml:"imageRepository"` - Port int `yaml:"port"` - HealthEndpoint string `yaml:"healthEndpoint"` - CPU int `yaml:"cpu"` - Memory int `yaml:"memory"` - PathPatterns []string `yaml:"pathPatterns"` - Priority int `yaml:"priority"` - Pipeline Pipeline + Name string `yaml:"name,omitempty"` + DesiredCount int `yaml:"desiredCount,omitempty"` + Dockerfile string `yaml:"dockerfile,omitempty"` + ImageRepository string `yaml:"imageRepository,omitempty"` + Port int `yaml:"port,omitempty"` + HealthEndpoint string `yaml:"healthEndpoint,omitempty"` + CPU int `yaml:"cpu,omitempty"` + Memory int `yaml:"memory,omitempty"` + Environment map[string]interface{} `yaml:"environment,omitempty"` + PathPatterns []string `yaml:"pathPatterns,omitempty"` + Priority int `yaml:"priority,omitempty"` + Pipeline Pipeline `yaml:"pipeline,omitempty"` } // Pipeline definition type Pipeline struct { Source struct { - Repo string `yaml:"repo"` - Branch string `yaml:"branch"` - } + Repo string `yaml:"repo,omitempty"` + Branch string `yaml:"branch,omitempty"` + } `yaml:"source,omitempty"` Build struct { - Type string `yaml:"type"` - ComputeType string `yaml:"computeType"` - Image string `yaml:"image"` - } + Type string `yaml:"type,omitempty"` + ComputeType string `yaml:"computeType,omitempty"` + Image string `yaml:"image,omitempty"` + } `yaml:"build,omitempty"` Acceptance struct { - Environment string `yaml:"environment"` - } + Environment string `yaml:"environment,omitempty"` + Type string `yaml:"type,omitempty"` + ComputeType string `yaml:"computeType,omitempty"` + Image string `yaml:"image,omitempty"` + } `yaml:"acceptance,omitempty"` Production struct { - Environment string `yaml:"environment"` - } - MuBaseurl string `yaml:"muBaseurl"` - MuVersion string `yaml:"muVersion"` + Environment string `yaml:"environment,omitempty"` + } `yaml:"production,omitempty"` + MuBaseurl string `yaml:"muBaseurl,omitempty"` + MuVersion string `yaml:"muVersion,omitempty"` } // Stack summary diff --git a/docs/DESIGN.md b/docs/DESIGN.md deleted file mode 100644 index b4b5a53c..00000000 --- a/docs/DESIGN.md +++ /dev/null @@ -1,115 +0,0 @@ -# Problem Statement -Amazon ECS (EC2 Container Service) provides an excellent platform for deploying microservices as containers. The challenge however is that there is a significant learning curve for microservice developers to deploy their applications in an efficient manner. Specifically, they must learn to use CloudFormation to orchestrate the management of ECS, ECR, EC2, ELB, VPC, and IAM resources. Additionally, tools like CodeBuild and CodePipeline must be mastered to create a continuous delivery pipeline for their microservices. - -To address these challenges, we will create a tool to simplify the declaration and administration of the AWS resources necessary to support microservices. Similar to how the [Serverless Framework](https://serverless.com/) improved the developer experience of Lambda and API Gateway, this tool will make it easier for developers to use ECS as a microservices platform. - -For more details on the intended architecture, see [Microservices Platform with ECS](https://stelligent.com/2016/10/06/microservices-platform-with-ecs/). - -#Assumptions -1. **Polyglot** - There will be no prescribed language or framework for developing the microservices. The only requirement will be that the service will be run inside a container and exposed via an HTTP endpoint. -2. **Cloud Provider** - At this point, the tool will assume AWS for the cloud provider and will not be written in a cloud agnostic manner. However, this does not preclude refactoring to add support for other providers at a later time. -3. **Declarative** - All resource administration will be handled in a declarative vs. imperative manner. A file will be used to declared the desired state of the resources and the tool will simply assert the actual state matches the desired state. The tool will accomplish this by generating CloudFormation templates. -4. **Stateless** - The tool will not maintain its own state. Rather, it will rely on the CloudFormation stacks to determine the state of the platform. -5. **Secure** - All security will be managed by AWS IAM credentials. No additional authentication or authorization mechanisms will be introduced. -6. **Language** - TBD. Need to determine the language to use for developing the tool. Options in order of preference include Go, Node.js, Python. - - -#Capabilities -## Resource Declaration -A YAML file will be used to declare microservice resources. There are two types of resources defined in the YAML file, environments and services. - -Environments contain an ECS cluster, ECS container instances (with ASG), and an ALB. Additionally, environments contain (or reference) a VPC. A sample environment resource may look like: - - -``` -- -environments: - dev: - loadbalancer: - hostname: api-dev.example.com - cluster: - desiredCapacity: 1 - maxSize: 1 - production: - loadbalancer: - hostname: api.example.com - cluster: - desiredCapacity: 2 - maxSize: 5 -``` - - -Services contain an ECS service, ECS task, ALB target group and ECR. Additionally service can contain an optional CodeBuild and CodePipeline resource for a CD pipeline. -``` -- -service: - desiredCount: 2 - pipeline: - devEnvironment: dev - prodEnvironment: production -``` - -## CLI -The majority of code for this tool will be to provide a CLI to manage CloudFormation stacks based on the resources declared in the YAML file. The list of available commands are: - -``` -# List all environments -> mu env list - -# Show details about a specific environment (ECS container instances, Running services, etc) -> mu env show - -# Upsert an environment -> mu env up - -# Terminate an environment -> mu env terminate - -# Show details about a specific service (Which versions in which environments, pipeline status) -> mu service show [-s ] - -# Deploy the service to an environment -> mu service deploy [-s ] - -# Set an environment variable(s) for a service -> mu service setenv [-s ] key=value[,...] - -# Undeploy the service from an environment -> mu service undeploy [-s ] - -# List the pipelines -> mu pipeline list - -# Show the pipeline details for a specific service -> mu pipeline show - -# Upsert the pipeline -> mu pipeline up [-s ] [-u ] [-t ] - -# Terminate the pipeline -> mu pipeline terminate [-s ] -``` - - -## Plugin -A plugin framework should be available for developer to contribute extensions for specific languages. For example, a Java developer using Spring Boot should be able to use a Spring Boot plugin to define the Eureka, ConfigServer and Zuul router for their environment as follows: - -``` -environments: - dev: - loadbalancer: - hostname: api-dev.example.com - springboot: - eureka: - desiredCapacity: 1 - configServer: - sourceUrl: https://github.com/example/configrepo -``` - - -## UI -A web based user interface will be created to provide visibility into the resources in the platform. The UI will allow a view into the list of pipelines, services, and environments defined in a given AWS account. It will only provide read only access to the resources and will not provide ability to change the resources. - -The UI will consist of an Angular2 application hosted in S3 with APIs in Lambda/API Gateway. The UI will be secured via AWS credentials and Cognito. - - diff --git a/docs/ms-architecture-1.png b/docs/ms-architecture-1.png deleted file mode 100644 index b256c005..00000000 Binary files a/docs/ms-architecture-1.png and /dev/null differ diff --git a/docs/ms-architecture-2.png b/docs/ms-architecture-2.png deleted file mode 100644 index eb0509ca..00000000 Binary files a/docs/ms-architecture-2.png and /dev/null differ diff --git a/docs/ms-architecture-3.png b/docs/ms-architecture-3.png deleted file mode 100644 index eb3ba819..00000000 Binary files a/docs/ms-architecture-3.png and /dev/null differ diff --git a/docs/ms-pipeline-1.png b/docs/ms-pipeline-1.png deleted file mode 100644 index 512ed391..00000000 Binary files a/docs/ms-pipeline-1.png and /dev/null differ diff --git a/examples/basic/Dockerfile b/examples/basic/Dockerfile new file mode 100644 index 00000000..86fc64f4 --- /dev/null +++ b/examples/basic/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx +COPY index.html /usr/share/nginx/html/aftp/index.html diff --git a/examples/basic/buildspec.yml b/examples/basic/buildspec.yml new file mode 100644 index 00000000..b66c67dc --- /dev/null +++ b/examples/basic/buildspec.yml @@ -0,0 +1,12 @@ +version: 0.1 + +phases: + build: + commands: + - echo "replace me with real build commands..." + +artifacts: + files: + - index.html + - mu.yml + - Dockerfile diff --git a/examples/basic/index.html b/examples/basic/index.html new file mode 100644 index 00000000..a72f33b0 --- /dev/null +++ b/examples/basic/index.html @@ -0,0 +1 @@ +Automation for the People \ No newline at end of file diff --git a/examples/basic/mu.yml b/examples/basic/mu.yml new file mode 100644 index 00000000..79c0e7f0 --- /dev/null +++ b/examples/basic/mu.yml @@ -0,0 +1,17 @@ +--- + +environments: + - name: dev + cluster: + maxSize: 2 + - name: production + cluster: + maxSize: 5 + +service: + port: 80 + pathPatterns: + - /* + pipeline: + source: + repo: cplee/aftp-mu diff --git a/examples/custom-cloudformation/mu.yml b/examples/custom-cloudformation/mu.yml new file mode 100644 index 00000000..5cd8807d --- /dev/null +++ b/examples/custom-cloudformation/mu.yml @@ -0,0 +1,26 @@ +--- + +environments: + - name: example + +templates: + mu-cluster-example: + Resources: + + # Define a new security group + ExtraSG: + Type: AWS::EC2::SecurityGroup + Properties: + VpcId: + Fn::ImportValue: !Sub ${VpcId} + GroupDescription: Example additional ECS Host Security Group + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: '8080' + ToPort: '8080' + CidrIp: !Ref SshAllow + + ## Update the existing launch config to reference new SG + ContainerInstances: + Properties: + SecurityGroups: [ !Ref ExtraSG ] \ No newline at end of file diff --git a/examples/elb-dns/mu.yml b/examples/elb-dns/mu.yml new file mode 100644 index 00000000..312184c8 --- /dev/null +++ b/examples/elb-dns/mu.yml @@ -0,0 +1,15 @@ +--- + +environments: + + # This env with have an ELB with http://example-default.casey.elasticoperations.com + - name: example-default + loadbalancer: + hostedzone: casey.elasticoperations.com + + # This env with have an ELB with http://foo.casey.elasticoperations.com + - name: example-override + loadbalancer: + hostedzone: casey.elasticoperations.com + name: foo + diff --git a/examples/elb-https/mu.yml b/examples/elb-https/mu.yml new file mode 100644 index 00000000..279958ee --- /dev/null +++ b/examples/elb-https/mu.yml @@ -0,0 +1,7 @@ +--- + +environments: + - name: env-with-elb-https + loadbalancer: + certificate: "arn:aws:acm:us-west-2:312340000000:certificate/973c1a2f-e1c6-4f65-8d43-0000000000" + diff --git a/examples/mu.yml b/examples/mu.yml deleted file mode 100644 index 383aeeec..00000000 --- a/examples/mu.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- - -environments: - - name: sample-dev - loadbalancer: - hostedzone: casey.elasticoperations.com - certificate: "arn:aws:acm:us-west-2:324320755747:certificate/973c1a2f-e1c6-4f65-8d43-d3ff65e6accc" - cluster: - desiredCapacity: 1 - maxSize: 2 diff --git a/examples/pipeline-newman/buildspec-test.yml b/examples/pipeline-newman/buildspec-test.yml new file mode 100644 index 00000000..a5108f17 --- /dev/null +++ b/examples/pipeline-newman/buildspec-test.yml @@ -0,0 +1,15 @@ +version: 0.1 + +## Use newman to run a postman collection. The env.json file is created magically by the pipeline with BASE_URL defined + +phases: + install: + commands: + - npm install newman --global + build: + commands: + - newman run -e env.json -r html,json,junit,cli collection.json + +artifacts: + files: + - newman/* diff --git a/examples/pipeline-newman/buildspec.yml b/examples/pipeline-newman/buildspec.yml new file mode 100644 index 00000000..e3369f16 --- /dev/null +++ b/examples/pipeline-newman/buildspec.yml @@ -0,0 +1,14 @@ +version: 0.1 + + +phases: + build: + commands: + - gradle build + + +artifacts: + files: + - build/libs/* + - Dockerfile + - mu.yml diff --git a/examples/pipeline-newman/collection.json b/examples/pipeline-newman/collection.json new file mode 100644 index 00000000..c96ad6a8 --- /dev/null +++ b/examples/pipeline-newman/collection.json @@ -0,0 +1,80 @@ +{ + "variables": [], + "info": { + "name": "Bananas", + "_postman_id": "706d8db3-8945-bfe4-abab-d1563d76139f", + "description": "", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ + { + "name": "New Banana", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "", + "", + "", + "var jsonData = JSON.parse(responseBody);", + "tests[\"Has picked date\"] = jsonData.pickedAt;", + "tests[\"Not peeled\"] = !jsonData.peeled;" + ] + } + } + ], + "request": { + "url": "{{BASE_URL}}/bananas ", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"pickedAt\": \"2016-01-25T21:34:55\",\n\t\"peeled\": false\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "All Bananas", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "tests[\"Status code is 200\"] = responseCode.code === 200;" + ] + } + } + ], + "request": { + "url": "{{BASE_URL}}/bananas ", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"pickedAt\": \"2016-01-25T21:34:55\",\n\t\"peeled\": false\n}" + }, + "description": "" + }, + "response": [] + } + ] +} diff --git a/examples/pipeline-newman/mu.yml b/examples/pipeline-newman/mu.yml new file mode 100644 index 00000000..50745230 --- /dev/null +++ b/examples/pipeline-newman/mu.yml @@ -0,0 +1,15 @@ +--- + +service: + name: banana-service + healthEndpoint: /health + pathPatterns: + - /bananas + pipeline: + source: + repo: stelligent/microservice-exemplar + branch: mu + build: + image: aws/codebuild/java:openjdk-8 + acceptance: + image: aws/codebuild/eb-nodejs-4.4.6-amazonlinux-64:2.1.3 diff --git a/examples/service-env-vars/mu.yml b/examples/service-env-vars/mu.yml new file mode 100644 index 00000000..e288fc3b --- /dev/null +++ b/examples/service-env-vars/mu.yml @@ -0,0 +1,9 @@ +--- + +service: + name: service-with-env-vars + environment: + DB_TYPE: mysql # Define an environment variable for all environments by have a string for value + DB_URL: # Define an different value per environment by have a map for value + dev: 10.0.0.1:3306 + prod: 10.0.100.5:3306 diff --git a/examples/vpc-target/mu.yml b/examples/vpc-target/mu.yml new file mode 100644 index 00000000..bfb0e3ba --- /dev/null +++ b/examples/vpc-target/mu.yml @@ -0,0 +1,14 @@ +--- + +environments: + - name: example-vpc-target + vpcTarget: + vpcId: vpc-xxxxx # The id of the VPC to launch ECS container instances into + ecsSubnetIds: # The list of subnets to use for ECS container instances + - subnet-xxxxx + - subnet-xxxxy + - subnet-xxxxz + elbSubnetIds: # The list of subnets to use for ELBs + - subnet-xxxxx + - subnet-xxxxy + - subnet-xxxxz diff --git a/glide.lock b/glide.lock index ef2e0e06..bfd183ca 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,8 @@ -hash: fdf6aec9a3747b3ba7145af44d2a840e6bb2cce2ece6a8554b7364c436d17e0e -updated: 2017-01-31T11:14:43.508254901-08:00 +hash: f2f00c42cf0cfa225926e9ee5f96093eb52ca0f57075a31126315bf793e0c630 +updated: 2017-03-08T09:21:05.63735419-08:00 imports: - name: github.com/aws/aws-sdk-go - version: 3f8f870ec9939e32b3372abf74d24e468bcd285d + version: 00fb2125993965df739fa3398b03bef3eb2e198f subpackages: - aws - aws/awserr @@ -31,20 +31,26 @@ imports: - private/waiter - service/cloudformation - service/cloudformation/cloudformationiface + - service/codepipeline + - service/codepipeline/codepipelineiface - service/ec2 - service/ec2/ec2iface - service/ecr - service/ecr/ecriface - service/ecs - service/ecs/ecsiface + - service/elbv2 + - service/elbv2/elbv2iface - service/sts +- name: github.com/briandowns/spinner + version: 27dbbd67ecbfe572bbb1a79c2c6d62ad0cadde3c - name: github.com/docker/distribution version: fb0bebc4b64e3881cc52a2478d749845ed76d2a8 subpackages: - digestset - reference - name: github.com/docker/docker - version: 2527cfcc49744f56a8d31c420a3ca4657f9fec2e + version: 092cba3727bb9b4a2f0e922cd6c0f93ea270e363 subpackages: - api/types - api/types/blkiodev @@ -112,6 +118,8 @@ imports: - diffmatchpatch - name: github.com/Sirupsen/logrus version: 61e43dc76f7ee59a82bdf3d71033dc12bea4c77d +- name: github.com/tcnksm/go-gitconfig + version: d154598bacbf4501c095a309753c5d4af66caa81 - name: github.com/urfave/cli version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6 - name: golang.org/x/crypto diff --git a/glide.yaml b/glide.yaml index 5f4e90c4..4c4b3568 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,7 +1,7 @@ package: github.com/stelligent/mu import: - package: github.com/aws/aws-sdk-go - version: ~1.6.18 + version: ~1.6.19 subpackages: - aws - aws/awserr @@ -35,6 +35,9 @@ import: - ssh - package: gopkg.in/src-d/go-git.v3 version: ~3.2.0 +- package: github.com/tcnksm/go-gitconfig + version: ~0.1.2 +- package: github.com/briandowns/spinner testImport: - package: github.com/stretchr/testify version: ~1.1.4 diff --git a/scripts/run-task.sh b/scripts/run-task.sh new file mode 100755 index 00000000..5ad22488 --- /dev/null +++ b/scripts/run-task.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +if [ ${#} -lt 2 ]; then + echo "Usage: $0 [mu-service-stack-name] [command]" + exit 1 +fi + +MU_SERVICE_STACK=$1 +CMD= +for i in "${@:2}"; do + if [ ! -z ${CMD} ]; then + CMD="$CMD," + fi + CMD="$CMD\"$i\"" +done + +ECS_SERVICE_NAME=$(aws cloudformation describe-stacks --stack-name ${MU_SERVICE_STACK} --query "Stacks[0].Parameters[?ParameterKey=='ServiceName'].ParameterValue" --output text) +ECS_TASK_DEFINITION=$(aws cloudformation describe-stacks --stack-name ${MU_SERVICE_STACK} --query "Stacks[0].Outputs[?OutputKey=='MicroserviceTaskDefinition'].OutputValue" --output text) +ECS_CLUSTER=$(aws cloudformation describe-stacks --stack-name ${MU_SERVICE_STACK} --query "Stacks[0].Outputs[?OutputKey=='EcsCluster'].OutputValue" --output text) + + +OVERRIDES=" +{ + \"containerOverrides\": [ + { + \"name\": \"${ECS_SERVICE_NAME}\", + \"command\": [${CMD}] + } + ] +} +" + +ECS_TASK_ARN=$(aws ecs run-task --cluster ${ECS_CLUSTER} --task-definition ${ECS_TASK_DEFINITION} --query "tasks[0].taskArn" --output text --overrides "${OVERRIDES}") +aws ecs wait tasks-running --cluster ${ECS_CLUSTER} --tasks ${ECS_TASK_ARN} +aws ecs wait tasks-stopped --cluster ${ECS_CLUSTER} --tasks ${ECS_TASK_ARN} +EXIT_CODE=$(aws ecs describe-tasks --cluster ${ECS_CLUSTER} --tasks ${ECS_TASK_ARN} --query "tasks[0].containers[0].exitCode" --output text) +REASON=$(aws ecs describe-tasks --cluster ${ECS_CLUSTER} --tasks ${ECS_TASK_ARN} --query "tasks[0].containers[0].reason" --output text) + +if [ "$EXIT_CODE" != "0" ]; then + echo "Error: ${REASON} (exit=${EXIT_CODE})" + exit 1 +fi + diff --git a/templates/assets.go b/templates/assets.go index 1d610de4..bf3b54be 100644 --- a/templates/assets.go +++ b/templates/assets.go @@ -1,6 +1,7 @@ // Code generated by go-bindata. // sources: // assets/bucket.yml +// assets/buildspec.yml // assets/cluster.yml // assets/pipeline.yml // assets/repo.yml @@ -74,7 +75,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _assetsBucketYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x64\x8f\x4d\x4b\xc3\x40\x10\x86\xef\xfb\x2b\x46\x28\xf4\xb4\x10\xed\xa9\x7b\xab\xa8\xe0\x41\x0d\xd9\xda\x9e\xb7\x71\x52\x17\xb3\x1f\x4c\x66\xa1\x52\xfa\xdf\x25\x1b\x22\x5b\xcd\x6d\xf2\xce\x3c\xfb\xbc\x52\x4a\xb1\xd9\xeb\x2d\xba\xd8\x1b\xc6\xa7\x40\xce\xf0\x0e\x69\xb0\xc1\x2b\x58\xde\x55\xb7\x95\xac\xd6\xb2\x5a\x2f\xc5\x03\x0e\x2d\xd9\xc8\x39\x79\x79\x87\x43\x6a\xbf\x90\xc1\x7a\xd0\x2b\x51\x1b\x32\x0e\x19\x69\x50\x02\xe0\x3e\x47\x35\x61\x67\x4f\xe3\x0c\xb0\xfd\x8e\xa8\x40\x33\x59\x7f\xcc\x3f\xae\x68\xd3\x26\x70\x80\x34\x20\x74\x81\x80\x3f\x71\x7e\xc0\x1b\x87\xa2\xc1\x21\x24\x6a\xb1\xc0\x97\xe0\xcd\x5e\x2b\xa5\x57\x4a\x4d\x51\x4e\x6a\x0a\x11\x89\xed\x74\x33\x7e\x53\xf8\x6a\x1c\x2a\xb8\xd1\xe9\x00\x8b\x73\x69\x7a\x91\x8b\x73\x06\x35\x78\xb4\xc1\xff\x8e\x9b\xb6\x0d\xc9\xf3\xf3\xc7\x45\xbc\x25\x8e\x89\xff\x49\x5c\x95\x19\xf9\x10\xba\x5c\x21\xda\x88\xbd\xf5\x73\x97\xbc\xbc\x33\x7d\x1a\x05\x1a\xec\xa0\xd0\x7d\x3c\xc5\x40\x3c\xab\x16\x92\x2e\xc9\xe9\x5a\xfe\xd1\x15\x3f\x01\x00\x00\xff\xff\x25\xb1\xdb\xa7\xbd\x01\x00\x00") +var _assetsBucketYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x64\x8f\xcf\x4e\xc3\x30\x0c\x87\xef\x79\x0a\x23\x4d\xda\x29\x52\x61\xa7\xe5\x56\x04\x48\x1c\x80\xaa\x19\xdb\x39\x2b\xee\x88\x68\xfe\xc8\x75\xa4\xa1\x69\xef\x8e\x9a\xaa\xa8\x85\xdc\x9c\xcf\xfe\xf9\xb3\x94\x52\x94\x07\xbd\x43\x17\x3b\xc3\xf8\x14\xc8\x19\xde\x23\xf5\x36\x78\x05\xeb\xbb\xe2\xb6\x90\xc5\x56\x16\xdb\xb5\x78\xc0\xbe\x21\x1b\x39\x93\x97\x77\x38\xa6\xe6\x0b\x19\xac\x07\xbd\x11\x95\x21\xe3\x90\x91\x7a\x25\x00\xee\x33\xaa\x08\x5b\x7b\x1e\x6a\x80\xdd\x77\x44\x05\x9a\xc9\xfa\x53\xfe\x58\xa4\x8d\x9d\xc0\x01\x52\x8f\xd0\x06\x02\xfe\xc4\x69\x81\x37\x0e\x45\x8d\x7d\x48\xd4\xe0\x2c\x7e\x1e\x5c\x1e\xb4\x52\x7a\xa3\xd4\x88\x32\xa9\x28\x44\x24\xb6\xe3\xcc\xf0\x46\xf8\x6a\x1c\x2a\xb8\xd1\xe9\x08\x2e\xc9\xd5\x65\x2e\x7b\x95\xab\x4b\xce\xaa\xf1\x64\x83\xff\x2d\xcb\xa6\x09\xc9\xf3\xf3\xc7\x55\xbc\x25\x8e\x89\xff\x79\x2c\xee\x19\x56\x40\x68\xf3\x15\xd1\x46\xec\xac\x9f\xce\xc9\xcd\x7b\xd3\xa5\xc1\xa1\xc6\x16\x66\xc6\x8f\xe7\x18\x88\x27\xdb\xa5\xe7\x38\xfd\x57\x57\xfc\x04\x00\x00\xff\xff\x36\x33\x97\x82\xc0\x01\x00\x00") func assetsBucketYmlBytes() ([]byte, error) { return bindataRead( @@ -89,12 +90,32 @@ func assetsBucketYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/bucket.yml", size: 445, mode: os.FileMode(420), modTime: time.Unix(1485929866, 0)} + info := bindataFileInfo{name: "assets/bucket.yml", size: 448, mode: os.FileMode(420), modTime: time.Unix(1488950882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsClusterYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xdc\x1b\x7f\x53\x1b\x37\xf6\xef\xf2\x29\x5e\x5c\x3a\x24\x3d\xd6\x36\x24\xe9\x35\x7b\x47\x3a\xc6\x38\xc1\x53\x43\x3c\x5e\x92\x5c\x9b\x64\x18\x79\x57\xb6\x75\xec\x4a\x3e\x49\x0b\x38\x29\xdf\xfd\x46\xd2\xfe\x90\x76\xd7\x60\x52\xae\xd3\x1e\x99\x4c\x58\xe9\xe9\xfd\x7e\x4f\xef\x49\x8a\xe7\x79\x5b\xbd\xf7\xc1\x19\x4e\x96\x31\x92\xf8\x15\xe3\x09\x92\xef\x30\x17\x84\x51\x1f\x76\xf6\xbb\x7b\x5d\xaf\xfb\xc2\xeb\xbe\xd8\xd9\x3a\xc2\x22\xe4\x64\x29\xf5\xcc\xc9\x5b\xc0\xf4\x92\x70\x46\x13\x4c\x25\x84\x8c\x4a\x44\x28\xa1\x73\x40\x14\x06\xfd\x00\xc2\x38\x15\x12\x73\x40\x34\x02\x42\x85\x44\x34\xc4\x02\x66\x8c\x03\x4f\xa9\x06\xcc\xd6\x60\x2e\xb6\xc6\x88\xa3\x04\x4b\xcc\x85\xbf\x05\x30\xcc\xc0\xcf\x56\x4b\xac\xbe\x01\xf4\x6f\x10\x48\x4e\xe8\x5c\x0f\x38\xbc\xe4\xf0\x20\x57\x4b\x0c\x92\x41\x2a\x70\x3b\x03\x9b\xa1\x34\x96\x3e\xc8\xfd\x76\x42\x42\xce\xb6\xf4\x52\xc2\x71\xd4\x47\x4b\x14\x12\xb9\xb2\x09\x9c\xa6\xc9\x14\x73\x77\xe5\xce\xde\x4e\x9d\xa2\x01\x04\x36\xb3\x44\x93\x0c\x62\x94\xd2\x70\x01\x84\xc2\x8a\xa5\xdc\x56\xc3\x16\xc0\x09\xba\x0e\xc8\x67\x7c\x27\xbd\xfd\x06\x7a\x27\xe8\x9a\x24\x69\x02\xb4\x89\xee\x02\x49\x08\x11\x85\x29\xce\x18\xc0\xd1\x1a\x16\x7e\xc6\xab\x53\x94\x6c\xa4\xd3\x0c\x54\x49\x85\x84\x60\x21\x41\x12\xc3\x15\x91\x0b\xb8\x62\xfc\x02\xf3\x92\x81\x36\xc0\x08\xa3\x4b\x0c\xd3\x18\xd1\x0b\xb5\x20\x22\x02\x4d\x63\x0c\x41\x70\x0c\x28\x0c\xb1\x10\x15\x6b\xec\x28\x11\x03\xb1\xe8\xc5\x31\xbb\xf2\xeb\xc4\x83\x74\x4a\xb1\x84\x19\x67\x09\x5c\x2d\x48\xb8\xd0\x6c\x28\xe0\x1a\xce\x9a\x14\x27\x84\x8e\x30\x9d\xcb\x85\x0f\x3b\x2f\x8c\x2a\x4f\xd0\x75\x31\xb4\xf7\xe3\x8e\xcb\x4b\xb7\xad\xff\x74\xba\x7a\x58\x73\x84\xa3\x31\x92\x12\x73\xea\x43\xeb\xf1\xc7\x8f\xd1\x97\xbd\xdd\xa7\x37\x4f\x3e\x7e\x6c\x6f\xf2\xd1\xc9\x7e\xdd\xbf\x79\xd2\xd2\x28\xfb\x8c\x0a\xc9\x11\xa1\xd2\x91\x71\x27\x49\x85\x54\x36\x43\x70\x89\x62\x12\x41\x7f\x78\x34\x81\x69\xcc\xc2\x0b\x1f\xae\xdb\xfa\x4f\xe7\xba\xad\x35\x15\xa2\x18\xbf\x49\xe5\xd9\x82\x63\xb1\x60\x71\xd4\xa0\xb2\x62\x0e\x90\xcc\x74\x86\x40\xa8\x85\xc0\x52\x09\xf8\x52\x45\xe9\x15\x89\x63\x45\x92\x50\x22\x95\x41\xa3\xbb\x7c\xf1\xc7\x6e\x41\x7f\x48\xbf\x92\x3c\xa1\x5f\x4b\xfd\xa9\xa6\x3e\x4c\xd0\x1c\x0f\x9b\x88\x2a\xe7\xee\x9d\x0c\xcb\xc8\x5b\xe7\xd7\xb6\xdb\x0d\xe2\xe9\x90\x2a\xe3\xa2\xb8\xc9\xf3\x16\x2c\x8d\x23\x18\x8c\x0e\x0d\xa7\x06\xf0\xa7\x3b\x10\xcf\x50\x2c\xb0\xed\x3f\xef\x50\x9c\x62\x61\xf0\x03\x78\x20\x79\x8a\x8b\x8f\x1c\xfa\xdd\x32\xcc\xc5\xba\x3d\x16\x75\x20\xb2\x19\xc8\x05\x56\xbe\x92\xea\xa8\x24\xc9\x92\x71\xa9\x13\xaa\x1a\xd7\xc8\x94\x78\xa1\x30\xd1\x33\x8c\xc4\x43\xe1\xc6\xa1\x00\x61\x42\x92\x44\xc2\xe8\xf0\xe1\x89\xc4\x53\x97\xc8\xb1\x94\xcb\x31\x67\xd7\xab\x4d\x28\x68\xc0\x2c\xf5\x9b\xbc\x61\xef\x41\x65\xb2\x7c\xbc\x60\x42\xfa\x8a\xf0\x93\x66\xdf\x38\x62\x09\x22\x74\xd3\x2c\x79\xb6\xc0\x10\xe9\x15\x40\xb3\x6c\xa9\x19\xc8\x64\x52\x6e\x74\x74\x1a\xb4\x01\x7a\x42\xa4\x09\x16\x2a\x29\xc3\x42\x25\x4b\x35\xfd\x99\xd1\x1c\x96\x08\x28\x29\x83\x62\xd2\x24\xf1\x09\x4b\x25\x7e\xfe\xb4\x9e\x63\x29\x93\x9a\xd4\xd1\x69\xa0\x51\x0c\xe2\x69\x53\x92\x1d\xc4\xd3\x63\x26\xe4\x7d\xe4\x51\xc4\xef\x90\x66\xcc\xf1\x12\xd3\x48\x28\x0e\x67\x9c\x51\xa9\x8c\xeb\x28\xaf\xce\x70\xc8\xb1\xda\x42\xd0\x12\x5f\x03\xc7\x21\xe3\xd1\x1a\x7e\xfb\x98\xcb\x4d\x79\xed\x4d\x4e\x0b\xf6\x42\xcc\x25\x99\x91\x50\x51\xb1\x38\x1f\x8c\x0e\xdb\xeb\x36\xa8\xe3\xb3\xb3\x71\x50\xe3\xe2\x04\x4b\x14\x21\x89\x14\x13\xbd\xf7\x81\xef\xf7\x63\x96\x46\xa6\x32\x52\x94\x7d\x9d\x3f\x66\x28\xcc\x54\x5a\x14\x2f\xaf\x39\x4b\x97\x56\xd4\x8f\xd0\x14\xc7\xf9\xa7\xfa\x89\x72\x2a\xad\xa2\x64\xe9\x33\x3a\x23\xf3\x94\x6b\xd4\xad\x02\xd6\x2d\x88\xf2\x1f\xcf\x29\x8d\x9c\x89\x6c\xbf\x76\xc6\xf2\x1d\x76\x13\x86\x7a\xa9\x64\x3a\xd1\xab\xca\xec\x9e\x4c\x55\x2a\x2a\x67\x2e\xab\x7a\x5c\x45\x69\x3e\x0a\x24\xf5\x72\x6f\x8d\xae\xf2\xf2\xce\x24\xfb\x9f\x72\xc6\x9c\xaa\xc6\x5d\xfa\x33\xd6\x49\x61\xce\x11\x95\x56\xe9\x00\x8f\x8d\x2b\x28\xff\xa0\x8c\xe2\x27\x05\x2e\xb7\x28\x71\x91\x95\x1b\x74\x13\xce\x02\x45\x63\x7d\xe9\x62\xca\x40\x1a\x73\x14\x84\x2c\xa5\xb2\xc0\xe6\x54\x8d\x2e\x96\xbc\x28\xbc\x15\x4b\x9f\xd1\x88\x28\x33\x6a\x75\x1f\x23\xe1\x68\xab\xf5\x8a\xfa\xfe\x29\x93\xad\xd2\x69\xf5\xd0\xe0\x3f\x29\x8a\x45\xcb\x87\x0f\x8f\x26\x78\x96\x6b\x78\x17\x76\x76\x3e\x19\x2c\x95\xe4\xbc\x21\x9e\x62\x95\x85\xa9\x21\xe7\x6e\x88\xcd\x59\xe9\x62\x74\x73\xde\xe6\xf8\xf2\x75\x2e\xb6\x32\x23\x6d\x8e\x49\xad\xc9\xb1\x0c\x45\xad\xe8\x58\xb3\x2a\x07\xda\x85\x1d\x55\x32\xec\x7c\xda\x9a\x60\xc1\x52\x1e\x9a\x62\x62\x10\x8a\xbe\x31\xb4\x9d\x20\x75\x8e\x1a\xf4\x75\xa2\xca\x2b\xfd\x41\x3f\x50\x11\x9d\x05\xb4\x4e\x4c\xb5\x25\x16\x80\xf3\xa1\xa1\xb3\xac\xa8\xd3\xfc\x1b\xea\xc3\x87\x4f\x26\x84\x39\x5b\xaa\x3c\x5b\x16\x37\xef\xc6\xfd\x5f\x19\xc5\xc3\x08\x53\x95\x7e\x73\xd6\xd4\x8f\x92\x31\x58\xc6\x44\x96\x43\x1e\xb4\x76\x5b\xd6\x97\x02\x19\xea\x42\x40\x97\x4c\x3e\x3c\x0a\xd2\x29\x6c\x7f\xb1\xcb\x98\x9b\x0c\x7e\xa4\xc3\xde\xc9\x4d\xda\xc6\xa0\xb5\xd7\xcf\xbb\xc9\x3c\x5d\x88\x3c\x82\x08\xd5\x11\x54\x74\x72\x65\x50\x99\x95\x76\x8a\xaa\x87\xaf\x81\x69\x4e\x71\x67\x68\x6e\xa5\xfb\x9f\xf1\xca\x94\x3a\x85\x7c\xb9\x4c\x0a\x83\x56\x79\x20\x51\x78\xe1\x80\x28\x85\xa2\x39\x92\xb8\x27\x8d\x7c\x7e\x59\x2c\xf6\xd5\x96\x49\x18\x1d\xb3\x98\x84\x45\x22\xc9\x3d\x22\x20\xf3\xc2\x9d\x34\x33\x24\xc1\x2c\x95\x3e\x8c\xcf\xf6\x9e\x9f\xe8\xe1\xb7\xcb\x08\x49\xec\x2e\xb7\x0c\x3d\x61\xb1\xfa\xc7\x40\x95\x88\x4e\x08\x2d\x74\x38\xa4\x01\xe6\x97\x24\x74\xd4\xa7\x15\x78\x88\x64\xb8\xa8\x2a\x56\xe5\xf8\x54\x60\xc5\x8a\xcd\x87\xfa\x79\x8f\x88\x7c\x43\x5d\xe6\x85\x9f\xf9\xb9\xd5\xe4\xd8\xdc\xae\x73\xd6\xec\x17\x03\xba\xc6\x2f\x7b\xd1\xbf\x53\x21\x13\x4c\xa5\xc1\xd2\x5f\x20\x3a\xc7\x43\x5a\x31\x61\xd5\xef\x2d\x8f\x6a\x88\xa1\x6c\x51\x9f\xb1\x38\x62\x57\xd4\x87\xa7\xdd\x6e\xbe\x73\x18\xb0\x92\xac\x0f\x7b\x65\xef\xf4\x7f\x24\x95\xa7\xc4\x3a\xc1\x09\xe3\xab\x5e\x8c\x78\x72\x4c\xe6\x8b\x9a\x60\xba\x64\x7a\xaf\x5c\xc4\xf7\x35\xd4\x3a\x79\xd4\x9c\x53\xd6\xe9\x04\xa0\xb5\xe6\xa9\xc6\x95\xcc\x32\x5a\xf0\x12\xb6\xbf\xd4\x3a\xe1\x9b\xef\xf4\x26\xfe\x1c\x12\x42\x53\x59\xc6\x3c\x96\x9c\x84\x46\x6a\xb3\x7c\x82\x05\xe6\x97\x3a\x9c\x32\x18\x35\x2b\x96\xaa\x8a\x53\x2c\x77\x06\xfd\x20\x97\x59\x22\x49\x84\x24\xa1\x0f\xbd\x4b\xcc\xd1\x3c\x8f\xd6\x31\xe6\x84\x45\xb6\x7a\x06\xaa\x9b\x31\x31\xaa\xe7\x84\x31\xba\x56\x46\xd1\x2d\x1b\xbd\xd7\x58\xb7\x35\xd0\x0b\x8b\x2d\xda\xe4\x12\x67\x89\xe5\x11\x00\x47\x24\xc1\x54\xb8\xd0\x46\xd0\x2c\xfb\xaf\xcd\x40\xe5\xee\x51\x18\x3c\x59\x22\x4e\x04\xa3\x6f\x96\x98\x23\xc9\xb8\x0f\xaf\x75\x99\xce\xcf\x16\x88\xda\x9c\x5a\xf6\x1e\xe5\xd5\xd1\x43\x9b\x9b\x50\xcb\xda\xff\xcc\xad\x6d\x9d\x3b\xfc\xc5\x8c\x3d\xa4\xf7\xb5\x75\x9e\x27\xfe\x18\x53\x8f\xb0\x10\x55\x3b\xd7\x77\xd1\xdb\x73\x56\xc3\x9e\xbc\x95\xd9\xa4\xe8\xa1\xb4\xe4\xcd\x7d\x94\x5d\x1a\x84\x1a\x89\xdd\x59\x84\x2c\x49\x10\x8d\x9c\x6e\x03\xa0\xbb\x77\x8e\xa2\xe8\x3c\xaf\x74\xcf\x25\x3b\x0f\xed\xa2\xa8\xb6\x3e\x73\xb2\xdf\x2a\xb3\x00\xdf\x3e\xea\x4c\x09\xed\x4c\x91\x58\xd4\xe6\x70\xb8\x60\x2a\x57\x9e\xf7\x47\x6f\x83\xb3\xc1\xe4\x40\xd7\x24\x99\x52\x6f\x00\x5e\xbe\x84\x0e\x96\x61\x07\x87\x42\xfd\x6d\x1b\xee\x2d\x34\x33\x12\xe3\x0a\xe7\x2d\xbd\x22\x9c\x51\xf5\xd7\x5b\xa4\x4b\xbd\xaa\x55\x67\x9b\x4a\x9d\x69\xd7\xb0\xfd\x41\xd5\xbc\x9f\x6a\xc3\x42\xd5\x16\x07\xdb\x5f\xca\x42\x63\x18\xdd\xd4\xa0\x38\x9e\x13\x46\x73\xb0\x89\xfe\xaa\x42\x25\x2c\x52\xbb\x7a\xb7\xdb\x7d\xd6\xed\xee\x54\x26\xd9\x15\xc5\xdc\x07\xce\x98\xac\xcc\xcc\x75\x95\x59\x9f\x29\xc5\x5e\x30\x76\x21\xda\x91\x16\x1f\xa5\x92\x79\x1c\xc7\x0c\x45\x98\x7f\xa5\x22\x6a\x78\x3c\x45\xa1\xae\x1a\xc9\xc9\x7c\x8e\xb9\x38\x58\x32\x21\xdb\xa9\xae\x77\x6a\x40\x4b\x24\x17\x07\x45\xb9\xdd\xae\x47\x42\x3b\x77\xea\xf6\x5a\x6f\xae\x21\x45\x3a\xd8\x0f\x3a\x6c\x29\x3b\xe8\x4a\x68\x7f\x53\x5c\x13\x4a\x24\x78\x97\xe0\x79\xda\x6c\x60\x9b\x4d\x45\xf5\x0d\x78\x1e\xcf\x78\x69\x08\x4a\x3d\xab\x4c\x07\xb7\x1a\x12\x80\xa7\x14\x89\x83\x8a\x49\x84\x29\xe9\x2a\xde\x29\x56\xe2\x92\x38\x11\x99\x59\xc1\xf8\x6a\x75\x18\x00\x53\x34\x8d\x71\x64\xd5\x70\xd5\x79\x91\x72\x3c\x31\xb7\x3d\x6b\xa1\x1a\xe2\x04\x4c\xa3\xd0\x1c\x2d\xb7\x42\xde\xe1\x60\x6b\x76\xa5\xfc\x84\xd9\xe4\xce\xec\x2b\xdf\x1f\x70\x98\x72\x22\x57\xd9\xd9\x0e\x7c\x30\x40\xaa\x51\x0c\x5e\xc3\xa7\xa6\x23\x8c\x0c\x4d\xfd\xa4\x66\x88\x92\x7c\x74\xcc\x99\x12\xbc\xa8\xc9\xf6\x2b\x13\x95\xa3\x0d\x78\x34\x9c\xc1\x07\xab\x7d\xdf\x05\xb7\x31\xd7\x5f\x2d\xed\x0a\xa7\x4c\xef\x04\xad\x9c\xb7\xb7\x02\xf3\x23\x2b\x15\x9b\xbe\xec\x10\x09\xfc\xc3\x33\x5b\xef\x0d\x41\x66\x25\x48\xf0\xae\xf1\x96\x33\xd9\x7f\x75\x7a\x3e\x9e\xbc\xf9\xd7\x2f\xe7\xbd\xc9\xeb\xe0\xa0\xe5\x5a\x86\xcc\xe0\xc3\x07\x78\x04\xde\x67\x68\x6d\x7f\x29\x1a\xff\x9b\x16\x7c\xfa\xf4\x0f\x90\x0b\x4c\x2b\x86\xd4\x09\xb7\x95\xef\x24\x84\xce\xf5\xe9\x9c\xa1\x70\xe0\x60\xd8\xaa\xac\xfc\x16\x02\x2c\xe1\x97\x34\xd1\x2b\x60\xa9\xa0\x2a\x20\x8a\x1b\xc5\xcc\x0c\x3a\x97\x88\x77\x62\x32\xed\x84\x2a\x7c\x3b\xf9\x2e\xd2\x11\x38\xe9\x98\x1c\x7e\xbe\x4a\x93\xf3\x85\x94\xcb\x73\x8d\x09\x9a\xd9\xfd\xc6\xf0\xab\x41\x0e\x14\xb4\xdf\xe9\xb8\x72\xe6\x3b\xc4\x2a\x4d\xb4\xf7\x35\x23\xd8\xde\xf6\x61\xfb\xb1\x4a\x48\xf0\xb7\xef\x44\xfb\xbb\x53\xf8\x0d\xc2\x54\x82\x37\xdd\xf3\xf6\x9e\x3e\x69\xc1\xcb\xfb\xb3\x5c\xa1\x34\x23\xcd\x1a\x3b\x62\xe1\x05\xe6\x0f\xa4\xb4\x48\x23\xdb\x58\x6f\xf8\x5a\x1f\xfe\x5b\x26\x6e\xd0\x61\xa7\x54\xa2\x58\x09\x43\xa8\x63\x08\xdd\x89\x35\x78\x40\xb4\x0f\x63\xa3\x9a\x86\xaa\x56\xf9\x26\xcb\xcb\x60\x20\x81\x63\x21\x11\xaf\xee\x29\xeb\xac\x39\xe8\x07\x80\xe6\x98\xca\x07\x32\x28\x0e\x85\xa7\xf1\x6d\x6c\xd3\x75\xf1\x7a\x77\xad\x64\x21\x39\x7d\x93\xa1\xd8\xfb\xe1\x45\x7b\xff\xf9\xb3\x76\xf6\xef\x6e\xf1\xfd\xf7\x6e\x7b\x7f\x57\x0b\xc0\x53\x9a\xd9\xad\x2d\x58\x78\x71\x3f\x42\x0f\x63\xd4\x26\x2d\xdd\x69\xb0\x6a\xe6\xf4\x3c\xb5\xd8\x33\x2a\x6e\x70\x58\x30\x00\x62\x3d\x84\x9b\x7b\x15\x45\x67\x60\x95\x26\xe6\x64\x38\x8e\xc1\x5b\x01\xba\x12\x9e\xda\x21\xa7\x8c\x49\x21\x39\x5a\x3a\xc0\x7f\x54\xad\x02\xdb\xae\x1e\x6e\x67\x42\xe8\x03\x23\xf0\x30\x6c\xff\xb4\x19\x27\x0d\x07\x1d\xf7\x60\xa5\xbe\x25\xd7\x1a\xa1\x61\xef\x44\x55\x7d\xf5\x7d\xbb\x5e\x61\x8c\x91\x5c\xf8\xd0\xea\xe4\x76\x9a\xb0\x18\x57\x3b\xc0\x41\x7f\x5f\x0d\x1b\xda\xea\xb7\x66\x82\x19\x4c\x63\x77\xad\x2f\x19\x15\x80\x69\x23\x8f\x58\x98\xea\x33\x9b\x42\xb5\xaa\xdb\xc5\xee\x90\x07\x83\xd9\x0c\x87\xd2\x07\xfb\xae\xc8\x10\x20\x34\x24\x4b\x14\xbb\xd5\x59\x7e\x20\xe8\x0c\x7a\x80\xc3\xfd\x36\x4a\xd0\x67\x46\xd1\x95\x8a\xbc\xc4\x9a\x37\x2d\xaf\x7b\x69\x24\xa4\xf0\x4b\x86\xd7\xe8\x49\xcb\x41\x6c\x55\x19\xc9\x4c\x51\xa4\x82\x2f\xcb\x99\x05\xea\x75\x92\x37\xca\x7e\x9b\xf4\x4d\x5c\x1b\x39\x85\x76\x1b\xd5\x07\xd6\x9c\xbd\x01\xf6\x08\xf3\xfb\x40\x13\x11\xb2\x4b\xcc\xc7\x2c\x8e\x07\x34\x5a\x32\x42\x65\x03\x58\xa0\x36\x86\x33\x1c\xe3\x04\x4b\xbe\x0a\xb0\x10\xe5\x51\x87\x03\x97\x4e\x13\x22\xbf\xaf\xcd\x70\xbf\x3e\x26\x7c\x45\xd4\x19\xce\xbb\x21\x1f\x5a\xdf\x2b\x93\x98\xaa\xb7\xe1\xee\x61\xdf\xf7\x9d\x42\x79\x8d\x83\x5a\x2f\x1e\x20\xab\x43\x9b\x0e\xff\x35\x58\xde\xcb\x68\x7c\xb5\x67\x1f\x8a\x93\xa2\x36\x07\xfb\x14\xd3\xe1\x63\x48\xe7\x1c\x0b\xcb\x7d\x86\x2a\x61\x4a\x16\xb2\xd8\x07\x19\x96\x89\xef\x15\x67\xc9\x98\x71\xfd\xfe\x6a\xbf\x6c\x52\xce\x58\xc3\x60\x9f\x44\x7c\xb8\xcc\x4f\x7a\xca\x3b\xd6\x41\x3c\xfd\x13\x28\x67\x74\xf8\x3f\xd2\xcb\x8f\xdd\x06\xbd\xd8\x83\xb9\x5e\xdc\x17\x55\x1b\x62\x7f\xf6\xec\x69\x03\x7a\x67\xb4\x09\xff\x60\x74\xb8\xaf\x7c\x61\x92\x36\xe4\xcb\xba\xea\x33\xb9\xd7\xf5\x81\x8d\x6c\x5a\x4c\x16\xc2\x16\x0c\xfe\xf0\xfc\xf9\xd3\xe7\xf9\x68\x60\xae\x36\x1c\x82\xaa\xab\x7c\x8d\x65\x4f\x4a\xe3\x1f\xed\x6c\xd8\x36\xa0\x0d\x64\x42\xcc\x82\x52\x03\xfb\x83\xd1\xe1\x5f\x41\xc2\x1a\xf3\x8d\x22\x56\xf5\x30\x08\xc5\x20\x9e\xd6\x65\x8b\x91\x90\x24\x1c\x31\x14\x1d\xa2\x18\xd1\x90\xd0\xf9\xbb\x7d\xdf\x2f\x07\xb2\x7a\xbd\x2e\x66\x10\x2e\x70\xd9\x35\x3b\xd7\xaf\xbb\xd0\xca\x5f\x75\xb5\x8a\xdf\xb1\xf4\x66\x48\xe1\x6f\x15\x1d\xbd\xb9\x7d\x14\x0f\x73\x99\x69\x3d\x97\xca\x23\xf7\x77\x5f\x1d\x56\x0e\x26\x2a\xb5\x84\x52\x71\xa1\x5a\x55\x27\x8e\xd4\x16\x44\x9b\x2e\x8e\xd7\xa9\x39\x5b\xb0\x46\xc5\xb6\x15\x7a\x9c\x96\xe7\xce\x83\x78\x9a\x81\x64\x6f\x6a\x6a\xe7\xdd\x86\xf8\x8c\xf1\x2b\xc4\xa3\x32\xe6\x11\x9f\x63\xa9\xa5\xa9\xe2\xcb\x10\x59\x10\x45\x79\x50\x49\x42\xa5\x77\xab\x66\xc4\x51\x80\xf8\x7d\x1a\x28\xde\x51\xf8\xd6\xa3\x80\x3f\xb3\x6e\xfa\xe5\x73\x28\x61\xbb\xac\x35\x6e\xe1\xb2\xe4\x29\xd4\x6a\x25\x5f\x57\xaf\x41\xa1\xd8\x3a\xf5\x8d\xb5\x5b\xe5\xb8\xa1\x68\xce\xad\x7b\x8b\x7d\xe1\x2b\x37\xce\x4d\xc3\xcf\x2c\xad\x74\x19\x5e\xf6\x06\x27\x7b\x31\x48\xeb\x77\x24\xd9\xb3\x3d\xd5\x60\x84\x8c\x47\x81\x2d\x68\xcd\x93\xca\x47\x2c\x6b\x14\x71\xac\x9f\x03\xfe\xca\x28\xce\x0e\x03\x8b\xb4\x52\x2e\xbd\x69\xe7\x76\x67\x89\xb9\xa7\x2d\x5e\x06\x8e\x0e\x81\xd0\xba\x14\x79\x2b\x92\x73\x58\xbd\x64\xb2\x2a\x41\xad\xd2\x99\x5b\x37\xbb\x0f\x6d\x9c\xa9\x92\xbf\x7c\xfa\xa6\xbd\x8e\xdd\xea\x8a\x46\x90\x4c\xb3\xc5\x77\x2f\x26\x48\x18\x07\xb2\x99\x2a\xf5\xe4\x6c\x37\xda\x53\xdb\x7d\x44\x19\x25\x21\x8a\x2d\xa8\x23\x6b\xf1\xd1\x69\x90\x69\xd7\x5d\x97\x8d\x5b\x90\xd9\x3d\x20\x36\x0c\x1c\x63\x14\xcb\xfc\xd5\xc6\x9b\x54\x2e\x53\xa3\xc9\x43\x24\xf0\x5b\x9e\xf5\x4e\xc6\x9d\xb6\x9a\xb4\xe9\x35\x7b\x41\xb1\x9f\xcc\xec\xe0\x5d\xa3\xf4\x4c\x81\xc5\xc1\xc0\xad\x9a\xef\xdc\xb6\xac\x01\xae\x06\xe5\xe8\x25\x03\xab\xd5\xa0\x6f\x27\x23\xf7\x89\x73\x5d\x05\xb7\xc7\xa8\x83\xf1\xdd\xb8\x0f\xc3\xa3\xf2\x25\xaa\xf3\xbf\x1a\x00\x06\xfa\xdc\x2f\x47\xee\x04\x49\x35\x74\xad\x77\xd2\x95\x9d\x51\x65\xc3\x2d\xa8\x5f\xa4\x56\xc0\xea\xcc\xf5\x38\xcd\x1f\x37\x2b\xc9\xf5\xf1\x5b\x0e\xdd\xbe\x2f\x7f\x8d\x7c\x35\x6f\x64\x05\xc3\xeb\xf7\xa6\x66\x51\xc4\xbd\x64\x09\x1e\x42\x18\x51\x93\xc6\x79\xc8\x76\xcb\xdd\xf5\xda\x87\xe4\x96\x13\x7c\x0d\x63\x39\x8d\xad\xff\x06\x00\x00\xff\xff\xa2\x19\x8e\xec\x02\x35\x00\x00") +var _assetsBuildspecYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x3c\xcc\x31\x0e\xc2\x30\x0c\x85\xe1\xdd\xa7\x78\x5b\xa4\x48\x35\xb0\xf6\x36\x26\x75\x15\x4b\x69\x53\xd9\x01\xae\x8f\xa0\x88\xed\xbd\xe1\xff\x9e\xea\x61\x7d\x9f\x71\xe5\x1b\xd1\x51\x25\x34\x66\x02\xee\x0f\x6b\xcb\x67\x00\xa5\x6f\x9b\xec\x4b\x9c\x0f\x98\xa0\xa5\x76\x24\x66\x76\x3d\x9a\x14\xc5\xcb\x46\x85\xab\xb4\xb3\xfb\x27\xcc\x9c\x88\xc4\x87\xad\x52\xc6\x57\x58\xad\xe9\x8f\x9a\x90\x72\xbe\xe4\x44\xf4\x0e\x00\x00\xff\xff\x30\xbb\xad\x22\x86\x00\x00\x00") + +func assetsBuildspecYmlBytes() ([]byte, error) { + return bindataRead( + _assetsBuildspecYml, + "assets/buildspec.yml", + ) +} + +func assetsBuildspecYml() (*asset, error) { + bytes, err := assetsBuildspecYmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/buildspec.yml", size: 134, mode: os.FileMode(420), modTime: time.Unix(1488948076, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _assetsClusterYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xdc\x3b\xfb\x73\xdb\x36\x93\xbf\xfb\xaf\xd8\xe8\xb3\xc7\x4d\x2f\x94\x6c\x27\xe9\xb5\xbc\x73\x3b\xb2\xac\xc6\x9a\xca\x8e\xc6\x54\xd2\xeb\x6b\x3c\x10\x09\x49\x38\x93\x80\x0e\x00\xfd\x48\x3e\xff\xef\x37\x00\xf8\x00\x48\xc8\x96\x5d\xa7\xd7\xef\x94\xc9\x98\x04\x16\xfb\xc6\x62\x17\x00\x83\x20\xd8\xea\xff\x1c\x4d\x71\xb6\x4a\x91\xc4\x3f\x32\x9e\x21\xf9\x11\x73\x41\x18\x0d\x61\xf7\x60\x6f\x7f\x2f\xd8\xfb\x2e\xd8\xfb\x6e\x77\xeb\x18\x8b\x98\x93\x95\xd4\x3d\xa7\x1f\x00\xd3\x2b\xc2\x19\xcd\x30\x95\x10\x33\x2a\x11\xa1\x84\x2e\x00\x51\x18\x0e\x22\x88\xd3\x5c\x48\xcc\x01\xd1\x04\x08\x15\x12\xd1\x18\x0b\x98\x33\x0e\x3c\xa7\x1a\xb0\x18\x83\xb9\xd8\x9a\x20\x8e\x32\x2c\x31\x17\xe1\x16\xc0\xa8\x00\x9f\xde\xae\xb0\x7a\x07\xd0\x4f\x10\x49\x4e\xe8\x42\x37\x38\xbc\x94\xf0\x20\x6f\x57\x18\x24\x83\x5c\xe0\x6e\x01\x36\x47\x79\x2a\x43\x90\x07\xdd\x8c\xc4\x9c\x6d\xe9\xa1\x84\xe3\x64\x80\x56\x28\x26\xf2\xd6\x26\x70\x96\x67\x33\xcc\xdd\x91\xbb\xfb\xbb\x6d\x8a\x06\x10\xd8\xdc\x12\x4d\x32\x48\x51\x4e\xe3\x25\x10\x0a\xb7\x2c\xe7\xb6\x1a\xb6\x00\x4e\xd1\x4d\x44\x3e\xe1\x07\xe9\x1d\x78\xe8\x9d\xa2\x1b\x92\xe5\x19\x50\x1f\xdd\x25\x92\x10\x23\x0a\x33\x5c\x30\x80\x93\x35\x2c\xfc\x84\x6f\xcf\x50\xb6\x91\x4e\x0b\x50\x25\x15\x12\x82\xc5\x04\x49\x0c\xd7\x44\x2e\xe1\x9a\xf1\x4b\xcc\x6b\x06\xba\x00\x63\x8c\xae\x30\xcc\x52\x44\x2f\xd5\x80\x84\x08\x34\x4b\x31\x44\xd1\x09\xa0\x38\xc6\x42\x34\xac\xb1\xab\x44\x8c\xc4\xb2\x9f\xa6\xec\x3a\x6c\x13\x8f\xf2\x19\xc5\x12\xe6\x9c\x65\x70\xbd\x24\xf1\x52\xb3\xa1\x80\x5b\x38\x5b\x52\x9c\x12\x3a\xc6\x74\x21\x97\x21\xec\x7e\x67\x54\x79\x8a\x6e\xaa\xa6\xfd\x6f\x77\x5d\x5e\xf6\xba\xfa\x5f\x6f\x4f\x37\x6b\x8e\x70\x32\x41\x52\x62\x4e\x43\xe8\x7c\xf5\xfb\xef\xc9\xe7\xfd\x57\xaf\xef\x5e\xfe\xfe\x7b\x77\x93\x97\x5e\xf1\x78\x70\xf7\xb2\xa3\x51\x0e\x18\x15\x92\x23\x42\xa5\x23\xe3\x6e\x96\x0b\xa9\x6c\x86\xe0\x0a\xa5\x24\x81\xc1\xe8\xf8\x1c\x66\x29\x8b\x2f\x43\xb8\xe9\xea\x7f\xbd\x9b\xae\xd6\x54\x8c\x52\xfc\x3e\x97\xd3\x25\xc7\x62\xc9\xd2\xc4\xa3\xb2\xaa\x0f\x90\x2c\x74\x86\x40\xa8\x81\xc0\x72\x09\xf8\x4a\xcd\xd2\x6b\x92\xa6\x8a\x24\xa1\x44\x2a\x83\x26\x0f\xf9\xe2\xb7\x7b\x15\xfd\x11\x7d\x22\x79\x42\x9f\x4a\xfd\xb5\xa6\x3e\xca\xd0\x02\x8f\x7c\x44\x95\x73\xf7\x4f\x47\xf5\xcc\x5b\xe7\xd7\xb6\xdb\x0d\xd3\xd9\x88\x2a\xe3\xa2\xd4\xe7\x79\x4b\x96\xa7\x09\x0c\xc7\x47\x86\x53\x03\xf8\xc3\x03\x88\xe7\x28\x15\xd8\xf6\x9f\x8f\x28\xcd\xb1\x30\xf8\x01\x02\x90\x3c\xc7\xd5\x4b\x09\xfd\x71\x15\x97\x62\xdd\x3f\x17\xf5\x44\x64\x73\x90\x4b\xac\x7c\x25\xd7\xb3\x92\x64\x2b\xc6\xa5\x0e\xa8\xaa\x5d\x23\x53\xe2\xc5\xc2\xcc\x9e\x51\x22\x9e\x0b\x37\x8e\x05\x08\x33\x25\x49\x22\x8c\x0e\x9f\x9f\x48\x3a\x73\x89\x9c\x48\xb9\x9a\x70\x76\x73\xbb\x09\x05\x0d\x58\x84\x7e\x13\x37\xec\x35\xa8\x0e\x96\x5f\x2d\x99\x90\xa1\x22\xfc\xd2\xef\x1b\xc7\x2c\x43\x84\x6e\x1a\x25\xa7\x4b\x0c\x89\x1e\x01\xb4\x88\x96\x9a\x81\x42\x26\xe5\x46\xc7\x67\x51\x17\xa0\x2f\x44\x9e\x61\xa1\x82\x32\x2c\x55\xb0\x54\xdd\x9f\x18\x2d\x61\x89\x80\x9a\x32\x28\x26\x4d\x10\x3f\x67\xb9\xc4\x6f\x5f\xb7\x63\x2c\x65\x52\x93\x3a\x3e\x8b\x34\x8a\x61\x3a\xf3\x05\xd9\x61\x3a\x3b\x61\x42\x3e\x46\x1e\x45\xfc\x01\x69\x26\x1c\xaf\x30\x4d\x84\xe2\x70\xce\x19\x95\xca\xb8\x8e\xf2\xda\x0c\xc7\x1c\xab\x25\x04\xad\xf0\x0d\x70\x1c\x33\x9e\xac\xe1\x77\x80\xb9\xdc\x94\xd7\xfe\xf9\x59\xc5\x5e\x8c\xb9\x24\x73\x12\x2b\x2a\x16\xe7\xc3\xf1\x51\x77\xdd\x02\x75\x32\x9d\x4e\xa2\x16\x17\xa7\x58\xa2\x04\x49\xa4\x98\xe8\xff\x1c\x85\xe1\x20\x65\x79\x62\x32\x23\x45\x39\xd4\xf1\x63\x8e\xe2\x42\xa5\x55\xf2\xf2\x8e\xb3\x7c\x65\xcd\xfa\x31\x9a\xe1\xb4\x7c\x55\xbf\xa4\xa4\xd2\xa9\x52\x96\x01\xa3\x73\xb2\xc8\xb9\x46\xdd\xa9\x60\xdd\x84\xa8\xfc\x05\x4e\x6a\xe4\x74\x14\xeb\xb5\xd3\x56\xae\xb0\x9b\x30\xd4\xcf\x25\xd3\x81\x5e\x65\x66\x8f\x64\xaa\x91\x51\x39\x7d\x45\xd6\xe3\x2a\x4a\xf3\x51\x21\x69\xa7\x7b\x6b\x74\x55\xa6\x77\x26\xd8\xff\x50\x32\xe6\x64\x35\xee\xd0\x9f\xb0\x0e\x0a\x0b\x8e\xa8\xb4\x52\x07\xf8\xca\xb8\x82\xf2\x0f\xca\x28\x7e\x59\xe1\x72\x93\x12\x17\x59\xbd\x40\xfb\x70\x56\x28\xbc\xf9\xa5\x8b\xa9\x00\xf1\xc6\x28\x88\x59\x4e\x65\x85\xcd\xc9\x1a\x5d\x2c\x65\x52\x78\x2f\x96\x01\xa3\x09\x51\x66\xd4\xea\x3e\x41\xc2\xd1\x56\xe7\x47\x1a\x86\x67\x4c\x76\x6a\xa7\xd5\x4d\xc3\xff\xc9\x51\x2a\x3a\x35\xd5\x00\x5e\x9c\xe3\x79\xcb\xcb\x02\x33\x6b\x4f\x90\x68\x44\xeb\xc7\x22\xae\x86\x7b\x50\x7b\xa2\xf2\x63\xd1\x3b\x28\xfc\x24\xdc\x30\xf9\x04\x02\x25\x02\x3f\xfa\x3a\xaa\x3d\x01\xb5\x1a\xdc\x44\x3b\x12\xad\x4c\xc6\x87\xa6\x46\x52\xc2\x56\x1d\xbb\x2a\x2b\xd9\xdd\x3a\xc7\x82\xe5\x3c\x36\xe9\xca\x30\x16\x03\xe3\x4a\x76\x08\xd6\x51\x70\x38\xd0\xa1\xb0\xac\x25\xd4\x2a\xc0\x56\x2a\xe6\xd6\x89\x4e\xd1\xab\xd5\x68\xe8\xea\x91\x91\x44\xf1\x65\xa1\x9a\xe1\x20\x52\xa1\xa6\x88\x34\x3a\x62\xb6\x28\x59\x00\xce\x8b\x86\x2e\xc2\xb5\x5e\x7f\xde\xd3\x10\x7e\xfb\x63\x0d\x33\x1f\x27\x83\x5f\x19\xc5\xa3\x04\x53\xb5\x2e\x94\x12\xa9\x9f\xd2\x53\xb4\x4a\x89\xb4\x95\xdd\x79\xd5\xb1\xde\x14\xc8\x48\x67\x28\x3a\x97\x0b\xe1\x45\x94\xcf\x60\xfb\xb3\x9d\x5f\xdd\x15\xf0\x63\x1d\x8f\x9c\xa0\x69\xa9\x60\x50\x96\xb9\x65\x1c\x13\xe5\xd4\x26\x54\x4f\xed\xaa\xc4\xac\x67\xbb\x19\x69\xc7\xce\x76\x5c\x31\x30\xfe\xd8\x3b\x45\x0b\x6b\x1d\xfa\x09\xdf\x9a\x1c\xac\x92\xaf\x94\xc9\x6b\x22\xf3\x53\x0a\x45\x0b\x24\x71\x5f\x1a\xf9\xc2\x3a\x8b\x1d\xa8\xb5\x9c\x30\x3a\x61\x29\x89\xab\x08\x57\x3a\x52\x44\x16\x95\x4b\x6a\x66\x48\x86\x59\x2e\x43\x98\x4c\xf7\xdf\x9e\xea\xe6\x0f\xab\x04\x49\xec\x0e\xb7\x0c\x7d\xce\x52\xf5\xc7\x40\xd5\x88\x4e\x09\xad\x74\x38\xa2\x11\xe6\x57\x24\x76\xd4\xa7\x15\x78\x84\x64\xbc\x6c\x2a\x56\x2d\x3e\xb9\xc0\x8a\x15\x9b\x0f\xf5\xfb\x19\x11\xf9\x9e\xba\xcc\x8b\xb0\x9c\x1d\x75\xf5\x65\x73\xbb\xce\x59\x8b\x07\x03\xba\xc6\x2f\xfb\xc9\x7f\xe7\x42\x66\x98\x4a\x83\x65\xb0\x44\x74\x81\x47\xb4\x61\xc2\xa6\xdf\x5b\x1e\xe5\x99\x43\xe5\x04\x64\x2c\x4d\xd8\x35\x0d\xe1\xf5\xde\x5e\xb9\xa4\x19\xb0\x9a\x6c\x08\xfb\x75\x51\xf7\xff\x48\xaa\x40\x89\x75\x8a\x33\xc6\x6f\xfb\x29\xe2\xd9\x09\x59\x2c\x5b\x82\xe9\x5c\xee\x67\xe5\x22\x61\xa8\xa1\xd6\xc9\xa3\xfa\x9c\x7c\x53\x07\x00\xad\xb5\x40\x55\xd4\x64\x5e\xd0\x82\xef\x61\xfb\x73\xab\x44\xbf\xdb\xd1\xd9\xc5\x5b\xc8\x08\xcd\x65\x3d\xe7\xb1\xe4\x24\x36\x52\x9b\xe1\xe7\x58\x60\x7e\xa5\xa7\x53\x01\xa3\x7a\xc5\x4a\xa5\x97\x8a\xe5\xde\x70\x10\x95\x32\x4b\x24\x89\x90\x24\x0e\xa1\x7f\x85\x39\x5a\x94\xb3\x75\x82\x39\x61\x89\xad\x9e\xa1\x2a\xb3\xcc\x1c\xd5\x7d\xc2\x18\x5d\x2b\xa3\x2a\xe3\x8d\xde\x5b\xac\xdb\x1a\xe8\xc7\x55\xee\x00\xf5\x6a\xe2\x4e\x89\x32\x3e\x91\x0c\x53\xe1\x42\x1b\x41\xad\x65\xc1\x1b\x81\xea\x45\xa7\x32\x78\xb6\x42\x9c\x08\x46\xdf\xaf\x30\x47\x92\xf1\x10\xde\xe9\xfa\x81\x4f\x97\x88\xda\x9c\x5a\xf6\x1e\x97\x69\xdb\x73\x9b\x9b\x50\xcb\xda\xff\x59\x5a\xdb\xda\x10\xf9\x17\x33\xf6\x88\x3e\xd6\xd6\x65\x9c\xf8\x6b\x4c\x3d\xc6\x42\x34\xed\xdc\x5e\x45\xef\x8f\x59\x9e\x35\x79\xab\xb0\x49\x55\xdc\x69\xc9\xfd\x05\x9e\x9d\x1a\xc4\x1a\x89\x5d\xf2\xc4\x2c\xcb\x10\x4d\x9c\x32\x08\x60\x6f\xff\x02\x25\xc9\x45\x99\x82\x5f\x48\x76\x11\xdb\xb9\x54\x6b\x7c\xe1\x64\xff\x6c\xf4\x02\xfc\xe3\x45\x6f\x46\x68\x6f\x86\xc4\xb2\xd5\x87\xe3\x25\x53\xb1\xf2\x62\x30\xfe\x10\x4d\x87\xe7\x87\x3a\x27\x29\x94\x7a\x07\xf0\xfd\xf7\xd0\xc3\x32\xee\xe1\x58\xa8\xff\x5d\xc3\xbd\x85\x66\x4e\x52\xdc\xe0\xbc\xa3\x47\xc4\x73\xaa\xfe\x07\xcb\x7c\xa5\x47\x75\xda\x6c\x53\xa9\x23\xed\x1a\xb6\x7f\x53\x19\xf6\x1f\xad\x66\xa1\x72\x8b\xc3\xed\xcf\x75\xa2\x31\x4a\xee\x5a\x50\x1c\x2f\x08\xa3\x25\xd8\xb9\x7e\x6b\x42\x65\x2c\x51\xab\xfa\xde\xde\xde\x9b\xbd\xbd\xdd\x46\x27\xbb\xa6\x98\x87\xc0\x19\x93\x8d\x9e\x85\xce\x32\xdb\x3d\xb5\xd8\x4b\xc6\x2e\x45\x37\xd1\xe2\xa3\x5c\xb2\x80\xe3\x94\xa1\x04\xf3\x27\x2a\xa2\x85\x27\x50\x14\xda\xaa\x91\x9c\x2c\x16\x98\x8b\xc3\x15\x13\xb2\x9b\xeb\x7c\xa7\x05\xb4\x42\x72\x79\x58\x65\xe9\xdd\xf6\x4c\xe8\x96\x4e\xdd\x5d\xeb\xcd\x2d\xa4\x48\x4f\xf6\xc3\x1e\x5b\xc9\x1e\xba\x16\xda\xdf\x14\xd7\x84\x12\x09\xc1\x15\x04\x81\x36\x1b\xd8\x66\x53\xb3\xfa\x0e\x82\x80\x17\xbc\x78\x26\xa5\xee\x55\xa6\x83\x7b\x0d\x09\xc0\x73\x8a\xc4\xe1\x1a\x93\xa0\x6b\x91\xb2\x85\xd0\xcf\x2b\x55\x15\x3e\xcd\x0c\x27\xd3\xe9\xe4\x62\x72\xfe\xfe\xbf\x7e\x39\x5c\x4a\xb9\x0a\x7b\xbd\xed\xcf\x55\xa1\x79\xd7\xf3\xc2\x47\x9b\x0e\x70\x39\x45\xd7\x22\x4e\xc9\x13\xbd\x65\x95\xe6\x0b\x42\x45\xdb\x3d\xe2\x6b\x85\x1c\x0e\x8b\x87\xf6\xc8\x62\x33\xa0\x3d\xb2\x30\xc2\xe1\x7d\x66\x68\x49\xa0\xfe\x3e\x51\x84\x05\xa6\x98\xa3\xd4\x3b\xf9\x25\xbe\x50\x31\x07\x0e\xa1\x77\x85\x78\x2f\x25\xb3\x9a\xe8\x02\x53\x19\x68\x98\x2d\x8f\x74\x19\x16\x8b\x36\x4a\x07\x19\x5b\xf4\x34\x58\x0b\x2a\x65\x8b\x0b\x3d\xf1\x2f\xf4\x5e\xe6\x21\x38\x71\xd2\x0b\x2e\x24\xc7\x28\x2b\xe1\xcb\x38\x6e\xf0\xf7\x3e\x57\x71\x9d\x24\x77\x1e\x66\x33\x2c\x04\x5a\x60\x8f\x15\x9b\xfc\x96\x90\x5f\x90\xe5\x92\x44\x83\xeb\x26\x06\x15\x6e\x24\xc9\xf0\xc5\x5c\x47\x0b\x38\x84\x9d\x19\xec\x24\xb0\x73\x12\xee\x9c\x86\x3b\x91\xcf\x26\x2c\xbe\xc4\x7c\x03\xa3\x68\xb8\x2f\x69\x15\x4d\xe0\xf1\x02\xfe\x12\xec\x64\xc1\x4e\x32\x2d\x65\xec\xee\xcc\x3d\x62\xe2\x78\x03\x43\xaa\x25\xf6\xeb\x2f\x28\xa2\xc2\xff\xa7\xe5\xfb\xd5\x23\x5d\xac\x56\x08\x1d\xea\x1f\x16\xb2\x86\xfd\x92\xa2\xd6\x54\xbe\x90\xc4\xc5\xd2\xb6\x81\xbc\x05\xe4\x17\x95\xb6\xa0\xf1\x64\x59\xeb\x19\x5a\x43\xaf\x50\x7c\xa9\xe6\xbc\x1b\xbb\x6f\xf3\xac\x19\xcc\x8b\xe0\x5b\x6d\x9d\x99\x9f\x30\xdb\x29\x8d\xe1\xe2\x56\x5c\x11\x27\x1b\x76\x71\xb4\xd3\x53\x8a\x66\x29\x4e\xac\xfd\x93\x66\xbf\xc8\x39\x3e\x37\x57\x40\xd6\x42\x79\x72\x54\x30\x9b\x74\xeb\xd7\xac\x07\xa1\x1b\xd9\x44\x73\x89\x33\x99\xef\xff\x91\x44\xad\xdc\xfb\x5e\xc8\x07\xd2\xd5\x35\x35\x6e\x79\x90\x6e\x2a\xb1\xe2\xad\xac\x36\x71\x9c\x73\x22\x6f\x9b\x47\x58\x66\x73\x9e\x09\x19\xbd\x2b\xb1\xd8\xa7\x35\x05\xaa\xf6\xa1\xd4\x08\x65\x65\xeb\x84\x33\x25\x7c\xb5\xcb\x73\xd0\xe8\x28\x46\xb4\x4e\x71\xf4\x0e\xec\xdc\x3d\x69\xaa\xcf\x2f\x9c\x66\xef\xd9\x44\xd5\xd1\xd1\x19\xd0\x19\xd3\x75\x68\xa9\xd8\x0f\x02\xf3\x63\xab\x0e\x34\xf4\x8e\x90\xc0\xdf\xbc\xf1\xa4\x39\x56\x45\x06\xc1\x8d\x93\xac\x0c\x7e\x3c\x33\x39\xe3\x45\xff\xfc\x5d\x74\xd8\xb1\x4d\x47\xe6\xf0\xdb\x6f\xf0\x02\x82\x4f\xd0\xb1\x53\xc9\x0e\xfc\xf1\xc7\x7f\x80\x5c\x62\xea\xd8\x59\xd7\x76\x9d\xb2\x68\x25\x74\x61\xa7\xb0\xce\x78\x37\xc4\xfd\x03\x22\x2c\xe1\x97\x3c\xd3\xf0\xb0\x72\x0e\x52\x4a\x3e\x14\x1b\xf3\x3a\x05\xd3\x11\xb7\x57\x85\x24\x81\xb3\x9e\x29\x15\x2f\x6e\xf3\xec\x42\xa5\xbf\x17\x1a\x0f\xf8\x18\x2d\x59\xd5\x10\xbe\x5c\xb9\x53\xd5\xa1\xb7\x79\xa6\xbd\xd2\x3b\x7e\x7b\x3b\x84\xed\xaf\x54\x9c\x83\x7f\xdb\x11\xdd\x9d\x33\xf8\x27\xc4\xb9\x84\x60\xb6\x1f\xec\xbf\x7e\xd9\x81\xef\x1f\xcf\xb0\x43\x68\x4e\x7c\x9a\x3a\xd6\xb9\xc3\xb3\x28\xcb\xa4\x21\x9b\xea\x0b\xdf\xe8\x3b\x0f\x0f\x14\x26\xb5\xf2\xc4\xad\x30\x74\xfc\xf9\x54\x0b\xe9\x7d\xd5\xcb\x23\xb1\x3e\x8f\x69\x5a\xea\x69\xae\xcd\xc5\xb2\x03\x06\x10\x38\x16\x12\x71\xb9\x81\x0d\x87\x83\x08\x74\xf9\xf0\x2c\x66\xc4\xb1\x08\x34\xb6\x4d\x2d\xb9\x6e\x62\x3e\xbc\xff\x52\xe3\x38\x7b\x5f\x60\xd8\xff\xe6\xbb\xee\xc1\xdb\x37\xdd\xe2\xef\xab\xea\xfd\xdf\xf7\xba\x07\xaf\x34\xfb\x3c\xa7\x85\xb5\xba\x82\xc5\x97\x8f\xa2\xf3\x3c\xa6\xf4\xa9\xe8\x5e\x3b\x35\x03\x63\x10\xa8\x81\x81\xd1\xad\xc7\x43\xc1\x00\x88\xf5\x10\x9d\x2d\x97\x9a\xf5\x7a\x9b\x67\x26\xbf\x4a\x53\x08\x6e\x55\x7e\x12\xa8\xd5\x71\xc6\x98\x14\x92\xa3\x95\x05\xfa\x57\xed\x79\xc0\xb6\xab\x81\xfb\x58\x10\xfa\xd8\x09\x02\x0c\xdb\x3f\x6c\xc6\x87\xe7\xb8\xe4\x11\x8c\xb4\x97\xe1\xd6\x76\xea\xa8\x7f\x1a\x86\xbe\xb5\xba\x9d\x59\x4c\x90\x5c\x86\xd0\xe9\x95\xf6\x39\x67\x29\x6e\x26\x11\xc3\xc1\x81\x6a\x36\xb4\xd5\x93\x9f\x60\x01\xe3\xdd\xa3\xd7\x77\xa8\x14\x80\xd9\x8c\x3e\x66\x71\xae\x4f\x7e\x2a\xc5\x46\x12\x49\xec\x36\x05\x30\x9c\xcf\x71\x2c\x43\xb0\xaf\xc2\x18\x02\x84\xc6\x64\x85\x52\x37\x2b\x2b\x8f\x15\x9d\xc6\x00\x70\x7c\xd0\x45\x19\xfa\xc4\x28\xba\x56\x93\x2d\xb3\xfa\xcd\xc6\xb9\x9b\xa9\x08\x29\xc2\x9a\xe1\x35\x7a\xd2\x72\x10\x5b\x55\x46\x32\xb3\x97\xae\x26\x5c\x11\x1e\x2b\xd4\xeb\x24\xf7\xca\x7e\x9f\xf4\x3e\xae\x8d\x9c\x42\xbb\x8d\xaa\x69\x5a\xae\xee\x81\x3d\xc6\xfc\x11\xd0\x51\x3e\xcb\x88\xac\x20\x35\xcf\xe6\xa4\x70\x2d\xf0\x14\x89\xcb\x75\x70\xe5\xa6\x68\x91\xb0\x21\x4e\x43\x74\x2d\x42\x35\xd8\x9d\x01\xe5\x6b\x3f\xd6\xf7\x62\x46\xc9\x5d\x58\x6c\xcd\xf7\xd6\x15\x71\x4f\x53\x5d\xa4\x56\xaf\x29\x4e\x71\x86\x25\xbf\x8d\xb0\x10\xf5\x51\x8f\x0d\x37\x61\x69\xba\x46\x94\xce\xd7\x6e\xf2\x5f\xdd\xe0\x69\x56\x0f\x7d\x4e\xcd\x65\x8f\x76\x59\xd1\x51\x34\x0a\x09\x3b\x7f\x1f\xed\x1c\x13\x11\xb3\x2b\xcc\x95\xf4\x43\x9a\xac\x18\xa1\xb2\x05\xc6\xc3\xaf\x37\xd2\xcc\xe3\x59\xd0\x15\xab\xbe\xbb\x80\xc7\xcc\x39\x61\xf6\x43\x44\xba\x7a\xf7\x81\x4c\x72\x39\x66\x8b\xe1\x15\xa6\x52\xf8\xfa\xcd\x49\xe2\xac\x46\x22\xfc\x22\x35\xc6\x3a\x76\xd2\x88\x1e\x34\x54\xca\x16\x81\x39\xdf\x58\xbf\x1b\xf1\x9c\x88\xb5\x71\x8a\x97\x52\x89\xad\x48\x3e\x56\x14\x42\x47\xc7\xed\x70\x5e\x76\x17\x57\x00\xaa\xeb\x35\x96\x08\xa6\xf0\xf4\xdc\x44\x3a\x08\x43\xa7\x62\x5d\x43\xc2\xba\x61\x0d\x65\x45\xe9\xb9\xd3\xa3\xc1\x4a\x95\x69\x7c\xad\x6b\xe6\x8a\x93\xaa\x48\x06\xdb\x75\x1c\x3e\x46\x74\xc1\xb1\xb0\xe2\xf9\x48\x65\x2e\x92\xc5\x2c\x0d\x41\xc6\xb5\xbb\xfd\xc8\x59\x36\x61\x5c\x7f\xef\x71\x50\xef\x16\x4c\x99\xa7\x71\x40\x12\x3e\x5a\x95\x07\xb8\xf5\x9d\xce\x61\x3a\xfb\x1b\x28\x67\x7c\xf4\x85\xf4\xf2\xed\x9e\x47\x2f\x76\x63\xa9\x17\xf7\x0b\x8e\x0d\xb1\xbf\x79\xf3\xda\x83\xde\x69\xf5\xe1\x1f\x8e\x8f\x0e\x94\x2f\x9c\xe7\x9e\x04\xa6\xad\xfa\x42\xee\x75\x1b\x32\x5e\x36\x2d\x26\x2b\x61\x2b\x06\xbf\x79\xfb\xf6\xf5\xdb\xb2\x35\x32\x37\x96\x1c\x82\x49\x08\x2f\xde\x61\xd9\x97\xd2\xf8\x47\xb7\x68\xb6\x0d\x68\x03\x99\x29\x66\x41\xa9\x86\x83\xe1\xf8\xe8\x5f\x41\xc2\x16\xf3\x5e\x11\x9b\x7a\x18\xc6\x62\x98\xce\xda\xb2\xa5\x48\x48\x12\x8f\x19\x4a\x8e\x50\x8a\x68\x4c\xe8\xe2\xe3\x81\x0a\x63\x65\xc3\xda\x7b\x8f\x51\xbc\xc4\x3a\x88\x8d\x54\xcd\xe9\xdc\xcc\x7c\x05\x9d\xf2\x2b\x92\x4e\xf5\x8c\x65\x30\x47\x0a\x7f\x07\xca\x2d\x58\x73\xa9\x50\x3c\xcf\x1d\x45\xeb\xf3\x8c\x72\xe6\xfe\xe9\x1b\x81\xf7\xee\x10\x6a\x15\x57\xaa\x55\x05\xdb\x58\xe5\x84\xd4\x77\x8d\x74\x9d\x9a\x8b\x01\x6b\x57\x8b\xda\x0a\x7d\x4e\xeb\xeb\x24\xc3\x74\x56\x80\x14\x77\xf8\x5b\xd7\x58\x0c\xf1\x39\xe3\xd7\x88\x27\xf5\x9c\x47\x7c\x81\xa5\x96\xa6\x89\xaf\x40\x64\x41\x54\xf9\x7a\x23\x08\xd5\xde\x7d\x32\x9d\x4e\x1c\x05\x88\x3f\xa7\x81\x3a\xeb\xb3\x2e\x10\xff\x9d\x75\x33\xa8\x3f\xbf\x10\xb6\xcb\x5a\xed\x16\x2e\xe7\x4e\x73\x3b\xf8\xba\x7a\x8d\x2a\xc5\xb6\xa9\x6f\xac\xdd\x26\xc7\x9e\x2a\xb6\xb4\xee\x3d\xf6\x85\x27\x2e\x9c\x9b\x4e\x3f\x33\xb4\x51\xf6\x07\xc5\x31\x7f\xf1\x85\x12\x6d\x5f\x7d\x2a\x3e\x13\x52\xf9\x5c\xcc\x78\x12\xd9\x82\xb6\x3c\xa9\x71\x13\xbe\xad\x88\x13\xfd\xf9\xd1\xaf\x8c\x62\x37\x37\xb3\x87\xde\x75\x4b\xbb\xb3\xcc\x5c\xbf\xac\xbe\x44\x1a\x1f\x01\xa1\x6d\x29\x0a\xf8\x8a\xc3\xe6\xdd\x31\x2b\x65\x5d\xb3\xf5\xef\xbb\x64\x5f\x65\xb7\x9a\xbf\xb2\xfb\xae\xbb\x8e\xdd\xe6\x08\x2f\x48\xa1\xd9\xea\xbd\x9f\x12\x24\x8c\x03\xd9\x4c\xd5\x7a\x72\x96\x1b\xed\xa9\xdd\x01\xa2\x8c\x92\x18\xa5\x16\xd4\xb1\x35\xf8\xf8\x2c\x2a\xb4\xeb\x8e\x2b\xda\x2d\xc8\xe2\x7a\x1f\x36\x0c\x9c\x60\x94\xca\xf2\x32\xf6\xfb\x5c\xae\x72\xa3\xc9\x23\x24\xf0\x07\x5e\x6c\x66\x18\x77\xda\xf2\x69\x33\x68\xc6\x93\x6a\x25\x99\xdb\xd3\xd6\xeb\x2a\x7e\xe0\x4d\xac\xa3\x37\xf6\xf4\x6e\xde\xbd\x56\x7a\x60\xa0\x0f\xb2\x0d\xe7\xe8\xf1\xee\xaf\x95\xf1\xa9\x22\x6e\x28\xe1\x5a\x01\x5b\x39\xf9\x87\xf3\xb1\xfb\x89\x69\xdb\x25\xee\x8f\x59\x0e\xc6\x8f\x93\x01\x8c\x8e\xeb\x2f\x01\x9d\xaf\xca\x01\x86\xfa\x04\xa2\x44\xee\x04\x8d\x66\x28\xb3\xbe\x53\x6d\x64\x0a\x6a\x75\xb0\x38\xb5\x17\x1d\x1b\xac\xcd\x5c\x9f\xd3\xf2\xe3\x52\x25\xb9\x3e\x0e\x28\xa1\xbb\x8f\xe5\xcf\xcb\x97\x7f\x61\xaf\x18\x5e\xbf\x56\xfb\x45\x11\x8f\x92\x25\x7a\x0e\x61\x44\x4b\x1a\xe7\x33\x9f\x7b\xae\xe8\xae\xfd\x90\xd7\x72\x82\xa7\x30\x56\xd2\xd8\xfa\xdf\x00\x00\x00\xff\xff\xa7\x5b\x45\xb8\x82\x42\x00\x00") func assetsClusterYmlBytes() ([]byte, error) { return bindataRead( @@ -109,12 +130,12 @@ func assetsClusterYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/cluster.yml", size: 13570, mode: os.FileMode(420), modTime: time.Unix(1486453729, 0)} + info := bindataFileInfo{name: "assets/cluster.yml", size: 17026, mode: os.FileMode(420), modTime: time.Unix(1487797419, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsPipelineYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x5a\xeb\x6f\x1b\xb9\x11\xff\xee\xbf\x62\xe2\x06\x08\x90\x76\xb5\x5e\xdb\x49\x2e\x0b\x14\xc5\xc6\xd6\xb9\x42\x6d\x4b\x90\xec\xe4\x43\x51\x04\xd4\xee\x48\x62\xbd\x4b\x2e\xf8\x90\x4f\x97\xfa\x7f\x2f\xc8\x7d\x71\x57\x0f\xcb\x97\xf4\xdc\xbb\x8b\x3e\x79\x67\x86\x33\x3f\xce\x83\xe4\x90\xf6\x3c\xef\x20\xfa\x34\xb9\xc1\x2c\x4f\x89\xc2\x1f\xb9\xc8\x88\xfa\x88\x42\x52\xce\x42\x78\x75\x7c\x14\x1c\x79\x47\xef\xbd\xa3\xf7\xaf\x0e\xce\x51\xc6\x82\xe6\xca\x72\xae\x6e\x21\xa7\x39\xa6\x94\x21\x68\x49\xd9\x1c\xce\x78\x82\xa3\x8a\x44\x58\x62\x09\x1f\x34\x4d\x13\x98\x71\x01\x31\x67\x8a\x32\xcd\xb5\x84\x04\x53\xba\x44\xb1\x3a\x18\x11\x41\x32\x54\x28\x64\x78\x00\x70\x41\xd5\xdf\xf5\xf4\x56\xa2\x30\x5f\x00\x37\xab\x1c\x43\x98\x28\x41\xd9\xdc\x12\x5a\x00\x0a\x69\x30\xe2\xf5\xd8\x31\xe6\xfc\x09\x63\x8d\x38\x28\x0e\xb9\x4e\x53\x98\x09\x9e\xf5\x60\xc8\xd2\x15\xa8\x05\xc2\x35\xc9\xb0\x07\x8c\x2b\xfb\x75\x3b\xbe\xac\x8d\x7c\x10\x84\xc5\x8b\x27\x98\x29\x06\x94\xec\x19\xd1\xa9\x0a\xe1\x30\x23\x52\xa1\x38\xac\xb5\xde\xf0\x3b\x64\x85\xd2\x6b\xde\x8f\x17\x3c\x04\x25\x34\xee\x61\x65\x82\xb1\x40\xd5\x83\x81\x82\x8c\xce\x17\x0a\x52\xce\xef\x40\xf2\x0c\xd5\xc2\xc4\x25\xa5\x77\x08\xef\xa7\xc1\x0f\xef\x49\xf0\xf6\xcd\xe9\xdb\xd3\x93\x37\xc7\xc7\x6f\xde\x06\xb3\x77\xd3\x13\x9c\x26\xa7\xa7\x24\x78\x73\x12\x90\xd3\xe3\x1f\xde\x91\x19\x0c\x23\xad\x16\x16\x0c\xdc\x53\xb5\x00\x12\xc7\x28\xa5\xf1\x92\xf1\x56\x0f\x2e\xac\xc7\x16\x4a\xe5\x32\xf4\xfd\x39\x55\x0b\x3d\xed\xc5\x3c\xf3\x25\x2a\x45\xd9\x5c\xfa\xca\x0c\x96\x07\x00\x36\xf6\x16\xfb\xb6\x59\x54\xce\x48\x29\xd3\x3f\x9d\x71\xa6\x08\x65\x85\x53\x3a\x73\xbc\x59\x20\x4c\x6d\x2a\xc5\x95\x14\xa8\x55\x8e\x06\x8b\x96\x68\x13\xcc\xf2\xcd\x84\x4d\xc0\x48\x9e\x57\x08\xce\x78\x96\x6b\x85\x7b\x01\xf9\x70\x3b\xb8\x3c\xff\x7c\xd1\xbf\xee\x8f\xa3\xcb\xe0\xf3\xe4\x2a\xba\xbc\x7c\x04\x8e\x55\xbe\x37\x98\x41\x46\xe6\x8f\xc3\x20\xf7\xd2\x8f\x79\x82\x56\x8b\xaf\xa7\x9a\x29\xed\x4d\x89\xc4\x30\x38\xed\x1d\x9d\xee\x44\x44\x8d\x85\x47\xa0\x5c\xe9\x5f\x2f\x2c\x99\x36\x4e\xca\x08\x4b\xa4\xb5\xfc\x2c\xe1\xe8\x82\xf8\x25\x61\x48\x78\x7c\x87\x22\x0c\x7a\xc1\x71\x2f\x78\x5a\x08\xba\xe6\xcf\xf9\x3d\x4b\x39\x49\x3e\x10\x89\x5a\xa4\x8f\x02\xd9\x54\x6e\x0a\xd3\x94\xce\x91\x29\x3f\xd3\xbe\xc0\x14\x89\x44\xe9\x27\xa5\xe6\x6d\xf8\x88\x44\xd0\x22\x35\xe0\x28\x93\x8a\xa4\xa9\x01\x67\xd6\xbe\x16\xb0\x6a\x03\xd8\x63\xf9\x31\x6a\x97\x85\x38\xf0\x99\xd1\xe6\xe8\xa6\xac\xd9\x05\x5a\x06\x7e\xa4\xe9\xe3\xfe\xcf\xb4\x67\x53\xd0\x23\x59\xf2\x76\x5b\xd6\x33\x92\xa1\x31\x6c\xb2\xdb\x4c\x85\xa6\xd6\xf5\x95\x23\x1c\x34\x16\xc0\x4e\xc3\x8e\xea\x11\x51\x0b\x33\x36\xd3\xbd\x55\x96\xfe\x05\x04\xa6\x44\xd1\xa5\xd5\xdd\x6c\x36\x6b\x80\x8d\xb0\x01\x7a\x83\x52\xf5\xd9\x72\x1f\x4b\xd7\xe5\x04\x32\x0d\xc8\x96\x54\x70\x96\x21\x53\x76\x0e\x98\xa7\x7c\x65\xfe\x32\x49\xa4\x50\xaa\x75\x1f\x25\xb8\x34\xf6\x46\x82\x27\xdf\xd8\x5e\x2e\x78\xa2\x63\x33\xa6\x63\xb2\x61\x1c\x1e\x8c\x51\x72\x2d\x62\xb4\x1b\x78\x1d\xea\x31\x6f\x3b\x39\xfa\x34\x09\xc3\x41\x74\x15\x86\x86\x63\x19\x23\xc1\x73\x14\x8a\x16\x23\xcd\x2f\x92\x52\x67\x68\x04\x46\x3c\xa5\xf1\xea\x9c\xc7\xda\x20\xab\xf8\x00\x13\x45\x14\xb6\x49\x1e\xf4\x67\x33\x8c\x55\x08\x51\x9a\xf2\xfb\x9a\x6e\x0c\x50\x16\xd3\x9c\xa4\xa1\x43\x04\x98\xa0\x58\xd2\x18\xdb\x44\x0f\xea\x32\xef\x91\x8c\xfc\xcc\x19\xb9\x97\xa6\xcc\x1c\xa9\xc8\xce\xd8\x1d\xe7\x81\x54\x32\x6c\x60\x97\x2c\x93\x39\x21\x1c\xfa\x87\xd5\xb7\x99\x8d\x33\x4f\xaf\xa0\xac\x4c\x1c\xc2\xc6\xb0\x27\x0b\x64\xb5\x81\x6d\x5e\xd8\xe8\x87\x5d\x9e\xa8\xb1\xc3\xe1\xeb\xc3\x16\xbd\x8a\x5e\x97\xd3\x3a\xff\x1d\x7b\xc1\x91\x17\xbc\x7b\x55\x06\xb8\x3a\xe2\xfd\x36\x63\x5c\x9d\x59\x9f\x23\xcc\x95\xed\xaf\x8f\xf4\x3a\xc8\x56\x0e\x87\xaf\xf7\x8a\x32\x6c\xf5\xeb\x36\x0b\xf2\x24\xbc\x40\x35\x9c\xfe\x1b\x63\xb5\x83\x55\xa6\xcf\x46\x89\x0f\x3a\xbe\xc3\x4a\xa2\x5a\x9f\xfe\x27\x48\x47\x7a\x13\xd2\xda\x44\x67\x00\x11\x2c\x24\xf7\x32\x94\x27\x61\x18\xba\xd1\x7a\xfd\xb5\x38\x5e\x77\xc3\x94\x72\x9d\xcc\x6c\x9f\x65\x06\x74\xd9\x18\xcb\x35\x1a\x25\x59\x38\x22\x52\x3a\xe9\xf7\x4b\x1d\xb6\xab\xb4\xed\xda\x1d\x09\x45\x67\x24\x56\x6b\xb5\x5d\x4b\x84\xe1\x48\xf0\xda\xb1\xe7\x98\x23\x4b\xe4\x90\x85\xed\xf5\x7f\xcb\x0a\x50\x94\xc3\x8b\x89\x9e\xc2\xcb\x2f\x56\xef\x44\x91\xf8\xce\x90\x1f\x3c\x52\xda\x2e\x65\x5b\xdb\x56\xd1\x49\x56\x12\xf6\xc8\x02\xc5\xe4\x4b\xe9\xb2\xea\xed\xba\x04\x2f\x2e\x50\x45\x4a\xb5\x21\xf5\x22\x51\x25\x65\x35\x4b\xd9\x04\xac\x98\xea\xd9\xf0\xbc\x3f\x1a\x8c\xfa\x97\x83\xeb\x7e\xc9\xea\x37\xbb\x64\x57\xfa\xc5\x18\x67\x4d\x9f\x53\x33\xdd\x33\xae\x23\xe3\x90\x6b\xd1\xe2\x24\x5a\x39\xa4\xe9\x11\x1e\xaa\x59\x75\xb2\x75\x2b\xca\x1b\x9a\x21\xd7\x6a\xc0\xae\x28\xd3\x0a\x65\x08\xc1\x91\x1b\xd7\xb5\x23\xef\xaf\x15\x54\x7b\x1a\xde\x1e\xd1\xe2\xb0\x6c\xc3\xd9\x09\xff\xb3\x05\xb4\x68\x8f\x76\x44\xb3\xd5\xc5\x6c\x09\x65\xd9\x64\x3c\xd4\x6c\xc7\xea\x47\x22\x28\x99\xa6\xe8\x80\x05\xaf\xf4\xe2\xf9\xf0\xec\x1f\xfd\xf1\xe7\x68\x34\xf8\xfc\xb1\x3f\x9e\x0c\x86\xd7\x6e\x5d\x7f\x24\xa9\xc6\x10\x82\xde\xf1\xe9\x53\x13\xa4\xec\x40\x27\x39\xc6\x25\xc8\xff\x38\x9a\x97\xd5\xba\x70\xd4\x0b\x1c\x72\xbe\x30\x6d\x45\x7b\x55\x2b\xf6\x98\x16\x09\xea\xfe\xa6\x4b\xb7\x2b\x9e\xe9\x38\x3c\x79\x69\xbd\xd2\xe9\x7d\x1e\xfc\xa5\x4b\x2d\x97\xa7\x07\xdf\x25\x9a\x33\xfb\x03\x78\x1c\x7c\x2d\x85\x3f\xa5\xcc\xcf\xf4\x26\x33\x8b\x8c\x27\xf0\x67\xf1\xd3\x23\x72\x99\x06\x2f\xb6\x58\x0a\xc5\x72\x19\x43\xae\xe5\x02\x3c\x05\x29\x31\xc7\x6c\x67\x10\x59\xcf\x2b\xf3\x53\xd6\xcb\x3f\xd3\xbc\x45\x35\xcd\xc7\x9a\x07\xbc\xc6\xd4\xee\x52\x3d\xb7\xc7\x6f\xd3\x39\x3c\x47\x99\x16\x87\x7f\xcf\x99\x7e\xab\x58\x0b\x70\x4d\x6b\x6b\xe4\xdc\xfe\xe1\xf7\x58\xb3\xdf\x4b\xab\x36\xf3\x15\xa5\x55\x76\x95\x2f\xbf\x94\x3d\xf1\xc3\xff\x47\x9d\x99\x8e\xf9\x19\xeb\xcc\x34\xd1\xfb\xd4\x99\x91\xfb\x5e\x67\xdf\xeb\xac\x2d\xb7\xb3\xce\xca\xbb\xa0\x67\xaf\xb3\xea\xa6\x60\x63\x95\xd5\xcc\xb0\xfa\x6b\x4b\x31\x99\x5c\x8e\x04\x6b\x67\xb9\x7b\x09\xe1\x24\xfa\x44\x91\xb9\xdb\x81\x17\x85\x38\x71\xbb\x84\xaa\xc3\x97\xee\xfd\xc2\x80\xe5\x5a\x35\x55\x02\xff\xfc\x97\x33\xe7\x8d\x4a\x2a\x35\x66\x56\x83\x4e\x26\x9d\x11\x85\x73\x2e\x56\x1b\x06\x01\x0c\xef\x19\x8a\x10\x6e\x16\x54\x24\x23\x22\xd4\xaa\xc5\x6d\xba\xb3\xe0\x55\x8b\x31\x12\x7c\x49\x13\x33\xb2\xb8\x7c\x74\x98\x43\xad\x5a\xe8\x5b\x5d\xa9\x0b\xbe\x10\x74\xd8\x67\x9c\xcd\xe8\x5c\x0b\xb2\xde\xb9\x96\x30\x6d\x45\x37\xef\x72\x9d\xde\x33\xe7\x2d\x89\xfa\x42\xb4\xfa\x95\x2f\x65\xae\x8c\xf3\x16\x56\x9b\xaa\x5f\x9c\x5a\x92\x96\xe2\x08\x8e\x35\x1b\x0a\xeb\x81\xa0\x13\xdf\xea\x76\x79\x6b\x78\x0b\xb1\xa8\xdd\x5d\xec\x1b\xc3\xb6\x76\xc7\x37\xd1\xa7\xc9\x53\x63\xe7\xde\x85\x57\xbf\x4e\xee\xed\x1f\xbd\x3d\xe2\x5e\x31\x9f\x12\xf9\x72\xdf\x2b\xf7\x30\x13\x8f\xb5\xab\x81\x9d\x51\x69\xac\x0f\x9c\xc6\xef\xb7\xe3\xed\xad\x3e\xdb\xc3\xdf\x76\xc6\xdf\xc8\xd9\x5d\xef\x35\x9e\x3e\xee\xe4\x7f\x14\xc7\x98\x2b\xc2\x76\xaf\x71\x65\x77\x69\x37\x89\x3f\x54\x50\x1a\xf7\x7c\x5d\x64\x9a\xf6\x6c\xaf\x55\x69\xd4\x7e\x3e\xd9\x19\x95\x28\xcf\x05\x5f\x3e\xb5\x56\x8a\x51\x24\xfd\x46\x91\xb9\x22\x4c\xb7\x94\xed\xf0\xce\x99\x96\x8a\x67\xe7\x44\x91\x1a\x7c\x79\xfe\xa8\xde\x92\xf2\xf5\xe9\xef\x5e\x2d\xfe\x80\x99\xd9\xa4\xc8\xb7\xc8\xcc\x51\xd3\x51\xc0\xc6\xf5\xa2\xc2\x32\x51\x5c\xac\x9d\xb6\x27\x27\x35\xe1\x92\xc7\x85\x59\x78\x31\xc8\x72\x2e\x94\xbd\x74\x82\x4c\x7b\x53\x7b\x79\xef\xb9\xd7\xe3\x07\x05\xf4\xfa\xfd\xaf\x3a\x99\xdd\x56\xcf\xda\xe5\x8d\x95\x3d\xad\x57\xef\xd8\x31\x67\xd2\x9c\xdc\xc8\xbd\x2c\x1f\x62\xec\x9b\xb6\xab\xd7\x5f\xf0\x0c\xff\x26\x70\x4e\x39\xfb\x6b\xd9\x45\x8d\xed\xd7\xc3\x9f\xfc\x25\xc5\x7b\xff\xe5\x97\xca\xd6\x43\xd9\xa4\x39\x6d\x54\xeb\x9f\x91\x8a\xff\xde\xa9\x3e\xad\xe3\xd6\xa0\xed\xd4\x56\x6b\x32\x63\x0f\x0e\xfe\x1b\x00\x00\xff\xff\xe5\x48\x15\xb7\x34\x25\x00\x00") +var _assetsPipelineYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x5a\xff\x6f\x1a\x3b\x12\xff\x9d\xbf\x62\xca\x55\x8a\x94\xbb\x85\x90\xa4\xed\xeb\x4a\xf7\x4e\xdb\x40\x73\xe8\x48\x40\x40\xda\x93\x4e\xa7\xc8\xec\x1a\xf0\xcb\xae\xbd\xf2\x17\x28\xd7\xe6\x7f\x3f\xd9\xfb\xcd\xbb\x7c\x4d\x9b\x36\xe9\x7b\x8f\x9f\xc0\x33\x9e\x19\xcf\x67\x3c\xf6\x0c\x76\x1c\xa7\xe6\x7d\x1c\x8d\x71\x14\x87\x48\xe2\xf7\x8c\x47\x48\x7e\xc0\x5c\x10\x46\x5d\x38\x3a\x3d\x69\x9d\x38\x27\x6f\x9d\x93\xb7\x47\xb5\x36\x16\x3e\x27\xb1\x34\x94\xab\x1b\x88\x49\x8c\x43\x42\x31\x28\x41\xe8\x0c\x2e\x58\x80\x07\xd9\x10\xa2\x81\x19\x78\xa7\x48\x18\xc0\x94\x71\xf0\x19\x95\x84\x2a\xa6\x04\x04\x38\x24\x0b\xcc\x57\xb5\x01\xe2\x28\xc2\x12\x73\xe1\xd6\x00\x2e\x89\xfc\xa7\x9a\xdc\x08\xcc\xf5\x2f\x80\xf1\x2a\xc6\x2e\x8c\x24\x27\x74\x66\x06\x4a\x06\x24\xdc\xa0\xd9\xf3\xb9\x43\x1c\xb3\x07\xcc\xd5\xec\x20\x19\xc4\x2a\x0c\x61\xca\x59\xd4\x80\x3e\x0d\x57\x20\xe7\x18\xae\x51\x84\x1b\x40\x99\x34\xbf\x6e\x86\xbd\x5c\xc9\x3b\x8e\xa8\x3f\x7f\x80\x9a\x64\x42\x4a\x9e\x22\x15\x4a\x17\xea\x11\x12\x12\xf3\x7a\x2e\x75\xcc\xee\x30\x4d\x84\x5e\xb3\x8e\x3f\x67\x2e\x48\xae\xf0\x01\x5a\x46\xd8\xe7\x58\x36\xa0\x2b\x21\x22\xb3\xb9\x84\x90\xb1\x3b\x10\x2c\xc2\x72\xae\x71\x09\xc9\x1d\x86\xb7\x93\xd6\x2f\x6f\x51\xeb\xf5\xab\xf3\xd7\xe7\x67\xaf\x4e\x4f\x5f\xbd\x6e\x4d\xdf\x4c\xce\xf0\x24\x38\x3f\x47\xad\x57\x67\x2d\x74\x7e\xfa\xcb\x1b\x34\x85\xbe\xa7\xe4\xdc\x18\x03\x4b\x22\xe7\x80\x7c\x1f\x0b\xa1\xbd\xa4\xbd\xd5\x80\x4b\xe3\xb1\xb9\x94\xb1\x70\x9b\xcd\x19\x91\x73\x35\x69\xf8\x2c\x6a\x0a\x2c\x25\xa1\x33\xd1\x94\x7a\xb2\xa8\x01\x18\xec\x8d\xed\xdb\x56\x91\x39\xa3\xd7\xbd\xbe\xf9\xf7\xed\x45\xff\x7a\xec\x75\xaf\x3b\xc3\xfa\xfa\x22\xc7\x73\x0c\x13\x13\x4b\x3a\x8e\x10\xa1\x98\x83\x5c\xc5\x58\x1b\xa3\x04\x36\x11\x66\xe8\x7a\xc5\x1a\x31\x14\xc7\x99\x09\x17\x2c\x8a\x95\xc4\x07\x59\xf2\xee\xa6\xdb\x6b\xdf\x5e\x76\xae\x3b\x43\xaf\xd7\xba\x1d\x5d\x79\xbd\xde\x1e\x73\x8c\xf0\x83\x8d\xe9\x46\x68\xb6\xdf\x0c\xb4\x14\x4d\x9f\x05\xd8\x48\x69\xaa\x89\xa2\x52\x39\x13\x24\xb0\xdb\x3a\x6f\x9c\x9c\xef\xb4\x88\x68\x0d\x7b\x4c\x19\x63\x21\x7f\x20\x32\x12\x0b\xb9\x6e\xc0\x93\xe0\xb2\xd9\x94\x27\x40\x65\xdd\x90\x2b\xf5\x03\x21\x89\x94\x76\x51\x84\x68\x20\x8c\xea\x27\x01\xa3\x6a\xc4\xd7\xc0\x10\x30\xff\x0e\x73\xb7\xd5\x68\x9d\x36\x5a\x0f\x83\xa0\xaa\xbe\xcd\x96\x34\x64\x28\x78\x87\x04\x56\x3c\xdc\x6b\xc8\xa6\x2c\x28\x71\x18\x92\x19\xa6\xb2\x19\xa9\x26\xc7\x21\x46\x02\x8b\x66\x90\x4a\xde\x66\x1f\x12\x18\x14\x0f\xb5\x71\x84\x0a\x89\xc2\x50\x1b\xa7\x8f\xa4\x92\x61\xd9\xb9\x7c\xc0\xa9\xa0\xc5\x2e\x12\x76\x60\x53\x2d\xcd\x92\x4d\x68\x71\x38\x97\x14\xbc\x27\xe1\x7e\xff\x47\xca\x09\x09\x55\x9f\x1c\x14\x05\xaf\xb7\x45\x3d\x45\x11\xd6\x8a\x75\x78\xeb\xa5\x90\xd0\xb8\x3e\x73\x84\x65\x4d\x2d\x97\x6d\xec\x11\x31\xf6\x0f\x5d\x60\x90\xcc\x4b\xf0\xd5\x13\x53\x74\xcd\x9a\x76\xae\xc5\x12\x34\x40\x72\xae\xe7\x45\xaa\xb1\x8a\xc2\xbf\x01\xc7\x21\x92\x64\x61\xcc\x2d\xae\x15\x6b\x3e\xd0\xcc\xf5\x34\x7d\x74\xe8\xe2\x10\x4d\xd7\xa9\x4f\x22\x05\x98\x2e\x08\x67\x34\xc2\x54\x1a\xb7\xe0\x38\x64\x2b\xfd\xcd\x4a\x0d\x15\x95\x01\x5e\x68\x7d\x03\xce\x82\x47\xd6\x17\x73\x16\x28\x5f\xcf\xa9\xa8\x2c\x08\xf5\xda\x10\x0b\xa6\xb8\x8f\xcd\x55\x2d\x8f\x9e\x21\x2b\x3b\xd9\xfb\x38\x72\xdd\xae\x77\xe5\xba\x9a\x62\x08\x03\xce\x62\xcc\x25\x49\x66\xea\x8f\x27\x84\x8a\xb0\x66\x18\xb0\x90\xf8\xab\x36\xf3\x95\xb6\x2c\xa3\x03\x8c\x24\x92\xb8\x3c\xe4\x40\x67\x3a\xc5\xbe\x74\xc1\x0b\x43\xb6\xcc\xc7\xb5\x02\x42\x7d\x12\xa3\xd0\xb5\x06\x01\x46\x98\x2f\x88\x8f\xcb\x83\x0e\xe4\x99\xa3\x81\x22\xf4\x3f\x46\xd1\x52\xe8\x9d\x6b\x71\x79\x66\xc5\xf6\x3c\x07\x84\x14\x6e\x61\x76\x4a\xd2\x91\xe3\x42\xbd\x59\xcf\x7e\xeb\xd5\x58\xeb\x74\x92\x91\x95\xc6\xc1\x2d\x14\x3b\x22\xb1\x2c\x57\xb0\xcd\x0b\x1b\xfd\xb0\xcb\x13\xb9\xed\x50\x3f\xae\x97\xc6\x33\xf4\xaa\x94\xd2\x4d\xff\xd4\x69\x9d\x38\xad\x37\x47\x29\xc0\xd9\x65\xfe\xe7\xc4\x38\xab\x4e\x9e\x02\xe6\x4c\xf7\xb7\x23\xbd\x6e\x64\x29\x86\xdd\xe3\x83\x50\x86\xad\x7e\xdd\xa6\x41\x9c\xb9\x97\x58\xf6\x27\xbf\x61\x5f\xee\x20\xa5\xe1\xb3\x91\xe3\x9d\xf2\xef\x70\xc6\x91\xe5\xa7\xef\x62\xe9\x40\x6d\xb2\x34\x57\x51\x99\x80\x38\x75\xd1\x52\xb8\xe2\xcc\x75\x5d\x1b\xad\xe3\x6f\xb5\xe3\xb8\x0a\x53\xc8\x54\x30\x35\x15\xb5\x9e\x50\x25\x63\x5f\xac\x8d\x11\x14\xb9\x03\x24\x84\x15\x7e\x5f\xeb\xb0\x5d\x5b\xdb\xe4\x6e\x8f\x4b\x32\x45\xbe\x5c\xdb\xdb\x39\x87\xeb\x0e\x38\xcb\x1d\xdb\xc6\x31\xa6\x81\xe8\x53\xb7\x9c\xff\xb7\x64\x80\x64\x3b\xbc\x18\xa9\x09\xbc\xfc\x6c\xe4\x8e\x24\xf2\xef\xf4\xf0\xbd\x83\x52\xdd\x29\x6f\xe9\xd8\x4a\x7a\x06\x19\x87\xb9\x05\x41\xb2\xf8\x94\x3b\xdd\xf5\x26\x2f\xc1\x8b\x4b\x2c\x3d\x29\xcb\x26\x35\x3c\x9e\x05\x65\xb6\x4a\x51\x00\x96\x2c\xf5\xa2\xdf\xee\x0c\xba\x83\x4e\xaf\x7b\xdd\x49\x49\x9d\xe2\x94\xac\x72\xbf\x18\xe2\x69\x51\xd1\xe6\x44\xfb\xda\x6c\xf1\x58\xc3\x39\x6b\x72\xb9\xcd\x1c\x52\x14\x83\xf7\xd9\xaa\x2a\xd1\xba\xd5\xca\x31\x89\x30\x53\xb2\x4b\xaf\x08\x55\x12\x0b\x17\xce\x4e\x6c\x5c\xd7\x6e\xd1\x3f\x0a\x54\x73\xc1\xde\x8e\x68\x72\xff\x36\x70\x56\xe0\x7f\x32\x40\x93\x92\x6b\x07\x9a\xa5\xc2\x68\x0b\x94\x69\xdd\x72\x9f\x93\x2d\xad\x1f\x10\x27\x68\x12\x62\xcb\x58\x70\x52\x2f\xb6\xfb\x17\xff\xea\x0c\x6f\xbd\x41\xf7\xf6\x43\x67\x38\xea\xf6\xaf\xed\x7d\xfd\x01\x85\x0a\xbb\xd0\x6a\x9c\x9e\x3f\x34\x40\xd2\x56\xc3\x48\xdf\xa4\x13\x23\xbf\x58\x92\x17\x59\x5e\x38\x69\xb4\xac\xe1\x78\xae\x2b\x95\x72\x56\x4b\xce\x98\xd2\x10\xe4\x25\x53\x75\xdc\x64\x3c\x5d\xc4\x38\xa2\x67\xbc\x52\x29\xa7\xee\x9b\x0b\x7b\x34\x4d\x4f\xf7\x4d\x7b\x50\xdf\xd9\xef\xc1\x61\xd0\x54\x82\x37\x27\x84\x36\x23\xb5\x49\xcd\x3c\x62\x01\xfc\x95\x7f\xda\xc3\x17\x29\x70\x7c\x63\x4b\x22\x58\x2c\x7c\x88\x95\x98\x83\x23\x21\x44\xfa\x9a\x6d\x4d\x42\xeb\x71\xa5\x3f\xba\x72\x59\x5b\xab\x53\x08\xdd\xbd\x29\xdb\xe6\xa2\xed\xf9\x3e\x8e\x25\xa2\xfe\x93\x6c\xcb\xe4\xb2\xef\xa0\xdc\x88\x4d\x5b\x34\x31\xb4\xa8\x91\xb5\x73\xec\xaa\xe1\xf7\xb8\x53\xbf\xef\x86\xb2\x9c\x77\xbb\xd8\x90\x06\xf4\x9e\x0b\x11\xa1\x12\x7f\x92\xd5\xf8\x6a\x77\xde\x7b\x37\xbd\xf1\xad\xe9\xb4\x8c\x06\x9d\x0b\x17\xea\x2f\x3f\x57\xcb\xe4\xfb\xfa\x1f\x6c\xff\x62\xba\x00\x15\xc3\xcb\xcf\x69\xd1\x7d\x0f\x5f\xbe\x00\xf6\xe7\x0c\xea\xa3\x3b\x12\xc7\x84\xce\x40\xc5\x01\x92\xa6\xec\xb5\x00\xa8\x1f\x98\x1c\xd2\xba\xd8\x52\xb0\x29\x53\x58\x12\xb4\x45\x62\xce\x96\xa5\x29\x53\xf8\x4d\x30\x0a\xbf\x6a\x6a\x43\x7f\xdd\x34\x77\x51\x74\x2d\x1c\x2d\xbf\xb1\x8a\xc2\x62\xc8\xfc\xca\x17\xf7\x72\x2d\x1e\xea\xf0\x6b\x99\xf9\xab\x13\xd9\xd1\xf1\x71\xf3\xf8\x68\x77\x16\xd3\x4b\x7b\xda\x1c\xa6\x3d\xb4\x27\x83\x69\x23\x81\xd0\xa4\xa1\x9a\x73\x3e\xa7\x1c\x96\x75\xdd\x77\x64\xb1\x4a\x5f\x7c\x4b\x1e\xcb\x5b\xd6\x8f\x75\x77\x4c\xb2\xff\x20\x6f\xf7\x3c\xe1\x31\x55\x69\x46\xed\x39\xa6\x34\xf7\x73\x82\xf8\xcf\x63\xea\xcf\x63\xaa\x38\xa6\xd2\x5e\xed\xf7\x3b\xa6\x72\x05\x87\x1f\x53\xc5\x94\x07\x1f\x53\x7a\xb3\x3d\xf7\x63\xea\x69\x73\x98\x39\xa6\x76\x67\x30\xfb\x98\x2a\x38\x9f\x53\x0e\x7b\xbe\xc7\x54\xd6\x91\xde\x08\x6d\x4e\x74\xb3\x6f\x5b\x10\xd4\xae\xf3\x38\x2d\x3b\xd5\x6e\x76\x5b\x7e\x1d\x49\x34\xb3\x3b\xbd\x09\xfa\x23\xbb\x1b\x95\x75\x92\x85\xdd\xc7\xee\xd2\x58\xc9\x02\x14\xf8\xcf\x7f\xad\x88\xde\x28\x24\x13\xa3\x57\xd5\xad\x64\xc3\x0b\x24\xf1\x8c\xf1\xd5\x86\x49\x00\xfd\x25\xc5\xdc\x85\xf1\x9c\xf0\x60\x80\xb8\x5c\x95\xa8\x45\x17\xb0\x75\x54\x22\x0c\x38\x5b\x90\x40\xcf\x4c\xfe\xe4\xb2\x88\x7d\x25\x4b\xd6\x5b\xa4\xb2\x07\x12\x46\x8b\x7c\xc1\xe8\x94\xcc\x14\x47\xeb\x1d\xd2\xd4\x4c\x13\x3e\xc5\x4b\x9f\x12\x8b\x79\xbe\x63\x73\xe4\x7f\xbc\x65\x9f\xf4\xed\x8d\xcd\x63\xbd\xae\xc9\x55\xe5\x6f\x58\x4a\x9c\x66\xc4\x62\x1c\x2a\xda\xe7\xc6\x03\xad\x0a\xbe\xd9\x1f\xa3\x5b\xe1\x4d\xd8\xbc\x72\x17\xeb\x50\x0c\xcb\xd2\x2d\xdf\x78\x1f\x47\x0f\xc5\xce\xfe\x1b\x37\xfb\x54\x62\xef\x70\xf4\x0e\xc0\x3d\x23\x3e\x04\xf9\x34\xd9\xa6\x89\x53\xe3\xb1\xd6\x82\xde\x89\x4a\xa1\xbd\x6b\x35\x18\x7f\x1e\x6f\x6f\xf5\xd9\x01\xfe\x36\x2b\x7e\x24\x67\x57\xbd\x57\x78\xfa\xb4\x12\xff\x5e\xb5\xd0\xda\xb1\x09\x92\x1b\xf9\x4f\x07\xca\xd7\x6f\x81\x6a\x4b\xef\xdb\xd0\xa9\x4a\x3b\x70\x27\x8c\xcb\x57\xbd\x9f\xc3\xe7\x7b\x3d\x77\x80\xf7\xcb\xad\x88\x6f\xf3\x7d\x59\xd6\x41\x3b\x63\x50\xbd\xdb\xed\x3a\x1e\xe2\x98\xb3\xc5\x43\xf3\x55\x32\x0b\x85\x8f\x84\xd4\x15\xa2\xaa\x24\x6c\x87\x87\x2e\x94\x90\x2c\x6a\x23\x89\x72\xe3\xd3\x52\x23\x7b\x37\xb2\x76\xb5\x85\x3d\x71\xfa\x07\xcd\x0e\x45\x98\x3c\x46\x76\xd8\x10\x74\x9b\x62\xf4\x77\x91\x1d\x76\x78\xee\xc0\xec\xf0\x58\xbe\x2f\xcb\xda\xe8\xf9\xb3\x4a\xf5\x35\x92\x8c\xaf\x95\x37\xa3\xb3\x7c\xa0\xc7\xfc\x44\x35\xbc\xe8\x46\x31\xe3\xd2\xfc\xc9\x07\x91\x72\x26\xe6\xb1\x84\x63\x3f\x47\xa8\x25\xe6\xe7\xef\xad\xb2\x0a\xe5\x26\x7b\x99\x98\xfe\x43\x68\x8a\xad\xec\x29\xa2\xcf\xa8\xd0\x15\x0c\x5a\x8a\xf4\xe1\x8b\x79\x96\x68\xcb\x6d\xce\x59\x84\xff\xc1\xf1\x8c\x30\xfa\xf7\xb4\x84\x1d\x9a\x5f\xf7\x7f\x69\x2e\x08\x5e\x36\x5f\x7e\xce\x74\x25\x85\x5b\xa9\x7e\x2d\x3d\xf3\x4f\xde\xc5\x67\x3f\x8d\xf3\xd6\x4c\xdb\x29\x2d\x97\xa4\xe7\xd6\x6a\xff\x0f\x00\x00\xff\xff\x3d\xba\x2c\x4f\x8e\x30\x00\x00") func assetsPipelineYmlBytes() ([]byte, error) { return bindataRead( @@ -129,7 +150,7 @@ func assetsPipelineYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/pipeline.yml", size: 9524, mode: os.FileMode(420), modTime: time.Unix(1486102842, 0)} + info := bindataFileInfo{name: "assets/pipeline.yml", size: 12430, mode: os.FileMode(420), modTime: time.Unix(1488953817, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -154,7 +175,7 @@ func assetsRepoYml() (*asset, error) { return a, nil } -var _assetsServiceYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xe4\x58\xdf\x6f\xdb\xb6\x13\x7f\xf7\x5f\x71\x35\xbe\x80\xbf\x2b\xe6\xd4\x49\xb1\xa1\xe1\x9b\xe3\x38\x4b\xb6\x38\x30\x62\x27\x7d\x28\x8a\x82\xa1\xce\x16\x11\x8a\xd4\x48\x2a\xa9\x3b\xf4\x7f\x1f\x48\x4a\x32\x25\x2b\x99\xb3\xae\xc0\x80\xe5\x29\xe2\x1d\xef\x73\xbf\xef\xe8\xe1\x70\xd8\x1b\xbf\x5f\x2c\x31\xcb\x05\xb5\x78\xa6\x74\x46\xed\x2d\x6a\xc3\x95\x24\x30\x38\x1a\x1d\x8e\x86\xa3\xe3\xe1\xe8\x78\xd0\x3b\x45\xc3\x34\xcf\xad\xa7\xcc\x6e\xc0\xa0\x7e\xe0\x0c\x81\x4b\xa0\x60\x72\x64\x7c\xc5\x19\xa0\x7c\xe0\x5a\xc9\x0c\xa5\x05\xa6\xa4\xa5\x5c\x72\xb9\x06\x2a\x61\x7c\x79\x02\x96\xea\x35\x5a\x58\x6b\x55\xe4\xf0\xc8\x6d\x0a\xd3\xc9\xa2\x96\x44\x65\x02\x96\x9a\x7b\x48\x70\xc5\x25\x77\x48\xbd\x39\xd5\x34\x43\x8b\xda\x90\x1e\xc0\x22\x70\x5e\xd1\x0c\xdd\x27\xc0\x72\x93\x23\x81\x85\xd5\x5c\xae\xfd\x41\x43\x4b\xc7\x07\x6a\x55\x01\xf4\x00\x2e\x32\xba\xc6\x1b\x2d\xf6\xb9\x7d\xaa\xd8\x3d\xea\x70\x05\x6e\xae\x2f\xb7\xf8\x73\xa5\xed\x3e\x12\x1c\x1f\xe0\xe7\x5c\x19\x4c\x9c\x9f\xb6\x7a\x38\xc6\x15\x2d\x84\x25\x30\x78\x37\x7a\x37\x1a\x6c\x85\x9f\x23\x15\x36\x9d\xca\x24\x57\x5c\xee\x05\x53\xf1\x82\x55\x60\xd1\xd8\xda\xa1\xa9\x17\xd5\x82\x7b\x13\x4e\x23\xc4\x49\x5e\xec\x03\x33\x99\xdf\x40\x21\xb9\x35\x0e\x47\xa3\x03\x41\x58\x29\x5d\xc5\x19\x75\x0b\xe9\x30\x36\x6b\x86\x99\xd2\x9b\x7d\x70\x02\xa7\x03\xa1\x42\x28\x46\x2d\xba\xff\x1d\x08\xa7\x12\x35\xfc\x9f\x4b\x98\xf1\x93\x1f\x5a\x68\x6f\x47\x31\xdc\x29\x1a\xae\x31\x99\xa8\xa2\xe9\xc3\xab\x22\xbb\xdb\x51\xf4\x68\xd0\x91\x3b\x9e\xd1\x65\x4f\x6d\x9e\x37\x5c\xd0\x42\xb2\xd4\x45\x73\xa3\x0a\x1d\xe7\xef\x41\x0f\x60\x4e\x6d\x3a\xa7\xd6\xa2\x96\x31\xea\x44\x65\x19\x3d\x45\xc1\x33\x6e\x31\xb9\xe4\xc6\xee\x02\xba\x53\x07\x97\x53\x9b\x06\x17\xab\x22\x98\x6e\x53\x8c\x31\x62\xcd\x9d\xe2\xee\x22\x4a\xd4\xd7\x85\xc0\xb9\xe6\x4a\x73\xbb\x79\xd2\xe4\x08\x70\x99\x22\xe4\x25\xbf\x03\x76\x30\xba\x10\x08\x77\xe8\x4b\x36\x49\x30\xa9\xe0\x45\x89\xd1\x84\x3f\xec\x01\xdc\xe6\xec\x22\x79\x49\x31\x3a\x71\x0f\x54\x14\xde\x34\x9e\xe5\xae\x46\x5c\x16\xb9\x73\x2f\xac\x07\x30\x65\x66\x22\x0a\x63\x51\xff\x13\x92\xa7\xcc\x40\x29\xce\x51\x12\xcc\x85\x72\xf9\x75\x10\x90\xa6\xe2\xee\xdc\xda\xbc\x72\xe3\xb8\x19\xba\x6f\x31\x67\xac\x65\xc5\x32\xbd\x3c\xa9\x9d\xe8\x53\xdb\x5a\xca\x52\x4f\x6a\x34\xc5\xb6\x56\xe6\xdf\xa4\x56\x33\xf3\x26\x4a\x26\xbe\x49\xfb\xd6\x7c\x4e\xcd\x4e\xf2\xf7\xcf\x24\x21\x57\xca\xf6\xc3\x27\xc0\x30\x1c\x4d\x7f\x2f\xa8\x30\xf5\x69\x7d\xfe\xab\xe2\xb2\x4f\xe0\x03\xf4\xfb\x3f\xc2\xab\x6b\x5c\xc5\xf5\xf4\x31\xe2\xf6\x79\x7f\x4e\xdb\xa1\x8b\x50\xc7\x32\x29\xe5\x0f\x77\xd5\x78\x52\x0f\x18\xc2\xab\x45\x71\x07\xff\xfb\xa3\x33\x2f\xbe\xc6\x8c\x83\x41\x29\xfe\x55\xed\x88\x96\x13\x1a\x3a\x9a\xef\xaa\xa4\xf9\x06\x2d\xaf\xd1\xa8\x42\x33\xf4\x61\x9c\x32\x53\x76\xd0\x38\xdb\xc6\xef\x17\x84\x4c\x27\x0b\x42\x16\xd1\x08\x9b\x6b\x95\xa3\xb6\x3c\xdc\x74\x7f\x8d\xa2\x75\x7f\xce\x80\x0b\x9f\x7d\xb7\x2e\x19\x49\xa4\x79\xc9\x5b\x69\xdb\x68\xd8\x21\xf8\x1d\xad\xbc\x66\x76\x25\xec\x96\x8c\x89\x92\x2b\xbe\x2e\x34\xf5\xe9\x5f\xe3\xce\xe8\x67\x9e\x15\xd9\x1c\x35\x43\x27\xef\x70\x34\xda\xd2\xb8\x74\xb4\x30\x67\x37\x35\x4b\xc5\x70\xa9\x68\x72\x42\x05\x95\xac\x5c\x3a\x2a\x57\x7a\x5b\x56\x51\x1c\x60\xb8\x1b\xf0\x2d\x69\x52\x8d\x0d\xbf\xad\x34\x2c\x72\x27\x11\x2f\x6c\x79\xfd\x66\xd1\xe0\x75\x27\x0d\xde\xa5\xaf\xcb\x5f\x5c\x59\xba\x9e\x10\x98\x43\x26\x44\xa4\x86\x2a\x9e\xc5\xc7\xf0\x4a\xf9\x38\x94\xd4\x6b\x25\xb0\x15\xab\xfd\xed\xab\x70\x4b\x3d\x9d\xac\xbd\x40\x97\xd4\xdc\x9f\xd6\x0b\x5e\xa9\xff\x8c\x33\xad\xca\x19\xd7\x64\x68\xe4\xe4\x56\xe1\x28\x2f\x2f\xc6\x33\x42\x6a\xf8\x3a\xcd\x49\x97\xf2\xbb\x29\x3b\x36\xa6\xc8\xbc\xe0\xb9\x12\x9c\x6d\x4e\x15\x2b\x5c\x62\x6d\xfd\xb0\xb0\xd4\x62\xf3\x68\x08\xd3\xd5\x0a\x99\x25\x30\x16\x42\x3d\x46\x76\xcf\x35\x97\x8c\xe7\x54\x90\x46\xd0\x1a\x35\xb5\x95\x82\xcc\x1c\xd0\x8c\x7e\x51\x92\x3e\x9a\x03\xa6\xb2\x88\x3e\x66\xcd\x94\x76\xfc\xc6\x1a\xb2\x55\xb8\x24\x39\x1b\x09\xf4\xdf\xf4\xab\x6f\x67\x47\x64\xe1\x30\x9c\x6c\x42\x1e\x22\x33\xc3\x78\x0f\xad\x6f\x74\x58\xde\x69\xfb\x73\xd6\x77\x69\x1d\xec\x3c\x22\xe3\xc2\xa6\x4a\xf3\x2f\xb8\x40\x56\xb8\x9d\xc3\xe7\xe9\x85\x5c\x6b\x34\xa6\x83\x3f\x8c\xb5\x3b\x7c\xdd\xa6\x09\x6a\x2c\x67\x42\xd1\xe4\xce\x97\x29\x97\x6b\x72\x8a\x1a\xd7\xae\x05\xea\x0b\x69\xac\xab\x5d\x73\xa6\x55\x16\xd7\xf2\x7e\x62\x5e\x80\x79\xdd\x46\x7c\xcf\x6d\xfa\x72\xc4\x4a\xf1\x50\xbc\x3b\x9e\x78\x4e\xcd\xa8\xde\xff\xc6\xbd\xf3\xed\xeb\x60\x4f\x4b\xbb\x54\xac\xa6\x07\x81\xfe\x6b\x97\x81\x4f\x57\x72\xf7\x40\xd9\xa9\xf6\xce\xb9\x52\xf5\xc7\x2d\x67\x94\xde\x7f\xd1\x60\xdd\xf3\xa6\x41\x9d\xe4\x45\x4d\x9c\x1a\x83\xee\x61\x21\x08\x0c\xac\x2e\x70\x50\x53\xfc\xbb\xaf\xbc\x58\x3d\x1b\xb7\x33\x24\xbc\x66\x1a\x62\xc3\x59\x54\x54\xda\xce\x68\x9e\x73\xb9\x6e\x8c\x91\x73\x65\x6c\x68\xf3\xa3\xc8\x91\xfb\x4d\x80\x5b\x25\x8a\x0c\x0d\x81\x0f\x1f\x3b\x77\x57\xf7\x04\xd8\x75\x73\x08\xe9\x36\x33\xb9\x5c\xdf\x1e\x11\x12\x5f\xea\x68\x9d\x2d\xd1\x4f\xb5\x4f\xd6\x0a\x46\x00\x5e\x29\xfd\x48\x75\x52\x1b\xf8\xa2\xa1\xd5\x5c\x2c\x83\xd8\x33\x8e\x22\x21\xfe\x71\x34\xcc\x5b\xd3\xc8\x4f\x17\x43\x76\x36\xc6\x6a\xa0\xb7\x37\x68\x78\x7e\x31\x79\x7a\xef\xab\x5f\x57\x01\xaa\xeb\xe1\xd5\xbd\xbc\x7f\x87\xb8\x98\xff\x6c\x60\xba\x76\xdd\x97\x44\x26\xb2\x6b\xef\x98\xb4\x7d\xf1\xd2\x2d\x23\xf4\xda\x49\x8a\xec\xfe\x42\x5a\xd4\x0f\x54\x2c\x90\x29\x99\x18\x02\x3f\xed\xf2\x84\x91\x1e\x37\x81\xe6\xaf\x42\x1d\x37\xb4\xb2\x8a\x29\x41\xe0\x7c\xb9\x9c\xef\xd2\x97\x3c\x43\x55\xd8\x1a\xf4\x6d\x83\x65\xb3\x4c\x35\x9a\x54\x89\x6a\xff\x3e\x2a\xc9\x33\x6a\x59\x1a\x2f\xf4\x2e\x00\x13\x95\x20\x81\xa3\xd1\x68\x78\x74\x7c\xdc\xdb\x76\xbb\x27\xdb\x56\xa7\x72\x4b\xba\x8e\xd2\xe8\x37\xdc\x84\xc7\x6b\x33\x79\x48\xb4\x46\x2e\x2c\x65\xf7\x11\x4b\x9c\xba\xd6\x6a\x7e\x57\x58\x6c\x4b\x4c\xca\xf1\x1a\xde\x07\x9f\x12\x14\x74\x73\x60\x83\x2f\x3e\x99\xe0\x8c\x36\xe2\xcf\x55\x5f\xbe\x91\x69\xb7\x77\xaa\x88\x45\xbf\x7d\x3c\x97\xba\x9e\xed\x6b\xef\xcf\x00\x00\x00\xff\xff\x7d\xf2\x1a\xe7\x6c\x15\x00\x00") +var _assetsServiceYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xe4\x58\x5f\x6f\x23\xb7\x11\x7f\xd7\xa7\x98\x18\x07\xb8\x0d\xba\x8e\xce\x41\x8b\x1c\x81\x3e\xe8\x64\x5d\xec\xd6\xbe\x08\x96\xec\x3c\x14\x45\x40\x73\x47\x12\x61\x2e\xb9\x25\xb9\xf6\xa9\xc2\x7d\xf7\x82\xe4\xfe\x21\x57\xab\xb3\xdc\x6b\xda\xa2\xd5\x93\x96\x33\x9c\xdf\xfc\xe7\x90\x59\x96\x8d\x26\x3f\x2f\x96\x58\x94\x82\x5a\xfc\xa0\x74\x41\xed\x3d\x6a\xc3\x95\x24\x70\x7a\x3e\x7e\x3b\xce\xc6\xef\xb2\xf1\xbb\xd3\xd1\x05\x1a\xa6\x79\x69\x3d\xe5\xe6\x0e\x0c\xea\x27\xce\x10\xb8\x04\x0a\xa6\x44\xc6\x57\x9c\x01\xca\x27\xae\x95\x2c\x50\x5a\x60\x4a\x5a\xca\x25\x97\x6b\xa0\x12\x26\xd7\xef\xc1\x52\xbd\x46\x0b\x6b\xad\xaa\x12\x9e\xb9\xdd\xc0\x6c\xba\x68\x25\x51\x99\x83\xa5\xe6\x11\x72\x5c\x71\xc9\x1d\xd2\x68\x4e\x35\x2d\xd0\xa2\x36\x64\x04\xb0\x08\x9c\x1f\x69\x81\xee\x13\x60\xb9\x2d\x91\xc0\xc2\x6a\x2e\xd7\x7e\x21\xd1\xd2\xf1\x81\x5a\x35\x00\x23\x80\xab\x82\xae\xf1\x4e\x8b\x63\x76\x5f\x28\xf6\x88\x3a\x6c\x81\xbb\xdb\xeb\x0e\x7f\xae\xb4\x3d\x46\x82\xe3\x03\xfc\x54\x2a\x83\xb9\xf3\x53\xa7\x87\x63\x5c\xd1\x4a\x58\x02\xa7\x3f\x8c\x7f\x18\x9f\x76\xc2\x2f\x91\x0a\xbb\x99\xc9\xbc\x54\x5c\x1e\x05\xd3\xf0\x82\x55\x60\xd1\xd8\xd6\xa1\x1b\x2f\xaa\x07\xf7\x5d\x58\x8d\x10\xa7\x65\x75\x0c\xcc\x74\x7e\x07\x95\xe4\xd6\x38\x1c\x8d\x0e\x04\x61\xa5\x74\x13\x67\xd4\x3d\xa4\xb7\xb1\x59\x37\x58\x28\xbd\x3d\x06\x27\x70\x3a\x10\x2a\x84\x62\xd4\xa2\xfb\xef\x40\x38\x95\xa8\xe1\x37\x5c\xc2\x0d\x7f\xff\xdb\x1e\xda\xf7\xe3\x18\xee\x02\x0d\xd7\x98\x4f\x55\x95\xfa\xf0\x63\x55\x3c\xec\x29\x7a\x7e\x3a\x90\x3b\x9e\xd1\x65\x4f\x6b\x9e\x37\x5c\xd0\x4a\xb2\x8d\x8b\xe6\x56\x55\x3a\xce\xdf\xb3\x11\xc0\x9c\xda\xcd\x9c\x5a\x8b\x5a\xc6\xa8\x53\x55\x14\xf4\x02\x05\x2f\xb8\xc5\xfc\x9a\x1b\xbb\x0f\xe8\x56\x1d\x5c\x49\xed\x26\xb8\x58\x55\xc1\x74\xbb\xc1\x18\x23\xd6\xdc\x29\xee\x36\xa2\x44\x7d\x5b\x09\x9c\x6b\xae\x34\xb7\xdb\x83\x26\x47\x80\xcb\x0d\x42\x59\xf3\x3b\x60\x07\xa3\x2b\x81\xf0\x80\xbe\x64\xf3\x1c\xf3\x06\x5e\xd4\x18\x29\xfc\xdb\x11\xc0\x7d\xc9\xae\xf2\xd7\x14\xa3\x13\xf7\x44\x45\xe5\x4d\xe3\x45\xe9\x6a\xc4\x65\x91\x5b\xf7\xc2\x46\x00\x33\x66\xa6\xa2\x32\x16\xf5\xbf\x42\xf2\x8c\x19\xa8\xc5\x39\x4a\x8e\xa5\x50\x2e\xbf\xce\x02\xd2\x4c\x3c\x5c\x5a\x5b\x36\x6e\x9c\xa4\xa1\xfb\x1a\x73\x26\x5a\x36\x2c\xb3\xeb\xf7\xad\x13\x7d\x6a\x5b\x4b\xd9\xc6\x93\x92\xa6\xd8\xd7\xca\xfc\x37\xa9\x95\x66\xde\x54\xc9\xdc\x37\x69\xdf\x9a\x2f\xa9\xd9\x4b\xfe\x93\x0f\x92\x90\x8f\xca\x9e\x84\x4f\x80\x2c\x2c\xcd\xfe\x56\x51\x61\xda\xd5\x76\xfd\x4f\x8a\xcb\x68\xd5\xad\x9f\x9e\x26\x9f\xdf\xdc\xe2\x2a\x2e\xb2\x51\x8f\xf3\x92\xf6\xc3\x19\x69\x32\x91\x79\x2d\x3d\xdb\x57\xed\xa0\x6e\x0e\x75\x51\x3d\xc0\x9b\xdd\x60\xae\x7c\x1e\xed\x6b\x9b\xc1\x37\xad\x73\x7a\x8e\x49\x74\x34\xbf\xaa\x92\xe6\x2b\xb4\x1c\xdd\xa2\x51\x95\x66\xe8\x63\x3b\x63\xa6\x6e\xab\x71\x0a\x4e\x7e\x5e\x10\x32\x9b\x2e\x08\x59\x44\xe7\xda\x5c\xab\x12\xb5\xe5\x61\xa7\xfb\x25\x95\xec\x7e\xce\x82\x2b\x9f\x92\xf7\x2e\x43\x49\xa4\x7a\xcd\xdb\xa8\x9b\x74\xf1\x10\xfc\x81\xfe\xde\x32\xbb\xba\x76\x93\xc7\x54\xc9\x15\x5f\x57\x9a\xfa\x9a\x68\x71\x6f\xe8\x27\x5e\x54\xc5\x1c\x35\x43\x27\xef\xed\x78\xdc\xd1\xb8\x74\xb4\x70\xf8\x6e\x5b\x96\x86\xe1\x5a\xd1\xfc\x3d\x15\x54\xb2\x7a\x12\x69\x7c\xe9\x6d\x59\xa5\x39\xbb\x17\xf1\x8e\x34\x6d\xce\x12\x3f\xc2\x24\x16\xb9\x95\x88\x17\x3a\x5e\x3f\x6e\x24\xbc\x6e\x25\xe1\x5d\xfa\x62\xfd\xd1\xd5\xaa\x6b\x14\x81\x39\xa4\x42\x44\xda\x2f\x25\x1f\xc3\x8f\xca\xc7\xa1\xa6\xde\x2a\x81\xbd\x58\x1d\x6f\x5f\x83\x5b\xeb\xe9\x64\x1d\x05\xba\xa4\xe6\xf1\xa2\x9d\xfa\x6a\xfd\x6f\x38\xd3\xaa\x3e\xf8\x52\x86\x24\x27\x3b\x85\xa3\xbc\xbc\x9a\xdc\x10\xd2\xc2\xb7\x79\x4e\x86\x94\xdf\x4f\xd9\x89\x31\x55\xe1\x05\xcf\x95\xe0\x6c\x7b\xa1\x58\xe5\x12\xab\xf3\xc3\xc2\x52\x8b\xe9\x52\x06\xb3\xd5\x0a\x99\x25\x30\x11\x42\x3d\x47\x76\xcf\x35\x97\x8c\x97\x54\x90\x24\x68\x49\x4d\x75\x52\x90\x99\x33\x5a\xd0\xbf\x2b\x49\x9f\xcd\x19\x53\x45\x44\x9f\xb0\x34\xa5\x1d\xbf\xb1\x86\x74\x0a\xd7\x24\x67\x23\x81\x93\xef\x4e\x9a\x6f\x67\x47\x64\x61\x16\x56\xb6\x21\x0f\x91\x99\x2c\x1e\x4e\xdb\x1d\x03\x96\x0f\xda\xfe\x25\xeb\x87\xb4\x0e\x76\x9e\x93\x70\x76\x3d\xe0\xb7\x7d\x9a\xa0\xc6\x72\x26\x14\xcd\x1f\x7c\xd9\x71\xb9\x3e\xc0\xdc\x74\x2a\x02\x27\xdf\x9e\x7c\x9d\x42\xc3\xa0\x1a\xd7\xae\x91\xea\x50\x48\xe6\x35\x9a\x46\xb5\xf7\x4f\xec\xbb\xec\xc6\xf7\x17\xf6\xdd\x7e\x41\xc5\xbe\x7f\x66\xcc\xb8\x62\xfa\x1f\xac\x9a\xcc\x5d\x1e\xff\x23\xb5\xe3\x80\xff\x9d\x85\x63\xa2\x4c\x31\x8f\x5f\x8e\xf7\xe1\x2e\x3a\x7c\x98\xef\x75\xda\xc1\x33\xbd\x39\x9b\x3a\xce\xc8\x3d\x2f\x1c\x6e\xee\xbe\x99\x50\xa7\x65\xd5\x12\x67\xc6\xa0\xbb\xe9\x09\x02\xa7\x56\x57\xd8\xcd\x7f\xfe\x22\x5e\x6f\x6c\xee\xf1\xdd\xf9\x1d\xae\x97\x89\xd8\xb0\xd6\xb2\xec\x76\xfe\xc5\xe1\x6c\xd6\x3d\x51\x7c\xee\xe6\xa2\x68\x35\xf6\xf7\x6e\xa7\xa9\x5c\x23\xbc\x79\xc4\xed\xef\xe0\xcd\x13\x15\x40\xfe\x08\x67\xd1\xc6\xce\xe0\xdd\xce\x71\x25\x24\x80\x7a\xc4\xd9\xed\xdc\xde\x84\xb6\xdb\xa1\xcc\xa3\x95\xfe\xf7\xb5\x5a\x1f\x18\x64\x3c\xed\x42\xf3\x27\xd4\x04\xe8\xb3\x11\x6a\x1d\xe7\xc0\x4f\x65\x12\x8f\xf0\xab\xd9\x32\x3f\xcf\xa7\xa4\xd7\x4c\x64\xa9\x30\xd7\x1a\xdb\xe3\xda\xa7\xd0\xad\x5f\x19\x64\x36\x56\x23\x2d\xb2\x52\xe3\x8a\x7f\x22\xb0\x7f\xde\x68\x7b\x43\xcb\x92\xcb\x75\x32\x61\x5d\x2a\x63\xc3\x04\x34\x8e\xc4\x1e\x37\x1c\xdd\x2b\x51\x15\x68\x08\xfc\xe5\xaf\xd1\xa0\xe1\x4a\x3d\x4c\x49\x3f\xa2\x9d\x58\x1b\xb7\xc4\xb3\x89\x96\x83\xd7\x42\x77\xbb\xde\x2f\x98\xd0\x8c\xbb\xe9\x90\xcb\xf5\xfd\x39\x21\xf1\xa6\x81\x56\xda\x13\x7d\xa8\x9d\xb2\x5e\x59\x05\xe0\x95\xd2\xcf\x54\xe7\xad\x2f\x5e\x35\xfa\xa5\x77\xb6\x20\xf6\x03\x47\x91\x13\xff\xee\x90\x95\xbd\x99\xce\xa7\x83\x21\x87\xee\x5d\x7b\x97\xd3\x17\x92\xe9\xf0\xf5\xa9\x7d\xb8\x08\x50\x43\x6f\x1a\xc3\xf7\xe2\x5f\x21\x2e\xe6\xff\x36\x30\x43\x57\xc6\xd7\x44\x26\xb2\xeb\xe8\x98\xf4\x7d\xf1\xda\xa9\x23\x4c\x49\xd3\x0d\xb2\xc7\x2b\x69\x51\x3f\x51\xb1\x40\xa6\x64\x6e\x08\xfc\x7e\x9f\x27\x1c\xee\x71\xbf\x48\x1f\x5c\x07\x76\x68\x65\x15\x53\x82\xc0\xe5\x72\x39\xdf\xa7\x2f\x79\x81\xaa\xb2\x2d\xe8\xf7\x09\xcb\x76\xb9\xd1\x68\x36\x4a\x34\xb7\xd8\xf3\x9a\x7c\x43\x2d\xdb\xc4\xd7\x62\x17\x80\xa9\xca\x91\xc0\xf9\x78\x9c\x9d\xbf\x7b\x37\xea\x1a\xe3\xc1\x0e\x37\xa8\xdc\x92\xae\xa3\x34\xfa\x33\x6e\xc3\xbb\x50\x9a\x3c\x71\xd7\x5e\x58\xca\x1e\x23\x96\x38\x75\xad\xd5\xfc\xa1\xb2\xd8\x97\x98\xd7\x83\x71\x38\x9c\x7e\xc9\x51\xd0\xed\x99\x0d\xbe\xf8\xc5\x04\x67\xf4\x11\xff\xd0\xb4\xf0\x3b\xb9\x19\xf6\x4e\x13\xb1\xe8\x59\xf1\x4b\xa9\xeb\xd9\x3e\x8f\x7e\xaa\x6c\x59\x59\xaf\xe1\x4b\xe3\x4e\xf2\x5a\x76\xab\x68\x5e\xd0\xd2\x9f\x0a\xd0\x1b\x7a\x62\x27\xbd\x74\x13\x4d\x9e\x38\x06\x11\x6a\x8e\x48\xf2\xe8\x90\x65\x75\x9b\x68\x76\xfc\x23\x00\x00\xff\xff\xad\x23\x20\xf9\xa3\x19\x00\x00") func assetsServiceYmlBytes() ([]byte, error) { return bindataRead( @@ -169,7 +190,7 @@ func assetsServiceYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/service.yml", size: 5484, mode: os.FileMode(420), modTime: time.Unix(1486453729, 0)} + info := bindataFileInfo{name: "assets/service.yml", size: 6563, mode: os.FileMode(420), modTime: time.Unix(1488228215, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -194,7 +215,7 @@ func assetsVpcTargetYml() (*asset, error) { return a, nil } -var _assetsVpcYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x5a\xdf\x73\xda\x3e\x12\x7f\xe7\xaf\xd8\xd2\x9b\xe1\xbe\x33\x21\x05\x3b\xe9\x7c\xeb\x97\x1b\x4a\x48\xc3\x5d\x4a\x98\xc0\xd1\x99\x34\x7d\x90\x6d\x11\x3c\x35\xb2\xcf\x92\xf3\x63\x3a\xfd\xdf\x6f\x2c\x1b\x2c\x8c\x8c\xb1\xb1\x0d\x1d\x5e\x8c\xb4\xd2\xee\x7e\x56\xfb\x4b\x76\xbb\xdd\x6e\xf4\xbe\x4d\xa6\x78\xe9\xda\x88\xe1\x6b\xc7\x5b\x22\x36\xc3\x1e\xb5\x1c\xa2\x41\x4b\xe9\x74\x3b\xed\xce\xa7\x76\xe7\x53\xab\x71\x85\xa9\xe1\x59\x2e\xe3\x33\x5f\xff\x0b\xb3\x71\x1f\x5e\x2c\xb6\x00\x82\xd9\x8b\xe3\xfd\x04\x0f\x53\xc7\xf7\x0c\x4c\x61\x89\x08\x7a\xc2\x26\xe8\x6f\xb0\xf4\x1b\x63\xe4\xa1\x25\x66\xd8\xa3\x5a\x03\x60\x48\x28\x43\xc4\xc0\x53\x4c\x10\x31\xde\x82\x21\x80\x8d\xbd\x57\x14\xc0\x42\x12\x60\x0e\xf8\x14\xc3\xdc\xf1\x02\xa6\x7c\xc1\xf4\xcd\xc5\x1a\x4c\x98\x67\x91\x27\x3e\xd0\xb3\x6d\xe7\x05\x9b\x33\x64\xfb\x98\x86\x9b\xb6\xc1\xc4\x73\xe4\xdb\x6c\xfd\xcf\xb4\x0c\xc4\xb0\x19\xb1\xe4\x73\x9a\x40\x34\xa1\x0b\xbe\x8d\x44\xa6\x89\xaf\x13\xcc\x60\xee\x39\x4b\x78\x59\x58\xc6\x22\x10\x0a\x05\xc4\x30\x99\xdc\x00\x32\x0c\x4c\xe9\xb9\x5c\xb4\xaf\x16\xb9\xc5\xe4\x89\x2d\x34\x68\x7d\x6a\x85\x43\xe8\x75\x3d\xd4\xfd\xbb\xb5\x29\x50\xe7\x9c\xff\x3e\x74\x44\xc5\xc6\x88\x31\xec\x11\x0d\x9a\xff\x7c\x7c\x34\x7f\x75\xcf\xd4\xdf\x7f\x3d\x3e\x9e\xef\xf3\xe7\x43\xf4\xa8\xfc\xfe\xab\xc9\xb7\xec\x3b\x84\x32\x0f\x59\x84\x6d\xe8\xd8\x5a\xfa\x94\x81\x8e\x01\xc1\x33\xb2\x2d\x13\xfa\xc3\xab\x7b\xd0\x6d\xc7\xf8\xa9\xc1\xeb\x39\xff\x7d\x78\x3d\x0f\xa4\x9d\xb9\x46\xdf\x32\xbd\xcf\x7c\x2e\x15\x2d\xbe\x74\xb7\xd9\xf2\x62\xd3\x5d\x81\xd3\xfd\x78\xba\xe8\x0c\x6c\x3d\x44\xa0\xf7\xd0\xdd\x05\xd3\xe0\xf6\xf3\x36\x54\xbd\x87\x6e\xc9\x50\x29\x17\x7f\x04\x54\x4a\x01\xa8\x94\x12\xa1\xea\xfe\x39\x50\xa9\x05\xa0\x52\x4b\x84\x4a\x39\x75\xa8\x0c\xba\x9f\x03\xf6\x27\x55\x3b\x60\xb7\x73\xf2\x2e\x18\x83\xb5\xdb\x05\xe5\x60\x95\xea\x82\x9d\x93\x77\xc2\x18\xac\xdd\x4e\x28\x07\xab\x4c\x27\xec\x76\x4e\xdd\x0d\x47\x88\xed\xe5\x86\xa3\xde\xb4\x6a\x37\x54\x4e\xdf\x0d\x6d\x7d\x48\x02\x79\x90\x2d\xab\xa8\x16\x8e\x6f\x9b\x3c\xb6\xeb\x18\xac\x88\xf0\x5f\x72\x84\xd6\xaa\xcf\x91\x4d\x71\x5a\x79\x1c\x94\xc4\xcc\xf3\xf1\xfa\x4f\x48\xdd\x77\x88\x69\x05\x4c\x39\xd9\x0d\xa2\x62\x35\x13\xae\x6c\x5e\x13\x4d\x1b\x39\xac\x19\x6f\xc4\x87\x06\xff\xf3\x91\x4d\x9b\x1a\x7c\x7f\x77\x8f\xe7\xf2\x32\xe8\x0c\x5a\xad\x1f\x5b\x1b\x2b\x45\x37\x56\x32\x36\x56\x8b\x6e\xac\x4a\x37\x16\xf2\x4a\xbe\x8d\x65\x09\x49\xbe\x71\x4e\x28\x64\xc1\x5b\xbe\x71\x4e\x28\x64\x81\x6e\xb5\xf1\x90\x8e\x7d\xdd\xb6\x8c\x81\xad\xe7\xc5\x77\x75\xc6\xcf\xa0\x15\x1c\xbe\xd6\x8f\xc6\xfd\xaa\x6d\x0c\xd6\xce\xc6\x7d\x4d\x38\xd4\xbd\x6f\x13\x4d\x1b\xf4\x15\x4d\x5b\x75\x10\x63\xcf\x71\xb1\xc7\xac\xf8\x14\xc7\x91\x05\x38\x13\xb1\x37\x89\x48\x06\x04\xe9\x36\xbe\x22\x74\xe2\xbb\xae\xe3\x31\x2d\x62\x9e\x9c\xbe\x71\x28\x23\x68\x89\x69\x82\x20\xd9\xb3\x86\x8c\x12\xa3\x11\xed\x14\x3d\x09\x0e\xf6\x1f\xfc\xa6\xc1\x08\x2d\x57\x5e\x06\xc0\x5d\x50\x83\x77\x13\x5f\x87\x7f\xfc\xe2\x0a\x4e\x18\x32\x7e\x06\x44\xbf\x79\x7f\x1c\x00\x84\xd9\x17\xc4\xf0\x0b\x7a\x93\xa3\x91\x20\x4a\x41\x46\x26\x4a\xd8\xab\x27\xa5\x09\xcd\x79\x80\xd4\xb3\x71\x7f\x2f\xc1\x67\xe3\x7e\x34\xdf\x63\x0c\x19\x8b\x25\x26\x2c\x45\xfa\x99\x6b\x0c\xcd\x95\x4d\x23\xeb\x6f\xc1\xb3\xa6\x48\x22\xd2\x00\x78\xff\xfe\x3d\x4f\x2d\x91\xce\x61\x74\x1c\xa1\xd8\x79\xaf\xb0\x8b\x89\x49\xef\x88\x26\x91\x5f\x2a\xfe\x08\x65\x40\x1e\xc4\x59\x03\x05\xe1\x93\x4b\xf6\x05\xb3\x1e\x63\x30\xb0\xdc\xde\x43\xf7\x5c\x9c\x8c\xe8\x43\x1f\x5b\x6b\x21\x66\xcc\x20\x27\xf0\x75\x72\x24\x07\xc3\x71\x8a\x0c\x57\xce\x12\x59\x44\x83\x67\xd7\x48\x24\x61\xf9\x4e\xe1\x74\x2e\x2b\x24\x7d\x4e\x9a\xe9\x23\xda\xaf\xc8\x0d\x8f\xd7\xd0\xbd\x23\xb7\xc8\x27\xc6\x42\x13\x13\x4f\xef\x19\x59\x36\xd2\x2d\xdb\x62\x6f\x0f\x0e\xe1\xa7\x0c\xdb\xd8\x60\xf0\x1d\x3a\x67\x21\x86\x0f\x34\x8a\x3c\x75\x1e\xeb\x36\x41\xac\xdd\x0d\x21\xbc\x77\x7c\x86\xa7\x41\x98\x90\x63\x18\xcf\xe7\xc2\xb1\x4e\x55\x42\x45\xa2\xda\x80\xcb\xbb\x43\x95\x14\x2d\x62\x35\xc5\x23\x9b\x50\x9e\x97\x2e\xcc\x22\xfc\xa4\x0b\x27\x65\xf3\x5e\x0b\x20\xcb\x85\x37\xcf\x6e\xcc\xa4\x47\xa9\x63\x58\x7c\xf7\x5d\x07\x5a\xba\x20\x45\xaf\xdd\x6e\xb8\xa7\xe6\x23\xc4\x22\xe3\xf5\x0c\x5b\x2e\x59\x3c\x7f\xe2\xc7\x64\x48\x74\xc7\x27\x66\x2c\xef\x80\x30\x2f\x25\xa6\x27\x88\x52\x34\x8b\xa9\x44\x00\x13\x80\x00\xdc\xfb\x36\x1e\xf9\x4b\x1d\x7b\x41\x2d\xdf\xe9\xac\x52\xf0\xd8\x73\x98\x63\x38\xb6\x06\xad\x8f\x2d\x81\xb6\x67\x84\x05\x32\xbf\x8b\x5d\xe5\xf3\x27\x0f\xd3\x20\x87\xf3\x8a\xb6\xb5\x1d\xb3\x92\x27\x71\xec\x78\xec\x1e\x91\x27\xac\xad\x71\xba\xf6\x9c\xa5\x06\xad\x35\x7f\x80\xa9\x13\xf0\xbe\xbc\x54\x2f\xa3\xde\xe6\xce\x67\xa7\x01\x52\xf7\x20\x90\xc4\x42\xa7\x74\x8c\xd6\x6e\x14\x1e\xcc\x58\x93\x3d\x9d\x58\xba\xa0\xb0\x13\x67\xc2\x1b\x55\x0d\x41\xf7\xbe\x51\x35\x6c\x17\xfe\x3b\x92\xe8\xba\x83\xd2\x92\x3d\xc3\x41\x39\x56\xda\x43\xec\xca\xb1\x71\xf7\x57\x6d\x92\xf5\xac\x67\xc4\x70\xe1\x98\x83\x0d\xca\xb3\xec\x76\x0f\x54\x04\x63\xa5\x2c\x8c\x95\x52\x31\xee\x1e\x1f\x63\x25\x71\x7d\x55\x1c\x63\xb5\x2c\x8c\xd5\x52\x31\x56\x8e\x8f\xb1\x1a\x62\x7c\xfc\x6a\xf1\x70\x5d\x42\x4d\x4a\x2f\x17\x37\xd0\xc9\x55\x2e\xc6\xbd\x97\x18\xbc\xa3\x46\x49\x88\x8d\x25\x16\x8a\xb9\x23\x79\x22\x05\x6d\xad\xd8\x0b\x12\x31\x08\xd5\xa1\x4c\x5a\xc8\x4c\x57\x46\x29\xa4\x8c\x5a\x87\x32\x69\xb1\x29\x5d\x19\x35\x97\x32\xc7\xaf\xf0\xcb\x72\xed\x7a\xca\xd7\x0d\xc8\xaa\xa8\xf1\xab\x2b\x5f\x07\x06\x8d\xda\xa0\x81\xbb\xc0\x4b\xec\x21\x3b\xd8\xe5\xe8\x78\xa9\x47\xee\x89\xba\x1d\xe5\x22\x1b\xb3\xc9\xe4\x26\x59\xf3\x1f\x09\xaf\x8b\x4a\xf0\xca\xbc\xf7\x4a\x07\x50\x51\x12\xf0\x85\x03\x62\xb6\x28\xbd\x5d\xaa\x20\x95\xed\x61\x0d\x31\x67\xd4\xa7\x52\xf1\x84\x96\x4f\x25\xb5\x3e\x95\x8a\xa7\xb5\x4c\x95\x56\x3d\xef\xed\xe7\x44\xcf\xbb\xf5\xde\x6f\xff\x5e\x41\x58\x7a\x58\xaf\x20\x7b\x85\xb8\xab\x57\x78\x37\x9c\xc3\x77\xf1\xfd\xd4\x19\xbf\x6a\x3e\x0b\x9b\x08\xf8\x51\x7d\x3b\x2c\x91\xa0\x19\x3e\x37\x83\xa7\x30\x7b\x37\xd7\x92\x14\x48\xe0\xb6\x1e\xf6\xcb\x5b\xaf\x4f\x8b\xd8\xe7\xc0\x7e\x59\xf6\x26\xb6\x62\xfb\x1c\xd8\x4a\xd7\x62\x1f\x25\xf1\xbd\x56\x71\xfb\x1c\xd8\x6b\xcb\x5e\x68\x57\x6c\x9f\x03\xdb\xf0\x5a\xec\xa3\x86\xf6\x39\x7a\x9f\x5e\x87\xb2\xa1\xaa\x79\x1a\x79\xe1\x30\x0a\xa2\xf1\xa9\xec\x57\xa6\xfb\xdd\x02\x88\xd8\xe7\xba\x05\xc8\x7e\x69\x24\xe6\x8c\xea\x3a\xce\xec\x0c\x97\x4c\xcd\xc9\x15\x7b\x01\x23\x06\xd8\x3a\x94\xd9\xb7\x74\x4a\xae\xc8\xab\x4c\x85\x77\x01\xd9\xb1\x33\x5d\x99\x5d\x77\x01\xdb\xca\x1c\xfd\x2e\xa0\xbe\xf0\x11\x35\x75\xab\xaf\x77\x2a\xee\xe9\x44\x68\x6b\xef\xe9\x24\xdf\x0f\x15\xb9\x3c\x58\x63\x76\xc3\x98\x7b\x74\xbc\x2e\x8f\x7c\x67\xf0\x77\x12\xa8\x70\x60\x13\x25\x7a\x74\x98\x3e\x1e\x19\xa6\x8b\x0b\x35\x81\x53\x34\x32\xb0\xf5\x9a\xee\xeb\xb2\x41\x3a\xd9\xfb\x3a\x21\xc3\x56\xdd\x99\x97\x91\xfe\xf7\x40\x5f\xcc\xb3\xf5\xa9\x54\xbc\x08\xc8\xa7\x52\xe5\xf7\x27\x65\x94\x02\x99\x2a\x35\xee\x7c\xe6\xfa\x2c\xfc\x7c\x95\x27\xf4\xa8\x52\x16\x3e\xdf\x9e\x2e\x30\x58\x26\x38\x73\x60\x0b\x1c\x7d\x98\x17\xe7\x5e\x31\xfd\x0f\x5e\xf9\x07\xaa\x2b\xe6\x68\x99\x9e\x9c\x39\x33\x11\xd1\xa1\x49\x53\x78\x63\x5b\x07\xca\x69\x2c\x93\x0a\xbc\x23\x36\xd7\x44\xd3\xfe\xed\x58\x24\xf6\xb5\x36\xb4\xce\x62\x3f\x6b\xaf\x9f\x82\x99\x80\x7a\x38\xd7\x84\xb1\x60\x54\xe6\x15\xf1\x6c\x9a\x0f\x6c\xcc\x73\x05\x47\x0e\x17\x2d\x37\x47\x25\x83\xa3\x74\xfe\x20\x8e\x6a\x06\x47\xe9\xfc\x16\xc7\x1c\x06\x17\x0d\x2d\xde\x44\xee\xb0\xbb\x41\xeb\xb0\xfb\xf6\xdd\xf0\x26\x26\x19\xf3\x05\xac\xb0\x7d\x75\x9b\xc6\xb1\x2c\xbb\x6f\xdf\xac\xa6\x71\x2c\xdf\xee\x82\xa1\x1b\x8d\xff\x07\x00\x00\xff\xff\x23\x59\x9b\xce\xb3\x3d\x00\x00") +var _assetsVpcYml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x5b\x5f\x6f\xdb\x38\x12\x7f\xf7\xa7\x60\xd3\x03\x74\x0b\xd4\xa9\x2d\xa7\xc5\x56\x2f\x07\xd7\x71\x1b\x5d\x5b\xd7\x88\x72\x59\x20\xd7\x7b\xa0\x24\x3a\x16\x2a\x51\x3a\x91\x4a\x62\x2c\xfa\xdd\x17\xa4\xfe\x51\x32\x25\xd9\xb2\x2c\x7b\x37\x0f\x6b\x93\x43\xce\xcc\x8f\x9c\xbf\x66\x87\xc3\xe1\x60\xfa\x87\x71\x87\xbc\xc0\x85\x14\x7d\xf2\x43\x0f\xd2\x7b\x14\x12\xc7\xc7\x1a\x50\xd4\xd1\x78\x34\x1c\x7d\x18\x8e\x3e\x28\x83\x6b\x44\xac\xd0\x09\x28\x9f\xf9\xf6\x1f\x70\xbf\x9c\x81\x67\x87\xae\x01\x46\xf4\xd9\x0f\x7f\x82\x10\x11\x3f\x0a\x2d\x44\x80\x07\x31\x7c\x44\x36\x30\x37\xc0\x8b\x06\x4b\x18\x42\x0f\x51\x14\x12\x6d\x00\x80\x8e\x09\x85\xd8\x42\x77\x08\x43\x6c\x6d\xd8\x10\x00\x85\xbd\x53\x0a\x40\x63\x12\x40\x7d\x10\x11\x04\x56\x7e\xc8\x98\xf2\x05\x77\x9b\x00\x69\xc0\xa0\xa1\x83\x1f\xf9\xc0\xd4\x75\xfd\x67\x64\xdf\x43\x37\x42\x24\xde\x74\x08\x6c\xb4\x82\x91\x4b\xb3\x6f\xb6\x63\x41\x8a\xec\x84\x25\x9f\xd3\x04\x22\x83\xac\xf9\x36\x12\x99\x8c\xc8\xc4\x88\x82\x55\xe8\x7b\xe0\x79\xed\x58\x6b\x26\x14\x64\xc4\xc0\x30\x6e\x00\xb4\x2c\x44\xc8\xa5\x5c\xb4\x6f\x0e\xfe\x8a\xf0\x23\x5d\x6b\x40\xf9\xa0\xc4\x43\xf0\x25\x1b\x1a\xff\xae\x14\x05\x1a\x5d\xf2\xbf\xb7\x23\x51\xb1\x25\xa4\x14\x85\x58\x03\x17\xff\xfc\xf1\xc3\xfe\x73\xfc\x66\xf2\xeb\xb7\x1f\x3f\x2e\x77\xf9\xf2\x36\xf9\xa8\xfe\xfa\xed\x82\x6f\x39\xf3\x31\xa1\x21\x74\x30\x2d\xe8\xa8\x78\x11\xa1\xc0\x44\x00\x82\x27\xe8\x3a\x36\x98\xe9\xd7\xb7\xc0\x74\x7d\xeb\xa7\x06\x5e\x2e\xf9\xdf\xdb\x97\x4b\x26\xed\x7d\x60\xcd\x1c\x3b\xfc\xc8\xe7\x2a\xd1\xe2\x4b\xeb\x8f\x6d\x5f\x6c\xc6\x29\x38\xe3\xf7\xe7\x8b\xce\xdc\x35\x63\x04\xa6\x0f\xe3\x3a\x98\xe6\x5f\x3f\x6e\x43\x35\x7d\x18\x77\x0c\x95\x7a\xf5\xb7\x80\x4a\x6d\x01\x95\xda\x21\x54\xe3\xbf\x0f\x54\x93\x16\x50\x4d\x3a\x84\x4a\x3d\x77\xa8\x2c\xb2\x9b\x01\xce\x8c\x63\x1b\xe0\x78\x74\xf6\x26\x98\x83\x55\x6f\x82\x72\xb0\x3a\x35\xc1\xd1\xd9\x1b\x61\x0e\x56\xbd\x11\xca\xc1\xea\xd2\x08\xc7\xa3\x73\x37\xc3\x05\xa4\x3b\x99\xe1\x62\x7a\x77\x6c\x33\x54\xcf\xdf\x0c\x5d\x53\xc7\x4c\x1e\xe8\xca\x32\xaa\xb5\x1f\xb9\x36\xf7\xed\x26\x02\x4e\x42\xf8\x2f\x39\x42\x99\xea\x2b\xe8\x12\x54\x95\x1e\xb3\x94\x98\x86\x11\xca\xbe\xa4\xd4\x1f\x21\x61\x4c\x75\x0f\x3e\x22\xdd\x96\x08\x93\x10\x80\xe9\x37\x9d\xa5\xc1\x2e\x8c\xb0\xb5\x6e\x10\x45\x51\x84\x9d\xd3\x12\x80\x51\x57\x6f\x9f\xd5\x01\x8c\x6e\x77\x46\x54\xbd\xf4\x1c\x2b\xf4\x05\x86\x5f\xd0\x66\x01\x3d\x19\x2f\x96\xbc\x7f\x41\xbc\xc4\x80\xb6\xcd\xfe\x67\xc6\x4b\x9a\xd5\x99\xf9\xd8\x76\x18\x29\xc7\xf3\x06\x12\x31\xed\x8b\x59\x5d\x7c\xc2\x9a\xb6\xf0\xe9\x45\x8e\x38\x1f\x9a\xff\x3f\x82\x2e\xc9\x46\xd9\xf8\xab\x5b\xb4\x92\x27\x8e\x02\x11\x07\xb1\xc8\x49\x3d\x98\x93\xba\x23\xa7\xc9\xc1\x9c\x26\xb5\x9c\x84\x98\xdd\x92\x93\x2c\xea\xd7\x73\x6a\x8b\x9e\x2c\x64\xd6\x73\x6a\x8b\x9e\x2c\xde\x94\x39\xe9\x64\x19\x99\xae\x63\xcd\x5d\xb3\xf5\x19\xa5\xbe\x47\xdc\x9b\x79\x87\x44\x93\xc4\x90\x84\xed\xa7\xd8\x2e\x6f\x2f\x72\xac\xe6\x99\x71\x2d\xfa\x99\xc2\xbc\xa2\x74\xb1\x73\x62\xf6\xe5\x9d\x6f\xd3\x3e\x85\x96\x7b\x88\x1b\x9f\x50\x4d\xb0\xf9\xe9\x1f\x86\xa6\xcd\x67\xaa\xa6\xa5\x5e\x28\x75\xf3\xb1\xcd\x6b\x02\x28\x7c\x66\x19\xfa\x01\x0a\xa9\x93\x7b\xd7\xd4\x7f\xd6\x28\x5b\x70\x84\x45\x3a\x61\x26\x21\x4e\x9d\x58\x8d\x8a\x8b\xb8\x13\xc3\xcf\x72\x05\x2d\xd1\xd3\x4f\x09\xf1\x2d\x07\x52\x14\x5f\x15\x3d\x98\xda\x76\x88\x08\xd1\xc4\x18\xc0\x1c\xdc\x93\x63\x21\x1d\xdb\xe8\x45\x03\xa3\x6c\xf8\x73\xe8\x47\x81\x81\xe8\xd6\xcd\x49\xc4\x30\x3e\x67\x13\xf1\x75\xcd\xf4\x16\x73\x80\x84\xe6\x0e\x3e\x0a\x82\x7d\x41\x1b\x0d\x14\x8e\x89\x07\x29\x0d\xbc\x32\x22\x13\xfc\xe3\x4f\x7e\x12\x06\x85\xd6\x4f\x46\xf4\x6b\x98\xfb\xe7\x8c\xb7\xfc\xe0\x0c\x64\x45\xa1\x43\x37\x5c\xf6\xbd\x4f\xef\x3e\xb0\x32\x1d\xd2\xee\x45\x82\x83\x34\x58\xb1\x0b\x04\x52\x96\x20\xe7\x09\x40\x41\x0e\x1d\x3f\x72\xd4\x33\xf5\xf5\x60\x19\xfa\xd4\xb7\x7c\x57\x03\xd4\x0a\x32\x10\x3e\x85\xbe\xb7\xf4\x43\x16\x6a\x54\x55\xc9\x86\xef\x7c\xc9\x20\x73\x0c\x7a\x90\x08\x9b\x36\xb1\x06\x80\xc9\x2d\x87\x26\x55\x68\x5b\xed\x3c\x4d\x4b\x54\x17\x1a\x3d\x09\xc9\x1c\x43\xd3\x45\xd7\x98\x18\x51\x10\xc4\xc2\xa4\x8e\xa2\x30\xcd\x10\xc1\xd0\x43\xa4\x44\x50\x6e\x00\xc6\x8c\x4a\xa3\x5d\x5c\x15\xde\x6c\x64\x5e\x0d\xd1\xcf\x90\xa2\x67\xb8\xa9\xb2\xf0\x02\x51\x05\x32\x32\x51\x62\x73\x2b\x4b\x13\x5b\xd8\x01\x52\xdf\x2f\x67\x3b\x09\x7e\xbf\x9c\x25\xf3\x53\x4a\xa1\xb5\xf6\x10\xa6\x7b\x5d\xe7\x12\x97\x8c\xa2\x8c\xc8\x00\x80\xd7\xaf\x5f\xf3\x3c\x3d\xd1\x39\x4e\x88\x16\x30\x8f\xd6\xd7\x28\x40\xd8\x26\xdf\xb1\x26\x91\x5f\x2a\xfe\x02\x36\x40\xce\xee\xb1\x05\xb9\x3b\x64\x92\x7d\x46\x74\x4a\x29\x98\x3b\xc1\xf4\x61\x7c\x29\x4e\x0e\x76\x71\x3d\xf1\x3a\x39\x92\x73\x7d\x59\x21\xc3\xb5\xef\x41\x07\x6b\xe0\x29\xb0\x4a\x15\x4d\x85\xd7\xe1\xd3\x7b\x9d\x42\xd9\xe6\xa4\x65\x53\x42\xfb\x0d\x06\xa9\x03\xff\x8e\xbf\xf2\x7c\xb8\xe0\xc1\xa7\x4f\xd0\x71\xa1\xe9\xb8\x0e\xdd\x3c\xf8\x18\xe5\x0e\x9b\x45\x4c\x03\xb9\xc8\x2a\x38\xf1\x91\xe8\xd0\x19\xc0\x0f\x24\x0f\xbe\x7d\x5d\xf9\x21\x86\x74\x38\x8e\xe1\xbd\xf5\x23\x8a\xee\x98\x0b\x91\xe3\x9b\xcf\xef\x85\x71\x9f\xaa\xc4\x8a\x24\xa5\x02\x97\xb7\x46\x95\x0a\x2d\x72\x35\xc5\xeb\x5c\x52\x9e\xd7\x32\xd4\xc1\xdc\x0a\x84\x5b\x54\xfc\x01\x01\x80\x26\xf3\x2e\xde\xeb\x9c\x49\x9a\x33\x64\x89\x9f\xfc\xb2\x4b\x17\x54\xe8\xd5\x9c\x1d\xec\xa0\xf9\x02\xd2\xe4\xf0\xa6\x96\x2b\x97\x2c\x9f\x3f\xf3\x6b\xa2\x63\xd3\x8f\xb0\x9d\xcb\x3b\xc7\x34\xac\xf0\xf7\x25\xa2\x0a\xcd\x72\x2a\x11\xc0\x12\x20\x00\xdc\x46\x2e\x5a\x44\x9e\x89\x42\x0d\x28\xe3\xd1\x28\x35\xfa\x3c\x1d\x51\xde\x2b\x02\xed\xd4\x8a\x13\x1e\x98\x24\x17\xec\xbf\x79\x9c\xcc\x00\x85\xb7\x0e\x94\x6d\x7f\x56\xbe\x89\x2c\x79\xb9\x85\xf8\x51\xf4\x4b\xa1\xef\x69\x40\x19\x89\x39\x0e\xe3\xfd\xee\xdd\xe4\x5d\xd2\x44\xfa\x1e\xd1\xf3\x00\x69\x7c\x10\x48\x62\x12\xd4\x39\x46\x99\x19\xc5\x17\x33\xd7\x64\x47\x23\x96\x2e\x68\x6d\xc4\x8d\xf0\x26\x19\xc5\x7c\x66\x14\x33\x8a\xed\x2e\x40\x4d\x80\x2d\xe6\xf3\xe2\xd2\x83\xe2\x6f\x5d\x1f\x41\x1a\x7f\xf3\x36\xdb\xe9\x02\x70\xe8\x3c\x41\x8a\x5a\xfb\x23\x64\x11\x1e\x81\xb7\x7b\x23\x6d\xf0\x57\xbb\xc2\x7f\xab\xbb\xd2\x39\xfe\xe3\xf3\xc1\x5f\x2d\xfd\xbe\xd0\x1e\xff\x49\x57\xf8\x6f\xf5\x9c\x3a\xc7\x5f\x3d\x1f\xfc\x27\x31\xfe\xa7\xcf\x40\x0f\xd7\x25\xd6\xa4\xf3\x14\xb4\x80\xce\x5e\x29\x68\x5e\xeb\x89\x01\x21\x29\xcc\x04\x7f\xdb\x61\xf2\xb9\x77\x74\x28\x85\xb5\xad\x15\x3b\x41\x22\x3a\xaf\x3e\x94\xa9\x72\xb5\xd5\xca\xa8\xad\x94\x99\xf4\xa1\x4c\x95\xdf\xaa\x56\x66\xb2\x97\x32\xa7\xaf\x1a\xba\x32\xed\x7e\x52\xe2\x02\x64\xc7\xa8\x1b\x8e\x97\x12\xcf\x2d\x92\x94\x56\xf3\x60\x8d\x3c\x14\x42\x97\xed\x72\x72\xbc\x26\x27\xae\xb3\xc6\x23\xf5\xaa\x19\x33\xc3\xb8\x29\xd7\x11\x27\xc2\xeb\xea\x28\x78\x35\xf6\xd9\xaa\x01\x2c\x76\xe3\xb3\x01\x31\x5a\x74\x5e\x82\x1d\x21\x94\xed\x70\x1a\x62\xcc\xe8\x4f\xa5\xf6\x01\x6d\x3f\x95\x26\xfd\xa9\xd4\x3e\xac\x35\xaa\x94\xd6\xd1\x5f\x3f\x96\xea\xe8\xad\xb7\x08\xbb\xd7\x11\xc2\xd2\xc3\xea\x88\x9a\xd7\x0c\xd2\x3a\xe2\x95\xbe\x02\xff\x15\x7f\xcd\x7e\xc3\x5b\xdb\x6f\xe2\x02\x03\xfc\xaf\x65\x89\xd1\x51\x89\x2d\x91\xee\x22\xfe\x7c\xc1\x3e\xc5\x91\xfd\x22\x93\xb2\x45\x70\x77\xcd\xb8\x06\xdf\x7a\xdd\xd1\xe6\xec\x0e\xac\xc1\x6b\xde\x87\xf4\x79\x76\x1d\x95\xe7\xbd\x9c\x9d\x5a\x7a\xa4\xdb\xfe\xec\x0e\xac\xdf\x6b\x5e\xdc\xf4\x79\x76\x1d\x95\xf6\xbd\x9c\xdd\x24\x3e\xbb\x93\xd7\xfe\x7d\x28\x1b\xab\xba\x4f\x73\x40\xb8\xa8\x82\x68\x7c\xaa\xf9\x67\xdf\xdd\x3a\x0b\x22\xf6\x09\x59\x57\x3f\x6e\x89\x71\xe8\x78\x55\x6c\x73\xd4\x2c\x87\xfb\xf2\x8a\x9d\x80\x11\x1d\x73\x1f\xca\xec\x9a\x8e\x95\x57\xec\xab\xcc\x11\xfb\x0b\xcd\x7e\xb5\x5a\x99\xba\xfe\xc2\xb6\x32\x27\xef\x2f\xf4\xe7\x3e\x92\x42\x31\x7d\x36\x78\xe4\x3a\x51\x84\xb6\xf7\x3a\x51\xf2\x06\xaa\x4d\x43\x22\xc3\xec\x86\xd2\xe0\xe4\x78\xbd\x3b\x71\x1f\xe2\xf7\x32\x50\xf1\x40\x11\x25\x72\x72\x98\xde\x9f\x18\xa6\xab\xab\x49\x09\xa7\x64\x64\xee\x9a\x3d\xf5\x00\x9b\x41\x3a\xdb\x1e\xa0\x10\x61\x8f\x5d\xed\x77\x11\xfe\x77\x40\x5f\x8c\xb3\xfd\xa9\xd4\x3e\x09\xd8\x4f\xa5\xa3\xf7\x64\xba\x48\x05\x1a\x55\x1a\x7c\x8f\x68\x10\x51\xbe\x65\x1c\xd0\x93\x4c\x59\x78\x36\x7c\xb7\x46\xc0\xb1\x81\xbf\x02\x74\x8d\x92\xc7\x85\x79\xec\x15\xc3\xff\xfc\x85\x3f\xb2\x4d\x99\xc7\x2f\xc0\xe5\xc1\x99\x33\x93\xbd\x69\x2f\xfe\xf3\xfa\x65\xca\x36\x79\x4a\x0d\xd6\x3e\xa1\x05\xfe\xc9\x4b\x4b\x61\x9f\xcb\xb4\x6a\x94\xa0\x9a\x3f\xa4\xce\xd0\xd2\x6d\x52\xa1\x34\x72\x4d\x40\x38\x8d\x63\x13\x81\x69\xa2\x1f\xab\x24\xff\xed\x3b\x58\xac\x23\x95\x37\xb9\x81\x0f\xb3\x4f\x6c\x86\x51\xeb\x2b\x4d\x18\x63\xa3\x32\x73\xcc\x67\xab\x8c\xaf\x30\xcf\x91\x5d\xf8\x5c\xb4\xbd\x39\xaa\x0d\x1c\xa5\xf3\x07\x71\x9c\x34\x70\x94\xce\x6f\x71\xdc\xe3\xa6\x89\x07\x2d\xb6\x55\x6b\xce\xdd\x22\x7d\x9c\xfb\x76\xa3\xbb\x88\x49\xc3\x7c\x8b\x53\xd8\xee\x43\x57\x71\xec\xea\xdc\xb7\xdb\xc4\x55\x1c\xbb\x3f\x77\xe1\xa0\x07\x83\xbf\x02\x00\x00\xff\xff\xb1\x5c\x74\x68\x3d\x44\x00\x00") func assetsVpcYmlBytes() ([]byte, error) { return bindataRead( @@ -209,7 +230,7 @@ func assetsVpcYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "assets/vpc.yml", size: 15795, mode: os.FileMode(420), modTime: time.Unix(1486162685, 0)} + info := bindataFileInfo{name: "assets/vpc.yml", size: 17469, mode: os.FileMode(420), modTime: time.Unix(1487797419, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -266,13 +287,14 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "assets/bucket.yml": assetsBucketYml, - "assets/cluster.yml": assetsClusterYml, - "assets/pipeline.yml": assetsPipelineYml, - "assets/repo.yml": assetsRepoYml, - "assets/service.yml": assetsServiceYml, + "assets/bucket.yml": assetsBucketYml, + "assets/buildspec.yml": assetsBuildspecYml, + "assets/cluster.yml": assetsClusterYml, + "assets/pipeline.yml": assetsPipelineYml, + "assets/repo.yml": assetsRepoYml, + "assets/service.yml": assetsServiceYml, "assets/vpc-target.yml": assetsVpcTargetYml, - "assets/vpc.yml": assetsVpcYml, + "assets/vpc.yml": assetsVpcYml, } // AssetDir returns the file names below a certain @@ -314,16 +336,16 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } - var _bintree = &bintree{nil, map[string]*bintree{ "assets": &bintree{nil, map[string]*bintree{ - "bucket.yml": &bintree{assetsBucketYml, map[string]*bintree{}}, - "cluster.yml": &bintree{assetsClusterYml, map[string]*bintree{}}, - "pipeline.yml": &bintree{assetsPipelineYml, map[string]*bintree{}}, - "repo.yml": &bintree{assetsRepoYml, map[string]*bintree{}}, - "service.yml": &bintree{assetsServiceYml, map[string]*bintree{}}, + "bucket.yml": &bintree{assetsBucketYml, map[string]*bintree{}}, + "buildspec.yml": &bintree{assetsBuildspecYml, map[string]*bintree{}}, + "cluster.yml": &bintree{assetsClusterYml, map[string]*bintree{}}, + "pipeline.yml": &bintree{assetsPipelineYml, map[string]*bintree{}}, + "repo.yml": &bintree{assetsRepoYml, map[string]*bintree{}}, + "service.yml": &bintree{assetsServiceYml, map[string]*bintree{}}, "vpc-target.yml": &bintree{assetsVpcTargetYml, map[string]*bintree{}}, - "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, + "vpc.yml": &bintree{assetsVpcYml, map[string]*bintree{}}, }}, }} @@ -373,3 +395,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/bucket.yml b/templates/assets/bucket.yml index f2baf542..7aa92f21 100644 --- a/templates/assets/bucket.yml +++ b/templates/assets/bucket.yml @@ -9,7 +9,7 @@ Resources: Bucket: Type: AWS::S3::Bucket Properties: - BucketName: !Sub ${BucketPrefix}-${AWS::Region}-${AWS::AccountId} + BucketName: !Sub mu-${BucketPrefix}-${AWS::Region}-${AWS::AccountId} Outputs: Bucket: Description: Name of the pipeline bucket diff --git a/templates/assets/buildspec.yml b/templates/assets/buildspec.yml new file mode 100644 index 00000000..4984c43f --- /dev/null +++ b/templates/assets/buildspec.yml @@ -0,0 +1,11 @@ +version: 0.1 + +phases: + build: + commands: + - echo '...replace with real build commands...' + +artifacts: + files: + - '**/*' + diff --git a/templates/assets/cluster.yml b/templates/assets/cluster.yml index e54e3338..ac8b2200 100644 --- a/templates/assets/cluster.yml +++ b/templates/assets/cluster.yml @@ -98,24 +98,38 @@ Metadata: Conditions: HasKeyName: "Fn::Not": - - "Fn::Equals": [!Ref KeyName, ''] + - "Fn::Equals": + - !Ref KeyName + - '' HasHttpProxy: "Fn::Not": - - "Fn::Equals": [!Ref HttpProxy, ''] + - "Fn::Equals": + - !Ref HttpProxy + - '' HasElbDomainName: "Fn::Not": - - "Fn::Equals": [!Ref ElbDomainName, ''] + - "Fn::Equals": + - !Ref ElbDomainName + - '' HasElbHostName: "Fn::Not": - - "Fn::Equals": [!Ref ElbHostName, ''] + - "Fn::Equals": + - !Ref ElbHostName + - '' HasElbCert: "Fn::Not": - - "Fn::Equals": [!Ref ElbCert, ''] + - "Fn::Equals": + - !Ref ElbCert + - '' IsElbInternal: - "Fn::Equals": [!Ref ElbInternal, 'true'] + "Fn::Equals": + - !Ref ElbInternal + - 'true' Resources: EcsCluster: Type: AWS::ECS::Cluster + Properties: + ClusterName: !Ref AWS::StackName ECSAutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup DependsOn: [] @@ -213,8 +227,66 @@ Resources: path=Resources.ContainerInstances.Metadata.AWS::CloudFormation::Init action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ContainerInstances --region ${AWS::Region} runas=root + "/etc/awslogs/etc/proxy.conf": + content: !Sub | + HTTP_PROXY=http://${HttpProxy}/ + HTTPS_PROXY=http://${HttpProxy}/ + "/etc/awslogs/awscli.conf": + content: !Sub | + [plugins] + cwlogs = cwlogs + [default] + region = ${AWS::Region} + "/etc/awslogs/awslogs.conf": + content: !Sub | + [general] + state_file = /var/lib/awslogs/agent-state + + [dmesg] + file = /var/log/dmesg + log_group_name = ${EcsCluster} + log_stream_name = instance/dmesg/{instance_id} + + [messages] + file = /var/log/messages + log_group_name = ${EcsCluster} + log_stream_name = instance/messages/{instance_id} + datetime_format = %b %d %H:%M:%S + + [docker] + file = /var/log/docker + log_group_name = ${EcsCluster} + log_stream_name = instance/docker/{instance_id} + datetime_format = %Y-%m-%dT%H:%M:%S.%f + + [ecs] + file = /var/log/ecs/* + log_group_name = ${EcsCluster} + log_stream_name = instance/ecs/{instance_id} + datetime_format = %Y-%m-%dT%H:%M:%SZ + + [cloud-init] + file = /var/log/cloud-init* + log_group_name = ${EcsCluster} + log_stream_name = instance/cloud-init/{instance_id} + datetime_format = %Y-%m-%dT%H:%M:%SZ + + [cfn-init] + file = /var/log/cfn-init* + log_group_name = ${EcsCluster} + log_stream_name = instance/cfn-init/{instance_id} + datetime_format = %Y-%m-%d %H:%M:%S + packages: + yum: + awslogs: [] services: sysvinit: + awslogs: + enabled: 'true' + ensureRunning: 'true' + files: + - "/etc/awslogs/awslogs.conf" + - "/etc/awslogs/etc/proxy.conf" cfn-hup: enabled: 'true' ensureRunning: 'true' @@ -223,48 +295,52 @@ Resources: - "/etc/cfn/hooks.d/cfn-auto-reloader.conf" Properties: ImageId: !Ref ImageId - SecurityGroups: [ !Ref HostSG ] + SecurityGroups: + - !Ref HostSG InstanceType: !Ref InstanceType IamInstanceProfile: !Ref EC2InstanceProfile - KeyName: !If [ HasKeyName, !Ref KeyName, !Ref "AWS::NoValue"] + KeyName: + Fn::If: + - HasKeyName + - !Ref KeyName + - !Ref "AWS::NoValue" UserData: - Fn::Base64: - !Sub | - #!/bin/bash -xe + Fn::Base64: !Sub | + #!/bin/bash -xe - CFN_PROXY_ARGS="" - if [[ ! -z "${HttpProxy}" ]]; then - echo "Configuring HTTP_PROXY=${HttpProxy}" + CFN_PROXY_ARGS="" + if [[ ! -z "${HttpProxy}" ]]; then + echo "Configuring HTTP_PROXY=${HttpProxy}" - # Set Yum HTTP proxy - if [ ! -f /var/lib/cloud/instance/sem/config_yum_http_proxy ]; then - echo "proxy=http://${HttpProxy}" >> /etc/yum.conf - echo "$$: $(date +%s.%N | cut -b1-13)" > /var/lib/cloud/instance/sem/config_yum_http_proxy - fi - - # Set Docker HTTP proxy - if [ ! -f /var/lib/cloud/instance/sem/config_docker_http_proxy ]; then - echo "export HTTP_PROXY=http://${HttpProxy}/" >> /etc/sysconfig/docker - echo "export HTTPS_PROXY=http://${HttpProxy}/" >> /etc/sysconfig/docker - echo "$$: $(date +%s.%N | cut -b1-13)" > /var/lib/cloud/instance/sem/config_docker_http_proxy + # Set Yum HTTP proxy + if [ ! -f /var/lib/cloud/instance/sem/config_yum_http_proxy ]; then + echo "proxy=http://${HttpProxy}" >> /etc/yum.conf + echo "$$: $(date +%s.%N | cut -b1-13)" > /var/lib/cloud/instance/sem/config_yum_http_proxy + fi - service docker restart - fi + # Set Docker HTTP proxy + if [ ! -f /var/lib/cloud/instance/sem/config_docker_http_proxy ]; then + echo "export HTTP_PROXY=http://${HttpProxy}/" >> /etc/sysconfig/docker + echo "export HTTPS_PROXY=http://${HttpProxy}/" >> /etc/sysconfig/docker + echo "$$: $(date +%s.%N | cut -b1-13)" > /var/lib/cloud/instance/sem/config_docker_http_proxy - # Set ECS agent HTTP proxy - if [ ! -f /var/lib/cloud/instance/sem/config_ecs-agent_http_proxy ]; then - echo "HTTP_PROXY=${HttpProxy}" >> /etc/ecs/ecs.config - echo "NO_PROXY=169.254.169.254,169.254.170.2,/var/run/docker.sock" >> /etc/ecs/ecs.config - echo "$$: $(date +%s.%N | cut -b1-13)" > /var/lib/cloud/instance/sem/config_ecs-agent_http_proxy - fi + service docker restart + fi - CFN_PROXY_ARGS="--http-proxy http://${HttpProxy} --https-proxy http://${HttpProxy}" + # Set ECS agent HTTP proxy + if [ ! -f /var/lib/cloud/instance/sem/config_ecs-agent_http_proxy ]; then + echo "HTTP_PROXY=${HttpProxy}" >> /etc/ecs/ecs.config + echo "NO_PROXY=169.254.169.254,169.254.170.2,/var/run/docker.sock" >> /etc/ecs/ecs.config + echo "$$: $(date +%s.%N | cut -b1-13)" > /var/lib/cloud/instance/sem/config_ecs-agent_http_proxy fi + CFN_PROXY_ARGS="--http-proxy http://${HttpProxy} --https-proxy http://${HttpProxy}" + fi + - yum install -y aws-cfn-bootstrap - /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ContainerInstances --region ${AWS::Region} $CFN_PROXY_ARGS - /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region} $CFN_PROXY_ARGS + yum install -y aws-cfn-bootstrap + /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ContainerInstances --region ${AWS::Region} $CFN_PROXY_ARGS + /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region} $CFN_PROXY_ARGS EC2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: @@ -291,12 +367,35 @@ Resources: Action: - ecs:RegisterContainerInstance - ecs:DeregisterContainerInstance - - ecs:DiscoverPollEndpoint + - ecs:SubmitContainerStateChange + - ecs:SubmitTaskStateChange + Resource: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${EcsCluster} + - Effect: Allow + Action: - ecs:StartTelemetrySession - - ecs:Submit* - - ecr:* - ecs:Poll Resource: "*" + Condition: + ArnEquals: + "ecs:cluster": !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${EcsCluster} + - Effect: Allow + Action: + - ecs:DiscoverPollEndpoint + - ecr:* + Resource: "*" + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - logs:DescribeLogStreams + Resource: + - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${EcsCluster} + - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${EcsCluster}:* + ClusterLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub ${EcsCluster} HostSG: Type: AWS::EC2::SecurityGroup Properties: @@ -404,12 +503,21 @@ Outputs: BaseUrl: Value: Fn::If: - - HasElbDomainName + - HasElbCert + - Fn::If: + - HasElbDomainName + - Fn::If: + - HasElbHostName + - !Sub https://${ElbHostName}.${ElbDomainName} + - !Sub https://${ElbDomainName} + - !Sub https://${EcsElb.DNSName} - Fn::If: - - HasElbHostName - - !Sub http://${ElbHostName}.${ElbDomainName}/ - - !Sub http://${ElbDomainName}/ - - !Sub http://${EcsElb.DNSName}/ + - HasElbDomainName + - Fn::If: + - HasElbHostName + - !Sub http://${ElbHostName}.${ElbDomainName} + - !Sub http://${ElbDomainName} + - !Sub http://${EcsElb.DNSName} Description: ELB URL VpcId: Value: diff --git a/templates/assets/pipeline.yml b/templates/assets/pipeline.yml index beef35a3..13f1f0bb 100644 --- a/templates/assets/pipeline.yml +++ b/templates/assets/pipeline.yml @@ -18,7 +18,7 @@ Parameters: Description: Secret. It might look something like 9b189a1654643522561f7b3ebd44a1531a4287af OAuthToken with access to Repo. Go to https://github.com/settings/tokens BuildType: Type: String - Default: "linuxContainer" + Default: "LINUX_CONTAINER" Description: The build container type to use for building the app BuildComputeType: Type: String @@ -28,9 +28,21 @@ Parameters: Type: String Default: "aws/codebuild/ubuntu-base:14.04" Description: The build image to use for building the app + TestType: + Type: String + Default: "LINUX_CONTAINER" + Description: The build container type to use for testing the app + TestComputeType: + Type: String + Default: "BUILD_GENERAL1_SMALL" + Description: The build compute type to use for testing the app + TestImage: + Type: String + Default: "aws/codebuild/ubuntu-base:14.04" + Description: The build image to use for testing the app MuType: Type: String - Default: "linuxContainer" + Default: "LINUX_CONTAINER" Description: The build container type to use for mu commands MuComputeType: Type: String @@ -51,6 +63,9 @@ Parameters: Type: String Default: "mu-linux-amd64" Description: The name of the mu file to download to install + DefaultBuildspec: + Type: String + Description: The default buildspec to use MuFile: Type: String Description: Path to mu.yml, relative to GitHubRepo @@ -138,7 +153,7 @@ Resources: Image: !Sub ${BuildImage} Source: Type: CODEPIPELINE - TimeoutInMinutes: 10 + TimeoutInMinutes: 30 CodeBuildImage: Type: AWS::CodeBuild::Project DependsOn: CodeBuildRole @@ -166,15 +181,14 @@ Resources: - chmod +rx /usr/bin/mu - mu -c ${MuFile} svc push -t latest artifacts: - type: zip files: - ${MuFile} - TimeoutInMinutes: 10 - DeployTest: + TimeoutInMinutes: 30 + DeployAcceptance: Type: AWS::CodeBuild::Project DependsOn: CodeBuildRole Properties: - Name: !Sub ${AWS::StackName}-deploy-test + Name: !Sub ${AWS::StackName}-deploy-acceptance Description: Deploy image to test environment ServiceRole: !GetAtt CodeBuildRole.Arn Artifacts: @@ -187,22 +201,43 @@ Resources: Type: CODEPIPELINE BuildSpec: !Sub | version: 0.1 + environment_variables: + plaintext: + DEFAULT_BUILDSPEC: "${DefaultBuildspec}" phases: build: commands: - curl -sL ${MuDownloadBaseurl}/v${MuDownloadVersion}/${MuDownloadFile} -o /usr/bin/mu - chmod +rx /usr/bin/mu + - mu -c ${MuFile} env up ${TestEnv} || echo "Skipping update of environment" - mu -c ${MuFile} svc deploy ${TestEnv} -t latest + - mu env show ${TestEnv} -f json > env.json + - mv buildspec-test.yml buildspec.yml || echo "$DEFAULT_BUILDSPEC" > buildspec.yml artifacts: - type: zip files: - - ${MuFile} - TimeoutInMinutes: 10 - DeployProd: + - '**/*' + TimeoutInMinutes: 30 + TestAcceptance: + Type: AWS::CodeBuild::Project + DependsOn: CodeBuildRole + Properties: + Name: !Sub ${AWS::StackName}-test-acceptance + Description: Test in the acceptance environment + ServiceRole: !GetAtt CodeBuildRole.Arn + Artifacts: + Type: CODEPIPELINE + Environment: + Type: !Ref TestType + ComputeType: !Ref TestComputeType + Image: !Sub ${TestImage} + Source: + Type: CODEPIPELINE + TimeoutInMinutes: 30 + DeployProduction: Type: AWS::CodeBuild::Project DependsOn: CodeBuildRole Properties: - Name: !Sub ${AWS::StackName}-deploy-prod + Name: !Sub ${AWS::StackName}-deploy-production Description: Deploy image to prod environment ServiceRole: !GetAtt CodeBuildRole.Arn Artifacts: @@ -215,17 +250,38 @@ Resources: Type: CODEPIPELINE BuildSpec: !Sub | version: 0.1 + environment_variables: + plaintext: + DEFAULT_BUILDSPEC: "${DefaultBuildspec}" phases: build: commands: - curl -sL ${MuDownloadBaseurl}/v${MuDownloadVersion}/${MuDownloadFile} -o /usr/bin/mu - chmod +rx /usr/bin/mu + - mu -c ${MuFile} env up ${ProdEnv} || echo "Skipping update of environment" - mu -c ${MuFile} svc deploy ${ProdEnv} -t latest + - mu env show ${ProdEnv} -f json > env.json + - mv buildspec-prod.yml buildspec.yml || echo "$DEFAULT_BUILDSPEC" > buildspec.yml artifacts: - type: zip files: - - ${MuFile} - TimeoutInMinutes: 10 + - '**/*' + TimeoutInMinutes: 30 + TestProduction: + Type: AWS::CodeBuild::Project + DependsOn: CodeBuildRole + Properties: + Name: !Sub ${AWS::StackName}-test-production + Description: Test in the production environment + ServiceRole: !GetAtt CodeBuildRole.Arn + Artifacts: + Type: CODEPIPELINE + Environment: + Type: !Ref TestType + ComputeType: !Ref TestComputeType + Image: !Sub ${TestImage} + Source: + Type: CODEPIPELINE + TimeoutInMinutes: 30 Pipeline: Type: AWS::CodePipeline::Pipeline Properties: @@ -285,12 +341,25 @@ Resources: Version: '1' Provider: CodeBuild InputArtifacts: - - Name: ArtifactOutput + - Name: SourceOutput OutputArtifacts: - - Name: AcceptanceOutput + - Name: DeployAcceptanceOutput Configuration: - ProjectName: !Ref DeployTest + ProjectName: !Ref DeployAcceptance RunOrder: 1 + - Name: Test + ActionTypeId: + Category: Build + Owner: AWS + Version: '1' + Provider: CodeBuild + InputArtifacts: + - Name: DeployAcceptanceOutput + OutputArtifacts: + - Name: TestAcceptanceOutput + Configuration: + ProjectName: !Ref TestAcceptance + RunOrder: 2 - Name: Production Actions: - Name: Approve @@ -309,12 +378,25 @@ Resources: Version: '1' Provider: CodeBuild InputArtifacts: - - Name: ArtifactOutput + - Name: SourceOutput OutputArtifacts: - - Name: ProductionOutput + - Name: DeployProductionOutput Configuration: - ProjectName: !Ref DeployProd + ProjectName: !Ref DeployProduction RunOrder: 2 + - Name: Test + ActionTypeId: + Category: Build + Owner: AWS + Version: '1' + Provider: CodeBuild + InputArtifacts: + - Name: DeployProductionOutput + OutputArtifacts: + - Name: TestProductionOutput + Configuration: + ProjectName: !Ref TestProduction + RunOrder: 3 ArtifactStore: Type: S3 Location: !ImportValue mu-bucket-codepipeline diff --git a/templates/assets/service.yml b/templates/assets/service.yml index 329ab296..3a26972f 100644 --- a/templates/assets/service.yml +++ b/templates/assets/service.yml @@ -53,7 +53,9 @@ Conditions: HasPathPattern: "Fn::Not": - "Fn::Equals": - - "Fn::Join": [ "", !Ref PathPattern] + - "Fn::Join": + - '' + - !Ref PathPattern - '' HasElbHttpListener: "Fn::And": @@ -69,6 +71,7 @@ Conditions: - !Sub ${EcsElbHttpsListenerArn} - '' - !Condition HasPathPattern + Resources: EcsService: Type: AWS::ECS::Service @@ -111,16 +114,37 @@ Resources: Statement: - Effect: Allow Action: - - ec2:AuthorizeSecurityGroupIngress - ec2:Describe* - - elasticloadbalancing:DeregisterInstancesFromLoadBalancer - elasticloadbalancing:Describe* - - elasticloadbalancing:RegisterInstancesWithLoadBalancer + Resource: "*" + - Effect: Allow + Action: - elasticloadbalancing:DeregisterTargets - elasticloadbalancing:DescribeTargetGroups - elasticloadbalancing:DescribeTargetHealth - elasticloadbalancing:RegisterTargets Resource: "*" + EcsTaskRole: + Type: AWS::IAM::Role + Condition: HasPathPattern + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: + - ecs-tasks.amazonaws.com + Action: + - sts:AssumeRole + Path: "/" + Policies: + - PolicyName: ecs-task + PolicyDocument: + Statement: + - Effect: Allow + Action: + - ecs:DescribeTasks + Resource: "*" MicroserviceTaskDefinition: Type: AWS::ECS::TaskDefinition Properties: @@ -130,10 +154,25 @@ Resources: Essential: 'true' Image: !Ref ImageUrl Memory: !Ref ServiceMemory + {{with .Environment}} + Environment: + {{range $key, $val := .}} + - Name: {{$key}} + Value: {{$val}} + {{end}} + {{end}} + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: + Fn::ImportValue: !Sub ${EcsCluster} + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: service PortMappings: - HostPort: 0 ContainerPort: !Ref ServicePort Volumes: [] + TaskRoleArn: !GetAtt EcsTaskRole.Arn EcsElbHttpListenerRule: Type: AWS::ElasticLoadBalancingV2::ListenerRule Condition: HasElbHttpListener @@ -182,3 +221,11 @@ Resources: UnhealthyThresholdCount: 5 VpcId: Fn::ImportValue: !Sub ${VpcId} +Outputs: + MicroserviceTaskDefinition: + Description: Roadmap Task Definition + Value: !Ref MicroserviceTaskDefinition + EcsCluster: + Description: Roadmap Cluster + Value: + Fn::ImportValue: !Ref EcsCluster diff --git a/templates/assets/vpc.yml b/templates/assets/vpc.yml index 05f8be75..3a26417a 100644 --- a/templates/assets/vpc.yml +++ b/templates/assets/vpc.yml @@ -88,29 +88,92 @@ Parameters: AllowedValues: - true - false + BastionImageId: + Description: Bastion AMI to launch + Type: String + Default: '' + BastionInstanceType: + Description: Bastion Instance Type to launch + Type: String + Default: 't2.micro' + BastionKeyName: + Description: SSH Key to add to bastion + Type: String + Default: '' Conditions: HasElbSubnetAZ1: "Fn::Not": - - "Fn::Equals": [!Ref ElbSubnetAZ1CidrBlock, ''] + - "Fn::Equals": + - !Ref ElbSubnetAZ1CidrBlock + - '' HasElbSubnetAZ2: "Fn::Not": - - "Fn::Equals": [!Ref ElbSubnetAZ2CidrBlock, ''] + - "Fn::Equals": + - !Ref ElbSubnetAZ2CidrBlock + - '' HasElbSubnetAZ3: "Fn::Not": - - "Fn::Equals": [!Ref ElbSubnetAZ3CidrBlock, ''] + - "Fn::Equals": + - !Ref ElbSubnetAZ3CidrBlock + - '' HasEcsSubnetAZ1: "Fn::Not": - - "Fn::Equals": [!Ref EcsSubnetAZ1CidrBlock, ''] + - "Fn::Equals": + - !Ref EcsSubnetAZ1CidrBlock + - '' HasEcsSubnetAZ2: "Fn::Not": - - "Fn::Equals": [!Ref EcsSubnetAZ2CidrBlock, ''] + - "Fn::Equals": + - !Ref EcsSubnetAZ2CidrBlock + - '' HasEcsSubnetAZ3: "Fn::Not": - - "Fn::Equals": [!Ref EcsSubnetAZ3CidrBlock, ''] + - "Fn::Equals": + - !Ref EcsSubnetAZ3CidrBlock + - '' IsPublicElb: "Fn::Not": - - "Fn::Equals": [!Ref ElbInternal, 'true'] + - "Fn::Equals": + - !Ref ElbInternal + - 'true' + HasBastion: + "Fn::And": + - "Fn::Not": + - "Fn::Equals": + - !Ref BastionImageId + - '' + - "Fn::Not": + - "Fn::Equals": + - !Ref BastionKeyName + - '' Resources: + BastionHost: + Type: AWS::EC2::Instance + Condition: HasBastion + Properties: + ImageId: !Ref BastionImageId + InstanceType: !Ref BastionInstanceType + KeyName: !Ref BastionKeyName + NetworkInterfaces: + - AssociatePublicIpAddress: true + DeviceIndex: 0 + GroupSet: + - !Ref BastionSG + SubnetId: !Ref NatSubnetAZ1 + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-bastion + BastionSG: + Type: AWS::EC2::SecurityGroup + Condition: HasBastion + Properties: + VpcId: !Ref VPC + GroupDescription: Bastion Host Security Group + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: '22' + ToPort: '22' + CidrIp: !Ref SshAllow VPC: Type: AWS::EC2::VPC Properties: @@ -152,7 +215,10 @@ Resources: VpcId: !Ref VPC CidrBlock: !Ref NatSubnetAZ1CidrBlock MapPublicIpOnLaunch: true - AvailabilityZone: !Select [ 0, !GetAZs ''] + AvailabilityZone: + Fn::Select: + - 0 + - !GetAZs '' Tags: - Key: Network Value: Public @@ -225,7 +291,10 @@ Resources: VpcId: !Ref VPC CidrBlock: !Ref EcsSubnetAZ1CidrBlock MapPublicIpOnLaunch: false - AvailabilityZone: !Select [ 0, !GetAZs ''] + AvailabilityZone: + Fn::Select: + - 0 + - !GetAZs '' Tags: - Key: Network Value: Private @@ -238,7 +307,10 @@ Resources: VpcId: !Ref VPC CidrBlock: !Ref EcsSubnetAZ2CidrBlock MapPublicIpOnLaunch: false - AvailabilityZone: !Select [ 1, !GetAZs ''] + AvailabilityZone: + Fn::Select: + - 1 + - !GetAZs '' Tags: - Key: Network Value: Private @@ -251,7 +323,10 @@ Resources: VpcId: !Ref VPC CidrBlock: !Ref EcsSubnetAZ3CidrBlock MapPublicIpOnLaunch: false - AvailabilityZone: !Select [ 2, !GetAZs ''] + AvailabilityZone: + Fn::Select: + - 2 + - !GetAZs '' Tags: - Key: Network Value: Private @@ -362,7 +437,10 @@ Resources: VpcId: !Ref VPC CidrBlock: !Ref ElbSubnetAZ1CidrBlock MapPublicIpOnLaunch: !If [ IsPublicElb, true, false ] - AvailabilityZone: !Select [ 0, !GetAZs ''] + AvailabilityZone: + Fn::Select: + - 0 + - !GetAZs '' Tags: - Key: Network Value: !If [ IsPublicElb, "Public", "Private" ] @@ -375,7 +453,10 @@ Resources: VpcId: !Ref VPC CidrBlock: !Ref ElbSubnetAZ2CidrBlock MapPublicIpOnLaunch: !If [ IsPublicElb, true, false ] - AvailabilityZone: !Select [ 1, !GetAZs ''] + AvailabilityZone: + Fn::Select: + - 1 + - !GetAZs '' Tags: - Key: Network Value: !If [ IsPublicElb, "Public", "Private" ] @@ -388,7 +469,10 @@ Resources: VpcId: !Ref VPC CidrBlock: !Ref ElbSubnetAZ3CidrBlock MapPublicIpOnLaunch: !If [ IsPublicElb, true, false ] - AvailabilityZone: !Select [ 2, !GetAZs ''] + AvailabilityZone: + Fn::Select: + - 2 + - !GetAZs '' Tags: - Key: Network Value: !If [ IsPublicElb, "Public", "Private" ] @@ -511,6 +595,10 @@ Outputs: Value: !Ref VPC Export: Name: !Sub ${AWS::StackName}-VpcId + BastionHost: + Description: IP of the bastion host + Value: !GetAtt BastionHost.PublicIp + Condition: HasBastion ElbSubnetIds: Description: The elb subnetids Value: diff --git a/templates/template.go b/templates/template.go index f1a0279e..0fab8d45 100644 --- a/templates/template.go +++ b/templates/template.go @@ -4,13 +4,19 @@ import ( "bufio" "bytes" "fmt" + "github.com/pkg/errors" + "github.com/stelligent/mu/common" + "gopkg.in/yaml.v2" "io" + "regexp" + "strconv" + "strings" "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) { +func NewTemplate(assetName string, data interface{}, cfnUpdates interface{}) (io.Reader, error) { asset, err := Asset(fmt.Sprintf("assets/%s", assetName)) if err != nil { return nil, err @@ -31,5 +37,112 @@ func NewTemplate(assetName string, data interface{}) (io.Reader, error) { bufWriter.Flush() + if cfnUpdates != nil { + templateMap := make(map[interface{}]interface{}) + cleanYaml := fixupYaml(buf.Bytes()) + err = yaml.Unmarshal(cleanYaml, templateMap) + if err != nil { + return nil, newYamlError(err, cleanYaml) + } + common.MapApply(templateMap, cfnUpdates) + yamlBytes, err := yaml.Marshal(templateMap) + if err != nil { + return nil, err + } + + buf = bytes.NewBuffer(yamlBytes) + } + return buf, nil } + +func fixupYaml(yamlBytes []byte) []byte { + scanner := bufio.NewScanner(bytes.NewReader(yamlBytes)) + + buf := new(bytes.Buffer) + bufWriter := bufio.NewWriter(buf) + tagRegexp := regexp.MustCompile("^(\\s*)(.+?)!(\\S+)\\s+(.*?)$") + fnTags := []string{"Ref", "If", "Not", "And", "Or", "Equals", "GetAtt", "Select", "Condition", "ImportValue", "GetAZs", "Base64", "FindInMap", "Sub", "Join", "Split"} + + extraIndentUntil := 0 + for scanner.Scan() { + line := scanner.Text() + matches := tagRegexp.FindStringSubmatch(line) + extraIndent := "" + if extraIndentUntil > 0 { + if len(strings.TrimSpace(line)) == 0 { + // no op + } else if strings.HasPrefix(line, strings.Repeat(" ", extraIndentUntil+1)) { + extraIndent = " " + } else { + extraIndentUntil = 0 + } + } + if len(matches) > 0 { + indent := matches[1] + pre := matches[2] + tag := matches[3] + post := matches[4] + + for _, fn := range fnTags { + if tag == fn { + var tagWithPrefix string + if tag == "Ref" || tag == "Condition" { + tagWithPrefix = quoteString(tag) + } else { + tagWithPrefix = quoteString(fmt.Sprintf("Fn::%s", tag)) + } + if post == "|" { + line = fmt.Sprintf("%s%s\n%s %s: %s", indent, pre, indent, tagWithPrefix, post) + //add extra indent until we are back to indent is back to current level + extraIndentUntil = len(indent) + } else { + line = fmt.Sprintf("%s%s {%s: %s}", indent, pre, tagWithPrefix, quoteString(post)) + } + } + } + } + bufWriter.WriteString(fmt.Sprintf("%s%s\n", extraIndent, line)) + + } + bufWriter.Flush() + return buf.Bytes() +} + +func quoteString(s string) string { + s = strings.TrimSpace(s) + if strings.HasPrefix(s, "[") || strings.HasPrefix(s, "{") { + return s + } + + if !strings.HasPrefix(s, "\"") { + s = fmt.Sprintf("\"%s", s) + } + if !strings.HasSuffix(s, "\"") { + s = fmt.Sprintf("%s\"", s) + } + return s +} + +func newYamlError(err error, yamlBytes []byte) error { + errRegexp := regexp.MustCompile("line (\\d+)") + matches := errRegexp.FindStringSubmatch(err.Error()) + lineNumber := -1 + if matches != nil { + lineNumber, _ = strconv.Atoi(matches[1]) + } + + buf := new(bytes.Buffer) + bufWriter := bufio.NewWriter(buf) + scanner := bufio.NewScanner(bytes.NewReader(yamlBytes)) + num := 1 + for scanner.Scan() { + line := scanner.Text() + if lineNumber == -1 || lineNumber == num { + bufWriter.WriteString(fmt.Sprintf("%d:\t%s\n", num, line)) + } + num = num + 1 + } + bufWriter.Flush() + return errors.Wrap(err, buf.String()) +} diff --git a/templates/template_test.go b/templates/template_test.go index 5ba9f75d..a440b966 100644 --- a/templates/template_test.go +++ b/templates/template_test.go @@ -2,9 +2,13 @@ package templates import ( "bytes" + "fmt" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/stelligent/mu/common" "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" "testing" ) @@ -15,7 +19,7 @@ func TestNewTemplate(t *testing.T) { templates := []string{"cluster.yml", "vpc.yml"} for _, templateName := range templates { - templateBodyReader, err := NewTemplate(templateName, environment) + templateBodyReader, err := NewTemplate(templateName, environment, nil) assert.Nil(err) assert.NotNil(templateBodyReader) @@ -34,7 +38,161 @@ func TestNewTemplate_invalid(t *testing.T) { environment := new(common.Environment) - templateBodyReader, err := NewTemplate("invalid-template-name.yml", environment) + templateBodyReader, err := NewTemplate("invalid-template-name.yml", environment, nil) assert.Nil(templateBodyReader) assert.NotNil(err) } + +func TestNewTemplate_withOverrides(t *testing.T) { + assert := assert.New(t) + + overridesYaml := + ` +--- +Resources: + Foo: + Type: AWS::S3::Bucket + Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: overrideBucketName +` + + overrides := make(map[interface{}]interface{}) + err := yaml.Unmarshal([]byte(overridesYaml), overrides) + assert.Nil(err) + + templateBodyReader, err := NewTemplate("bucket.yml", nil, overrides) + assert.Nil(err) + assert.NotNil(templateBodyReader) + + if err != nil { + fmt.Printf("%+v\n", err) + return + } + + templateBodyBytes := new(bytes.Buffer) + templateBodyBytes.ReadFrom(templateBodyReader) + templateBody := templateBodyBytes.String() + + assert.NotNil(templateBody) + assert.NotEmpty(templateBody) + + finalMap := make(map[interface{}]interface{}) + err = yaml.Unmarshal(templateBodyBytes.Bytes(), finalMap) + assert.Equal("mu-bucket-${BucketPrefix}", nestedMap(finalMap, "Outputs", "Bucket", "Export", "Name")["Fn::Sub"]) +} + +func TestTempalate_fixupYaml(t *testing.T) { + assert := assert.New(t) + + rawYaml := + ` +--- +Resources: + Foo: + Type: AWS::S3::Bucket + Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Ref BucketName + OtherValue: !Sub ${SomeStuff} + LongString: !Sub | + foo + bar baz + bam! + + after empty line + FinalValue: hi + ListOfRefs: + - !Ref Alpha + - !Ref Beta + IfValue: !If [ Foo, "Bar", "Baz" ] + + ### Disabling following yaml...unable to handle in Golang + #AvailabilityZone: !Select [ 1, !GetAZs ''] + #DeepMap: + #- "Fn::Equals": [!Ref ElbInternal, 'true'] + #- "Fn::Join": [ "", !Ref PathPattern] + + + +` + fixedYaml := fixupYaml([]byte(rawYaml)) + + result := make(map[interface{}]interface{}) + err := yaml.Unmarshal(fixedYaml, result) + assert.Nil(err) + assert.Equal("BucketName", nestedMap(result, "Resources", "Bucket", "Properties", "BucketName")["Ref"]) + assert.Equal("${SomeStuff}", nestedMap(result, "Resources", "Bucket", "Properties", "OtherValue")["Fn::Sub"]) + assert.Equal("foo\nbar baz\nbam!\n\nafter empty line\n", nestedMap(result, "Resources", "Bucket", "Properties", "LongString")["Fn::Sub"]) + assert.Equal("hi", nestedMap(result, "Resources", "Bucket", "Properties")["FinalValue"]) + + ifVal := nestedMap(result, "Resources", "Bucket", "Properties", "IfValue")["Fn::If"].([]interface{}) + assert.Equal("Foo", ifVal[0]) + assert.Equal("Bar", ifVal[1]) + assert.Equal("Baz", ifVal[2]) + + listOfRefs := nestedMap(result, "Resources", "Bucket", "Properties")["ListOfRefs"].([]interface{}) + ref1 := listOfRefs[0].(map[interface{}]interface{}) + ref2 := listOfRefs[1].(map[interface{}]interface{}) + assert.Equal("Alpha", ref1["Ref"]) + assert.Equal("Beta", ref2["Ref"]) + /* + deepMap := nestedMap(result, "Resources", "Bucket", "Properties")["DeepMap"].([]interface{}) + dm1 := deepMap[0].(map[interface{}]interface{})["Fn::Equals"].([]interface{}) + assert.Equal("ElbInternal", dm1[0].(map[interface{}]interface{})["Ref"]) + assert.Equal("true", dm1[1]) + dm2 := deepMap[1].(map[interface{}]interface{})["Fn::Join"].([]interface{}) + assert.Equal("", dm2[0]) + assert.Equal("PathPattern", dm2[1].(map[interface{}]interface{})["Ref"]) + */ + +} + +func TestNewTemplate_assets(t *testing.T) { + assert := assert.New(t) + + overrides := make(map[interface{}]interface{}) + + sessOptions := session.Options{SharedConfigState: session.SharedConfigEnable} + sess, err := session.NewSessionWithOptions(sessOptions) + assert.Nil(err) + + svc := cloudformation.New(sess) + + templates := []string{"bucket.yml", "cluster.yml", "pipeline.yml", "repo.yml", "service.yml", "vpc.yml", "vpc-target.yml"} + for _, templateName := range templates { + templateBodyReader, err := NewTemplate(templateName, nil, overrides) + + assert.Nil(err, templateName) + assert.NotNil(templateBodyReader, templateName) + + if templateBodyReader != nil { + templateBodyBytes := new(bytes.Buffer) + templateBodyBytes.ReadFrom(templateBodyReader) + templateBody := aws.String(templateBodyBytes.String()) + + assert.NotNil(templateBody, templateName) + assert.NotEmpty(templateBody, templateName) + + params := &cloudformation.ValidateTemplateInput{ + TemplateBody: templateBody, + } + + _, err := svc.ValidateTemplate(params) + if err != nil { + assert.Fail(err.Error(), templateName) + } + + } + } +} + +func nestedMap(m map[interface{}]interface{}, keys ...string) map[interface{}]interface{} { + rtn := m + for _, key := range keys { + rtn = rtn[key].(map[interface{}]interface{}) + } + return rtn +} diff --git a/wiki b/wiki new file mode 160000 index 00000000..f615a3f4 --- /dev/null +++ b/wiki @@ -0,0 +1 @@ +Subproject commit f615a3f4ca6af1f3a9988d519aad0759ed5fc13a diff --git a/workflows/config_init.go b/workflows/config_init.go new file mode 100644 index 00000000..51578f85 --- /dev/null +++ b/workflows/config_init.go @@ -0,0 +1,87 @@ +package workflows + +import ( + "bytes" + "fmt" + "github.com/stelligent/mu/common" + "github.com/stelligent/mu/templates" + "gopkg.in/yaml.v2" + "io/ioutil" + "os" +) + +// NewConfigInitializer create a new mu.yml file +func NewConfigInitializer(ctx *common.Context, createEnvironment bool, listenPort int, forceOverwrite bool) Executor { + + workflow := new(configWorkflow) + + return newWorkflow( + workflow.configInitialize(&ctx.Config, createEnvironment, listenPort, forceOverwrite), + ) +} + +type configWorkflow struct { +} + +func (workflow *configWorkflow) configInitialize(config *common.Config, createEnvironment bool, listenPort int, forceOverwrite bool) Executor { + return func() error { + basedir := "." + if config.Basedir != "" { + basedir = config.Basedir + } + + // unless force is set, don't overwrite...make sure files don't exist + if forceOverwrite == false { + log.Debugf("Checking for existing config file at %s/mu.yml", basedir) + if _, err := os.Stat(fmt.Sprintf("%s/mu.yml", basedir)); err == nil { + return fmt.Errorf("Config file already exists - '%s/mu.yml'. Use --force to overwrite", basedir) + } + + log.Debugf("Checking for existing buildspec file at %s/buildspec.yml", basedir) + if _, err := os.Stat(fmt.Sprintf("%s/buildspec.yml", basedir)); err == nil { + return fmt.Errorf("buildspec file already exists - '%s/buildspec.yml'. Use --force to overwrite", basedir) + } + } + + // write config + config.Service.Port = listenPort + config.Service.Name = config.Repo.Name + config.Service.PathPatterns = []string{"/*"} + config.Service.Pipeline.Source.Repo = config.Repo.Slug + + if createEnvironment && len(config.Environments) == 0 { + config.Environments = append(config.Environments, + common.Environment{Name: "dev"}, + common.Environment{Name: "production"}) + } + + configBytes, err := yaml.Marshal(config) + if err != nil { + return err + } + + log.Noticef("Writing config to '%s/mu.yml'", basedir) + + err = ioutil.WriteFile(fmt.Sprintf("%s/mu.yml", basedir), configBytes, 0600) + if err != nil { + return err + } + + // write buildspec + buildspec, err := templates.NewTemplate("buildspec.yml", nil, nil) + if err != nil { + return err + } + buildspecBytes := new(bytes.Buffer) + buildspecBytes.ReadFrom(buildspec) + + log.Noticef("Writing builspec to '%s/buildspec.yml'", basedir) + + err = ioutil.WriteFile(fmt.Sprintf("%s/buildspec.yml", basedir), buildspecBytes.Bytes(), 0600) + if err != nil { + return err + } + + return nil + } +} diff --git a/workflows/config_init_test.go b/workflows/config_init_test.go new file mode 100644 index 00000000..fa0f2862 --- /dev/null +++ b/workflows/config_init_test.go @@ -0,0 +1,65 @@ +package workflows + +import ( + "bufio" + "bytes" + "fmt" + "github.com/stelligent/mu/common" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + "io/ioutil" + "os" + "testing" +) + +func TestNewConfigInitializer(t *testing.T) { + assert := assert.New(t) + ctx := common.NewContext() + lister := NewConfigInitializer(ctx, false, 8080, false) + assert.NotNil(lister) +} + +func TestNewConfigInitializer_FileExists(t *testing.T) { + assert := assert.New(t) + + var err error + config := new(common.Config) + config.Basedir, err = ioutil.TempDir("", "mu-test") + assert.Nil(err) + + defer os.RemoveAll(config.Basedir) + + workflow := new(configWorkflow) + err = workflow.configInitialize(config, false, 80, false)() + assert.Nil(err) + + if newConfig, err := loadConfig(config.Basedir); err == nil { + assert.Equal(80, newConfig.Service.Port) + } else { + assert.Fail(err.Error()) + } + + err = workflow.configInitialize(config, false, 80, false)() + assert.NotNil(err) + + err = workflow.configInitialize(config, false, 3000, true)() + assert.Nil(err) + + if newConfig, err := loadConfig(config.Basedir); err == nil { + assert.Equal(3000, newConfig.Service.Port) + } else { + assert.Fail(err.Error()) + } +} + +func loadConfig(basedir string) (*common.Config, error) { + config := new(common.Config) + yamlFile, err := os.Open(fmt.Sprintf("%s/mu.yml", basedir)) + if err != nil { + return nil, err + } + yamlReader := bufio.NewReader(yamlFile) + yamlBuffer := new(bytes.Buffer) + yamlBuffer.ReadFrom(yamlReader) + return config, yaml.Unmarshal(yamlBuffer.Bytes(), config) +} diff --git a/workflows/environment_terminate.go b/workflows/environment_terminate.go index 64308e14..f21daca6 100644 --- a/workflows/environment_terminate.go +++ b/workflows/environment_terminate.go @@ -10,11 +10,39 @@ func NewEnvironmentTerminator(ctx *common.Context, environmentName string) Execu workflow := new(environmentWorkflow) return newWorkflow( + workflow.environmentServiceTerminator(environmentName, ctx.StackManager, ctx.StackManager, ctx.StackManager), workflow.environmentEcsTerminator(environmentName, ctx.StackManager, ctx.StackManager), workflow.environmentVpcTerminator(environmentName, ctx.StackManager, ctx.StackManager), ) } +func (workflow *environmentWorkflow) environmentServiceTerminator(environmentName string, stackLister common.StackLister, stackDeleter common.StackDeleter, stackWaiter common.StackWaiter) Executor { + return func() error { + log.Noticef("Terminating Services for environment '%s' ...", environmentName) + stacks, err := stackLister.ListStacks(common.StackTypeService) + if err != nil { + return err + } + for _, stack := range stacks { + if stack.Tags["environment"] != environmentName { + continue + } + err := stackDeleter.DeleteStack(stack.Name) + if err != nil { + return err + } + } + for _, stack := range stacks { + if stack.Tags["environment"] != environmentName { + continue + } + log.Infof(" Undeploying service '%s' from environment '%s'", stack.Tags["service"], environmentName) + stackWaiter.AwaitFinalStatus(stack.Name) + } + + return nil + } +} func (workflow *environmentWorkflow) environmentEcsTerminator(environmentName string, stackDeleter common.StackDeleter, stackWaiter common.StackWaiter) Executor { return func() error { log.Noticef("Terminating ECS environment '%s' ...", environmentName) diff --git a/workflows/environment_upsert.go b/workflows/environment_upsert.go index 3aa84dda..02bad64d 100644 --- a/workflows/environment_upsert.go +++ b/workflows/environment_upsert.go @@ -10,6 +10,7 @@ import ( ) var ecsImagePattern = "amzn-ami-*-amazon-ecs-optimized" +var bastionImagePattern = "amzn-ami-hvm-*-x86_64-gp2" // NewEnvironmentUpserter create a new workflow for upserting an environment func NewEnvironmentUpserter(ctx *common.Context, environmentName string) Executor { @@ -19,7 +20,7 @@ func NewEnvironmentUpserter(ctx *common.Context, environmentName string) Executo return newWorkflow( workflow.environmentFinder(&ctx.Config, environmentName), - workflow.environmentVpcUpserter(vpcImportParams, ctx.StackManager, ctx.StackManager), + workflow.environmentVpcUpserter(vpcImportParams, ctx.StackManager, ctx.StackManager, ctx.StackManager), workflow.environmentEcsUpserter(vpcImportParams, ctx.StackManager, ctx.StackManager, ctx.StackManager), ) } @@ -38,20 +39,21 @@ func (workflow *environmentWorkflow) environmentFinder(config *common.Config, en } } -func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[string]string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { +func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[string]string, imageFinder common.ImageFinder, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { return func() error { environment := workflow.environment vpcStackParams := make(map[string]string) var template io.Reader var err error - var stackType common.StackType + var vpcStackName string if environment.VpcTarget.VpcID == "" { log.Debugf("No VpcTarget, so we will upsert the VPC stack that manages the VPC") - stackType = common.StackTypeVpc + vpcStackName = common.CreateStackName(common.StackTypeVpc, environment.Name) + overrides := common.GetStackOverrides(vpcStackName) // no target VPC, we need to create/update the VPC stack - template, err = templates.NewTemplate("vpc.yml", environment) + template, err = templates.NewTemplate("vpc.yml", environment, overrides) if err != nil { return err } @@ -62,13 +64,21 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ if environment.Cluster.SSHAllow != "" { vpcStackParams["SshAllow"] = environment.Cluster.SSHAllow } + if environment.Cluster.KeyName != "" { + vpcStackParams["BastionKeyName"] = environment.Cluster.KeyName + vpcStackParams["BastionImageId"], err = imageFinder.FindLatestImageID(bastionImagePattern) + if err != nil { + return err + } + } vpcStackParams["ElbInternal"] = strconv.FormatBool(environment.Loadbalancer.Internal) } else { log.Debugf("VpcTarget exists, so we will upsert the VPC stack that references the VPC attributes") - stackType = common.StackTypeTarget + vpcStackName = common.CreateStackName(common.StackTypeTarget, environment.Name) + overrides := common.GetStackOverrides(vpcStackName) - template, err = templates.NewTemplate("vpc-target.yml", environment) + template, err = templates.NewTemplate("vpc-target.yml", environment, overrides) if err != nil { return err } @@ -80,7 +90,6 @@ func (workflow *environmentWorkflow) environmentVpcUpserter(vpcImportParams map[ } log.Noticef("Upserting VPC environment '%s' ...", environment.Name) - vpcStackName := common.CreateStackName(stackType, environment.Name) err = stackUpserter.UpsertStack(vpcStackName, template, vpcStackParams, buildEnvironmentTags(environment.Name, common.StackTypeVpc)) if err != nil { return err @@ -103,7 +112,8 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ envStackName := common.CreateStackName(common.StackTypeCluster, environment.Name) log.Noticef("Upserting ECS environment '%s' ...", environment.Name) - template, err := templates.NewTemplate("cluster.yml", environment) + overrides := common.GetStackOverrides(envStackName) + template, err := templates.NewTemplate("cluster.yml", environment, overrides) if err != nil { return err } @@ -113,6 +123,9 @@ func (workflow *environmentWorkflow) environmentEcsUpserter(vpcImportParams map[ if environment.Cluster.SSHAllow != "" { stackParams["SshAllow"] = environment.Cluster.SSHAllow } + if environment.Cluster.InstanceType != "" { + stackParams["InstanceType"] = environment.Cluster.InstanceType + } if environment.Cluster.ImageID != "" { stackParams["ImageId"] = environment.Cluster.ImageID } else { diff --git a/workflows/environment_upsert_test.go b/workflows/environment_upsert_test.go index 125aaac8..9588e3cf 100644 --- a/workflows/environment_upsert_test.go +++ b/workflows/environment_upsert_test.go @@ -97,6 +97,33 @@ func TestEnvironmentEcsUpserter(t *testing.T) { func TestEnvironmentVpcUpserter(t *testing.T) { assert := assert.New(t) + workflow := new(environmentWorkflow) + workflow.environment = &common.Environment{ + Name: "foo", + } + workflow.environment.Cluster.KeyName = "mykey" + + vpcInputParams := make(map[string]string) + + stackManager := new(mockedStackManagerForUpsert) + stackManager.On("AwaitFinalStatus", "mu-vpc-foo").Return(&common.Stack{Status: cloudformation.StackStatusCreateComplete}) + stackManager.On("UpsertStack", "mu-vpc-foo", mock.AnythingOfType("map[string]string")).Return(nil) + stackManager.On("FindLatestImageID").Return("ami-00000", nil) + + err := workflow.environmentVpcUpserter(vpcInputParams, stackManager, stackManager, stackManager)() + assert.Nil(err) + assert.Equal("mu-vpc-foo-VpcId", vpcInputParams["VpcId"]) + assert.Equal("mu-vpc-foo-EcsSubnetIds", vpcInputParams["EcsSubnetIds"]) + + stackManager.AssertExpectations(t) + stackManager.AssertNumberOfCalls(t, "AwaitFinalStatus", 1) + stackManager.AssertNumberOfCalls(t, "UpsertStack", 1) + stackManager.AssertNumberOfCalls(t, "FindLatestImageID", 1) +} + +func TestEnvironmentVpcUpserter_NoBastion(t *testing.T) { + assert := assert.New(t) + workflow := new(environmentWorkflow) workflow.environment = &common.Environment{ Name: "foo", @@ -108,7 +135,7 @@ func TestEnvironmentVpcUpserter(t *testing.T) { stackManager.On("AwaitFinalStatus", "mu-vpc-foo").Return(&common.Stack{Status: cloudformation.StackStatusCreateComplete}) stackManager.On("UpsertStack", "mu-vpc-foo", mock.AnythingOfType("map[string]string")).Return(nil) - err := workflow.environmentVpcUpserter(vpcInputParams, stackManager, stackManager)() + err := workflow.environmentVpcUpserter(vpcInputParams, stackManager, stackManager, stackManager)() assert.Nil(err) assert.Equal("mu-vpc-foo-VpcId", vpcInputParams["VpcId"]) assert.Equal("mu-vpc-foo-EcsSubnetIds", vpcInputParams["EcsSubnetIds"]) @@ -116,6 +143,7 @@ func TestEnvironmentVpcUpserter(t *testing.T) { stackManager.AssertExpectations(t) stackManager.AssertNumberOfCalls(t, "AwaitFinalStatus", 1) stackManager.AssertNumberOfCalls(t, "UpsertStack", 1) + stackManager.AssertNumberOfCalls(t, "FindLatestImageID", 0) } func TestEnvironmentVpcUpserter_Unmanaged(t *testing.T) { @@ -143,7 +171,7 @@ environments: workflow := new(environmentWorkflow) workflow.environment = &config.Environments[0] - err = workflow.environmentVpcUpserter(vpcInputParams, stackManager, stackManager)() + err = workflow.environmentVpcUpserter(vpcInputParams, stackManager, stackManager, stackManager)() assert.Nil(err) assert.Equal("mu-target-dev-VpcId", vpcInputParams["VpcId"]) assert.Equal("mu-target-dev-EcsSubnetIds", vpcInputParams["EcsSubnetIds"]) diff --git a/workflows/environment_view.go b/workflows/environment_view.go index db2be102..a9c57d31 100644 --- a/workflows/environment_view.go +++ b/workflows/environment_view.go @@ -1,6 +1,7 @@ package workflows import ( + "encoding/json" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecs" @@ -11,16 +12,49 @@ import ( ) // NewEnvironmentViewer create a new workflow for showing an environment -func NewEnvironmentViewer(ctx *common.Context, environmentName string, writer io.Writer) Executor { +func NewEnvironmentViewer(ctx *common.Context, format string, environmentName string, writer io.Writer) Executor { workflow := new(environmentWorkflow) + var environmentViewer func() error + if format == "json" { + environmentViewer = workflow.environmentViewerJSON(environmentName, ctx.StackManager, ctx.StackManager, ctx.ClusterManager, writer) + } else { + environmentViewer = workflow.environmentViewerCli(environmentName, ctx.StackManager, ctx.StackManager, ctx.ClusterManager, writer) + } + return newWorkflow( - workflow.environmentViewer(environmentName, ctx.StackManager, ctx.StackManager, ctx.ClusterManager, writer), + environmentViewer, ) } -func (workflow *environmentWorkflow) environmentViewer(environmentName string, stackGetter common.StackGetter, stackLister common.StackLister, instanceLister common.ClusterInstanceLister, writer io.Writer) Executor { +type jsonOutput struct { + Values [1]struct { + Key string `json:"key"` + Value string `json:"value"` + } `json:"values"` +} + +func (workflow *environmentWorkflow) environmentViewerJSON(environmentName string, stackGetter common.StackGetter, stackLister common.StackLister, instanceLister common.ClusterInstanceLister, writer io.Writer) Executor { + return func() error { + clusterStackName := common.CreateStackName(common.StackTypeCluster, environmentName) + clusterStack, err := stackGetter.GetStack(clusterStackName) + if err != nil { + return err + } + + output := jsonOutput{} + output.Values[0].Key = "BASE_URL" + output.Values[0].Value = clusterStack.Outputs["BaseUrl"] + + enc := json.NewEncoder(writer) + enc.Encode(&output) + + return nil + } +} + +func (workflow *environmentWorkflow) environmentViewerCli(environmentName string, stackGetter common.StackGetter, stackLister common.StackLister, instanceLister common.ClusterInstanceLister, writer io.Writer) Executor { bold := color.New(color.Bold).SprintFunc() return func() error { clusterStackName := common.CreateStackName(common.StackTypeCluster, environmentName) @@ -38,9 +72,10 @@ func (workflow *environmentWorkflow) environmentViewer(environmentName string, s fmt.Fprintf(writer, "%s:\tunmanaged\n", bold("VPC Stack")) } else { fmt.Fprintf(writer, "%s:\t%s (%s)\n", bold("VPC Stack"), vpcStack.Name, colorizeStackStatus(vpcStack.Status)) + fmt.Fprintf(writer, "%s:\t%s\n", bold("Bastion Host"), vpcStack.Outputs["BastionHost"]) } - fmt.Fprintf(writer, "%s:\t\t%s\n", bold("Base URL"), clusterStack.Outputs["BaseUrl"]) + 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") diff --git a/workflows/environment_view_test.go b/workflows/environment_view_test.go index f626ef61..5085925b 100644 --- a/workflows/environment_view_test.go +++ b/workflows/environment_view_test.go @@ -9,6 +9,6 @@ import ( func TestNewEnvironmentViewer(t *testing.T) { assert := assert.New(t) ctx := common.NewContext() - viewer := NewEnvironmentViewer(ctx, "foo", nil) + viewer := NewEnvironmentViewer(ctx, "json", "foo", nil) assert.NotNil(viewer) } diff --git a/workflows/pipeline_upsert.go b/workflows/pipeline_upsert.go index bbf7321b..e711433f 100644 --- a/workflows/pipeline_upsert.go +++ b/workflows/pipeline_upsert.go @@ -1,9 +1,11 @@ package workflows import ( + "bytes" "fmt" "github.com/stelligent/mu/common" "github.com/stelligent/mu/templates" + "regexp" "strings" ) @@ -23,11 +25,13 @@ func NewPipelineUpserter(ctx *common.Context, tokenProvider func(bool) string) E func (workflow *pipelineWorkflow) pipelineBucket(stackUpserter common.StackUpserter, stackWaiter common.StackWaiter) Executor { return func() error { - template, err := templates.NewTemplate("bucket.yml", nil) + bucketStackName := common.CreateStackName(common.StackTypeBucket, "codepipeline") + overrides := common.GetStackOverrides(bucketStackName) + template, err := templates.NewTemplate("bucket.yml", nil, overrides) if err != nil { return err } - bucketStackName := common.CreateStackName(common.StackTypeBucket, "codepipeline") + log.Noticef("Upserting Bucket for CodePipeline") bucketParams := make(map[string]string) bucketParams["BucketPrefix"] = "codepipeline" err = stackUpserter.UpsertStack(bucketStackName, template, bucketParams, buildPipelineTags(workflow.serviceName, common.StackTypeBucket)) @@ -46,9 +50,10 @@ func (workflow *pipelineWorkflow) pipelineUpserter(tokenProvider func(bool) stri return func() error { pipelineStackName := common.CreateStackName(common.StackTypePipeline, workflow.serviceName) pipelineStack := stackWaiter.AwaitFinalStatus(pipelineStackName) + overrides := common.GetStackOverrides(pipelineStackName) - log.Noticef("Upserting Pipeline for service'%s' ...", workflow.serviceName) - template, err := templates.NewTemplate("pipeline.yml", nil) + log.Noticef("Upserting Pipeline for service '%s' ...", workflow.serviceName) + template, err := templates.NewTemplate("pipeline.yml", nil, overrides) if err != nil { return err } @@ -77,10 +82,40 @@ func (workflow *pipelineWorkflow) pipelineUpserter(tokenProvider func(bool) stri pipelineParams["BuildImage"] = workflow.pipelineConfig.Build.Image } + if workflow.pipelineConfig.Acceptance.Type != "" { + pipelineParams["TestType"] = workflow.pipelineConfig.Acceptance.Type + } + if workflow.pipelineConfig.Acceptance.ComputeType != "" { + pipelineParams["TestComputeType"] = workflow.pipelineConfig.Acceptance.ComputeType + } + + if workflow.pipelineConfig.Acceptance.Image != "" { + pipelineParams["TestImage"] = workflow.pipelineConfig.Acceptance.Image + } + + if workflow.pipelineConfig.Acceptance.Environment != "" { + pipelineParams["TestEnv"] = workflow.pipelineConfig.Acceptance.Environment + } + + if workflow.pipelineConfig.Production.Environment != "" { + pipelineParams["ProdEnv"] = workflow.pipelineConfig.Production.Environment + } + if workflow.pipelineConfig.MuBaseurl != "" { pipelineParams["MuDownloadBaseurl"] = workflow.pipelineConfig.MuBaseurl } + // get default buildspec + buildspec, err := templates.NewTemplate("buildspec.yml", nil, nil) + if err != nil { + return err + } + buildspecBytes := new(bytes.Buffer) + buildspecBytes.ReadFrom(buildspec) + newlineRegexp := regexp.MustCompile(`\r?\n`) + buildspecString := newlineRegexp.ReplaceAllString(buildspecBytes.String(), "\\n") + pipelineParams["DefaultBuildspec"] = buildspecString + version := workflow.pipelineConfig.MuVersion if version == "" { version = common.GetVersion() diff --git a/workflows/service_common.go b/workflows/service_common.go index 5508f999..236617fa 100644 --- a/workflows/service_common.go +++ b/workflows/service_common.go @@ -67,7 +67,9 @@ func (workflow *serviceWorkflow) serviceRepoUpserter(service *common.Service, st log.Noticef("Upsert repo for service '%s'", workflow.serviceName) - template, err := templates.NewTemplate("repo.yml", nil) + ecrStackName := common.CreateStackName(common.StackTypeRepo, workflow.serviceName) + overrides := common.GetStackOverrides(ecrStackName) + template, err := templates.NewTemplate("repo.yml", nil, overrides) if err != nil { return err } @@ -75,8 +77,6 @@ func (workflow *serviceWorkflow) serviceRepoUpserter(service *common.Service, st stackParams := make(map[string]string) stackParams["RepoName"] = workflow.serviceName - ecrStackName := common.CreateStackName(common.StackTypeRepo, workflow.serviceName) - err = stackUpserter.UpsertStack(ecrStackName, template, stackParams, buildEnvironmentTags(workflow.serviceName, common.StackTypeRepo)) if err != nil { return err diff --git a/workflows/service_deploy.go b/workflows/service_deploy.go index 850152b4..86c5b0ab 100644 --- a/workflows/service_deploy.go +++ b/workflows/service_deploy.go @@ -103,12 +103,15 @@ func (workflow *serviceWorkflow) serviceDeployer(service *common.Service, stackP stackParams["PathPattern"] = strings.Join(service.PathPatterns, ",") } - template, err := templates.NewTemplate("service.yml", service) + svcStackName := common.CreateStackName(common.StackTypeService, workflow.serviceName, environmentName) + + resolveServiceEnvironment(service, environmentName) + overrides := common.GetStackOverrides(svcStackName) + template, err := templates.NewTemplate("service.yml", service, overrides) if err != nil { return err } - svcStackName := common.CreateStackName(common.StackTypeService, workflow.serviceName, environmentName) err = stackUpserter.UpsertStack(svcStackName, template, stackParams, buildServiceTags(workflow.serviceName, environmentName, common.StackTypeService)) if err != nil { return err @@ -120,6 +123,29 @@ func (workflow *serviceWorkflow) serviceDeployer(service *common.Service, stackP } } +func resolveServiceEnvironment(service *common.Service, environment string) { + for key, value := range service.Environment { + switch value.(type) { + case map[interface{}]interface{}: + found := false + for env, v := range value.(map[interface{}]interface{}) { + if env.(string) == environment { + service.Environment[key] = v.(string) + found = true + } + } + if found != true { + service.Environment[key] = "" + } + case string: + // do nothing + default: + log.Warningf("Unable to resolve environment '%s': %v", key, value) + } + + } +} + func buildServiceTags(serviceName string, environmentName string, stackType common.StackType) map[string]string { return map[string]string{ "type": string(stackType),