codepipeline 0.1.0 → 0.2.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 (220) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -11
  3. data/.gitmodules +9 -0
  4. data/.rspec +1 -1
  5. data/CHANGELOG.md +16 -0
  6. data/Gemfile +3 -1
  7. data/Gemfile.lock +111 -0
  8. data/Guardfile +19 -0
  9. data/LICENSE.txt +18 -17
  10. data/README.md +44 -20
  11. data/Rakefile +10 -2
  12. data/codepipe.gemspec +38 -0
  13. data/docs/.gitignore +4 -0
  14. data/docs/CNAME +1 -0
  15. data/docs/Gemfile +3 -0
  16. data/docs/LICENSE +21 -0
  17. data/docs/README.md +25 -0
  18. data/docs/_config.yml +73 -0
  19. data/docs/_docs/contributing.md +99 -0
  20. data/docs/_docs/conventions.md +43 -0
  21. data/docs/_docs/deploy.md +72 -0
  22. data/docs/_docs/dsl.md +13 -0
  23. data/docs/_docs/dsl/approve.md +62 -0
  24. data/docs/_docs/dsl/pipeline.md +56 -0
  25. data/docs/_docs/dsl/pipeline/action.md +28 -0
  26. data/docs/_docs/dsl/pipeline/codebuild.md +62 -0
  27. data/docs/_docs/dsl/pipeline/prefix-and-suffix.md +57 -0
  28. data/docs/_docs/dsl/role.md +79 -0
  29. data/docs/_docs/dsl/schedule.md +29 -0
  30. data/docs/_docs/dsl/sns.md +27 -0
  31. data/docs/_docs/dsl/webhook.md +31 -0
  32. data/docs/_docs/ecs-deploy.md +24 -0
  33. data/docs/_docs/examples/different-branches.md +50 -0
  34. data/docs/_docs/install.md +14 -0
  35. data/docs/_docs/next-steps.md +16 -0
  36. data/docs/_docs/settings.md +34 -0
  37. data/docs/_docs/start.md +31 -0
  38. data/docs/_includes/commands.html +92 -0
  39. data/docs/_includes/content.html +25 -0
  40. data/docs/_includes/edit-on-github.html +9 -0
  41. data/docs/_includes/footer.html +41 -0
  42. data/docs/_includes/google_analytics.html +10 -0
  43. data/docs/_includes/head.html +45 -0
  44. data/docs/_includes/js.html +15 -0
  45. data/docs/_includes/nav.html +17 -0
  46. data/docs/_includes/prev_next.md +19 -0
  47. data/docs/_includes/reference.md +1 -0
  48. data/docs/_includes/subnav.html +46 -0
  49. data/docs/_includes/tutorials.md +38 -0
  50. data/docs/_layouts/default.html +12 -0
  51. data/docs/_reference/pipe-completion.md +44 -0
  52. data/docs/_reference/pipe-completion_script.md +25 -0
  53. data/docs/_reference/pipe-delete.md +25 -0
  54. data/docs/_reference/pipe-deploy.md +26 -0
  55. data/docs/_reference/pipe-init.md +25 -0
  56. data/docs/_reference/pipe-start.md +25 -0
  57. data/docs/_reference/pipe-version.md +21 -0
  58. data/docs/_sass/_bootstrap-overrides.scss +40 -0
  59. data/docs/_sass/_contact.scss +49 -0
  60. data/docs/_sass/_cta.scss +37 -0
  61. data/docs/_sass/_download.scss +31 -0
  62. data/docs/_sass/_features.scss +47 -0
  63. data/docs/_sass/_footer.scss +49 -0
  64. data/docs/_sass/_global.scss +102 -0
  65. data/docs/_sass/_main.scss +364 -0
  66. data/docs/_sass/_masthead.scss +70 -0
  67. data/docs/_sass/_mixins.scss +79 -0
  68. data/docs/_sass/_navbar.scss +92 -0
  69. data/docs/_sass/_syntax.scss +65 -0
  70. data/docs/_sass/_table.scss +34 -0
  71. data/docs/_sass/_timeline.scss +207 -0
  72. data/docs/_sass/_variables.scss +24 -0
  73. data/docs/bin/web +8 -0
  74. data/docs/docs.md +24 -0
  75. data/docs/dsl/pipeline.md +76 -0
  76. data/docs/dsl/role.md +66 -0
  77. data/docs/dsl/schedule.md +20 -0
  78. data/docs/img/docs/codepipeline-output.png +0 -0
  79. data/docs/img/logos/boltops-logo-full.png +0 -0
  80. data/docs/img/logos/boltops-logo.png +0 -0
  81. data/docs/img/logos/project-logo.png +0 -0
  82. data/docs/index.html +35 -0
  83. data/docs/js/nav.js +39 -0
  84. data/docs/js/new-age.js +38 -0
  85. data/docs/js/new-age.min.js +6 -0
  86. data/docs/new-age.scss +20 -0
  87. data/docs/quick-start.md +73 -0
  88. data/docs/reference.md +12 -0
  89. data/docs/support.md +22 -0
  90. data/docs/vendor/bootstrap/css/bootstrap-grid.css +1339 -0
  91. data/docs/vendor/bootstrap/css/bootstrap-grid.css.map +1 -0
  92. data/docs/vendor/bootstrap/css/bootstrap-grid.min.css +1 -0
  93. data/docs/vendor/bootstrap/css/bootstrap-grid.min.css.map +1 -0
  94. data/docs/vendor/bootstrap/css/bootstrap-reboot.css +459 -0
  95. data/docs/vendor/bootstrap/css/bootstrap-reboot.css.map +1 -0
  96. data/docs/vendor/bootstrap/css/bootstrap-reboot.min.css +1 -0
  97. data/docs/vendor/bootstrap/css/bootstrap-reboot.min.css.map +1 -0
  98. data/docs/vendor/bootstrap/css/bootstrap.css +9320 -0
  99. data/docs/vendor/bootstrap/css/bootstrap.css.map +1 -0
  100. data/docs/vendor/bootstrap/css/bootstrap.min.css +6 -0
  101. data/docs/vendor/bootstrap/css/bootstrap.min.css.map +1 -0
  102. data/docs/vendor/bootstrap/js/bootstrap.js +3535 -0
  103. data/docs/vendor/bootstrap/js/bootstrap.min.js +7 -0
  104. data/docs/vendor/font-awesome/css/font-awesome.css +2337 -0
  105. data/docs/vendor/font-awesome/css/font-awesome.min.css +4 -0
  106. data/docs/vendor/font-awesome/fonts/FontAwesome.otf +0 -0
  107. data/docs/vendor/font-awesome/fonts/fontawesome-webfont.eot +0 -0
  108. data/docs/vendor/font-awesome/fonts/fontawesome-webfont.svg +2671 -0
  109. data/docs/vendor/font-awesome/fonts/fontawesome-webfont.ttf +0 -0
  110. data/docs/vendor/font-awesome/fonts/fontawesome-webfont.woff +0 -0
  111. data/docs/vendor/font-awesome/fonts/fontawesome-webfont.woff2 +0 -0
  112. data/docs/vendor/font-awesome/less/animated.less +34 -0
  113. data/docs/vendor/font-awesome/less/bordered-pulled.less +25 -0
  114. data/docs/vendor/font-awesome/less/core.less +12 -0
  115. data/docs/vendor/font-awesome/less/fixed-width.less +6 -0
  116. data/docs/vendor/font-awesome/less/font-awesome.less +18 -0
  117. data/docs/vendor/font-awesome/less/icons.less +789 -0
  118. data/docs/vendor/font-awesome/less/larger.less +13 -0
  119. data/docs/vendor/font-awesome/less/list.less +19 -0
  120. data/docs/vendor/font-awesome/less/mixins.less +60 -0
  121. data/docs/vendor/font-awesome/less/path.less +15 -0
  122. data/docs/vendor/font-awesome/less/rotated-flipped.less +20 -0
  123. data/docs/vendor/font-awesome/less/screen-reader.less +5 -0
  124. data/docs/vendor/font-awesome/less/stacked.less +20 -0
  125. data/docs/vendor/font-awesome/less/variables.less +799 -0
  126. data/docs/vendor/font-awesome/scss/_animated.scss +34 -0
  127. data/docs/vendor/font-awesome/scss/_bordered-pulled.scss +25 -0
  128. data/docs/vendor/font-awesome/scss/_core.scss +12 -0
  129. data/docs/vendor/font-awesome/scss/_fixed-width.scss +6 -0
  130. data/docs/vendor/font-awesome/scss/_icons.scss +789 -0
  131. data/docs/vendor/font-awesome/scss/_larger.scss +13 -0
  132. data/docs/vendor/font-awesome/scss/_list.scss +19 -0
  133. data/docs/vendor/font-awesome/scss/_mixins.scss +60 -0
  134. data/docs/vendor/font-awesome/scss/_path.scss +15 -0
  135. data/docs/vendor/font-awesome/scss/_rotated-flipped.scss +20 -0
  136. data/docs/vendor/font-awesome/scss/_screen-reader.scss +5 -0
  137. data/docs/vendor/font-awesome/scss/_stacked.scss +20 -0
  138. data/docs/vendor/font-awesome/scss/_variables.scss +799 -0
  139. data/docs/vendor/font-awesome/scss/font-awesome.scss +18 -0
  140. data/docs/vendor/jquery-easing/jquery.easing.compatibility.js +59 -0
  141. data/docs/vendor/jquery-easing/jquery.easing.js +166 -0
  142. data/docs/vendor/jquery-easing/jquery.easing.min.js +1 -0
  143. data/docs/vendor/jquery/jquery.js +10253 -0
  144. data/docs/vendor/jquery/jquery.min.js +4 -0
  145. data/docs/vendor/simple-line-icons/css/simple-line-icons.css +778 -0
  146. data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.eot +0 -0
  147. data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.svg +200 -0
  148. data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.ttf +0 -0
  149. data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.woff +0 -0
  150. data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.woff2 +0 -0
  151. data/docs/vendor/simple-line-icons/less/simple-line-icons.less +982 -0
  152. data/docs/vendor/simple-line-icons/scss/simple-line-icons.scss +979 -0
  153. data/docs/vendor/tether/tether.js +1811 -0
  154. data/docs/vendor/tether/tether.min.js +1 -0
  155. data/exe/codepipeline +14 -0
  156. data/exe/pipe +14 -0
  157. data/lib/codepipe.rb +23 -0
  158. data/lib/codepipe/autoloader.rb +21 -0
  159. data/lib/codepipe/aws_services.rb +20 -0
  160. data/lib/codepipe/aws_services/helpers.rb +71 -0
  161. data/lib/codepipe/build.rb +13 -0
  162. data/lib/codepipe/cli.rb +60 -0
  163. data/lib/codepipe/command.rb +82 -0
  164. data/lib/codepipe/completer.rb +159 -0
  165. data/lib/codepipe/completer/script.rb +6 -0
  166. data/lib/codepipe/completer/script.sh +10 -0
  167. data/lib/codepipe/core.rb +63 -0
  168. data/lib/codepipe/create.rb +12 -0
  169. data/lib/codepipe/delete.rb +27 -0
  170. data/lib/codepipe/deploy.rb +40 -0
  171. data/lib/codepipe/dsl/pipeline.rb +37 -0
  172. data/lib/codepipe/dsl/pipeline/approve.rb +34 -0
  173. data/lib/codepipe/dsl/pipeline/codebuild.rb +57 -0
  174. data/lib/codepipe/dsl/pipeline/github.rb +36 -0
  175. data/lib/codepipe/dsl/role.rb +50 -0
  176. data/lib/codepipe/dsl/schedule.rb +30 -0
  177. data/lib/codepipe/dsl/sns.rb +15 -0
  178. data/lib/codepipe/dsl/ssm.rb +22 -0
  179. data/lib/codepipe/dsl/webhook.rb +27 -0
  180. data/lib/codepipe/evaluate.rb +47 -0
  181. data/lib/codepipe/help.rb +9 -0
  182. data/lib/codepipe/help/completion.md +22 -0
  183. data/lib/codepipe/help/completion_script.md +3 -0
  184. data/lib/codepipe/help/hello.md +5 -0
  185. data/lib/codepipe/init.rb +57 -0
  186. data/lib/codepipe/pipeline.rb +61 -0
  187. data/lib/codepipe/pipeline/s3_bucket.rb +88 -0
  188. data/lib/codepipe/role.rb +181 -0
  189. data/lib/codepipe/schedule.rb +99 -0
  190. data/lib/codepipe/sequence.rb +66 -0
  191. data/lib/codepipe/setting.rb +79 -0
  192. data/lib/codepipe/sns.rb +43 -0
  193. data/lib/codepipe/stack.rb +95 -0
  194. data/lib/codepipe/start.rb +83 -0
  195. data/lib/codepipe/update.rb +12 -0
  196. data/lib/codepipe/version.rb +3 -0
  197. data/lib/codepipe/webhook.rb +60 -0
  198. data/lib/codepipeline.rb +1 -6
  199. data/lib/template/.codepipeline/pipeline.rb.tt +33 -0
  200. data/lib/template/.codepipeline/schedule.rb +3 -0
  201. data/lib/template/.codepipeline/settings.yml +9 -0
  202. data/lib/template/.codepipeline/sns.rb +14 -0
  203. data/spec/fixtures/app/.codepipeline/pipeline.rb +12 -0
  204. data/spec/fixtures/app/.codepipeline/schedule.rb +1 -0
  205. data/spec/fixtures/app/.codepipeline/webhook.rb +1 -0
  206. data/spec/fixtures/pipelines/approve.rb +22 -0
  207. data/spec/fixtures/pipelines/approve_existing_sns.rb +24 -0
  208. data/spec/lib/cli_spec.rb +18 -0
  209. data/spec/lib/pipeline/approve_spec.rb +32 -0
  210. data/spec/lib/pipeline_spec.rb +12 -0
  211. data/spec/lib/role_spec.rb +12 -0
  212. data/spec/lib/schedule_spec.rb +12 -0
  213. data/spec/lib/webhook_spec.rb +12 -0
  214. data/spec/spec_helper.rb +35 -0
  215. metadata +419 -22
  216. data/.travis.yml +0 -7
  217. data/bin/console +0 -14
  218. data/bin/setup +0 -8
  219. data/codepipeline.gemspec +0 -27
  220. data/lib/codepipeline/version.rb +0 -3
@@ -0,0 +1,99 @@
1
+ module Codepipe
2
+ class Schedule
3
+ include Codepipe::Dsl::Schedule
4
+ include Evaluate
5
+
6
+ def initialize(options={})
7
+ @options = options
8
+ @schedule_path = options[:schedule_path] || get_schedule_path
9
+ @properties = default_properties
10
+ end
11
+
12
+ def run
13
+ return unless File.exist?(@schedule_path)
14
+
15
+ old_properties = @properties.clone
16
+ evaluate(@schedule_path)
17
+
18
+ @properties[:schedule_expression] = @schedule_expression if @schedule_expression
19
+ set_rule_event! if @rule_event_props
20
+ return if old_properties == @properties # empty schedule.rb file
21
+
22
+ resource = {
23
+ events_rule: {
24
+ type: "AWS::Events::Rule",
25
+ properties: @properties
26
+ },
27
+ events_rule_role: events_rule_role,
28
+ }
29
+ CfnCamelizer.transform(resource)
30
+ end
31
+
32
+ def set_rule_event!
33
+ props = @rule_event_props
34
+ if props.key?(:detail)
35
+ description = props.key?(:description) ? props.delete(:description) : rule_description
36
+ rule_props = { event_pattern: props, description: description }
37
+ else # if props.key?(:event_pattern)
38
+ props[:description] ||= rule_description
39
+ rule_props = props
40
+ end
41
+
42
+ @properties.merge!(rule_props)
43
+ end
44
+
45
+ def default_properties
46
+ description = "CodePipeline #{@options[:full_pipeline_name]}"
47
+ name = description.gsub(" ", "-").downcase
48
+ {
49
+ description: description,
50
+ # event_pattern: ,
51
+ name: name,
52
+ # schedule_expression: ,
53
+ state: "ENABLED",
54
+ targets: [{
55
+ arn: "arn:aws:codepipeline:#{aws.region}:#{aws.account}:#{@options[:full_pipeline_name]}",
56
+ role_arn: { "Fn::GetAtt": "EventsRuleRole.Arn" }, # required for specific CodePipeline target.
57
+ id: "CodePipelineTarget",
58
+ }]
59
+ }
60
+ end
61
+
62
+ private
63
+ def get_schedule_path
64
+ lookup_codepipeline_file("schedule.rb")
65
+ end
66
+
67
+ def events_rule_role
68
+ {
69
+ type: "AWS::IAM::Role",
70
+ properties: {
71
+ assume_role_policy_document: {
72
+ statement: [{
73
+ action: [ "sts:AssumeRole" ],
74
+ effect: "Allow",
75
+ principal: { service: [ "events.amazonaws.com" ] }
76
+ }],
77
+ version: "2012-10-17"
78
+ },
79
+ path: "/",
80
+ policies: [{
81
+ policy_name: "CodePipelineAccess",
82
+ policy_document: {
83
+ version: "2012-10-17",
84
+ statement: [{
85
+ action: "codepipeline:StartPipelineExecution",
86
+ effect: "Allow",
87
+ resource: "arn:aws:codepipeline:#{aws.region}:#{aws.account}:#{@options[:full_pipeline_name]}"
88
+ }]
89
+ }
90
+ }]
91
+ }
92
+ }
93
+ end
94
+
95
+ def aws
96
+ @aws ||= AwsData.new
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,66 @@
1
+ require 'fileutils'
2
+ require 'thor'
3
+
4
+ module Codepipe
5
+ class Sequence < Thor::Group
6
+ include AwsServices
7
+ include Thor::Actions
8
+
9
+ add_runtime_options! # force, pretend, quiet, skip options
10
+ # https://github.com/erikhuda/thor/blob/master/lib/thor/actions.rb#L49
11
+
12
+ def self.source_paths
13
+ [File.expand_path("../../template", __FILE__)]
14
+ end
15
+
16
+ private
17
+ def override_source_paths(*paths)
18
+ # Using string with instance_eval because block doesnt have access to
19
+ # path at runtime.
20
+ self.class.instance_eval %{
21
+ def self.source_paths
22
+ #{paths.flatten.inspect}
23
+ end
24
+ }
25
+ end
26
+
27
+ def sync_template_repo
28
+ unless git_installed?
29
+ abort "Unable to detect git installation on your system. Git needs to be installed in order to use the --template option."
30
+ end
31
+
32
+ template_path = "#{ENV['HOME']}/.codebuild/templates/#{full_repo_name}"
33
+ if File.exist?(template_path)
34
+ sh("cd #{template_path} && git pull")
35
+ else
36
+ FileUtils.mkdir_p(File.dirname(template_path))
37
+ sh("git clone #{repo_url} #{template_path}")
38
+ end
39
+ end
40
+
41
+ def full_repo_name
42
+ full_repo = options[:template].split("/")[-2..-1].join("/")
43
+ full_repo = full_repo.split(":").last
44
+ full_repo.sub(".git", "")
45
+ end
46
+
47
+ # normalize repo_url
48
+ def repo_url
49
+ template = options[:template]
50
+ if template.include?('github.com')
51
+ template # leave as is, user has provided full github url
52
+ else
53
+ "https://github.com/#{template}"
54
+ end
55
+ end
56
+
57
+ def git_installed?
58
+ system("type git > /dev/null")
59
+ end
60
+
61
+ def sh(command)
62
+ puts "=> #{command}"
63
+ system(command)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,79 @@
1
+ require 'yaml'
2
+ require 'render_me_pretty'
3
+
4
+ module Codepipe
5
+ class Setting
6
+ extend Memoist
7
+ def initialize(check_codepipeline_project=true)
8
+ @check_codepipeline_project = check_codepipeline_project
9
+ end
10
+
11
+ # data contains the settings.yml config. The order or precedence for settings
12
+ # is the project ufo/settings.yml and then the ~/.codepipeline/settings.yml.
13
+ def data
14
+ Codepipe.check_codepipeline_project! if @check_codepipeline_project
15
+ return {} unless File.exist?(project_settings_path)
16
+
17
+ # project based settings files
18
+ project = load_file(project_settings_path)
19
+
20
+ user_file = "#{ENV['HOME']}/.codepipeline/settings.yml"
21
+ user = File.exist?(user_file) ? YAML.load_file(user_file) : {}
22
+
23
+ default_file = File.expand_path("default/settings.yml", __dir__)
24
+ default = load_file(default_file)
25
+
26
+ all_envs = default.deep_merge(user.deep_merge(project))
27
+ all_envs = merge_base(all_envs)
28
+ data = all_envs[pipe_env] || all_envs["base"] || {}
29
+ data.deep_symbolize_keys
30
+ end
31
+ memoize :data
32
+
33
+ # Resolves infinite problem since Codepipe.env can be determined from PIPE_ENV or settings.yml files.
34
+ # When ufo is determined from settings it should not called Codepipe.env since that in turn calls
35
+ # Settings.new.data which can then cause an infinite loop.
36
+ def pipe_env
37
+ settings = YAML.load_file("#{cb_root}/.codepipeline/settings.yml")
38
+ env = settings.find do |_env, section|
39
+ section ||= {}
40
+ ENV['AWS_PROFILE'] && ENV['AWS_PROFILE'] == section['aws_profile']
41
+ end
42
+
43
+ pipe_env = env.first if env
44
+ pipe_env = ENV['PIPE_ENV'] if ENV['PIPE_ENV'] # highest precedence
45
+ pipe_env || 'development'
46
+ end
47
+
48
+ private
49
+ def load_file(path)
50
+ return Hash.new({}) unless File.exist?(path)
51
+
52
+ content = RenderMePretty.result(path)
53
+ data = YAML.load(content)
54
+ # If key is is accidentally set to nil it screws up the merge_base later.
55
+ # So ensure that all keys with nil value are set to {}
56
+ data.each do |env, _setting|
57
+ data[env] ||= {}
58
+ end
59
+ data
60
+ end
61
+
62
+ # automatically add base settings to the rest of the environments
63
+ def merge_base(all_envs)
64
+ base = all_envs["base"] || {}
65
+ all_envs.each do |env, settings|
66
+ all_envs[env] = base.merge(settings) unless env == "base"
67
+ end
68
+ all_envs
69
+ end
70
+
71
+ def project_settings_path
72
+ "#{cb_root}/.codepipeline/settings.yml"
73
+ end
74
+
75
+ def cb_root
76
+ ENV["CODEPIPELINE_ROOT"] || Dir.pwd
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,43 @@
1
+ module Codepipe
2
+ class Sns
3
+ include Codepipe::Dsl::Sns
4
+ include Evaluate
5
+
6
+ def initialize(options={})
7
+ @options = options
8
+ @sns_path = options[:sns_path] || get_sns_path
9
+ @properties = default_properties
10
+ end
11
+
12
+ def run
13
+ evaluate(@sns_path) if File.exist?(@sns_path)
14
+
15
+ resource = {
16
+ sns_topic: {
17
+ type: "AWS::SNS::Topic",
18
+ properties: @properties
19
+ }
20
+ }
21
+ CfnCamelizer.transform(resource)
22
+ end
23
+
24
+ def default_properties
25
+ display_name = "#{@options[:full_pipeline_name]} pipeline"
26
+ {
27
+ display_name: display_name,
28
+ # kms_master_key_id: "string",
29
+ # subscription: [{
30
+ # endpoint: '',
31
+ # protocol: ','
32
+ # }],
33
+ # topic_name: "string", # Not setting because update requires: Replacement. Dont want 2 pipelines to collide
34
+ }
35
+ # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-subscription.html
36
+ end
37
+
38
+ private
39
+ def get_sns_path
40
+ lookup_codepipeline_file("sns.rb")
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,95 @@
1
+ require "aws-sdk-cloudformation"
2
+
3
+ module Codepipe
4
+ class Stack
5
+ include AwsServices
6
+
7
+ def initialize(options)
8
+ @options = options
9
+ @pipeline_name = @options[:pipeline_name] || inferred_pipeline_name
10
+ @stack_name = options[:stack_name] || inferred_stack_name(@pipeline_name)
11
+
12
+ @full_pipeline_name = pipeline_name_convention(@pipeline_name)
13
+ @template = {
14
+ "Description" => "CodePipeline Project: #{@full_pipeline_name}",
15
+ "Resources" => {}
16
+ }
17
+ end
18
+
19
+ def run
20
+ options = @options.merge(
21
+ pipeline_name: @pipeline_name,
22
+ full_pipeline_name: @full_pipeline_name,
23
+ )
24
+
25
+ pipeline_builder = Pipeline.new(options)
26
+ unless pipeline_builder.exist?
27
+ puts "ERROR: pipeline does not exist: #{pipeline_builder.pipeline_path}".color(:red)
28
+ exit 1
29
+ return
30
+ end
31
+ pipeline = pipeline_builder.run
32
+ @template["Resources"].merge!(pipeline)
33
+
34
+ if pipeline["Pipeline"]["Properties"]["RoleArn"] == {"Fn::GetAtt"=>"IamRole.Arn"}
35
+ role = Role.new(options).run
36
+ @template["Resources"].merge!(role)
37
+ end
38
+
39
+ if sns_topic?(pipeline)
40
+ role = Sns.new(options).run
41
+ @template["Resources"].merge!(role)
42
+ end
43
+
44
+ webhook = Webhook.new(options).run
45
+ @template["Resources"].merge!(webhook) if webhook
46
+
47
+ schedule = Schedule.new(options).run
48
+ @template["Resources"].merge!(schedule) if schedule
49
+
50
+ template_path = "/tmp/codepipeline.yml"
51
+ FileUtils.mkdir_p(File.dirname(template_path))
52
+ IO.write(template_path, YAML.dump(@template))
53
+ puts "Generated CloudFormation template at #{template_path.color(:green)}"
54
+ return if @options[:noop]
55
+ puts "Deploying stack #{@stack_name.color(:green)} with CodePipeline project #{@full_pipeline_name.color(:green)}"
56
+
57
+ begin
58
+ perform
59
+ url_info
60
+ return unless @options[:wait]
61
+ status.wait
62
+ exit 2 unless status.success?
63
+ rescue Aws::CloudFormation::Errors::ValidationError => e
64
+ if e.message.include?("No updates") # No updates are to be performed.
65
+ puts "WARN: #{e.message}".color(:yellow)
66
+ else
67
+ puts "ERROR ValidationError: #{e.message}".color(:red)
68
+ exit 1
69
+ end
70
+ end
71
+ end
72
+
73
+ private
74
+ def sns_topic?(template)
75
+ stages = template['Pipeline']['Properties']['Stages']
76
+ stages.detect do |stage|
77
+ stage['Actions'].detect do |action|
78
+ action['Configuration']['NotificationArn'] == {'Ref'=>'SnsTopic'}
79
+ end
80
+ end
81
+ end
82
+
83
+ def url_info
84
+ stack = cfn.describe_stacks(stack_name: @stack_name).stacks.first
85
+ region = `aws configure get region`.strip rescue "us-east-1"
86
+ url = "https://console.aws.amazon.com/cloudformation/home?region=#{region}#/stacks"
87
+ puts "Stack name #{@stack_name.color(:yellow)} status #{stack["stack_status"].color(:yellow)}"
88
+ puts "Here's the CloudFormation url to check for more details #{url}"
89
+ end
90
+
91
+ def status
92
+ @status ||= Cfn::Status.new(@stack_name)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,83 @@
1
+ module Codepipe
2
+ class Start
3
+ extend Memoist
4
+ include AwsServices
5
+
6
+ def initialize(options)
7
+ @options = options
8
+ @pipeline_name = options[:pipeline_name] || inferred_pipeline_name
9
+ @full_pipeline_name = pipeline_name_convention(@pipeline_name)
10
+ @stack_name = options[:stack_name] || inferred_stack_name(@pipeline_name)
11
+ end
12
+
13
+ def run
14
+ check_pipeline_exists!
15
+ redeploy
16
+ resp = codepipeline.start_pipeline_execution(name: pipeline_name)
17
+ codepipeline_info(resp.pipeline_execution_id)
18
+ end
19
+
20
+ # Codepipeline does not currently support specifying a different branch starting an execution.
21
+ # Workaround this limitation by updating the pipeline and then starting the execution.
22
+ def redeploy
23
+ return unless different_branch?
24
+ puts "Different branch detected."
25
+ puts " Current pipeline branch: #{current_pipeline_branch}"
26
+ puts " Requested branch: #{@options[:branch]}"
27
+ puts "Updating pipeline with new branch.".color(:green)
28
+ Deploy.new(@options).run
29
+ end
30
+
31
+ def different_branch?
32
+ current_pipeline_branch != @options[:branch]
33
+ end
34
+
35
+ # Actual branch on current pipeline
36
+ def current_pipeline_branch
37
+ resp = codepipeline.get_pipeline(name: pipeline_name)
38
+ source_stage = resp.pipeline.stages.find { |s| s.name == "Source" }
39
+ action = source_stage.actions.first
40
+ action.configuration['Branch']
41
+ end
42
+ memoize :current_pipeline_branch
43
+
44
+ def check_pipeline_exists!
45
+ pipeline_name
46
+ end
47
+
48
+ def pipeline_name
49
+ if pipeline_exists?(@full_pipeline_name)
50
+ @full_pipeline_name
51
+ elsif stack_exists?(@stack_name) # allow `cb start STACK_NAME` to work too
52
+ resp = cfn.describe_stack_resources(stack_name: @stack_name)
53
+ resource = resp.stack_resources.find do |r|
54
+ r.logical_resource_id == "CodePipeline"
55
+ end
56
+ resource.physical_resource_id # codepipeline project name
57
+ else
58
+ puts "ERROR: Unable to find the codepipeline project with either full_pipeline_name: #{@full_pipeline_name} or stack name: #{@stack_name}".color(:red)
59
+ exit 1
60
+ end
61
+ end
62
+ memoize :pipeline_name
63
+
64
+ private
65
+ def codepipeline_info(execution_id)
66
+ region = `aws configure get region`.strip rescue "us-east-1"
67
+ url = "https://#{region}.console.aws.amazon.com/codesuite/codepipeline/pipelines/#{pipeline_name}/view"
68
+ cli = "aws codepipeline get-pipeline-execution --pipeline-execution-id #{execution_id} --pipeline-name #{pipeline_name}"
69
+
70
+ puts "Pipeline started: #{pipeline_name}"
71
+ puts "Please check the CodePipeline console for the status."
72
+ puts "CodePipeline Console: #{url}"
73
+ puts "Pipeline cli: #{cli}"
74
+ end
75
+
76
+ def pipeline_exists?(name)
77
+ codepipeline.get_pipeline(name: name)
78
+ true
79
+ rescue Aws::CodePipeline::Errors::PipelineNotFoundException
80
+ false
81
+ end
82
+ end
83
+ end