cody 0.9.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -9
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile +0 -2
  5. data/cody.gemspec +3 -3
  6. data/lib/cody.rb +4 -8
  7. data/lib/cody/aws_services.rb +1 -1
  8. data/lib/cody/aws_services/helpers.rb +20 -3
  9. data/lib/cody/cli.rb +2 -13
  10. data/lib/cody/{badge.rb → cli/badge.rb} +1 -1
  11. data/lib/cody/{base.rb → cli/base.rb} +9 -3
  12. data/lib/cody/{delete.rb → cli/delete.rb} +3 -3
  13. data/lib/cody/cli/deploy.rb +7 -0
  14. data/lib/cody/cli/help.rb +11 -0
  15. data/lib/cody/{help → cli/help}/deploy.md +0 -0
  16. data/lib/cody/{help → cli/help}/init.md +0 -0
  17. data/lib/cody/{help → cli/help}/logs.md +0 -0
  18. data/lib/cody/{help → cli/help}/start.md +0 -0
  19. data/lib/cody/{help → cli/help}/stop.md +0 -0
  20. data/lib/cody/{init.rb → cli/init.rb} +1 -2
  21. data/lib/cody/{list.rb → cli/list.rb} +7 -3
  22. data/lib/cody/cli/logs.rb +10 -0
  23. data/lib/cody/{sequence.rb → cli/sequence.rb} +3 -3
  24. data/lib/cody/{start.rb → cli/start.rb} +3 -3
  25. data/lib/cody/{status.rb → cli/status.rb} +2 -1
  26. data/lib/cody/{stop.rb → cli/stop.rb} +2 -1
  27. data/lib/cody/command.rb +11 -0
  28. data/lib/cody/core.rb +5 -5
  29. data/lib/cody/dsl/base.rb +9 -0
  30. data/lib/cody/dsl/project.rb +54 -49
  31. data/lib/cody/dsl/role.rb +11 -11
  32. data/lib/cody/dsl/schedule.rb +8 -8
  33. data/lib/cody/list/no_builds_project.rb +1 -1
  34. data/lib/cody/list/project.rb +1 -1
  35. data/lib/cody/project.rb +23 -23
  36. data/lib/cody/role.rb +20 -20
  37. data/lib/cody/schedule.rb +37 -37
  38. data/lib/cody/stack.rb +16 -71
  39. data/lib/cody/stack/base.rb +104 -0
  40. data/lib/cody/{create.rb → stack/create.rb} +2 -2
  41. data/lib/cody/{update.rb → stack/update.rb} +2 -2
  42. data/lib/cody/tailer.rb +1 -1
  43. data/lib/cody/version.rb +1 -1
  44. data/lib/template/project/buildspec.yml +1 -1
  45. data/lib/template/project/project.rb.tt +5 -19
  46. metadata +64 -70
  47. data/lib/cody/completer.rb +0 -159
  48. data/lib/cody/completer/script.rb +0 -6
  49. data/lib/cody/completer/script.sh +0 -10
  50. data/lib/cody/deploy.rb +0 -40
  51. data/lib/cody/help.rb +0 -9
  52. data/lib/cody/help/completion.md +0 -22
  53. data/lib/cody/help/completion_script.md +0 -3
  54. data/lib/cody/logs.rb +0 -14
  55. data/vendor/aws_data/CHANGELOG.md +0 -7
  56. data/vendor/aws_data/Gemfile +0 -4
  57. data/vendor/aws_data/LICENSE.txt +0 -21
  58. data/vendor/aws_data/README.md +0 -42
  59. data/vendor/aws_data/Rakefile +0 -6
  60. data/vendor/aws_data/aws_data.gemspec +0 -30
  61. data/vendor/aws_data/bin/console +0 -14
  62. data/vendor/aws_data/bin/setup +0 -8
  63. data/vendor/aws_data/lib/aws_data.rb +0 -91
  64. data/vendor/aws_data/lib/aws_data/version.rb +0 -3
  65. data/vendor/aws_data/spec/aws_data_spec.rb +0 -5
  66. data/vendor/aws_data/spec/spec_helper.rb +0 -14
  67. data/vendor/cfn-status/Gemfile +0 -4
  68. data/vendor/cfn-status/LICENSE.txt +0 -21
  69. data/vendor/cfn-status/README.md +0 -56
  70. data/vendor/cfn-status/Rakefile +0 -6
  71. data/vendor/cfn-status/bin/console +0 -14
  72. data/vendor/cfn-status/bin/setup +0 -8
  73. data/vendor/cfn-status/cfn-status.gemspec +0 -30
  74. data/vendor/cfn-status/lib/cfn/aws_service.rb +0 -56
  75. data/vendor/cfn-status/lib/cfn/status.rb +0 -220
  76. data/vendor/cfn-status/lib/cfn/status/version.rb +0 -5
  77. data/vendor/cfn-status/spec/cfn/status_spec.rb +0 -81
  78. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-complete.json +0 -1080
  79. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-in-progress.json +0 -1080
  80. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-update-rollback-complete.json +0 -1086
  81. data/vendor/cfn-status/spec/spec_helper.rb +0 -14
  82. data/vendor/cfn_camelizer/CHANGELOG.md +0 -10
  83. data/vendor/cfn_camelizer/Gemfile +0 -4
  84. data/vendor/cfn_camelizer/LICENSE.txt +0 -21
  85. data/vendor/cfn_camelizer/README.md +0 -40
  86. data/vendor/cfn_camelizer/Rakefile +0 -6
  87. data/vendor/cfn_camelizer/bin/console +0 -14
  88. data/vendor/cfn_camelizer/bin/setup +0 -8
  89. data/vendor/cfn_camelizer/cfn_camelizer.gemspec +0 -32
  90. data/vendor/cfn_camelizer/lib/camelizer.yml +0 -33
  91. data/vendor/cfn_camelizer/lib/cfn_camelizer.rb +0 -92
  92. data/vendor/cfn_camelizer/lib/cfn_camelizer/version.rb +0 -3
  93. data/vendor/cfn_camelizer/spec/cfn_camelizer_spec.rb +0 -79
  94. data/vendor/cfn_camelizer/spec/spec_helper.rb +0 -14
@@ -1,16 +1,16 @@
1
1
  module Cody::Dsl
2
2
  module Role
3
3
  PROPERTIES = %w[
4
- assume_role_policy_document
5
- managed_policy_arns
6
- max_session_duration
7
- path
8
- permissions_boundary
9
- policies
10
- role_name
4
+ AssumeRolePolicyDocument
5
+ ManagedPolicyArns
6
+ MaxSessionDuration
7
+ Path
8
+ PermissionsBoundary
9
+ Policies
10
+ RoleName
11
11
  ]
12
12
  PROPERTIES.each do |prop|
13
- define_method(prop) do |v|
13
+ define_method(prop.underscore) do |v|
14
14
  @properties[prop.to_sym] = v
15
15
  end
16
16
  end
@@ -27,9 +27,9 @@ module Cody::Dsl
27
27
  # Expands simple string from: logs => logs:*
28
28
  definition = "#{definition}:*" unless definition.include?(':')
29
29
  {
30
- action: [definition],
31
- effect: "Allow",
32
- resource: "*",
30
+ Action: [definition],
31
+ Effect: "Allow",
32
+ Resource: "*",
33
33
  }
34
34
  when Hash
35
35
  definition
@@ -1,16 +1,16 @@
1
1
  module Cody::Dsl
2
2
  module Schedule
3
3
  PROPERTIES = %w[
4
- description
5
- event_pattern
6
- name
7
- role_arn
8
- schedule_expression
9
- state
10
- targets
4
+ Description
5
+ EventPattern
6
+ Name
7
+ RoleArn
8
+ ScheduleExpression
9
+ State
10
+ Targets
11
11
  ]
12
12
  PROPERTIES.each do |prop|
13
- define_method(prop) do |v|
13
+ define_method(prop.underscore) do |v|
14
14
  @properties[prop.to_sym] = v
15
15
  end
16
16
  end
@@ -1,6 +1,6 @@
1
1
  # Represents a project with no builds yet. In this case we just return an info message for the columns.
2
2
  # This allows `cody list` to work without breaking for Fresh projects with zero builds.
3
- class Cody::List
3
+ module Cody::List
4
4
  class NoBuildsProject
5
5
  def method_missing(meth, *args, &block)
6
6
  "no builds"
@@ -1,5 +1,5 @@
1
1
  # Wrap project in object to allow lazy loading of status
2
- class Cody::List
2
+ module Cody::List
3
3
  class Project
4
4
  include Cody::AwsServices
5
5
  extend Memoist
@@ -20,40 +20,40 @@ module Cody
20
20
  load_variables
21
21
  evaluate(@project_path)
22
22
  resource = {
23
- code_build: {
24
- type: "AWS::CodeBuild::Project",
25
- properties: @properties
23
+ CodeBuild: {
24
+ Type: "AWS::CodeBuild::Project",
25
+ Properties: @properties
26
26
  }
27
27
  }
28
- CfnCamelizer.transform(resource)
28
+ auto_camelize(resource)
29
29
  end
30
30
 
31
31
  def default_properties
32
32
  {
33
- name: @full_project_name,
34
- description: @full_project_name,
35
- artifacts: { type: "NO_ARTIFACTS" },
36
- service_role: { ref: "IamRole" },
37
- badge_enabled: true,
38
- timeout_in_minutes: 20,
39
- logs_config: {
40
- cloud_watch_logs: {
41
- status: "ENABLED",
33
+ Name: @full_project_name,
34
+ Description: @full_project_name,
35
+ Artifacts: { Type: "NO_ARTIFACTS" },
36
+ ServiceRole: { Ref: "IamRole" },
37
+ BadgeEnabled: true,
38
+ TimeoutInMinutes: 20,
39
+ LogsConfig: {
40
+ CloudWatchLogs: {
41
+ Status: "ENABLED",
42
42
  # the default log group name is thankfully the project name
43
43
  }
44
44
  },
45
- source: {
46
- type: "GITHUB",
45
+ Source: {
46
+ Type: "GITHUB",
47
47
  # location: "", # required
48
- # git_clone_depth: 1,
49
- git_submodules_config: { fetch_submodules: true },
50
- build_spec: build_spec,
51
- # auth doesnt seem to work, refer to https://github.com/tongueroo/cody/blob/master/readme/github_oauth.md
52
- # auth: {
53
- # type: "OAUTH",
54
- # # resource: "", # required
48
+ # GitCloneDepth: 1,
49
+ GitSubmodulesConfig: { FetchSubmodules: true },
50
+ BuildSpec: build_spec,
51
+ # auth doesnt seem to work, refer to https://github.com/tongueroo/cody/blob/master/readme/GithubOauth.md
52
+ # Auth: {
53
+ # Type: "OAUTH",
54
+ # # Resource: "", # required
55
55
  # },
56
- report_build_status: true,
56
+ ReportBuildStatus: true,
57
57
  }
58
58
  }
59
59
  end
@@ -15,23 +15,23 @@ module Cody
15
15
  def run
16
16
  load_variables
17
17
  evaluate(@role_path) if File.exist?(@role_path)
18
- @properties[:policies] = [{
19
- policy_name: "CodeBuildAccess",
20
- policy_document: {
21
- version: "2012-10-17",
22
- statement: derived_iam_statements
18
+ @properties[:Policies] = [{
19
+ PolicyName: "CodeBuildAccess",
20
+ PolicyDocument: {
21
+ Version: "2012-10-17",
22
+ Statement: derived_iam_statements
23
23
  }
24
24
  }]
25
25
 
26
- @properties[:managed_policy_arns] ||= @managed_policy_arns || default_managed_policy_arns
26
+ @properties[:ManagedPolicyArns] ||= @managed_policy_arns || default_managed_policy_arns
27
27
 
28
28
  resource = {
29
29
  IamRole: {
30
- type: "AWS::IAM::Role",
31
- properties: @properties
30
+ Type: "AWS::IAM::Role",
31
+ Properties: @properties
32
32
  }
33
33
  }
34
- CfnCamelizer.transform(resource)
34
+ auto_camelize(resource)
35
35
  end
36
36
 
37
37
  private
@@ -41,17 +41,17 @@ module Cody
41
41
 
42
42
  def default_properties
43
43
  {
44
- assume_role_policy_document: {
45
- statement: [{
46
- action: ["sts:AssumeRole"],
47
- effect: "Allow",
48
- principal: {
49
- service: ["codebuild.amazonaws.com"]
44
+ AssumeRolePolicyDocument: {
45
+ Statement: [{
46
+ Action: ["sts:AssumeRole"],
47
+ Effect: "Allow",
48
+ Principal: {
49
+ Service: ["codebuild.amazonaws.com"]
50
50
  }
51
51
  }],
52
- version: "2012-10-17"
52
+ Version: "2012-10-17"
53
53
  },
54
- path: "/"
54
+ Path: "/"
55
55
  }
56
56
  end
57
57
 
@@ -61,7 +61,7 @@ module Cody
61
61
 
62
62
  def default_iam_statements
63
63
  [{
64
- action: [
64
+ Action: [
65
65
  "logs:CreateLogGroup",
66
66
  "logs:CreateLogStream",
67
67
  "logs:PutLogEvents",
@@ -69,8 +69,8 @@ module Cody
69
69
  "ssm:DescribeParameters",
70
70
  "ssm:GetParameter*",
71
71
  ],
72
- effect: "Allow",
73
- resource: "*"
72
+ Effect: "Allow",
73
+ Resource: "*"
74
74
  }]
75
75
  end
76
76
 
@@ -17,27 +17,27 @@ module Cody
17
17
  load_variables
18
18
  evaluate(@schedule_path)
19
19
 
20
- @properties[:schedule_expression] = @schedule_expression if @schedule_expression
20
+ @properties[:ScheduleExpression] = @schedule_expression if @schedule_expression
21
21
  set_rule_event! if @rule_event_props
22
22
  return if old_properties == @properties # empty schedule.rb file
23
23
 
24
24
  resource = {
25
- events_rule: {
26
- type: "AWS::Events::Rule",
27
- properties: @properties
25
+ EventsRule: {
26
+ Type: "AWS::Events::Rule",
27
+ Properties: @properties
28
28
  },
29
- events_rule_role: events_rule_role,
29
+ EventsRuleRole: events_rule_role,
30
30
  }
31
- CfnCamelizer.transform(resource)
31
+ auto_camelize(resource)
32
32
  end
33
33
 
34
34
  def set_rule_event!
35
35
  props = @rule_event_props
36
- if props.key?(:detail)
37
- description = props.key?(:description) ? props.delete(:description) : rule_description
38
- rule_props = { event_pattern: props, description: description }
39
- else # if props.key?(:event_pattern)
40
- props[:description] ||= rule_description
36
+ if props.key?(:Detail)
37
+ description = props.key?(:Description) ? props.delete(:Description) : rule_description
38
+ rule_props = { EventPattern: props, description: description }
39
+ else # if props.key?(:EventPattern)
40
+ props[:Description] ||= rule_description
41
41
  rule_props = props
42
42
  end
43
43
 
@@ -48,15 +48,15 @@ module Cody
48
48
  description = "Cody #{@options[:full_project_name]}"
49
49
  name = description.gsub(" ", "-").downcase
50
50
  {
51
- description: "#{description} CodeBuild project",
52
- # event_pattern: ,
53
- name: name,
54
- # schedule_expression: ,
55
- state: "ENABLED",
56
- targets: [{
57
- arn: { "Fn::GetAtt": "CodeBuild.Arn" },
58
- role_arn: { "Fn::GetAtt": "EventsRuleRole.Arn" }, # required for specific CodeBuild target.
59
- id: "CodeBuildTarget",
51
+ Description: "#{description} CodeBuild project",
52
+ # EventPattern: ,
53
+ Name: name,
54
+ # ScheduleExpression: ,
55
+ State: "ENABLED",
56
+ Targets: [{
57
+ Arn: { "Fn::GetAtt": "CodeBuild.Arn" },
58
+ RoleArn: { "Fn::GetAtt": "EventsRuleRole.Arn" }, # required for specific CodeBuild target.
59
+ Id: "CodeBuildTarget",
60
60
  }]
61
61
  }
62
62
  end
@@ -68,25 +68,25 @@ module Cody
68
68
 
69
69
  def events_rule_role
70
70
  {
71
- type: "AWS::IAM::Role",
72
- properties: {
73
- assume_role_policy_document: {
74
- statement: [{
75
- action: [ "sts:AssumeRole" ],
76
- effect: "Allow",
77
- principal: { service: [ "events.amazonaws.com" ] }
71
+ Type: "AWS::IAM::Role",
72
+ Properties: {
73
+ AssumeRolePolicyDocument: {
74
+ Statement: [{
75
+ Action: [ "sts:AssumeRole" ],
76
+ Effect: "Allow",
77
+ Principal: { service: [ "events.amazonaws.com" ] }
78
78
  }],
79
- version: "2012-10-17"
79
+ Version: "2012-10-17"
80
80
  },
81
- path: "/",
82
- policies: [{
83
- policy_name: "CodeBuildAccess",
84
- policy_document: {
85
- version: "2012-10-17",
86
- statement: [{
87
- action: "codebuild:StartBuild",
88
- effect: "Allow",
89
- resource: "arn:aws:codebuild:#{aws_data.region}:#{aws_data.account}:project/#{@options[:full_project_name]}"
81
+ Path: "/",
82
+ Policies: [{
83
+ PolicyName: "CodeBuildAccess",
84
+ PolicyDocument: {
85
+ Version: "2012-10-17",
86
+ Statement: [{
87
+ Action: "codebuild:StartBuild",
88
+ Effect: "Allow",
89
+ Resource: "arn:aws:codebuild:#{aws_data.region}:#{aws_data.account}:project/#{@options[:full_project_name]}"
90
90
  }]
91
91
  }
92
92
  }]
@@ -2,92 +2,37 @@ require "aws-sdk-cloudformation"
2
2
 
3
3
  module Cody
4
4
  class Stack
5
- include AwsServices
5
+ include Cody::AwsServices
6
6
 
7
7
  def initialize(options)
8
8
  @options = options
9
9
  @project_name = @options[:project_name] || inferred_project_name
10
- @stack_name = options[:stack_name] || inferred_stack_name(@project_name)
11
-
12
- @full_project_name = project_name_convention(@project_name)
13
- @template = {
14
- "Description" => "CodeBuild Project: #{@full_project_name}",
15
- "Resources" => {}
16
- }
10
+ @stack_name = normalize_stack_name(options[:stack_name] || inferred_stack_name(@project_name))
17
11
  end
18
12
 
19
13
  def run
20
- options = @options.merge(
21
- project_name: @project_name,
22
- full_project_name: @full_project_name,
23
- )
24
- project_builder = Project.new(options)
25
- unless project_builder.exist?
26
- puts "ERROR: Cody project does not exist: #{project_builder.project_path}".color(:red)
27
- exit 1
28
- return
29
- end
30
- project = project_builder.run
31
- @template["Resources"].merge!(project)
32
-
33
- if project["CodeBuild"]["Properties"]["ServiceRole"] == {"Ref"=>"IamRole"}
34
- role = Role.new(options).run
35
- @template["Resources"].merge!(role)
36
- end
37
-
38
- schedule = Schedule.new(options).run
39
- @template["Resources"].merge!(schedule) if schedule
40
-
41
- template_path = "/tmp/codebuild.yml"
42
- FileUtils.mkdir_p(File.dirname(template_path))
43
- IO.write(template_path, YAML.dump(@template))
44
- puts "Generated CloudFormation template at #{template_path.color(:green)}"
45
- return if @options[:noop]
46
- puts "Deploying stack #{@stack_name.color(:green)} with CodeBuild project #{@full_project_name.color(:green)}"
47
-
48
- begin
49
- perform
50
- url_info
51
- return unless @options[:wait]
52
- status.wait
53
- exit 2 unless status.success?
54
- rescue Aws::CloudFormation::Errors::ValidationError => e
55
- if e.message.include?("No updates") # No updates are to be performed.
56
- puts "WARN: #{e.message}".color(:yellow)
57
- else
58
- puts "ERROR ValidationError: #{e.message}".color(:red)
59
- exit 1
60
- end
14
+ handle_rollback_completed!
15
+ if stack_exists?(@stack_name)
16
+ Update.new(@options).run
17
+ else
18
+ Create.new(@options).run
61
19
  end
62
20
  end
63
21
 
64
22
  private
65
- def url_info
66
- stack = cfn.describe_stacks(stack_name: @stack_name).stacks.first
67
- region = `aws configure get region`.strip rescue "us-east-1"
68
- url = "https://console.aws.amazon.com/cloudformation/home?region=#{region}#/stacks"
69
- puts "Stack name #{@stack_name.color(:yellow)} status #{stack["stack_status"].color(:yellow)}"
70
- puts "Here's the CloudFormation url to check for more details #{url}"
71
- end
72
-
73
- def status
74
- @status ||= Cfn::Status.new(@stack_name)
23
+ def handle_rollback_completed!
24
+ @stack = find_stack(@stack_name)
25
+ if @stack && rollback_complete?(@stack)
26
+ puts "Existing stack in ROLLBACK_COMPLETE state. Deleting stack before continuing."
27
+ cfn.delete_stack(stack_name: @stack_name)
28
+ status.wait
29
+ status.reset
30
+ @stack = nil # at this point stack has been deleted
31
+ end
75
32
  end
76
33
 
77
34
  def rollback_complete?(stack)
78
35
  stack.stack_status == 'ROLLBACK_COMPLETE'
79
36
  end
80
-
81
- def find_stack(stack_name)
82
- resp = cfn.describe_stacks(stack_name: stack_name)
83
- resp.stacks.first
84
- rescue Aws::CloudFormation::Errors::ValidationError => e
85
- # example: Stack with id demo-web does not exist
86
- if e.message =~ /Stack with/ && e.message =~ /does not exist/
87
- nil
88
- else
89
- raise
90
- end
91
- end
92
37
  end
93
38
  end
@@ -0,0 +1,104 @@
1
+ class Cody::Stack
2
+ class Base
3
+ include Cody::AwsServices
4
+
5
+ def initialize(options)
6
+ @options = options
7
+ @project_name = @options[:project_name] || inferred_project_name
8
+ @stack_name = normalize_stack_name(options[:stack_name] || inferred_stack_name(@project_name))
9
+
10
+ @full_project_name = project_name_convention(@project_name)
11
+ @template = {
12
+ "Description" => "CodeBuild Project: #{@full_project_name}",
13
+ "Resources" => {}
14
+ }
15
+ end
16
+
17
+ def run
18
+ options = @options.merge(
19
+ project_name: @project_name,
20
+ full_project_name: @full_project_name,
21
+ )
22
+ project_builder = Cody::Project.new(options)
23
+ unless project_builder.exist?
24
+ puts "ERROR: Cody project does not exist: #{project_builder.project_path}".color(:red)
25
+ exit 1
26
+ return
27
+ end
28
+ project = project_builder.run
29
+ @template["Resources"].merge!(project)
30
+
31
+ if project["CodeBuild"]["Properties"]["ServiceRole"] == {"Ref"=>"IamRole"}
32
+ role = Cody::Role.new(options).run
33
+ @template["Resources"].merge!(role)
34
+ end
35
+
36
+ schedule = Cody::Schedule.new(options).run
37
+ @template["Resources"].merge!(schedule) if schedule
38
+
39
+ template_path = "/tmp/codebuild.yml"
40
+ FileUtils.mkdir_p(File.dirname(template_path))
41
+ IO.write(template_path, YAML.dump(@template))
42
+ puts "Generated CloudFormation template at #{template_path.color(:green)}"
43
+ return if @options[:noop]
44
+ puts "Deploying stack #{@stack_name.color(:green)} with CodeBuild project #{@full_project_name.color(:green)}"
45
+
46
+ begin
47
+ perform
48
+ url_info
49
+ return unless @options[:wait]
50
+ status.wait
51
+ exit 2 unless status.success?
52
+ rescue Aws::CloudFormation::Errors::ValidationError => e
53
+ if e.message.include?("No updates") # No updates are to be performed.
54
+ puts "WARN: #{e.message}".color(:yellow)
55
+ else
56
+ puts "ERROR ValidationError: #{e.message}".color(:red)
57
+ exit 1
58
+ end
59
+ end
60
+ end
61
+
62
+ private
63
+ def url_info
64
+ stack = cfn.describe_stacks(stack_name: @stack_name).stacks.first
65
+ region = `aws configure get region`.strip rescue "us-east-1"
66
+ url = "https://console.aws.amazon.com/cloudformation/home?region=#{region}#/stacks"
67
+ puts "Stack name #{@stack_name.color(:yellow)} status #{stack["stack_status"].color(:yellow)}"
68
+ puts "Here's the CloudFormation url to check for more details #{url}"
69
+ end
70
+
71
+ def status
72
+ @status ||= CfnStatus.new(@stack_name)
73
+ end
74
+
75
+ def rollback_complete?(stack)
76
+ stack.stack_status == 'ROLLBACK_COMPLETE'
77
+ end
78
+
79
+
80
+ def handle_rollback_completed!
81
+ @stack = find_stack(@stack_name)
82
+ if @stack && rollback_complete?(@stack)
83
+ puts "Existing stack in ROLLBACK_COMPLETE state. Deleting stack before continuing."
84
+ cfn.delete_stack(stack_name: @stack_name)
85
+ status.wait
86
+ status.reset
87
+ @stack = nil # at this point stack has been deleted
88
+ end
89
+ end
90
+
91
+ def find_stack(stack_name)
92
+ return if ENV['TEST']
93
+ resp = cfn.describe_stacks(stack_name: stack_name)
94
+ resp.stacks.first
95
+ rescue Aws::CloudFormation::Errors::ValidationError => e
96
+ # example: Stack with id demo-web does not exist
97
+ if e.message =~ /Stack with/ && e.message =~ /does not exist/
98
+ nil
99
+ else
100
+ raise
101
+ end
102
+ end
103
+ end
104
+ end