moonshot 0.7.7 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/moonshot +11 -0
- data/lib/default/Moonfile.rb +0 -0
- data/lib/moonshot.rb +33 -6
- data/lib/moonshot/always_use_default_source.rb +17 -0
- data/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb +140 -3
- data/lib/moonshot/ask_user_source.rb +38 -0
- data/lib/moonshot/build_mechanism/github_release.rb +1 -1
- data/lib/moonshot/build_mechanism/script.rb +1 -1
- data/lib/moonshot/command.rb +64 -0
- data/lib/moonshot/command_line.rb +150 -0
- data/lib/moonshot/commands/build.rb +12 -0
- data/lib/moonshot/commands/console.rb +19 -0
- data/lib/moonshot/commands/create.rb +37 -0
- data/lib/moonshot/commands/delete.rb +12 -0
- data/lib/moonshot/commands/deploy.rb +12 -0
- data/lib/moonshot/commands/doctor.rb +12 -0
- data/lib/moonshot/commands/list.rb +16 -0
- data/lib/moonshot/commands/new.rb +99 -0
- data/lib/moonshot/commands/parameter_arguments.rb +27 -0
- data/lib/moonshot/commands/push.rb +12 -0
- data/lib/moonshot/commands/ssh.rb +12 -0
- data/lib/moonshot/commands/status.rb +12 -0
- data/lib/moonshot/commands/update.rb +29 -0
- data/lib/moonshot/commands/version.rb +12 -0
- data/lib/moonshot/config.rb +0 -0
- data/lib/moonshot/controller.rb +106 -42
- data/lib/moonshot/controller_config.rb +31 -13
- data/lib/moonshot/deployment_mechanism/code_deploy.rb +17 -7
- data/lib/moonshot/json_stack_template.rb +17 -0
- data/lib/moonshot/parameter_collection.rb +50 -0
- data/lib/moonshot/parent_stack_parameter_loader.rb +51 -0
- data/lib/moonshot/resources.rb +3 -3
- data/lib/moonshot/resources_helper.rb +2 -2
- data/lib/moonshot/ssh_command.rb +31 -0
- data/lib/moonshot/ssh_config.rb +1 -1
- data/lib/moonshot/stack.rb +66 -77
- data/lib/moonshot/stack_list_printer.rb +21 -0
- data/lib/moonshot/stack_lister.rb +16 -6
- data/lib/moonshot/stack_parameter.rb +64 -0
- data/lib/moonshot/stack_parameter_printer.rb +3 -49
- data/lib/moonshot/stack_template.rb +13 -25
- data/lib/moonshot/task.rb +10 -0
- data/lib/moonshot/tools/asg_rollout.rb +1 -1
- data/lib/moonshot/tools/asg_rollout/instance_health.rb +1 -1
- data/lib/moonshot/tools/asg_rollout_config.rb +1 -1
- data/lib/moonshot/yaml_stack_template.rb +17 -0
- metadata +51 -9
- data/lib/moonshot/cli.rb +0 -220
- data/lib/moonshot/environment_parser.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19e43423a8173b2f19405834ed09736d99a39b51
|
4
|
+
data.tar.gz: 4b344bc4416972f25320e35510e381aae44bfe7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9807b9a1a23c3f395077ef83b5c950b43b0194fa1e981a1300381ef43a638f3950756ada942fb43b85105fc643d0232f939f2414ded42ee54dea30f5a5be6a83
|
7
|
+
data.tar.gz: 26bd43f893b325342bd7b6825b3045f0cbb88593efc1bc3916adb5751940ab70b116af6a5ef77a6a144869492048f2e5c4713d8694ab58d70bd8329a1b2f78d2
|
data/bin/moonshot
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'moonshot'
|
3
|
+
|
4
|
+
# This is the main entry point for the `moonshot` command-line tool.
|
5
|
+
begin
|
6
|
+
Moonshot::CommandLine.new.run!
|
7
|
+
rescue => e
|
8
|
+
warn "#{e} (at #{e.backtrace.first})"
|
9
|
+
raise e if ENV['MOONSHOT_BACKTRACE']
|
10
|
+
exit 1
|
11
|
+
end
|
File without changes
|
data/lib/moonshot.rb
CHANGED
@@ -2,15 +2,25 @@ require 'English'
|
|
2
2
|
require 'aws-sdk'
|
3
3
|
require 'logger'
|
4
4
|
require 'thor'
|
5
|
+
require 'interactive-logger'
|
5
6
|
|
6
7
|
module Moonshot
|
7
|
-
|
8
|
+
class << self
|
9
|
+
attr_writer :config
|
8
10
|
end
|
9
|
-
|
11
|
+
|
12
|
+
def self.config
|
13
|
+
@config ||= Moonshot::ControllerConfig.new
|
14
|
+
block_given? ? yield(@config) : @config
|
15
|
+
end
|
16
|
+
|
17
|
+
module ArtifactRepository
|
18
|
+
end
|
19
|
+
module BuildMechanism
|
10
20
|
end
|
11
|
-
module DeploymentMechanism
|
21
|
+
module DeploymentMechanism
|
12
22
|
end
|
13
|
-
module Plugins
|
23
|
+
module Plugins
|
14
24
|
end
|
15
25
|
end
|
16
26
|
|
@@ -20,17 +30,34 @@ end
|
|
20
30
|
'doctor_helper',
|
21
31
|
'resources',
|
22
32
|
'resources_helper',
|
23
|
-
'environment_parser',
|
24
33
|
|
25
34
|
# Core
|
26
35
|
'interactive_logger_proxy',
|
36
|
+
'command_line',
|
37
|
+
'command',
|
38
|
+
'ssh_command',
|
39
|
+
'commands/build',
|
40
|
+
'commands/console',
|
41
|
+
'commands/create',
|
42
|
+
'commands/delete',
|
43
|
+
'commands/deploy',
|
44
|
+
'commands/doctor',
|
45
|
+
'commands/list',
|
46
|
+
'commands/push',
|
47
|
+
'commands/ssh',
|
48
|
+
'commands/status',
|
49
|
+
'commands/update',
|
50
|
+
'commands/version',
|
27
51
|
'controller',
|
28
52
|
'controller_config',
|
29
|
-
'cli',
|
30
53
|
'stack',
|
31
54
|
'stack_config',
|
32
55
|
'stack_lister',
|
33
56
|
'stack_events_poller',
|
57
|
+
'merge_strategy',
|
58
|
+
'default_strategy',
|
59
|
+
'ask_user_source',
|
60
|
+
'always_use_default_source',
|
34
61
|
|
35
62
|
# Built-in mechanisms
|
36
63
|
'artifact_repository/s3_bucket',
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Moonshot
|
2
|
+
# The AlwaysUseDefaultSource will always use the previous value in
|
3
|
+
# the stack, or use the default value during stack creation. This is
|
4
|
+
# useful if plugins provide the value for a parameter, and we don't
|
5
|
+
# want to prompt the user for an override. Of course, overrides from
|
6
|
+
# answer files or command-line arguments will always apply.
|
7
|
+
class AlwaysUseDefaultSource
|
8
|
+
def get(sp)
|
9
|
+
unless sp.default?
|
10
|
+
raise "Parameter #{sp.name} does not have a default, cannot use AlwaysUseDefaultSource!"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Don't do anything, the default will apply on create, and the
|
14
|
+
# previous value will be used on update.
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'moonshot/artifact_repository/s3_bucket'
|
2
2
|
require 'moonshot/shell'
|
3
|
+
require 'digest'
|
3
4
|
require 'securerandom'
|
4
5
|
require 'semantic'
|
5
6
|
require 'tmpdir'
|
@@ -7,7 +8,7 @@ require 'tmpdir'
|
|
7
8
|
module Moonshot::ArtifactRepository
|
8
9
|
# S3 Bucket repository backed by GitHub releases.
|
9
10
|
# If a SemVer package isn't found in S3, it is copied from GitHub releases.
|
10
|
-
class S3BucketViaGithubReleases < S3Bucket
|
11
|
+
class S3BucketViaGithubReleases < S3Bucket # rubocop:disable ClassLength
|
11
12
|
include Moonshot::BuildMechanism
|
12
13
|
include Moonshot::Shell
|
13
14
|
|
@@ -57,6 +58,12 @@ module Moonshot::ArtifactRepository
|
|
57
58
|
def attach_release_asset(version, file)
|
58
59
|
# -m '' leaves message unchanged.
|
59
60
|
cmd = "hub release edit #{version} -m '' --attach=#{file}"
|
61
|
+
|
62
|
+
# If there is a checksum file, attach it as well. We only support MD5
|
63
|
+
# since that's what S3 uses.
|
64
|
+
checksum_file = File.basename(file, '.tar.gz') + '.md5'
|
65
|
+
cmd += " --attach=#{checksum_file}" if File.exist?(checksum_file)
|
66
|
+
|
60
67
|
sh_step(cmd)
|
61
68
|
end
|
62
69
|
|
@@ -70,13 +77,143 @@ module Moonshot::ArtifactRepository
|
|
70
77
|
def github_to_s3(version, s3_name)
|
71
78
|
Dir.mktmpdir('github_to_s3', Dir.getwd) do |tmpdir|
|
72
79
|
Dir.chdir(tmpdir) do
|
73
|
-
|
74
|
-
file = Dir.glob("*#{version}*.tar.gz").fetch(0)
|
80
|
+
file = download_from_github(version)
|
75
81
|
upload_to_s3(file, s3_name)
|
76
82
|
end
|
77
83
|
end
|
78
84
|
end
|
79
85
|
|
86
|
+
# Uploads the file to s3 and verifies the checksum.
|
87
|
+
#
|
88
|
+
# @param file [String] File to be uploaded to s3.
|
89
|
+
# @param key [String] Name of the object to be created on s3.
|
90
|
+
# @raise [RuntimeError] If the file fails to upload correctly after 3
|
91
|
+
# attempts.
|
92
|
+
def upload_to_s3(file, key)
|
93
|
+
attempts = 0
|
94
|
+
begin
|
95
|
+
super
|
96
|
+
|
97
|
+
unless (checksum = checksum_file(file)).nil?
|
98
|
+
verify_s3_checksum(key, checksum, attempt: attempts)
|
99
|
+
end
|
100
|
+
rescue RuntimeError => e
|
101
|
+
unless (attempts += 1) > 3
|
102
|
+
# Wait 10 seconds before trying again.
|
103
|
+
sleep 10
|
104
|
+
retry
|
105
|
+
end
|
106
|
+
|
107
|
+
raise e
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Downloads the release build from github and verifies the checksum.
|
112
|
+
#
|
113
|
+
# @param version [String] Version to be downloaded
|
114
|
+
# @param [String] Build file downloaded.
|
115
|
+
# @raise [RuntimeError] If the file fails to download correctly after 3
|
116
|
+
# attempts.
|
117
|
+
def download_from_github(version)
|
118
|
+
attempts = 0
|
119
|
+
begin
|
120
|
+
# Make sure the directory is empty before downloading the release.
|
121
|
+
FileUtils.rm(Dir.glob('*'))
|
122
|
+
|
123
|
+
# Download the release and find the actual build file.
|
124
|
+
sh_out("hub release download #{version}")
|
125
|
+
file = Dir.glob("*#{version}*.tar.gz").fetch(0)
|
126
|
+
|
127
|
+
unless (checksum = checksum_file(file)).nil?
|
128
|
+
verify_download_checksum(file, checksum, attempt: attempts)
|
129
|
+
end
|
130
|
+
rescue RuntimeError => e
|
131
|
+
attempts += 1
|
132
|
+
retry unless attempts > 3
|
133
|
+
raise e
|
134
|
+
end
|
135
|
+
|
136
|
+
file
|
137
|
+
end
|
138
|
+
|
139
|
+
# Find the checksum file for a release, if there is one.
|
140
|
+
#
|
141
|
+
# @param build_file [String] Build file to get the checksum for.
|
142
|
+
# @return [String] Checksum file or nil.
|
143
|
+
def checksum_file(build_file)
|
144
|
+
basename = File.basename(build_file, '.tar.gz')
|
145
|
+
Dir.glob("#{basename}.md5").fetch(0, nil)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Verifies the checksum for a file downloaded from github.
|
149
|
+
#
|
150
|
+
# @param build_file [String] Build file to verify.
|
151
|
+
# @param checksum_file [String] Checksum file to verify the build.
|
152
|
+
# @param attempt [Integer] The attempt for this verification.
|
153
|
+
def verify_download_checksum(build_file, checksum_file, attempt: 0)
|
154
|
+
expected = File.read(checksum_file)
|
155
|
+
actual = Digest::MD5.file(build_file).hexdigest
|
156
|
+
if actual != expected
|
157
|
+
log.error("GitHub fie #{build_file} checksum should be #{expected} " \
|
158
|
+
"but was #{actual}.")
|
159
|
+
backup_failed_github_file(build_file, attempt)
|
160
|
+
raise "Checksum for #{build_file} could not be verified."
|
161
|
+
end
|
162
|
+
|
163
|
+
log.info('Verified downloaded file checksum.')
|
164
|
+
end
|
165
|
+
|
166
|
+
# Backs up the failed file from a github verification.
|
167
|
+
#
|
168
|
+
# @param build_file [String] The build file to backup.
|
169
|
+
# @param attempt [Integer] Which attempt to verify the file failed.
|
170
|
+
def backup_failed_github_file(build_file, attempt)
|
171
|
+
basename = File.basename(build_file, '.tar.gz')
|
172
|
+
destination = File.join(Dir.tmpdir, basename,
|
173
|
+
".gh.failure.#{attempt}.tar.gz")
|
174
|
+
FileUtils.cp(build_file, destination)
|
175
|
+
log.info("Copied #{build_file} to #{destination}")
|
176
|
+
end
|
177
|
+
|
178
|
+
# Verifies the checksum for a file uploaded to s3.
|
179
|
+
#
|
180
|
+
# Uses a HEAD request and uses the etag, which is an MD5 hash.
|
181
|
+
#
|
182
|
+
# @param s3_name [String] The object's name on s3.
|
183
|
+
# @param checksum_file [String] Checksum file to verify the build.
|
184
|
+
# @param attempt [Integer] The attempt for this verification.
|
185
|
+
def verify_s3_checksum(s3_name, checksum_file, attempt: 0)
|
186
|
+
headers = s3_client.head_object(
|
187
|
+
key: s3_name,
|
188
|
+
bucket: @bucket_name
|
189
|
+
)
|
190
|
+
expected = File.read(checksum_file)
|
191
|
+
actual = headers.etag.tr('"', '')
|
192
|
+
if actual != expected
|
193
|
+
log.error("S3 file #{s3_name} checksum should be #{expected} but " \
|
194
|
+
"was #{actual}.")
|
195
|
+
backup_failed_s3_file(s3_name, attempt)
|
196
|
+
raise "Checksum for #{s3_name} could not be verified."
|
197
|
+
end
|
198
|
+
|
199
|
+
log.info('Verified uploaded file checksum.')
|
200
|
+
end
|
201
|
+
|
202
|
+
# Backs up the failed file from an s3 verification.
|
203
|
+
#
|
204
|
+
# @param s3_name [String] The object's name on s3.
|
205
|
+
# @param attempt [Integer] Which attempt to verify the file failed.
|
206
|
+
def backup_failed_s3_file(s3_name, attempt)
|
207
|
+
basename = File.basename(s3_name, '.tar.gz')
|
208
|
+
destination = "#{Dir.tmpdir}/#{basename}.s3.failure.#{attempt}.tar.gz"
|
209
|
+
s3_client.get_object(
|
210
|
+
response_target: destination,
|
211
|
+
key: s3_name,
|
212
|
+
bucket: @bucket_name
|
213
|
+
)
|
214
|
+
log.info("Copied #{s3_name} to #{destination}")
|
215
|
+
end
|
216
|
+
|
80
217
|
def doctor_check_hub_release_download
|
81
218
|
sh_out('hub release download --help')
|
82
219
|
rescue
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
module Moonshot
|
4
|
+
class AskUserSource
|
5
|
+
def get(sp)
|
6
|
+
return unless Moonshot.config.interactive
|
7
|
+
|
8
|
+
@sp = sp
|
9
|
+
|
10
|
+
prompt
|
11
|
+
loop do
|
12
|
+
input = gets.chomp
|
13
|
+
|
14
|
+
if String(input).empty? && @sp.default?
|
15
|
+
# We will use the default value, print it here so the output is clear.
|
16
|
+
puts 'Using default value.'
|
17
|
+
return
|
18
|
+
elsif String(input).empty?
|
19
|
+
puts "Cannot proceed without value for #{@sp.name}!"
|
20
|
+
else
|
21
|
+
@sp.set(String(input))
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
prompt
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def prompt
|
32
|
+
print "(#{@sp.name})".light_black
|
33
|
+
print " #{@sp.description}" unless @sp.description.empty?
|
34
|
+
print " [#{@sp.default}]".light_black if @sp.default?
|
35
|
+
print ': '
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -72,7 +72,7 @@ module Moonshot::BuildMechanism
|
|
72
72
|
say("#{@changes}\n\n")
|
73
73
|
|
74
74
|
q = "Do you want to tag and release this commit as #{version}? [y/n]"
|
75
|
-
raise
|
75
|
+
raise 'Release declined.' unless yes?(q)
|
76
76
|
end
|
77
77
|
|
78
78
|
def git_tag(tag, sha, annotation)
|
@@ -41,7 +41,7 @@ class Moonshot::BuildMechanism::Script
|
|
41
41
|
|
42
42
|
def post_build_hook(_version)
|
43
43
|
unless File.exist?(@output_file) # rubocop:disable GuardClause
|
44
|
-
raise
|
44
|
+
raise 'Build command did not produce output file!'
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Moonshot
|
4
|
+
# A Command that is automatically registered with the Moonshot::CommandLine
|
5
|
+
class Command
|
6
|
+
module ClassMethods
|
7
|
+
# TODO: Can we auto-generate usage for commands with no positional arguments, at least?
|
8
|
+
attr_accessor :usage, :description
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.inherited(base)
|
12
|
+
Moonshot::CommandLine.register(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
def parser
|
17
|
+
@use_interactive_logger = true
|
18
|
+
|
19
|
+
OptionParser.new do |o|
|
20
|
+
o.banner = "Usage: moonshot #{self.class.usage}"
|
21
|
+
|
22
|
+
o.on('-v', '--[no-]verbose', 'Show debug logging') do |v|
|
23
|
+
Moonshot.config.interactive_logger.debug = true if v
|
24
|
+
end
|
25
|
+
|
26
|
+
o.on('-nNAME', '--environment=NAME', 'Which environment to operate on.') do |v|
|
27
|
+
Moonshot.config.environment_name = v
|
28
|
+
end
|
29
|
+
|
30
|
+
o.on('--[no-]interactive-logger', TrueClass, 'Enable or disable fancy logging') do |v|
|
31
|
+
@use_interactive_logger = v
|
32
|
+
end
|
33
|
+
|
34
|
+
o.on('--[no-]show-all-events', FalseClass, 'Show all stack events during update') do |v|
|
35
|
+
Moonshot.config.show_all_stack_events = v
|
36
|
+
end
|
37
|
+
|
38
|
+
o.on('-pPARENT_STACK', '--parent=PARENT_STACK',
|
39
|
+
'Parent stack to import parameters from') do |v|
|
40
|
+
Moonshot.config.parent_stacks = [v]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Build a Moonshot::Controller from the CLI options.
|
48
|
+
def controller
|
49
|
+
controller = Moonshot::Controller.new
|
50
|
+
|
51
|
+
# Apply CLI options to configuration defined by Moonfile.
|
52
|
+
controller.config = Moonshot.config
|
53
|
+
|
54
|
+
# Degrade to a more compatible logger if the terminal seems outdated,
|
55
|
+
# or at the users request.
|
56
|
+
if !$stdout.isatty || !@use_interactive_logger
|
57
|
+
log = Logger.new(STDOUT)
|
58
|
+
controller.config.interactive_logger = InteractiveLoggerProxy.new(log)
|
59
|
+
end
|
60
|
+
|
61
|
+
controller
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Moonshot
|
4
|
+
# This class implements the command-line `moonshot` tool.
|
5
|
+
class CommandLine
|
6
|
+
def self.register(klass)
|
7
|
+
@classes ||= []
|
8
|
+
@classes << klass
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.registered_commands
|
12
|
+
@classes || []
|
13
|
+
end
|
14
|
+
|
15
|
+
def run! # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity
|
16
|
+
# Commands defined as Moonshot::Commands require a properly
|
17
|
+
# configured Moonshot.rb and supporting files. Without them, we only
|
18
|
+
# support `--help` and `new`.
|
19
|
+
return if handle_early_commands
|
20
|
+
|
21
|
+
# Find the Moonfile in this project.
|
22
|
+
orig_dir = Dir.pwd
|
23
|
+
|
24
|
+
loop do
|
25
|
+
break if File.exist?('Moonfile.rb')
|
26
|
+
|
27
|
+
if Dir.pwd == '/'
|
28
|
+
warn 'No Moonfile.rb found, are you in a project? Maybe you need to '\
|
29
|
+
'create one with `moonshot new <app_name>`?'
|
30
|
+
raise 'No Moonfile found'
|
31
|
+
end
|
32
|
+
|
33
|
+
Dir.chdir('..')
|
34
|
+
end
|
35
|
+
|
36
|
+
moonfile_dir = Dir.pwd
|
37
|
+
Dir.chdir(orig_dir)
|
38
|
+
|
39
|
+
# Load any plugins and CLI extensions relative to the Moonfile
|
40
|
+
if File.directory?(File.join(moonfile_dir, 'moonshot'))
|
41
|
+
load_plugins(moonfile_dir)
|
42
|
+
load_cli_extensions(moonfile_dir)
|
43
|
+
end
|
44
|
+
|
45
|
+
Object.include(Moonshot::ArtifactRepository)
|
46
|
+
Object.include(Moonshot::BuildMechanism)
|
47
|
+
Object.include(Moonshot::DeploymentMechanism)
|
48
|
+
load(File.join(moonfile_dir, 'Moonfile.rb'))
|
49
|
+
|
50
|
+
Moonshot.config.project_root = moonfile_dir
|
51
|
+
|
52
|
+
load_commands
|
53
|
+
|
54
|
+
# Determine what command is being run, which should be the first argument.
|
55
|
+
command = ARGV.shift
|
56
|
+
if %w(--help -h help).include?(command) || command.nil?
|
57
|
+
usage
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
# Dispatch to that command, by executing it's parser, then
|
62
|
+
# comparing ARGV to the execute methods arity.
|
63
|
+
unless @commands.key?(command)
|
64
|
+
usage
|
65
|
+
raise "Command not found '#{command}'"
|
66
|
+
end
|
67
|
+
|
68
|
+
handler = @commands[command].new
|
69
|
+
handler.parser.parse!
|
70
|
+
|
71
|
+
unless ARGV.size == handler.method(:execute).arity
|
72
|
+
warn handler.parser.help
|
73
|
+
raise "Invalid command line for '#{command}'."
|
74
|
+
end
|
75
|
+
|
76
|
+
handler.execute(*ARGV)
|
77
|
+
end
|
78
|
+
|
79
|
+
def load_plugins(moonfile_dir)
|
80
|
+
plugins_path = File.join(moonfile_dir, 'moonshot', 'plugins', '**', '*.rb')
|
81
|
+
Dir.glob(plugins_path).each do |file|
|
82
|
+
load(file)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def load_cli_extensions(moonfile_dir)
|
87
|
+
cli_extensions_path = File.join(moonfile_dir, 'moonshot', 'cli_extensions', '**', '*.rb')
|
88
|
+
Dir.glob(cli_extensions_path).each do |file|
|
89
|
+
load(file)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def usage
|
94
|
+
warn 'Usage: moonshot [command]'
|
95
|
+
warn
|
96
|
+
warn 'Valid commands include:'
|
97
|
+
fields = []
|
98
|
+
@commands.each do |c, k|
|
99
|
+
fields << [c, k.description]
|
100
|
+
end
|
101
|
+
|
102
|
+
max_len = fields.map(&:first).map(&:size).max
|
103
|
+
|
104
|
+
fields.each do |f|
|
105
|
+
line = format(" %-#{max_len}s # %s", *f)
|
106
|
+
warn line
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def load_commands
|
111
|
+
@commands = {}
|
112
|
+
|
113
|
+
# Include all Moonshot::Command and Moonshot::SSHCommand
|
114
|
+
# derived classes as subcommands, with the description of their
|
115
|
+
# default task.
|
116
|
+
self.class.registered_commands.each do |klass|
|
117
|
+
next unless klass.instance_methods.include?(:execute)
|
118
|
+
|
119
|
+
command_name = commandify(klass)
|
120
|
+
@commands[command_name] = klass
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def commandify(klass)
|
125
|
+
word = klass.to_s.split('::').last
|
126
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
|
127
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
|
128
|
+
word.tr!('_'.freeze, '-'.freeze)
|
129
|
+
word.downcase!
|
130
|
+
word
|
131
|
+
end
|
132
|
+
|
133
|
+
def handle_early_commands
|
134
|
+
# If this is a legacy (Thor) help command, re-write it as
|
135
|
+
# OptionParser format.
|
136
|
+
if ARGV[0] == 'help'
|
137
|
+
ARGV.delete_at(0)
|
138
|
+
ARGV.push('-h')
|
139
|
+
elsif ARGV[0] == 'new'
|
140
|
+
require_relative 'commands/new'
|
141
|
+
app_name = ARGV[1]
|
142
|
+
::Moonshot::Commands::New.run!(app_name)
|
143
|
+
return true
|
144
|
+
end
|
145
|
+
|
146
|
+
# Proceed to processing commands normally.
|
147
|
+
false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|