From 055b6aa46cc8cd6ed0f1fcd1b4ecf643598ccee4 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 15 Jan 2018 11:44:47 -0800 Subject: [PATCH 01/13] add support for centos7 for env provider --- templates/assets/env-ec2.yml | 10 +++ templates/assets/service-ec2.yml | 137 ++++++++++++++++++++++--------- workflows/service_deploy.go | 1 + 3 files changed, 109 insertions(+), 39 deletions(-) diff --git a/templates/assets/env-ec2.yml b/templates/assets/env-ec2.yml index a2907e7e..c9597449 100644 --- a/templates/assets/env-ec2.yml +++ b/templates/assets/env-ec2.yml @@ -47,6 +47,14 @@ Parameters: Description: ECS AMI to launch Type: String Default: '' + ImageOsType: + Description: OS Type for ECS AMI + Type: String + Default: 'amazon' + AllowedValues: + - 'amazon' + - 'centos7' + - 'windows' InstanceSubnetIds: Type: String Description: Name of the value to import for the ecs subnet ids @@ -130,6 +138,8 @@ Outputs: Value: !Ref SshAllow ImageId: Value: !Ref ImageId + ImageOsType: + Value: !Ref ImageOsType HttpProxy: Value: !Ref HttpProxy ConsulServerAutoScalingGroup: diff --git a/templates/assets/service-ec2.yml b/templates/assets/service-ec2.yml index c8d63450..df577540 100644 --- a/templates/assets/service-ec2.yml +++ b/templates/assets/service-ec2.yml @@ -93,6 +93,14 @@ Parameters: Description: EC2 AMI to launch Type: String Default: '' + ImageOsType: + Description: OS Type for ECS AMI + Type: String + Default: 'amazon' + AllowedValues: + - 'amazon' + - 'centos7' + - 'windows' InstanceSubnetIds: Type: String Description: Name of the value to import for the ecs subnet ids @@ -262,28 +270,19 @@ Resources: Type: AWS::AutoScaling::LaunchConfiguration Metadata: AWS::CloudFormation::Init: - config: - sources: - "/opt/consul/bin": !Ref ConsulUrl + configSets: + amazon: + - commonLinux + - amazonLinux + centos7: + - commonLinux + - centos7Linux + amazonLinux: packages: yum: awslogs: [] aws-cli: [] - ruby: [] files: - "/etc/environment": - content: !Sub | - # created via mu - {{with .Environment}} - {{range $key, $val := .}} - {{$key}}={{$val}} - {{end}} - {{end}} - "/tmp/codedeploy-install": - source: !Sub https://aws-codedeploy-${AWS::Region}.s3.amazonaws.com/latest/install - mode: '000755' - owner: root - group: root "/etc/init/consul.conf": content: !Sub | description "Consul Client process" @@ -297,6 +296,71 @@ Resources: mode: '000755' owner: root group: root + + services: + sysvinit: + codedeploy-agent: + enabled: 'true' + ensureRunning: 'true' + awslogs: + enabled: 'true' + ensureRunning: 'true' + files: + - "/etc/awslogs/awslogs.conf" + - "/etc/awslogs/etc/proxy.conf" + cfn-hup: + enabled: 'true' + ensureRunning: 'true' + files: + - "/etc/cfn/cfn-hup.conf" + - "/etc/cfn/hooks.d/cfn-auto-reloader.conf" + commands: + start-consul: + command: "start consul" + centos7Linux: + files: + "/etc/systemd/system/consul.service": + content: + Fn::Sub: | + [Unit] + Description=Consul Agent + + [Service] + Type=simple + ExecStart=/opt/consul/bin/consul agent -config-dir /opt/consul/config + Restart=always + User=root + Group=root + LimitNOFILE=10240 + LimitFSIZE=infinity + + [Install] + WantedBy=multi-user.target + group: root + mode: "000755" + owner: root + commands: + awscli-install: + command: pip install --upgrade awscli + awslogs-install: + command: curl https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py -O && python ./awslogs-agent-setup.py --region ${AWS::Region} --non-interactive --configfile=/etc/awslogs/awslogs.conf + start-consul: + command: "systemctl start consul.service && systemctl enable consul.service" + commonLinux: + sources: + "/opt/consul/bin": !Ref ConsulUrl + packages: + yum: + ruby: [] + files: + "/etc/environment": + content: !Sub | + # created via mu + {{with .Environment}} + {{range $key, $val := .}} + {{$key}}={{$val}} + {{end}} + {{end}} "/opt/consul/config/service.json": content: !Sub | { @@ -320,6 +384,11 @@ Resources: ] } } + "/tmp/codedeploy-install": + source: !Sub https://aws-codedeploy-${AWS::Region}.s3.amazonaws.com/latest/install + mode: '000755' + owner: root + group: root "/etc/codedeploy-agent/conf/codedeployagent.yml": content: !Sub | --- @@ -344,12 +413,20 @@ Resources: [cfn-auto-reloader-hook] triggers=post.update path=Resources.ServiceInstances.Metadata.AWS::CloudFormation::Init - action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ServiceInstances --region ${AWS::Region} + action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ServiceInstances --configsets ${ImageOsType} --region ${AWS::Region} runas=root "/etc/awslogs/etc/proxy.conf": content: !Sub | HTTP_PROXY=http://${HttpProxy}/ HTTPS_PROXY=http://${HttpProxy}/ + "/etc/profile.d/proxy.sh": + content: !Sub | + http_proxy=http://${HttpProxy}/ + https_proxy=http://${HttpProxy}/ + no_proxy=169.254.169.254 + mode: '000755' + owner: root + group: root "/etc/awslogs/awscli.conf": content: !Sub | [plugins] @@ -401,27 +478,9 @@ Resources: log_group_name = ${AWS::StackName} commands: codedeploy-install: - command: "./codedeploy-install auto" + command: + Fn::Sub: ./codedeploy-install auto --proxy http://${HttpProxy} cwd: "/tmp" - start-consul: - command: "start consul" - services: - sysvinit: - codedeploy-agent: - enabled: 'true' - ensureRunning: 'true' - awslogs: - enabled: 'true' - ensureRunning: 'true' - files: - - "/etc/awslogs/awslogs.conf" - - "/etc/awslogs/etc/proxy.conf" - cfn-hup: - enabled: 'true' - ensureRunning: 'true' - files: - - "/etc/cfn/cfn-hup.conf" - - "/etc/cfn/hooks.d/cfn-auto-reloader.conf" Properties: ImageId: !Ref ImageId SecurityGroups: @@ -458,7 +517,7 @@ Resources: yum -y update yum install -y aws-cfn-bootstrap - /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ServiceInstances --region ${AWS::Region} $CFN_PROXY_ARGS + /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ServiceInstances --configsets ${ImageOsType} --region ${AWS::Region} $CFN_PROXY_ARGS /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ServiceAutoScalingGroup --region ${AWS::Region} $CFN_PROXY_ARGS DeployGroup: Type: AWS::CodeDeploy::DeploymentGroup diff --git a/workflows/service_deploy.go b/workflows/service_deploy.go index 74027441..0eefe695 100644 --- a/workflows/service_deploy.go +++ b/workflows/service_deploy.go @@ -180,6 +180,7 @@ func (workflow *serviceWorkflow) serviceApplyEc2Params(params map[string]string, "SshAllow", "InstanceType", "ImageId", + "ImageOsType", "KeyName", "HttpProxy", "ConsulServerAutoScalingGroup", From c953cdc2e087128fe4b285bdbf6b3f2f46111b90 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 15 Jan 2018 16:53:19 -0800 Subject: [PATCH 02/13] conditional on proxy --- templates/assets/service-ec2.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/templates/assets/service-ec2.yml b/templates/assets/service-ec2.yml index df577540..1b20ff39 100644 --- a/templates/assets/service-ec2.yml +++ b/templates/assets/service-ec2.yml @@ -163,6 +163,11 @@ Parameters: Description: Target CPU Utilization for Tracking Policy on ASG Default: '75' Conditions: + HasProxy: + "Fn::Not": + - "Fn::Equals": + - !Ref HttpProxy + - '' HasPathPattern: "Fn::Not": - "Fn::Equals": @@ -479,7 +484,11 @@ Resources: commands: codedeploy-install: command: - Fn::Sub: ./codedeploy-install auto --proxy http://${HttpProxy} + Fn::Sub: + Fn::If: + - HasProxy + - ./codedeploy-install auto --proxy http://${HttpProxy} + - ./codedeploy-install auto cwd: "/tmp" Properties: ImageId: !Ref ImageId From 6a233dfc721b8e70c9f48c83bdabcc13228f84e4 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Mon, 15 Jan 2018 22:07:04 -0800 Subject: [PATCH 03/13] typo with proxy sub --- templates/assets/service-ec2.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/templates/assets/service-ec2.yml b/templates/assets/service-ec2.yml index 1b20ff39..76f42626 100644 --- a/templates/assets/service-ec2.yml +++ b/templates/assets/service-ec2.yml @@ -288,6 +288,12 @@ Resources: awslogs: [] aws-cli: [] files: + "/etc/awslogs/awscli.conf": + content: !Sub | + [plugins] + cwlogs = cwlogs + [default] + region = ${AWS::Region} "/etc/init/consul.conf": content: !Sub | description "Consul Client process" @@ -484,11 +490,10 @@ Resources: commands: codedeploy-install: command: - Fn::Sub: - Fn::If: - - HasProxy - - ./codedeploy-install auto --proxy http://${HttpProxy} - - ./codedeploy-install auto + Fn::If: + - HasProxy + - Fn::Sub: ./codedeploy-install auto --proxy http://${HttpProxy} + - ./codedeploy-install auto cwd: "/tmp" Properties: ImageId: !Ref ImageId From 8c352efa720cf2143ba9993b642a3fa9ec65076b Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 16 Jan 2018 00:13:26 -0800 Subject: [PATCH 04/13] fix #230 Creating SNS topic for pipeline --- common/types.go | 3 +- provider/aws/cloudformation.go | 3 ++ templates/assets/pipeline.yml | 80 ++++++++++++++++++++++++++++++++++ workflows/pipeline_common.go | 1 + workflows/pipeline_upsert.go | 17 +++++++- 5 files changed, 102 insertions(+), 2 deletions(-) diff --git a/common/types.go b/common/types.go index b6008136..a7fcd2c3 100644 --- a/common/types.go +++ b/common/types.go @@ -188,7 +188,8 @@ type Pipeline struct { Pipeline string `yaml:"pipeline,omitempty"` Build string `yaml:"build,omitempty"` } `yaml:"roles,omitempty"` - Bucket string `yaml:"bucket,omitempty"` + Bucket string `yaml:"bucket,omitempty"` + Notify []string `yaml:"notify,omitempty"` } // Stack summary diff --git a/provider/aws/cloudformation.go b/provider/aws/cloudformation.go index 994fddf5..bd0c3603 100644 --- a/provider/aws/cloudformation.go +++ b/provider/aws/cloudformation.go @@ -300,6 +300,9 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) *co numEvents := len(eventResp.StackEvents) for i := numEvents - 1; i >= 0; i-- { e := eventResp.StackEvents[i] + if aws.StringValue(e.ResourceType) == "AWS::CloudFormation::Stack" && strings.HasSuffix(aws.StringValue(e.ResourceStatus), "_COMPLETE") { + break + } if priorEventTime == nil || priorEventTime.Before(aws.TimeValue(e.Timestamp)) { status := aws.StringValue(e.ResourceStatus) diff --git a/templates/assets/pipeline.yml b/templates/assets/pipeline.yml index 9515eae6..5abb8161 100644 --- a/templates/assets/pipeline.yml +++ b/templates/assets/pipeline.yml @@ -514,6 +514,83 @@ Resources: Id: !Ref CodePipelineKeyArn Type: KMS Location: !Ref PipelineBucket + PipelineNotificationTopic: + Type: AWS::SNS::Topic + Properties: + TopicName: !Sub ${Namespace}-pipeline-${ServiceName}-notification + PipelineSucceededEventRule: + Type: AWS::Events::Rule + Properties: + Description: !Sub Pipeline Succeeded Event Rule for service ${ServiceName} + EventPattern: + source: + - aws.codepipeline + detail-type: + - CodePipeline Pipeline Execution State Change + detail: + state: + - SUCCEEDED + pipeline: + - !Sub ${Namespace}-${ServiceName} + State: "ENABLED" + Targets: + - Arn: !Ref PipelineNotificationTopic + Id: "SucceededTopic" + InputTransformer: + InputTemplate: !Sub '"Pipeline has succeeded. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/" ' + InputPathsMap: + pipeline: "$.detail.pipeline" + PipelineApprovalEventRule: + Type: AWS::Events::Rule + Properties: + Description: !Sub Pipeline Approval Event Rule for service ${ServiceName} + EventPattern: + source: + - aws.codepipeline + detail-type: + - CodePipeline Action Execution State Change + detail: + state: + - STARTED + pipeline: + - !Sub ${Namespace}-${ServiceName} + type: + owner: + - AWS + category: + - Approval + provider: + - Manual + State: "ENABLED" + Targets: + - Arn: !Ref PipelineNotificationTopic + Id: "ApprovalTopic" + InputTransformer: + InputTemplate: !Sub '"Pipeline requires approval. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/" ' + InputPathsMap: + pipeline: "$.detail.pipeline" + PipelineFailedEventRule: + Type: AWS::Events::Rule + Properties: + Description: !Sub Pipeline Failed Event Rule for service ${ServiceName} + EventPattern: + source: + - aws.codepipeline + detail-type: + - CodePipeline Pipeline Execution State Change + detail: + state: + - FAILED + pipeline: + - !Sub ${Namespace}-${ServiceName} + State: "ENABLED" + Targets: + - Arn: !Ref PipelineNotificationTopic + Id: "FailedTopic" + InputTransformer: + InputTemplate: !Sub '"Pipeline has failed. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/" ' + InputPathsMap: + pipeline: "$.detail.pipeline" Outputs: CodePipelineUrl: Value: !Sub https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/${Pipeline} @@ -521,3 +598,6 @@ Outputs: PipelineName: Value: !Sub ${Pipeline} Description: Pipeline Name + PipelineNotificationTopicArn: + Value: !Ref PipelineNotificationTopic + Description: SNS Topic for pipeline notifications diff --git a/workflows/pipeline_common.go b/workflows/pipeline_common.go index c25b409c..a03e2e13 100644 --- a/workflows/pipeline_common.go +++ b/workflows/pipeline_common.go @@ -13,6 +13,7 @@ type pipelineWorkflow struct { codeBranch string repoName string codeDeployBucket string + notificationArn string } func colorizeActionStatus(actionStatus string) string { diff --git a/workflows/pipeline_upsert.go b/workflows/pipeline_upsert.go index 4504802f..ddd6eef6 100644 --- a/workflows/pipeline_upsert.go +++ b/workflows/pipeline_upsert.go @@ -31,7 +31,8 @@ func NewPipelineUpserter(ctx *common.Context, tokenProvider func(bool) string) E workflow.pipelineBucket(ctx.Config.Namespace, stackParams, ctx.StackManager, ctx.StackManager), workflow.codedeployBucket(ctx.Config.Namespace, &ctx.Config.Service, ctx.StackManager, ctx.StackManager), workflow.pipelineRolesetUpserter(ctx.RolesetManager, ctx.RolesetManager, stackParams), - workflow.pipelineUpserter(ctx.Config.Namespace, tokenProvider, ctx.StackManager, ctx.StackManager, stackParams)) + workflow.pipelineUpserter(ctx.Config.Namespace, tokenProvider, ctx.StackManager, ctx.StackManager, stackParams), + workflow.pipelineNotifyUpserter(ctx.Config.Namespace, &ctx.Config.Service.Pipeline)) } @@ -281,6 +282,20 @@ func (workflow *pipelineWorkflow) pipelineUpserter(namespace string, tokenProvid return fmt.Errorf("Ended in failed status %s %s", stack.Status, stack.StatusReason) } + workflow.notificationArn = stack.Outputs["PipelineNotificationTopicArn"] + + return nil + } +} + +func (workflow *pipelineWorkflow) pipelineNotifyUpserter(namespace string, pipeline *common.Pipeline) Executor { + return func() error { + if len(pipeline.Notify) > 0 { + log.Noticef("Updating pipeline notifications for service '%s' ...", workflow.serviceName) + for _, notify := range pipeline.Notify { + log.Infof(" Subscribing '%s' to '%s'", notify, workflow.notificationArn) + } + } return nil } } From a1656e03ecc2c04b61c7c43b100ca48a3da9dfbe Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 16 Jan 2018 12:23:05 -0800 Subject: [PATCH 05/13] fix #230 - notifications on pipeline success/failure/manual approval fix #263 - prompt for GH token early on --- common/subscription.go | 17 +++++++ common/types.go | 1 + provider/aws/cloudformation.go | 71 ++++++++++++++++++++---------- provider/aws/init.go | 6 +++ provider/aws/sns.go | 56 ++++++++++++++++++++++++ templates/assets/pipeline-iam.yml | 2 +- templates/assets/service-iam.yml | 2 +- workflows/pipeline_upsert.go | 73 ++++++++++++++++++++----------- workflows/pipeline_upsert_test.go | 4 +- 9 files changed, 181 insertions(+), 51 deletions(-) create mode 100644 common/subscription.go create mode 100644 provider/aws/sns.go diff --git a/common/subscription.go b/common/subscription.go new file mode 100644 index 00000000..8cb8039e --- /dev/null +++ b/common/subscription.go @@ -0,0 +1,17 @@ +package common + +// SubscriptionCreator for creating subscriptions +type SubscriptionCreator interface { + CreateSubscription(topic string, protocol string, endpoint string) error +} + +// SubscriptionGetter for creating subscriptions +type SubscriptionGetter interface { + GetSubscription(topic string, protocol string, endpoint string) (interface{}, error) +} + +// SubscriptionManager composite of all subscription capabilities +type SubscriptionManager interface { + SubscriptionCreator + SubscriptionGetter +} diff --git a/common/types.go b/common/types.go index a7fcd2c3..14218dcc 100644 --- a/common/types.go +++ b/common/types.go @@ -21,6 +21,7 @@ type Context struct { DockerOut io.Writer TaskManager TaskManager ArtifactManager ArtifactManager + SubscriptionManager SubscriptionManager RolesetManager RolesetManager ExtensionsManager ExtensionsManager } diff --git a/provider/aws/cloudformation.go b/provider/aws/cloudformation.go index bd0c3603..303e5409 100644 --- a/provider/aws/cloudformation.go +++ b/provider/aws/cloudformation.go @@ -29,6 +29,8 @@ type cloudformationStackManager struct { cfnAPI cloudformationiface.CloudFormationAPI ec2API ec2iface.EC2API extensionsManager common.ExtensionsManager + statusSpinner *spinner.Spinner + spinnerRefCnt int } // NewStackManager creates a new StackManager backed by cloudformation @@ -42,12 +44,19 @@ func newStackManager(sess *session.Session, extensionsManager common.ExtensionsM log.Debug("Connecting to EC2 service") ec2API := ec2.New(sess) + // initialize Spinner + var statusSpinner *spinner.Spinner + if terminal.IsTerminal(int(os.Stdout.Fd())) { + statusSpinner = spinner.New(spinner.CharSets[9], 100*time.Millisecond) + } + return &cloudformationStackManager{ dryrunPath: dryrunPath, skipVersionCheck: skipVersionCheck, cfnAPI: cfnAPI, ec2API: ec2API, extensionsManager: extensionsManager, + statusSpinner: statusSpinner, }, nil } @@ -226,7 +235,14 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template if err != nil { if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "ValidationError" && awsErr.Message() == "No updates are to be performed." { + pauseSpinner := cfnMgr.spinnerRefCnt > 0 + if pauseSpinner { + cfnMgr.stopSpinner() + } log.Infof(" No changes for stack '%s'", stackName) + if pauseSpinner { + cfnMgr.startSpinner() + } return nil } } @@ -237,6 +253,21 @@ func (cfnMgr *cloudformationStackManager) UpsertStack(stackName string, template return nil } +func (cfnMgr *cloudformationStackManager) startSpinner() { + if cfnMgr.statusSpinner != nil { + cfnMgr.statusSpinner.Start() + cfnMgr.spinnerRefCnt++ + } +} +func (cfnMgr *cloudformationStackManager) stopSpinner() { + if cfnMgr.statusSpinner != nil { + cfnMgr.spinnerRefCnt-- + //if cfnMgr.spinnerRefCnt == 0 { + cfnMgr.statusSpinner.Stop() + //} + } +} + // AwaitFinalStatus waits for the stack to arrive in a final status // returns: final status, or empty string if stack doesn't exist func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) *common.Stack { @@ -246,25 +277,15 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) *co StackName: aws.String(stackName), } - // initialize Spinner - var statusSpinner *spinner.Spinner - if terminal.IsTerminal(int(os.Stdout.Fd())) { - statusSpinner = spinner.New(spinner.CharSets[9], 100*time.Millisecond) - } - - if statusSpinner != nil { - statusSpinner.Start() - defer statusSpinner.Stop() - } + cfnMgr.startSpinner() + defer cfnMgr.stopSpinner() var priorEventTime *time.Time for { resp, err := cfnAPI.DescribeStacks(params) - if statusSpinner != nil { - statusSpinner.Stop() - } + //cfnMgr.stopSpinner() if err != nil || resp == nil || len(resp.Stacks) != 1 { log.Debugf(" Stack doesn't exist ... stack=%s", stackName) @@ -296,23 +317,29 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) *co StackName: aws.String(stackName), } eventResp, err := cfnAPI.DescribeStackEvents(eventParams) - if err == nil && eventResp != nil { - numEvents := len(eventResp.StackEvents) - for i := numEvents - 1; i >= 0; i-- { + numEvents := len(eventResp.StackEvents) + if err == nil && eventResp != nil && numEvents > 0 { + firstEventIndex := 0 + for i := 0; i < numEvents; i++ { e := eventResp.StackEvents[i] + firstEventIndex = i if aws.StringValue(e.ResourceType) == "AWS::CloudFormation::Stack" && strings.HasSuffix(aws.StringValue(e.ResourceStatus), "_COMPLETE") { break } + } + for i := firstEventIndex; i >= 0; i-- { + e := eventResp.StackEvents[i] if priorEventTime == nil || priorEventTime.Before(aws.TimeValue(e.Timestamp)) { - status := aws.StringValue(e.ResourceStatus) - eventMesg := fmt.Sprintf(" %s (%s) %s %s", aws.StringValue(e.LogicalResourceId), + eventMesg := fmt.Sprintf(" %s: %s (%s) %s %s", + stackName, + aws.StringValue(e.LogicalResourceId), aws.StringValue(e.ResourceType), status, aws.StringValue(e.ResourceStatusReason)) if strings.HasSuffix(status, "_IN_PROGRESS") { - if statusSpinner != nil { - statusSpinner.Suffix = eventMesg + if cfnMgr.statusSpinner != nil { + cfnMgr.statusSpinner.Suffix = eventMesg log.Debug(eventMesg) } else { log.Info(eventMesg) @@ -330,9 +357,7 @@ func (cfnMgr *cloudformationStackManager) AwaitFinalStatus(stackName string) *co } log.Debugf(" Not in final status (%s)...sleeping for 5 seconds", *resp.Stacks[0].StackStatus) - if statusSpinner != nil { - statusSpinner.Start() - } + cfnMgr.startSpinner() time.Sleep(time.Second * 5) } } diff --git a/provider/aws/init.go b/provider/aws/init.go index 0d003733..c76a674a 100644 --- a/provider/aws/init.go +++ b/provider/aws/init.go @@ -108,6 +108,12 @@ func InitializeContext(ctx *common.Context, profile string, assumeRole string, r return err } + // initialize SubscriptionManager + ctx.SubscriptionManager, err = newSnsManager(sess) + if err != nil { + return err + } + // initialize the RolesetManager ctx.RolesetManager, err = newRolesetManager(ctx) if err != nil { diff --git a/provider/aws/sns.go b/provider/aws/sns.go new file mode 100644 index 00000000..c1913ef9 --- /dev/null +++ b/provider/aws/sns.go @@ -0,0 +1,56 @@ +package aws + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/sns" + "github.com/aws/aws-sdk-go/service/sns/snsiface" + "github.com/stelligent/mu/common" +) + +type snsManager struct { + snsAPI snsiface.SNSAPI +} + +func newSnsManager(sess *session.Session) (common.SubscriptionManager, error) { + log.Debug("Connecting to SNS service") + snsAPI := sns.New(sess) + + return &snsManager{ + snsAPI: snsAPI, + }, nil +} + +// CreateSubscription +func (snsMgr *snsManager) CreateSubscription(topic string, protocol string, endpoint string) error { + snsAPI := snsMgr.snsAPI + + _, err := snsAPI.Subscribe(&sns.SubscribeInput{ + TopicArn: aws.String(topic), + Endpoint: aws.String(endpoint), + Protocol: aws.String(protocol), + }) + return err +} + +// GetSubscription +func (snsMgr *snsManager) GetSubscription(topic string, protocol string, endpoint string) (interface{}, error) { + snsAPI := snsMgr.snsAPI + + out, err := snsAPI.ListSubscriptionsByTopic(&sns.ListSubscriptionsByTopicInput{ + TopicArn: aws.String(topic), + }) + + for _, sub := range out.Subscriptions { + if aws.StringValue(sub.Protocol) == protocol && aws.StringValue(sub.Endpoint) == endpoint { + return sub, nil + } + } + + if err != nil { + return nil, err + } + + return nil, fmt.Errorf("unable to find subscription") +} diff --git a/templates/assets/pipeline-iam.yml b/templates/assets/pipeline-iam.yml index e86fbe34..745ed491 100644 --- a/templates/assets/pipeline-iam.yml +++ b/templates/assets/pipeline-iam.yml @@ -1,6 +1,6 @@ --- AWSTemplateFormatVersion: '2010-09-09' -Description: MU IAM roles for pipeline +Description: MU IAM roles and keys for pipeline Parameters: Namespace: Type: String diff --git a/templates/assets/service-iam.yml b/templates/assets/service-iam.yml index b4d9038e..d3d32167 100644 --- a/templates/assets/service-iam.yml +++ b/templates/assets/service-iam.yml @@ -1,6 +1,6 @@ --- AWSTemplateFormatVersion: '2010-09-09' -Description: MU IAM roles for a service +Description: MU IAM roles and keys for a service Parameters: Namespace: Type: String diff --git a/workflows/pipeline_upsert.go b/workflows/pipeline_upsert.go index ddd6eef6..73edd466 100644 --- a/workflows/pipeline_upsert.go +++ b/workflows/pipeline_upsert.go @@ -28,11 +28,12 @@ func NewPipelineUpserter(ctx *common.Context, tokenProvider func(bool) string) E return newPipelineExecutor( workflow.serviceFinder("", ctx), + workflow.pipelineToken(ctx.Config.Namespace, tokenProvider, ctx.StackManager, stackParams), workflow.pipelineBucket(ctx.Config.Namespace, stackParams, ctx.StackManager, ctx.StackManager), workflow.codedeployBucket(ctx.Config.Namespace, &ctx.Config.Service, ctx.StackManager, ctx.StackManager), workflow.pipelineRolesetUpserter(ctx.RolesetManager, ctx.RolesetManager, stackParams), - workflow.pipelineUpserter(ctx.Config.Namespace, tokenProvider, ctx.StackManager, ctx.StackManager, stackParams), - workflow.pipelineNotifyUpserter(ctx.Config.Namespace, &ctx.Config.Service.Pipeline)) + workflow.pipelineUpserter(ctx.Config.Namespace, ctx.StackManager, ctx.StackManager, stackParams), + workflow.pipelineNotifyUpserter(ctx.Config.Namespace, &ctx.Config.Service.Pipeline, ctx.SubscriptionManager)) } @@ -114,6 +115,18 @@ func (workflow *pipelineWorkflow) pipelineBucket(namespace string, params map[st } } +// Fetch token if needed +func (workflow *pipelineWorkflow) pipelineToken(namespace string, tokenProvider func(bool) string, stackWaiter common.StackWaiter, params map[string]string) Executor { + return func() error { + pipelineStackName := common.CreateStackName(namespace, common.StackTypePipeline, workflow.serviceName) + pipelineStack := stackWaiter.AwaitFinalStatus(pipelineStackName) + if workflow.pipelineConfig.Source.Provider == "GitHub" { + params["GitHubToken"] = tokenProvider(pipelineStack == nil) + } + return nil + } +} + func (workflow *pipelineWorkflow) pipelineRolesetUpserter(rolesetUpserter common.RolesetUpserter, rolesetGetter common.RolesetGetter, params map[string]string) Executor { return func() error { err := rolesetUpserter.UpsertCommonRoleset() @@ -121,21 +134,16 @@ func (workflow *pipelineWorkflow) pipelineRolesetUpserter(rolesetUpserter common return err } + rolesetCount := 0 + errChan := make(chan error) + if !workflow.pipelineConfig.Acceptance.Disabled { envName := workflow.pipelineConfig.Acceptance.Environment if envName == "" { envName = "acceptance" } - err := rolesetUpserter.UpsertEnvironmentRoleset(envName) - if err != nil { - return err - } - - err = rolesetUpserter.UpsertServiceRoleset(envName, workflow.serviceName, workflow.codeDeployBucket) - if err != nil { - return err - } - + go updateEnvRoleset(rolesetUpserter, envName, workflow.serviceName, workflow.codeDeployBucket, errChan) + rolesetCount++ } if !workflow.pipelineConfig.Production.Disabled { @@ -143,12 +151,12 @@ func (workflow *pipelineWorkflow) pipelineRolesetUpserter(rolesetUpserter common if envName == "" { envName = "production" } - err := rolesetUpserter.UpsertEnvironmentRoleset(envName) - if err != nil { - return err - } + go updateEnvRoleset(rolesetUpserter, envName, workflow.serviceName, workflow.codeDeployBucket, errChan) + rolesetCount++ + } - err = rolesetUpserter.UpsertServiceRoleset(envName, workflow.serviceName, workflow.codeDeployBucket) + for i := 0; i < rolesetCount; i++ { + err := <-errChan if err != nil { return err } @@ -174,10 +182,21 @@ func (workflow *pipelineWorkflow) pipelineRolesetUpserter(rolesetUpserter common } } -func (workflow *pipelineWorkflow) pipelineUpserter(namespace string, tokenProvider func(bool) string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter, params map[string]string) Executor { +func updateEnvRoleset(rolesetUpserter common.RolesetUpserter, envName string, serviceName string, codeDeployBucket string, errChan chan error) { + err := rolesetUpserter.UpsertEnvironmentRoleset(envName) + if err != nil { + errChan <- err + return + } + + err = rolesetUpserter.UpsertServiceRoleset(envName, serviceName, codeDeployBucket) + errChan <- err + return +} + +func (workflow *pipelineWorkflow) pipelineUpserter(namespace string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter, params map[string]string) Executor { return func() error { pipelineStackName := common.CreateStackName(namespace, common.StackTypePipeline, workflow.serviceName) - pipelineStack := stackWaiter.AwaitFinalStatus(pipelineStackName) log.Noticef("Upserting Pipeline for service '%s' ...", workflow.serviceName) pipelineParams := params @@ -198,10 +217,6 @@ func (workflow *pipelineWorkflow) pipelineUpserter(namespace string, tokenProvid pipelineParams["SourceObjectKey"] = strings.Join(repoParts[1:], "/") } - if workflow.pipelineConfig.Source.Provider == "GitHub" { - pipelineParams["GitHubToken"] = tokenProvider(pipelineStack == nil) - } - if workflow.pipelineConfig.Build.Type != "" { pipelineParams["BuildType"] = workflow.pipelineConfig.Build.Type } @@ -288,12 +303,20 @@ func (workflow *pipelineWorkflow) pipelineUpserter(namespace string, tokenProvid } } -func (workflow *pipelineWorkflow) pipelineNotifyUpserter(namespace string, pipeline *common.Pipeline) Executor { +func (workflow *pipelineWorkflow) pipelineNotifyUpserter(namespace string, pipeline *common.Pipeline, subManager common.SubscriptionManager) Executor { return func() error { if len(pipeline.Notify) > 0 { log.Noticef("Updating pipeline notifications for service '%s' ...", workflow.serviceName) for _, notify := range pipeline.Notify { - log.Infof(" Subscribing '%s' to '%s'", notify, workflow.notificationArn) + sub, _ := subManager.GetSubscription(workflow.notificationArn, "email", notify) + if sub == nil { + log.Infof(" Subscribing '%s' to '%s'", notify, workflow.notificationArn) + err := subManager.CreateSubscription(workflow.notificationArn, "email", notify) + if err != nil { + return err + } + } + } } return nil diff --git a/workflows/pipeline_upsert_test.go b/workflows/pipeline_upsert_test.go index b55bef65..7655d920 100644 --- a/workflows/pipeline_upsert_test.go +++ b/workflows/pipeline_upsert_test.go @@ -87,7 +87,9 @@ func TestPipelineUpserter(t *testing.T) { } params := make(map[string]string) - err := workflow.pipelineUpserter("mu", tokenProvider, stackManager, stackManager, params)() + err := workflow.pipelineToken("mu", tokenProvider, stackManager, params)() + assert.Nil(err) + err = workflow.pipelineUpserter("mu", stackManager, stackManager, params)() assert.Nil(err) stackManager.AssertExpectations(t) From 687c371a5c66ad4c0ed2cf91d51d0bfc651ebad3 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 16 Jan 2018 15:01:39 -0800 Subject: [PATCH 06/13] updates for approval notification and topic policies --- templates/assets/pipeline-iam.yml | 5 ++++ templates/assets/pipeline.yml | 46 ++++++++++++------------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/templates/assets/pipeline-iam.yml b/templates/assets/pipeline-iam.yml index 745ed491..7748c5ff 100644 --- a/templates/assets/pipeline-iam.yml +++ b/templates/assets/pipeline-iam.yml @@ -263,6 +263,11 @@ Resources: Resource: - Fn::Sub: arn:aws:s3:::${PipelineBucket} Effect: Allow + - Action: + - sns:Publish + Effect: Allow + Resource: + - Fn::Sub: arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${Namespace}-pipeline-${ServiceName}-* - Action: - iam:PassRole Resource: diff --git a/templates/assets/pipeline.yml b/templates/assets/pipeline.yml index 5abb8161..c9f2a0c9 100644 --- a/templates/assets/pipeline.yml +++ b/templates/assets/pipeline.yml @@ -480,6 +480,7 @@ Resources: Provider: Manual Configuration: CustomData: Approve deployment to production + NotificationArn: !Ref PipelineNotificationTopic RunOrder: 10 - Name: Deploy ActionTypeId: @@ -518,6 +519,22 @@ Resources: Type: AWS::SNS::Topic Properties: TopicName: !Sub ${Namespace}-pipeline-${ServiceName}-notification + PipelineNotificationTopicPolicy: + Type: AWS::SNS::TopicPolicy + Properties: + Topics: + - !Ref PipelineNotificationTopic + PolicyDocument: + Version: "2012-10-17" + Id: "__default_policy_ID" + Statement: + - Sid: AWSEvents + Effect: Allow + Principal: + Service: events.amazonaws.com + Action: sns:Publish + Resource: !Ref PipelineNotificationTopic + PipelineSucceededEventRule: Type: AWS::Events::Rule Properties: @@ -540,35 +557,6 @@ Resources: InputTemplate: !Sub '"Pipeline has succeeded. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/" ' InputPathsMap: pipeline: "$.detail.pipeline" - PipelineApprovalEventRule: - Type: AWS::Events::Rule - Properties: - Description: !Sub Pipeline Approval Event Rule for service ${ServiceName} - EventPattern: - source: - - aws.codepipeline - detail-type: - - CodePipeline Action Execution State Change - detail: - state: - - STARTED - pipeline: - - !Sub ${Namespace}-${ServiceName} - type: - owner: - - AWS - category: - - Approval - provider: - - Manual - State: "ENABLED" - Targets: - - Arn: !Ref PipelineNotificationTopic - Id: "ApprovalTopic" - InputTransformer: - InputTemplate: !Sub '"Pipeline requires approval. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/" ' - InputPathsMap: - pipeline: "$.detail.pipeline" PipelineFailedEventRule: Type: AWS::Events::Rule Properties: From 8586c2fe1813e537396460e1dd85a8e22b624c28 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 16 Jan 2018 16:09:58 -0800 Subject: [PATCH 07/13] fix defect with customized cloudformation breaking event rules in the CFN for pipeline.yml --- templates/assets/pipeline.yml | 8 ++++++-- workflows/executor.go | 1 + workflows/pipeline_upsert.go | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/templates/assets/pipeline.yml b/templates/assets/pipeline.yml index c9f2a0c9..750fc103 100644 --- a/templates/assets/pipeline.yml +++ b/templates/assets/pipeline.yml @@ -554,7 +554,9 @@ Resources: - Arn: !Ref PipelineNotificationTopic Id: "SucceededTopic" InputTransformer: - InputTemplate: !Sub '"Pipeline has succeeded. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/" ' + InputTemplate: + Fn::Sub: > + Pipeline has succeeded. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/ InputPathsMap: pipeline: "$.detail.pipeline" PipelineFailedEventRule: @@ -576,7 +578,9 @@ Resources: - Arn: !Ref PipelineNotificationTopic Id: "FailedTopic" InputTransformer: - InputTemplate: !Sub '"Pipeline has failed. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/" ' + InputTemplate: + Fn::Sub: > + Pipeline has failed. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/ InputPathsMap: pipeline: "$.detail.pipeline" Outputs: diff --git a/workflows/executor.go b/workflows/executor.go index 5d1368f9..cb9f4ca0 100644 --- a/workflows/executor.go +++ b/workflows/executor.go @@ -16,6 +16,7 @@ func newPipelineExecutor(executors ...Executor) Executor { err := executor() if err != nil { log.Errorf("%v", err) + log.Debugf("%+v", err) return errors.New("") } } diff --git a/workflows/pipeline_upsert.go b/workflows/pipeline_upsert.go index 73edd466..cd104576 100644 --- a/workflows/pipeline_upsert.go +++ b/workflows/pipeline_upsert.go @@ -305,7 +305,7 @@ func (workflow *pipelineWorkflow) pipelineUpserter(namespace string, stackUpsert func (workflow *pipelineWorkflow) pipelineNotifyUpserter(namespace string, pipeline *common.Pipeline, subManager common.SubscriptionManager) Executor { return func() error { - if len(pipeline.Notify) > 0 { + if len(workflow.notificationArn) > 0 && len(pipeline.Notify) > 0 { log.Noticef("Updating pipeline notifications for service '%s' ...", workflow.serviceName) for _, notify := range pipeline.Notify { sub, _ := subManager.GetSubscription(workflow.notificationArn, "email", notify) From 2c9b3b20f5d383f153c717c790342bca46fed207 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 16 Jan 2018 16:20:59 -0800 Subject: [PATCH 08/13] InputTemplate surrounded with quotes --- templates/assets/pipeline.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/assets/pipeline.yml b/templates/assets/pipeline.yml index 750fc103..5afb6dbf 100644 --- a/templates/assets/pipeline.yml +++ b/templates/assets/pipeline.yml @@ -556,7 +556,7 @@ Resources: InputTransformer: InputTemplate: Fn::Sub: > - Pipeline has succeeded. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/ + "Pipeline has succeeded. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/" InputPathsMap: pipeline: "$.detail.pipeline" PipelineFailedEventRule: @@ -580,7 +580,7 @@ Resources: InputTransformer: InputTemplate: Fn::Sub: > - Pipeline has failed. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/ + "Pipeline has failed. Details available at https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/" InputPathsMap: pipeline: "$.detail.pipeline" Outputs: From 894161e4dbd5c9920e7dfdf408c47988ff629bbc Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Wed, 17 Jan 2018 14:34:14 -0800 Subject: [PATCH 09/13] hide extension output --- common/extension.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/extension.go b/common/extension.go index c6936f3a..000f09bb 100644 --- a/common/extension.go +++ b/common/extension.go @@ -306,12 +306,12 @@ func newTemplateArchiveExtension(u *url.URL, artifactManager ArtifactManager) (E // log info about the new extension if name, ok := extManifest["name"]; ok { if version, ok := extManifest["version"]; ok { - log.Noticef("Loaded extension %s (version=%v)", name, version) + log.Warningf("Loaded extension %s (version=%v)", name, version) } else { - log.Noticef("Loaded extension %s", name) + log.Warningf("Loaded extension %s", name) } } else { - log.Noticef("Loaded extension %s", u) + log.Warningf("Loaded extension %s", u) } return ext, nil From 73ba1d6afe9a3ec7170546ba133a3b7f27dec158 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 23 Jan 2018 10:30:18 -0800 Subject: [PATCH 10/13] avoid false positives on env and db in pipeline by making warnings rather than errors if env/db doesn't exist in the mu.yml --- cli/environments.go | 7 ++++--- common/types.go | 19 +++++++++++++++++++ templates/assets/pipeline.yml | 8 ++++---- workflows/environment_upsert.go | 5 +++-- workflows/executor.go | 14 +++++++++++--- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/cli/environments.go b/cli/environments.go index 512c6a50..e1342128 100644 --- a/cli/environments.go +++ b/cli/environments.go @@ -2,12 +2,13 @@ package cli import ( "errors" - "github.com/stelligent/mu/common" - "github.com/stelligent/mu/workflows" - "github.com/urfave/cli" "os" "strings" "time" + + "github.com/stelligent/mu/common" + "github.com/stelligent/mu/workflows" + "github.com/urfave/cli" ) func newEnvironmentsCommand(ctx *common.Context) *cli.Command { diff --git a/common/types.go b/common/types.go index 14218dcc..60aeb3da 100644 --- a/common/types.go +++ b/common/types.go @@ -1,6 +1,7 @@ package common import ( + "fmt" "io" "time" ) @@ -376,3 +377,21 @@ var CPUMemorySupport = []CPUMemory{ {CPU: 4096, Memory: []int{8 * GB, 9 * GB, 10 * GB, 11 * GB, 12 * GB, 13 * GB, 14 * GB, 15 * GB, 16 * GB, 17 * GB, 18 * GB, 19 * GB, 20 * GB, 21 * GB, 22 * GB, 23 * GB, 24 * GB, 25 * GB, 26 * GB, 27 * GB, 28 * GB, 29 * GB, 30 * GB}}, } + +// Warning that implements `error` but safe to ignore +type Warning struct { + Message string +} + +// Error the contract for error +func (w Warning) Error() string { + return w.Message +} + +// Warningf create a warning +func Warningf(format string, args ...interface{}) Warning { + w := Warning{ + Message: fmt.Sprintf(format, args), + } + return w +} diff --git a/templates/assets/pipeline.yml b/templates/assets/pipeline.yml index 5afb6dbf..6e08881f 100644 --- a/templates/assets/pipeline.yml +++ b/templates/assets/pipeline.yml @@ -271,8 +271,8 @@ Resources: commands: - curl -sL ${MuDownloadBaseurl}/v${MuDownloadVersion}/${MuDownloadFile} -o /usr/bin/mu - chmod +rx /usr/bin/mu - - mu -c ${MuFile} --assume-role ${MuAcptRoleArn} --disable-iam env up ${AcptEnv} || echo "Skipping update of environment" - - mu -c ${MuFile} --assume-role ${MuAcptRoleArn} --disable-iam db up ${AcptEnv} || echo "Skipping update of database" + - mu -c ${MuFile} --assume-role ${MuAcptRoleArn} --disable-iam env up ${AcptEnv} + - mu -c ${MuFile} --assume-role ${MuAcptRoleArn} --disable-iam db up ${AcptEnv} - mu -c ${MuFile} --assume-role ${MuAcptRoleArn} --disable-iam svc deploy ${AcptEnv} - mu -c ${MuFile} --assume-role ${MuAcptRoleArn} env show ${AcptEnv} -f json > env.json - mu -c ${MuFile} --assume-role ${MuAcptRoleArn} env show ${AcptEnv} -f shell > mu-env.sh @@ -330,8 +330,8 @@ Resources: commands: - curl -sL ${MuDownloadBaseurl}/v${MuDownloadVersion}/${MuDownloadFile} -o /usr/bin/mu - chmod +rx /usr/bin/mu - - mu -c ${MuFile} --assume-role ${MuProdRoleArn} --disable-iam env up ${ProdEnv} || echo "Skipping update of environment" - - mu -c ${MuFile} --assume-role ${MuProdRoleArn} --disable-iam db up ${ProdEnv} || echo "Skipping update of database" + - mu -c ${MuFile} --assume-role ${MuProdRoleArn} --disable-iam env up ${ProdEnv} + - mu -c ${MuFile} --assume-role ${MuProdRoleArn} --disable-iam db up ${ProdEnv} - mu -c ${MuFile} --assume-role ${MuProdRoleArn} --disable-iam svc deploy ${ProdEnv} - mu -c ${MuFile} --assume-role ${MuProdRoleArn} env show ${ProdEnv} -f json > env.json - mu -c ${MuFile} --assume-role ${MuProdRoleArn} env show ${ProdEnv} -f shell > mu-env.sh diff --git a/workflows/environment_upsert.go b/workflows/environment_upsert.go index d188a91a..1459315c 100644 --- a/workflows/environment_upsert.go +++ b/workflows/environment_upsert.go @@ -2,9 +2,10 @@ package workflows import ( "fmt" - "github.com/stelligent/mu/common" "strconv" "strings" + + "github.com/stelligent/mu/common" ) var ecsImagePattern = "amzn-ami-*-amazon-ecs-optimized" @@ -43,7 +44,7 @@ func (workflow *environmentWorkflow) environmentFinder(config *common.Config, en return nil } } - return fmt.Errorf("Unable to find environment named '%s' in configuration", environmentName) + return common.Warningf("Unable to find environment named '%s' in configuration", environmentName) } } diff --git a/workflows/executor.go b/workflows/executor.go index cb9f4ca0..cbc80358 100644 --- a/workflows/executor.go +++ b/workflows/executor.go @@ -2,6 +2,8 @@ package workflows import ( "errors" + + "github.com/stelligent/mu/common" ) // Executor define contract for the steps of a workflow @@ -15,9 +17,15 @@ func newPipelineExecutor(executors ...Executor) Executor { for _, executor := range executors { err := executor() if err != nil { - log.Errorf("%v", err) - log.Debugf("%+v", err) - return errors.New("") + switch err.(type) { + case common.Warning: + log.Warning(err.Error()) + return nil + default: + log.Errorf("%v", err) + log.Debugf("%+v", err) + return errors.New("") + } } } return nil From fdb312e6630f9c81deb5932e003e13e6ff610fd0 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 23 Jan 2018 10:36:28 -0800 Subject: [PATCH 11/13] fix unit test --- cli/environments_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/environments_test.go b/cli/environments_test.go index a3845a62..14eaf99f 100644 --- a/cli/environments_test.go +++ b/cli/environments_test.go @@ -2,10 +2,11 @@ package cli import ( "bytes" + "testing" + "github.com/stelligent/mu/common" "github.com/stretchr/testify/assert" "github.com/urfave/cli" - "testing" ) func TestNewEnvironmentsCommand(t *testing.T) { @@ -44,10 +45,12 @@ func TestNewEnvironmentsUpsertCommand(t *testing.T) { assertion.NotNil(err) assertion.Equal(FailExitCode, lastExitCode) + lastExitCode = 0 + args = []string{UpsertCmd, TestEnv} err = runCommand(command, args) - assertion.NotNil(err) - assertion.Equal(FailExitCode, lastExitCode) + assertion.Nil(err) + assertion.Equal(0, lastExitCode) } func TestNewEnvironmentsListCommand(t *testing.T) { From ec1305938788580da14ea4439616d4cedc69cae1 Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 23 Jan 2018 23:10:34 -0800 Subject: [PATCH 12/13] update changelog --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dc0716a..5655394d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,46 @@ # Change Log +## [v1.4.1](https://github.com/stelligent/mu/tree/v1.4.1) (2018-01-23) +[Full Changelog](https://github.com/stelligent/mu/compare/v1.3.2...v1.4.1) + +**Implemented enhancements:** + +- Add support for pipeline failure notifications [\#230](https://github.com/stelligent/mu/issues/230) + +**Closed issues:** + +- is it possible to infer the repo path from the local checkout? [\#268](https://github.com/stelligent/mu/issues/268) +- Use codepipeline ecs deploy target, rather than cloudformation? [\#266](https://github.com/stelligent/mu/issues/266) +- prompt for github token near the beginning of "mu pipeline up" [\#263](https://github.com/stelligent/mu/issues/263) +- support discovering subnet values and vpc id by targeting another mu service by name [\#253](https://github.com/stelligent/mu/issues/253) + +**Merged pull requests:** + +- add support for centos7 for env provider [\#262](https://github.com/stelligent/mu/pull/262) ([cplee](https://github.com/cplee)) + +## [v1.3.2](https://github.com/stelligent/mu/tree/v1.3.2) (2018-01-11) +[Full Changelog](https://github.com/stelligent/mu/compare/v1.3.1...v1.3.2) + +**Fixed bugs:** + +- Unable to update Service with target tracking capabilities if they weren't already there [\#259](https://github.com/stelligent/mu/issues/259) +- Issue 259 Added application-autoscaling:DescribeScheduledActions perm… [\#260](https://github.com/stelligent/mu/pull/260) ([akuma12](https://github.com/akuma12)) + +**Merged pull requests:** + +- v1.3.2 [\#261](https://github.com/stelligent/mu/pull/261) ([cplee](https://github.com/cplee)) + +## [v0.2.6](https://github.com/stelligent/mu/tree/v0.2.6) (2018-01-10) +[Full Changelog](https://github.com/stelligent/mu/compare/v0.2.5...v0.2.6) + +**Implemented enhancements:** + +- Add support for CodeBuild's caching feature [\#229](https://github.com/stelligent/mu/issues/229) + +**Closed issues:** + +- Add support for artifact caching in CodeBuild [\#242](https://github.com/stelligent/mu/issues/242) + ## [v1.3.1](https://github.com/stelligent/mu/tree/v1.3.1) (2018-01-08) [Full Changelog](https://github.com/stelligent/mu/compare/v1.2.3...v1.3.1) @@ -16,6 +57,9 @@ **Merged pull requests:** - use blue/green deploy as default [\#254](https://github.com/stelligent/mu/pull/254) ([brentley](https://github.com/brentley)) +- v1.3.1 [\#249](https://github.com/stelligent/mu/pull/249) ([cplee](https://github.com/cplee)) +- Issue 128 [\#247](https://github.com/stelligent/mu/pull/247) ([cplee](https://github.com/cplee)) +- Add support for docker links [\#245](https://github.com/stelligent/mu/pull/245) ([nilsga](https://github.com/nilsga)) ## [v1.2.3](https://github.com/stelligent/mu/tree/v1.2.3) (2017-12-11) [Full Changelog](https://github.com/stelligent/mu/compare/v1.2.2...v1.2.3) From 2c9d006471f6b0122fcd1cf05c6c78c94091ffce Mon Sep 17 00:00:00 2001 From: Casey Lee Date: Tue, 23 Jan 2018 23:23:18 -0800 Subject: [PATCH 13/13] merge master --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1892b926..347f5833 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.2 +1.4.1