codepipeline 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +16 -11
- data/.gitmodules +9 -0
- data/.rspec +1 -1
- data/CHANGELOG.md +16 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +111 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +18 -17
- data/README.md +44 -20
- data/Rakefile +10 -2
- data/codepipe.gemspec +38 -0
- data/docs/.gitignore +4 -0
- data/docs/CNAME +1 -0
- data/docs/Gemfile +3 -0
- data/docs/LICENSE +21 -0
- data/docs/README.md +25 -0
- data/docs/_config.yml +73 -0
- data/docs/_docs/contributing.md +99 -0
- data/docs/_docs/conventions.md +43 -0
- data/docs/_docs/deploy.md +72 -0
- data/docs/_docs/dsl.md +13 -0
- data/docs/_docs/dsl/approve.md +62 -0
- data/docs/_docs/dsl/pipeline.md +56 -0
- data/docs/_docs/dsl/pipeline/action.md +28 -0
- data/docs/_docs/dsl/pipeline/codebuild.md +62 -0
- data/docs/_docs/dsl/pipeline/prefix-and-suffix.md +57 -0
- data/docs/_docs/dsl/role.md +79 -0
- data/docs/_docs/dsl/schedule.md +29 -0
- data/docs/_docs/dsl/sns.md +27 -0
- data/docs/_docs/dsl/webhook.md +31 -0
- data/docs/_docs/ecs-deploy.md +24 -0
- data/docs/_docs/examples/different-branches.md +50 -0
- data/docs/_docs/install.md +14 -0
- data/docs/_docs/next-steps.md +16 -0
- data/docs/_docs/settings.md +34 -0
- data/docs/_docs/start.md +31 -0
- data/docs/_includes/commands.html +92 -0
- data/docs/_includes/content.html +25 -0
- data/docs/_includes/edit-on-github.html +9 -0
- data/docs/_includes/footer.html +41 -0
- data/docs/_includes/google_analytics.html +10 -0
- data/docs/_includes/head.html +45 -0
- data/docs/_includes/js.html +15 -0
- data/docs/_includes/nav.html +17 -0
- data/docs/_includes/prev_next.md +19 -0
- data/docs/_includes/reference.md +1 -0
- data/docs/_includes/subnav.html +46 -0
- data/docs/_includes/tutorials.md +38 -0
- data/docs/_layouts/default.html +12 -0
- data/docs/_reference/pipe-completion.md +44 -0
- data/docs/_reference/pipe-completion_script.md +25 -0
- data/docs/_reference/pipe-delete.md +25 -0
- data/docs/_reference/pipe-deploy.md +26 -0
- data/docs/_reference/pipe-init.md +25 -0
- data/docs/_reference/pipe-start.md +25 -0
- data/docs/_reference/pipe-version.md +21 -0
- data/docs/_sass/_bootstrap-overrides.scss +40 -0
- data/docs/_sass/_contact.scss +49 -0
- data/docs/_sass/_cta.scss +37 -0
- data/docs/_sass/_download.scss +31 -0
- data/docs/_sass/_features.scss +47 -0
- data/docs/_sass/_footer.scss +49 -0
- data/docs/_sass/_global.scss +102 -0
- data/docs/_sass/_main.scss +364 -0
- data/docs/_sass/_masthead.scss +70 -0
- data/docs/_sass/_mixins.scss +79 -0
- data/docs/_sass/_navbar.scss +92 -0
- data/docs/_sass/_syntax.scss +65 -0
- data/docs/_sass/_table.scss +34 -0
- data/docs/_sass/_timeline.scss +207 -0
- data/docs/_sass/_variables.scss +24 -0
- data/docs/bin/web +8 -0
- data/docs/docs.md +24 -0
- data/docs/dsl/pipeline.md +76 -0
- data/docs/dsl/role.md +66 -0
- data/docs/dsl/schedule.md +20 -0
- data/docs/img/docs/codepipeline-output.png +0 -0
- data/docs/img/logos/boltops-logo-full.png +0 -0
- data/docs/img/logos/boltops-logo.png +0 -0
- data/docs/img/logos/project-logo.png +0 -0
- data/docs/index.html +35 -0
- data/docs/js/nav.js +39 -0
- data/docs/js/new-age.js +38 -0
- data/docs/js/new-age.min.js +6 -0
- data/docs/new-age.scss +20 -0
- data/docs/quick-start.md +73 -0
- data/docs/reference.md +12 -0
- data/docs/support.md +22 -0
- data/docs/vendor/bootstrap/css/bootstrap-grid.css +1339 -0
- data/docs/vendor/bootstrap/css/bootstrap-grid.css.map +1 -0
- data/docs/vendor/bootstrap/css/bootstrap-grid.min.css +1 -0
- data/docs/vendor/bootstrap/css/bootstrap-grid.min.css.map +1 -0
- data/docs/vendor/bootstrap/css/bootstrap-reboot.css +459 -0
- data/docs/vendor/bootstrap/css/bootstrap-reboot.css.map +1 -0
- data/docs/vendor/bootstrap/css/bootstrap-reboot.min.css +1 -0
- data/docs/vendor/bootstrap/css/bootstrap-reboot.min.css.map +1 -0
- data/docs/vendor/bootstrap/css/bootstrap.css +9320 -0
- data/docs/vendor/bootstrap/css/bootstrap.css.map +1 -0
- data/docs/vendor/bootstrap/css/bootstrap.min.css +6 -0
- data/docs/vendor/bootstrap/css/bootstrap.min.css.map +1 -0
- data/docs/vendor/bootstrap/js/bootstrap.js +3535 -0
- data/docs/vendor/bootstrap/js/bootstrap.min.js +7 -0
- data/docs/vendor/font-awesome/css/font-awesome.css +2337 -0
- data/docs/vendor/font-awesome/css/font-awesome.min.css +4 -0
- data/docs/vendor/font-awesome/fonts/FontAwesome.otf +0 -0
- data/docs/vendor/font-awesome/fonts/fontawesome-webfont.eot +0 -0
- data/docs/vendor/font-awesome/fonts/fontawesome-webfont.svg +2671 -0
- data/docs/vendor/font-awesome/fonts/fontawesome-webfont.ttf +0 -0
- data/docs/vendor/font-awesome/fonts/fontawesome-webfont.woff +0 -0
- data/docs/vendor/font-awesome/fonts/fontawesome-webfont.woff2 +0 -0
- data/docs/vendor/font-awesome/less/animated.less +34 -0
- data/docs/vendor/font-awesome/less/bordered-pulled.less +25 -0
- data/docs/vendor/font-awesome/less/core.less +12 -0
- data/docs/vendor/font-awesome/less/fixed-width.less +6 -0
- data/docs/vendor/font-awesome/less/font-awesome.less +18 -0
- data/docs/vendor/font-awesome/less/icons.less +789 -0
- data/docs/vendor/font-awesome/less/larger.less +13 -0
- data/docs/vendor/font-awesome/less/list.less +19 -0
- data/docs/vendor/font-awesome/less/mixins.less +60 -0
- data/docs/vendor/font-awesome/less/path.less +15 -0
- data/docs/vendor/font-awesome/less/rotated-flipped.less +20 -0
- data/docs/vendor/font-awesome/less/screen-reader.less +5 -0
- data/docs/vendor/font-awesome/less/stacked.less +20 -0
- data/docs/vendor/font-awesome/less/variables.less +799 -0
- data/docs/vendor/font-awesome/scss/_animated.scss +34 -0
- data/docs/vendor/font-awesome/scss/_bordered-pulled.scss +25 -0
- data/docs/vendor/font-awesome/scss/_core.scss +12 -0
- data/docs/vendor/font-awesome/scss/_fixed-width.scss +6 -0
- data/docs/vendor/font-awesome/scss/_icons.scss +789 -0
- data/docs/vendor/font-awesome/scss/_larger.scss +13 -0
- data/docs/vendor/font-awesome/scss/_list.scss +19 -0
- data/docs/vendor/font-awesome/scss/_mixins.scss +60 -0
- data/docs/vendor/font-awesome/scss/_path.scss +15 -0
- data/docs/vendor/font-awesome/scss/_rotated-flipped.scss +20 -0
- data/docs/vendor/font-awesome/scss/_screen-reader.scss +5 -0
- data/docs/vendor/font-awesome/scss/_stacked.scss +20 -0
- data/docs/vendor/font-awesome/scss/_variables.scss +799 -0
- data/docs/vendor/font-awesome/scss/font-awesome.scss +18 -0
- data/docs/vendor/jquery-easing/jquery.easing.compatibility.js +59 -0
- data/docs/vendor/jquery-easing/jquery.easing.js +166 -0
- data/docs/vendor/jquery-easing/jquery.easing.min.js +1 -0
- data/docs/vendor/jquery/jquery.js +10253 -0
- data/docs/vendor/jquery/jquery.min.js +4 -0
- data/docs/vendor/simple-line-icons/css/simple-line-icons.css +778 -0
- data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.eot +0 -0
- data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.svg +200 -0
- data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.ttf +0 -0
- data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.woff +0 -0
- data/docs/vendor/simple-line-icons/fonts/Simple-Line-Icons.woff2 +0 -0
- data/docs/vendor/simple-line-icons/less/simple-line-icons.less +982 -0
- data/docs/vendor/simple-line-icons/scss/simple-line-icons.scss +979 -0
- data/docs/vendor/tether/tether.js +1811 -0
- data/docs/vendor/tether/tether.min.js +1 -0
- data/exe/codepipeline +14 -0
- data/exe/pipe +14 -0
- data/lib/codepipe.rb +23 -0
- data/lib/codepipe/autoloader.rb +21 -0
- data/lib/codepipe/aws_services.rb +20 -0
- data/lib/codepipe/aws_services/helpers.rb +71 -0
- data/lib/codepipe/build.rb +13 -0
- data/lib/codepipe/cli.rb +60 -0
- data/lib/codepipe/command.rb +82 -0
- data/lib/codepipe/completer.rb +159 -0
- data/lib/codepipe/completer/script.rb +6 -0
- data/lib/codepipe/completer/script.sh +10 -0
- data/lib/codepipe/core.rb +63 -0
- data/lib/codepipe/create.rb +12 -0
- data/lib/codepipe/delete.rb +27 -0
- data/lib/codepipe/deploy.rb +40 -0
- data/lib/codepipe/dsl/pipeline.rb +37 -0
- data/lib/codepipe/dsl/pipeline/approve.rb +34 -0
- data/lib/codepipe/dsl/pipeline/codebuild.rb +57 -0
- data/lib/codepipe/dsl/pipeline/github.rb +36 -0
- data/lib/codepipe/dsl/role.rb +50 -0
- data/lib/codepipe/dsl/schedule.rb +30 -0
- data/lib/codepipe/dsl/sns.rb +15 -0
- data/lib/codepipe/dsl/ssm.rb +22 -0
- data/lib/codepipe/dsl/webhook.rb +27 -0
- data/lib/codepipe/evaluate.rb +47 -0
- data/lib/codepipe/help.rb +9 -0
- data/lib/codepipe/help/completion.md +22 -0
- data/lib/codepipe/help/completion_script.md +3 -0
- data/lib/codepipe/help/hello.md +5 -0
- data/lib/codepipe/init.rb +57 -0
- data/lib/codepipe/pipeline.rb +61 -0
- data/lib/codepipe/pipeline/s3_bucket.rb +88 -0
- data/lib/codepipe/role.rb +181 -0
- data/lib/codepipe/schedule.rb +99 -0
- data/lib/codepipe/sequence.rb +66 -0
- data/lib/codepipe/setting.rb +79 -0
- data/lib/codepipe/sns.rb +43 -0
- data/lib/codepipe/stack.rb +95 -0
- data/lib/codepipe/start.rb +83 -0
- data/lib/codepipe/update.rb +12 -0
- data/lib/codepipe/version.rb +3 -0
- data/lib/codepipe/webhook.rb +60 -0
- data/lib/codepipeline.rb +1 -6
- data/lib/template/.codepipeline/pipeline.rb.tt +33 -0
- data/lib/template/.codepipeline/schedule.rb +3 -0
- data/lib/template/.codepipeline/settings.yml +9 -0
- data/lib/template/.codepipeline/sns.rb +14 -0
- data/spec/fixtures/app/.codepipeline/pipeline.rb +12 -0
- data/spec/fixtures/app/.codepipeline/schedule.rb +1 -0
- data/spec/fixtures/app/.codepipeline/webhook.rb +1 -0
- data/spec/fixtures/pipelines/approve.rb +22 -0
- data/spec/fixtures/pipelines/approve_existing_sns.rb +24 -0
- data/spec/lib/cli_spec.rb +18 -0
- data/spec/lib/pipeline/approve_spec.rb +32 -0
- data/spec/lib/pipeline_spec.rb +12 -0
- data/spec/lib/role_spec.rb +12 -0
- data/spec/lib/schedule_spec.rb +12 -0
- data/spec/lib/webhook_spec.rb +12 -0
- data/spec/spec_helper.rb +35 -0
- metadata +419 -22
- data/.travis.yml +0 -7
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/codepipeline.gemspec +0 -27
- 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
|
data/lib/codepipe/sns.rb
ADDED
@@ -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
|