moonshot 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/moonshot/artifact_repository/s3_bucket.rb +60 -0
- data/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb +89 -0
- data/lib/moonshot/build_mechanism/github_release.rb +148 -0
- data/lib/moonshot/build_mechanism/script.rb +84 -0
- data/lib/moonshot/build_mechanism/travis_deploy.rb +70 -0
- data/lib/moonshot/build_mechanism/version_proxy.rb +55 -0
- data/lib/moonshot/cli.rb +146 -0
- data/lib/moonshot/controller.rb +151 -0
- data/lib/moonshot/controller_config.rb +25 -0
- data/lib/moonshot/creds_helper.rb +28 -0
- data/lib/moonshot/deployment_mechanism/code_deploy.rb +303 -0
- data/lib/moonshot/doctor_helper.rb +57 -0
- data/lib/moonshot/environment_parser.rb +32 -0
- data/lib/moonshot/interactive_logger_proxy.rb +49 -0
- data/lib/moonshot/resources.rb +13 -0
- data/lib/moonshot/resources_helper.rb +24 -0
- data/lib/moonshot/shell.rb +52 -0
- data/lib/moonshot/stack.rb +345 -0
- data/lib/moonshot/stack_asg_printer.rb +151 -0
- data/lib/moonshot/stack_config.rb +12 -0
- data/lib/moonshot/stack_events_poller.rb +56 -0
- data/lib/moonshot/stack_lister.rb +20 -0
- data/lib/moonshot/stack_output_printer.rb +16 -0
- data/lib/moonshot/stack_parameter_printer.rb +73 -0
- data/lib/moonshot/stack_template.rb +35 -0
- data/lib/moonshot/unicode_table.rb +63 -0
- data/lib/moonshot.rb +41 -0
- metadata +239 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cdc444c7d1abfc032353018913e6a8eccb90b035
|
4
|
+
data.tar.gz: 82a6a2f524bc812d9c0c7155b1437a10c378594d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 870266482ea59c020549ec66f4cd114b4cdb2dff89957a18387ff3eb6409b5f7f1b831c06c0503397ed79cc597bfdc659b8d9100bfeb9d2df3f177aad5953538
|
7
|
+
data.tar.gz: efbef2587e5b0305c608bcdd21695388145a98f981097eb5be875dcb27faa1cbeac1e354631891b3fd8ced6b3d898d7c3d59050199ea21c006decb65e8072c84
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# The S3Bucket stores builds in an S3 Bucket.
|
2
|
+
#
|
3
|
+
# For example:
|
4
|
+
#
|
5
|
+
# def MyApplication < Moonshot::CLI
|
6
|
+
# self.artifact_repository = S3Bucket.new('my-application-builds')
|
7
|
+
# end
|
8
|
+
class Moonshot::ArtifactRepository::S3Bucket
|
9
|
+
include Moonshot::ResourcesHelper
|
10
|
+
include Moonshot::CredsHelper
|
11
|
+
include Moonshot::DoctorHelper
|
12
|
+
|
13
|
+
attr_reader :bucket_name
|
14
|
+
|
15
|
+
def initialize(bucket_name)
|
16
|
+
@bucket_name = bucket_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def store_hook(build_mechanism, version_name)
|
20
|
+
unless build_mechanism.respond_to?(:output_file)
|
21
|
+
raise "S3Bucket does not know how to store artifacts from #{build_mechanism.class}, no method '#output_file'." # rubocop:disable LineLength
|
22
|
+
end
|
23
|
+
|
24
|
+
file = build_mechanism.output_file
|
25
|
+
bucket_name = @bucket_name
|
26
|
+
key = filename_for_version(version_name)
|
27
|
+
|
28
|
+
ilog.start_threaded "Uploading #{file} to s3://#{bucket_name}/#{key}" do |s|
|
29
|
+
s3_client.put_object(key: key, body: File.open(file), bucket: bucket_name)
|
30
|
+
s.success "Uploaded s3://#{bucket_name}/#{key} successfully."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def filename_for_version(version_name)
|
35
|
+
"#{version_name}.tar.gz"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def doctor_check_bucket_exists
|
41
|
+
s3_client.get_bucket_location(bucket: @bucket_name)
|
42
|
+
success "Bucket '#{@bucket_name}' exists."
|
43
|
+
rescue => e
|
44
|
+
# This is warning because the role you use for deployment may not actually
|
45
|
+
# be able to read builds, however the instance role assigned to the nodes
|
46
|
+
# might.
|
47
|
+
str = "Could not get information about bucket '#{@bucket_name}'."
|
48
|
+
warning(str, e.message)
|
49
|
+
end
|
50
|
+
|
51
|
+
def doctor_check_bucket_writable
|
52
|
+
s3_client.put_object(key: 'test-object', body: '', bucket: @bucket_name)
|
53
|
+
s3_client.delete_object(key: 'test-object', bucket: @bucket_name)
|
54
|
+
success 'Bucket is writable, new builds can be uploaded.'
|
55
|
+
rescue => e
|
56
|
+
# This is a warning because you may deploy to an environment where you have
|
57
|
+
# read access to builds, but could not publish a new build.
|
58
|
+
warning('Could not write to bucket, you may still be able to deploy existing builds.', e.message) # rubocop:disable LineLength
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'moonshot/artifact_repository/s3_bucket'
|
2
|
+
require 'moonshot/shell'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'semantic'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
module Moonshot::ArtifactRepository
|
8
|
+
# S3 Bucket repository backed by GitHub releases.
|
9
|
+
# If a SemVer package isn't found in S3, it is copied from GitHub releases.
|
10
|
+
class S3BucketViaGithubReleases < S3Bucket
|
11
|
+
include Moonshot::BuildMechanism
|
12
|
+
include Moonshot::Shell
|
13
|
+
|
14
|
+
# @override
|
15
|
+
# If release version, transfer from GitHub to S3.
|
16
|
+
def store_hook(build_mechanism, version)
|
17
|
+
if release?(version)
|
18
|
+
if (@output_file = build_mechanism.output_file)
|
19
|
+
attach_release_asset(version, @output_file)
|
20
|
+
# Upload to s3.
|
21
|
+
super
|
22
|
+
else
|
23
|
+
# If there is no output file, assume it's on GitHub already.
|
24
|
+
transfer_release_asset_to_s3(version)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @override
|
32
|
+
# If release version, transfer from GitHub to S3.
|
33
|
+
# @todo This is a super hacky place to handle the transfer, give
|
34
|
+
# artifact repositories a hook before deploy.
|
35
|
+
def filename_for_version(version)
|
36
|
+
s3_name = super
|
37
|
+
if !@output_file && release?(version) && !in_s3?(s3_name)
|
38
|
+
github_to_s3(version, s3_name)
|
39
|
+
end
|
40
|
+
s3_name
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def release?(version)
|
46
|
+
::Semantic::Version.new(version)
|
47
|
+
rescue ArgumentError
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def in_s3?(key)
|
52
|
+
s3_client.head_object(key: key, bucket: bucket_name)
|
53
|
+
rescue ::Aws::S3::Errors::NotFound
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
def attach_release_asset(version, file)
|
58
|
+
# -m '' leaves message unchanged.
|
59
|
+
cmd = "hub release edit #{version} -m '' --attach=#{file}"
|
60
|
+
sh_step(cmd)
|
61
|
+
end
|
62
|
+
|
63
|
+
def transfer_release_asset_to_s3(version)
|
64
|
+
ilog.start_threaded "Transferring #{version} to S3" do |s|
|
65
|
+
key = filename_for_version(version)
|
66
|
+
s.success "Uploaded s3://#{bucket_name}/#{key} successfully."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def github_to_s3(version, s3_name)
|
71
|
+
Dir.mktmpdir('github_to_s3', Dir.getwd) do |tmpdir|
|
72
|
+
Dir.chdir(tmpdir) do
|
73
|
+
sh_out("hub release download #{version}")
|
74
|
+
file = File.open(Dir.glob("*#{version}*.tar.gz").fetch(0))
|
75
|
+
s3_client.put_object(key: s3_name, body: file, bucket: bucket_name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def doctor_check_hub_release_download
|
81
|
+
sh_out('hub release download --help')
|
82
|
+
rescue
|
83
|
+
critical '`hub release download` command missing, upgrade hub.' \
|
84
|
+
' See https://github.com/github/hub/pull/1103'
|
85
|
+
else
|
86
|
+
success '`hub release download` command available.'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'moonshot/shell'
|
3
|
+
require 'open3'
|
4
|
+
require 'semantic'
|
5
|
+
require 'shellwords'
|
6
|
+
require 'tempfile'
|
7
|
+
require 'vandamme'
|
8
|
+
|
9
|
+
module Moonshot::BuildMechanism
|
10
|
+
# A build mechanism that creates a tag and GitHub release.
|
11
|
+
class GithubRelease # rubocop:disable Metrics/ClassLength
|
12
|
+
extend Forwardable
|
13
|
+
include Moonshot::ResourcesHelper
|
14
|
+
include Moonshot::DoctorHelper
|
15
|
+
include Moonshot::Shell
|
16
|
+
|
17
|
+
def_delegator :@build_mechanism, :output_file
|
18
|
+
|
19
|
+
# @param build_mechanism Delegates building after GitHub release is created.
|
20
|
+
def initialize(build_mechanism)
|
21
|
+
@build_mechanism = build_mechanism
|
22
|
+
end
|
23
|
+
|
24
|
+
def doctor_hook
|
25
|
+
super
|
26
|
+
@build_mechanism.doctor_hook
|
27
|
+
end
|
28
|
+
|
29
|
+
def resources=(r)
|
30
|
+
super
|
31
|
+
@build_mechanism.resources = r
|
32
|
+
end
|
33
|
+
|
34
|
+
def pre_build_hook(version)
|
35
|
+
@semver = ::Semantic::Version.new(version)
|
36
|
+
@target_version = [@semver.major, @semver.minor, @semver.patch].join('.')
|
37
|
+
sh_step('git fetch --tags upstream')
|
38
|
+
@sha = `git rev-parse HEAD`.chomp
|
39
|
+
validate_commit
|
40
|
+
@changes = validate_changelog(@target_version)
|
41
|
+
confirm_or_fail(@semver)
|
42
|
+
@build_mechanism.pre_build_hook(version)
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_hook(version)
|
46
|
+
assert_state(version)
|
47
|
+
git_tag(version, @sha, @changes)
|
48
|
+
git_push_tag('upstream', version)
|
49
|
+
hub_create_release(@semver, @sha, @changes)
|
50
|
+
ilog.msg("#{releases_url}/tag/#{version}")
|
51
|
+
@build_mechanism.build_hook(version)
|
52
|
+
end
|
53
|
+
|
54
|
+
def post_build_hook(version)
|
55
|
+
assert_state(version)
|
56
|
+
@build_mechanism.post_build_hook(version)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# We carry state between hooks, make sure that's still valid.
|
62
|
+
def assert_state(version)
|
63
|
+
raise "#{version} != #{@semver}" unless version == @semver.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
def confirm_or_fail(version)
|
67
|
+
say("\nCommit Summary", :yellow)
|
68
|
+
say("#{@commit_detail}\n")
|
69
|
+
say('Commit CI Status', :yellow)
|
70
|
+
say("#{@ci_statuses}\n")
|
71
|
+
say("Changelog for #{version}", :yellow)
|
72
|
+
say("#{@changes}\n\n")
|
73
|
+
|
74
|
+
q = "Do you wan't to tag and release this commit as #{version}? [y/n]"
|
75
|
+
raise Thor::Error, 'Release declined.' unless yes?(q)
|
76
|
+
end
|
77
|
+
|
78
|
+
def git_tag(tag, sha, annotation)
|
79
|
+
cmd = "git tag -a #{tag} #{sha} --file=-"
|
80
|
+
sh_step(cmd, stdin: annotation)
|
81
|
+
end
|
82
|
+
|
83
|
+
def git_push_tag(remote, tag)
|
84
|
+
cmd = "git push #{remote} refs/tags/#{tag}:refs/tags/#{tag}"
|
85
|
+
sh_step(cmd) do
|
86
|
+
sleep 2 # GitHub needs a moment to register the tag.
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def hub_create_release(semver, commitish, changelog_entry)
|
91
|
+
message = "#{semver}\n\n#{changelog_entry}"
|
92
|
+
cmd = "hub release create #{semver} --commitish=#{commitish}"
|
93
|
+
cmd << ' --prerelease' if semver.pre || semver.build
|
94
|
+
cmd << " --message=#{Shellwords.escape(message)}"
|
95
|
+
sh_step(cmd)
|
96
|
+
end
|
97
|
+
|
98
|
+
def validate_commit
|
99
|
+
cmd = "git show --stat #{@sha}"
|
100
|
+
sh_step(cmd, msg: "Validate commit #{@sha}.") do |_, out|
|
101
|
+
@commit_detail = out
|
102
|
+
end
|
103
|
+
cmd = "hub ci-status --verbose #{@sha}"
|
104
|
+
sh_step(cmd, msg: "Check CI status for #{@sha}.") do |_, out|
|
105
|
+
@ci_statuses = out
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_changelog(version)
|
110
|
+
changes = nil
|
111
|
+
ilog.start_threaded('Validate `CHANGELOG.md`.') do |step|
|
112
|
+
changes = fetch_changes(version)
|
113
|
+
step.success
|
114
|
+
end
|
115
|
+
changes
|
116
|
+
end
|
117
|
+
|
118
|
+
def fetch_changes(version)
|
119
|
+
parser = Vandamme::Parser.new(
|
120
|
+
changelog: File.read('CHANGELOG.md'),
|
121
|
+
format: 'markdown'
|
122
|
+
)
|
123
|
+
parser.parse.fetch(version) do
|
124
|
+
raise "#{version} not found in CHANGELOG.md"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def releases_url
|
129
|
+
`hub browse -u -- releases`.chomp
|
130
|
+
end
|
131
|
+
|
132
|
+
def doctor_check_upstream
|
133
|
+
sh_out('git remote | grep ^upstream$')
|
134
|
+
rescue => e
|
135
|
+
critical "git remote `upstream` not found.\n#{e.message}"
|
136
|
+
else
|
137
|
+
success 'git remote `upstream` exists.'
|
138
|
+
end
|
139
|
+
|
140
|
+
def doctor_check_hub_auth
|
141
|
+
sh_out('hub ci-status 0.0.0')
|
142
|
+
rescue => e
|
143
|
+
critical "`hub` failed, install hub and authorize it.\n#{e.message}"
|
144
|
+
else
|
145
|
+
success '`hub` installed and authorized.'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'open3'
|
2
|
+
include Open3
|
3
|
+
|
4
|
+
# Compile a release artifact using a shell script.
|
5
|
+
#
|
6
|
+
# The output file will be deleted before the script is run, and is expected to
|
7
|
+
# exist after the script exits. Any non-zero exit status will be consider a
|
8
|
+
# build failure, and any output will be displayed to the user.
|
9
|
+
#
|
10
|
+
# Creating a new Script BuildMechanism looks like this:
|
11
|
+
#
|
12
|
+
# class MyReleaseTool < Moonshot::CLI
|
13
|
+
# include Moonshot::BuildMechanism
|
14
|
+
# self.build_mechanism = Script.new('script/build.sh')
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
class Moonshot::BuildMechanism::Script
|
18
|
+
include Moonshot::ResourcesHelper
|
19
|
+
include Moonshot::DoctorHelper
|
20
|
+
|
21
|
+
attr_reader :output_file
|
22
|
+
|
23
|
+
def initialize(script, output_file: 'output.tar.gz')
|
24
|
+
@script = script
|
25
|
+
@output_file = output_file
|
26
|
+
end
|
27
|
+
|
28
|
+
def pre_build_hook(_version)
|
29
|
+
File.delete(@output_file) if File.exist?(@output_file)
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_hook(version)
|
33
|
+
env = {
|
34
|
+
'VERSION' => version,
|
35
|
+
'OUTPUT_FILE' => @output_file
|
36
|
+
}
|
37
|
+
ilog.start_threaded "Running Script: #{@script}" do |s|
|
38
|
+
run_script(s, env: env)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def post_build_hook(_version)
|
43
|
+
unless File.exist?(@output_file) # rubocop:disable GuardClause
|
44
|
+
raise Thor::Error, 'Build command did not produce output file!'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def run_script(step, env: {}) # rubocop:disable AbcSize
|
51
|
+
popen2e(env, @script) do |_, out, wait|
|
52
|
+
output = []
|
53
|
+
|
54
|
+
loop do
|
55
|
+
str = out.gets
|
56
|
+
unless str.nil?
|
57
|
+
output << str.chomp
|
58
|
+
ilog.debug(str.chomp)
|
59
|
+
end
|
60
|
+
break if out.eof?
|
61
|
+
end
|
62
|
+
|
63
|
+
result = wait.value
|
64
|
+
if result.exitstatus == 0
|
65
|
+
step.success "Build script #{@script} exited successfully!"
|
66
|
+
end
|
67
|
+
unless result.exitstatus == 0
|
68
|
+
ilog.error "Build script failed with exit status #{result.exitstatus}!"
|
69
|
+
ilog.error 'Last 10 lines of output follows:'
|
70
|
+
output.pop(10).each { |l| ilog.error l }
|
71
|
+
|
72
|
+
step.failure "Build script #{@script} failed with exit status #{result.exitstatus}!"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def doctor_check_script_exists
|
78
|
+
if File.exist?(@script)
|
79
|
+
success "Script '#{@script}' exists."
|
80
|
+
else
|
81
|
+
critical "Could not find build script '#{@script}'!"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'moonshot/shell'
|
2
|
+
|
3
|
+
module Moonshot::BuildMechanism
|
4
|
+
# This simply waits for Travis-CI to finish building a job matching the
|
5
|
+
# version and 'BUILD=1'.
|
6
|
+
class TravisDeploy
|
7
|
+
include Moonshot::ResourcesHelper
|
8
|
+
include Moonshot::DoctorHelper
|
9
|
+
include Moonshot::Shell
|
10
|
+
|
11
|
+
attr_reader :output_file
|
12
|
+
|
13
|
+
def initialize(slug, pro: false)
|
14
|
+
@slug = slug
|
15
|
+
@endpoint = pro ? '--pro' : '--org'
|
16
|
+
@cli_args = "-r #{@slug} #{@endpoint}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def pre_build_hook(_)
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_hook(version)
|
23
|
+
job_number = find_build_and_job(version)
|
24
|
+
wait_for_job(job_number)
|
25
|
+
check_build(version)
|
26
|
+
end
|
27
|
+
|
28
|
+
def post_build_hook(_)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def find_build_and_job(version)
|
34
|
+
job_number = nil
|
35
|
+
ilog.start_threaded('Find Travis CI build') do |step|
|
36
|
+
sleep 2
|
37
|
+
build_out = sh_out("bundle exec travis show #{@cli_args} #{version}")
|
38
|
+
unless (job_number = build_out.match(/^#(\d+\.\d+) .+BUILD=1.+/)[1])
|
39
|
+
raise "Build for #{version} not found.\n#{build_out}"
|
40
|
+
end
|
41
|
+
step.success("Travis CI ##{job_number.gsub(/\..*/, '')} running.")
|
42
|
+
end
|
43
|
+
job_number
|
44
|
+
end
|
45
|
+
|
46
|
+
def wait_for_job(job_number)
|
47
|
+
cmd = "bundle exec travis logs #{@cli_args} #{job_number}"
|
48
|
+
# This log tailing fails at the end of the file. travis bug.
|
49
|
+
sh_step(cmd, fail: false)
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_build(version)
|
53
|
+
cmd = "bundle exec travis show #{@cli_args} #{version}"
|
54
|
+
sh_step(cmd) do |step, out|
|
55
|
+
raise "Build didn't pass.\n#{build_out}" \
|
56
|
+
if out =~ /^#(\d+\.\d+) (?!passed).+BUILD=1.+/
|
57
|
+
|
58
|
+
step.success("Travis CI build for #{version} passed.")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def doctor_check_travis_auth
|
63
|
+
sh_out("bundle exec travis raw #{@endpoint} repos/#{@slug}")
|
64
|
+
rescue => e
|
65
|
+
critical "`travis` not available or not authorized.\n#{e.message}"
|
66
|
+
else
|
67
|
+
success '`travis` installed and authorized.'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'semantic'
|
3
|
+
|
4
|
+
# This proxies build request do different mechanisms. One for semver compliant
|
5
|
+
# releases and another for everything else.
|
6
|
+
class Moonshot::BuildMechanism::VersionProxy
|
7
|
+
extend Forwardable
|
8
|
+
include Moonshot::ResourcesHelper
|
9
|
+
|
10
|
+
def_delegator :@active, :output_file
|
11
|
+
|
12
|
+
def initialize(release:, dev:)
|
13
|
+
@release = release
|
14
|
+
@dev = dev
|
15
|
+
end
|
16
|
+
|
17
|
+
def doctor_hook
|
18
|
+
@release.doctor_hook
|
19
|
+
@dev.doctor_hook
|
20
|
+
end
|
21
|
+
|
22
|
+
def resources=(r)
|
23
|
+
super
|
24
|
+
@release.resources = r
|
25
|
+
@dev.resources = r
|
26
|
+
end
|
27
|
+
|
28
|
+
def pre_build_hook(version)
|
29
|
+
active(version).pre_build_hook(version)
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_hook(version)
|
33
|
+
active(version).build_hook(version)
|
34
|
+
end
|
35
|
+
|
36
|
+
def post_build_hook(version)
|
37
|
+
active(version).post_build_hook(version)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def active(version)
|
43
|
+
@active = if release?(version)
|
44
|
+
@release
|
45
|
+
else
|
46
|
+
@dev
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def release?(version)
|
51
|
+
::Semantic::Version.new(version)
|
52
|
+
rescue ArgumentError
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
data/lib/moonshot/cli.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'interactive-logger'
|
2
|
+
|
3
|
+
# Base class for Moonshot-powered project tooling.
|
4
|
+
module Moonshot
|
5
|
+
# The main entry point for Moonshot, this class should be extended by
|
6
|
+
# project tooling.
|
7
|
+
class CLI < Thor # rubocop:disable ClassLength
|
8
|
+
class_option(:name, aliases: 'n', default: nil, type: :string)
|
9
|
+
class_option(:interactive_logger, type: :boolean, default: true)
|
10
|
+
class_option(:verbose, aliases: 'v', type: :boolean)
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :application_name
|
14
|
+
attr_accessor :artifact_repository
|
15
|
+
attr_accessor :auto_prefix_stack
|
16
|
+
attr_accessor :build_mechanism
|
17
|
+
attr_accessor :deployment_mechanism
|
18
|
+
attr_accessor :default_parent_stack
|
19
|
+
attr_reader :plugins
|
20
|
+
|
21
|
+
def plugin(plugin)
|
22
|
+
@plugins ||= []
|
23
|
+
@plugins << plugin
|
24
|
+
end
|
25
|
+
|
26
|
+
def parent(value)
|
27
|
+
@default_parent_stack = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_class_configuration
|
31
|
+
raise Thor::Error, 'No application_name is set!' unless application_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def exit_on_failure?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def inherited(base)
|
39
|
+
base.include(Moonshot::ArtifactRepository)
|
40
|
+
base.include(Moonshot::BuildMechanism)
|
41
|
+
base.include(Moonshot::DeploymentMechanism)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(*args)
|
46
|
+
super
|
47
|
+
@log = Logger.new(STDOUT)
|
48
|
+
@log.formatter = proc do |s, d, _, msg|
|
49
|
+
"[#{self.class.name} #{s} #{d.strftime('%T')}] #{msg}\n"
|
50
|
+
end
|
51
|
+
@log.level = options[:verbose] ? Logger::DEBUG : Logger::INFO
|
52
|
+
|
53
|
+
EnvironmentParser.parse(@log)
|
54
|
+
self.class.check_class_configuration
|
55
|
+
end
|
56
|
+
|
57
|
+
no_tasks do
|
58
|
+
# Build a Moonshot::Controller from the CLI options.
|
59
|
+
def controller # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity
|
60
|
+
Moonshot::Controller.new do |config|
|
61
|
+
config.app_name = self.class.application_name
|
62
|
+
config.artifact_repository = self.class.artifact_repository
|
63
|
+
config.auto_prefix_stack = self.class.auto_prefix_stack
|
64
|
+
config.build_mechanism = self.class.build_mechanism
|
65
|
+
config.deployment_mechanism = self.class.deployment_mechanism
|
66
|
+
config.environment_name = options[:name]
|
67
|
+
config.logger = @log
|
68
|
+
|
69
|
+
# Degrade to a more compatible logger if the terminal seems outdated,
|
70
|
+
# or at the users request.
|
71
|
+
if !$stdout.isatty || !options[:interactive_logger]
|
72
|
+
config.interactive_logger = InteractiveLoggerProxy.new(@log)
|
73
|
+
end
|
74
|
+
|
75
|
+
config.show_all_stack_events = true if options[:show_all_events]
|
76
|
+
config.plugins = self.class.plugins if self.class.plugins
|
77
|
+
|
78
|
+
if options[:parent]
|
79
|
+
config.parent_stacks << options[:parent]
|
80
|
+
elsif self.class.default_parent_stack
|
81
|
+
config.parent_stacks << self.class.default_parent_stack
|
82
|
+
end
|
83
|
+
end
|
84
|
+
rescue => e
|
85
|
+
raise Thor::Error, e.message
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
desc :list, 'List stacks for this application.'
|
90
|
+
def list
|
91
|
+
controller.list
|
92
|
+
end
|
93
|
+
|
94
|
+
desc :create, 'Create a new environment.'
|
95
|
+
option(
|
96
|
+
:parent,
|
97
|
+
type: :string,
|
98
|
+
aliases: '-p',
|
99
|
+
desc: "Parent stack to import parameters from. (Default: #{default_parent_stack || 'None'})")
|
100
|
+
option :deploy, default: true, type: :boolean, aliases: '-d',
|
101
|
+
desc: 'Choose if code should be deployed after stack is created'
|
102
|
+
option :show_all_events, desc: 'Show all stack events during update. (Default: errors only)'
|
103
|
+
def create
|
104
|
+
controller.create
|
105
|
+
controller.deploy_code if options[:deploy]
|
106
|
+
end
|
107
|
+
|
108
|
+
desc :update, 'Update the CloudFormation stack within an environment.'
|
109
|
+
option :show_all_events, desc: 'Show all stack events during update. (Default: errors only)'
|
110
|
+
def update
|
111
|
+
controller.update
|
112
|
+
end
|
113
|
+
|
114
|
+
desc :status, 'Get the status of an existing environment.'
|
115
|
+
def status
|
116
|
+
controller.status
|
117
|
+
end
|
118
|
+
|
119
|
+
desc 'deploy-code', 'Create a build from the working directory, and deploy it.' # rubocop:disable LineLength
|
120
|
+
def deploy_code
|
121
|
+
controller.deploy_code
|
122
|
+
end
|
123
|
+
|
124
|
+
desc 'build-version VERSION', 'Build a tarball of the software, ready for deployment.' # rubocop:disable LineLength
|
125
|
+
def build_version(version_name)
|
126
|
+
controller.build_version(version_name)
|
127
|
+
end
|
128
|
+
|
129
|
+
desc 'deploy-version VERSION_NAME', 'Deploy a versioned release to both EB environments in an environment.' # rubocop:disable LineLength
|
130
|
+
def deploy_version(version_name)
|
131
|
+
controller.deploy_version(version_name)
|
132
|
+
end
|
133
|
+
|
134
|
+
desc :delete, 'Delete an existing environment.'
|
135
|
+
option :show_all_events, desc: 'Show all stack events during update. (Default: errors only)'
|
136
|
+
def delete
|
137
|
+
controller.delete
|
138
|
+
end
|
139
|
+
|
140
|
+
desc :doctor, 'Run configuration checks against current environment.'
|
141
|
+
def doctor
|
142
|
+
success = controller.doctor
|
143
|
+
raise Thor::Error, 'One or more checks failed.' unless success
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|