cody 0.9.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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