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.
- 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
|