codepipeline 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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