tomo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +221 -0
- data/exe/tomo +4 -0
- data/lib/tomo/cli/command.rb +36 -0
- data/lib/tomo/cli/common_options.rb +48 -0
- data/lib/tomo/cli/completions.rb +70 -0
- data/lib/tomo/cli/deploy_options.rb +59 -0
- data/lib/tomo/cli/error.rb +16 -0
- data/lib/tomo/cli/interrupted_error.rb +9 -0
- data/lib/tomo/cli/options.rb +38 -0
- data/lib/tomo/cli/parser.rb +92 -0
- data/lib/tomo/cli/project_options.rb +47 -0
- data/lib/tomo/cli/rules/argument.rb +42 -0
- data/lib/tomo/cli/rules/switch.rb +43 -0
- data/lib/tomo/cli/rules/value_switch.rb +58 -0
- data/lib/tomo/cli/rules.rb +98 -0
- data/lib/tomo/cli/rules_evaluator.rb +71 -0
- data/lib/tomo/cli/state.rb +29 -0
- data/lib/tomo/cli/usage.rb +42 -0
- data/lib/tomo/cli.rb +75 -0
- data/lib/tomo/colors.rb +46 -0
- data/lib/tomo/commands/completion_script.rb +46 -0
- data/lib/tomo/commands/default.rb +72 -0
- data/lib/tomo/commands/deploy.rb +67 -0
- data/lib/tomo/commands/help.rb +9 -0
- data/lib/tomo/commands/init.rb +92 -0
- data/lib/tomo/commands/run.rb +76 -0
- data/lib/tomo/commands/setup.rb +54 -0
- data/lib/tomo/commands/tasks.rb +32 -0
- data/lib/tomo/commands/version.rb +23 -0
- data/lib/tomo/commands.rb +13 -0
- data/lib/tomo/configuration/dsl/batch_block.rb +17 -0
- data/lib/tomo/configuration/dsl/config_file.rb +39 -0
- data/lib/tomo/configuration/dsl/environment_block.rb +13 -0
- data/lib/tomo/configuration/dsl/error_formatter.rb +75 -0
- data/lib/tomo/configuration/dsl/hosts_and_settings.rb +24 -0
- data/lib/tomo/configuration/dsl/tasks_block.rb +24 -0
- data/lib/tomo/configuration/dsl.rb +12 -0
- data/lib/tomo/configuration/environment.rb +12 -0
- data/lib/tomo/configuration/glob.rb +26 -0
- data/lib/tomo/configuration/plugin_file_not_found_error.rb +14 -0
- data/lib/tomo/configuration/plugin_resolver.rb +63 -0
- data/lib/tomo/configuration/plugins_registry/file_resolver.rb +43 -0
- data/lib/tomo/configuration/plugins_registry/gem_resolver.rb +63 -0
- data/lib/tomo/configuration/plugins_registry.rb +67 -0
- data/lib/tomo/configuration/project_not_found_error.rb +28 -0
- data/lib/tomo/configuration/role_based_task_filter.rb +42 -0
- data/lib/tomo/configuration/unknown_environment_error.rb +46 -0
- data/lib/tomo/configuration/unknown_plugin_error.rb +28 -0
- data/lib/tomo/configuration/unspecified_environment_error.rb +28 -0
- data/lib/tomo/configuration.rb +124 -0
- data/lib/tomo/console/key_reader.rb +51 -0
- data/lib/tomo/console/menu.rb +109 -0
- data/lib/tomo/console.rb +33 -0
- data/lib/tomo/error/suggestions.rb +44 -0
- data/lib/tomo/error.rb +22 -0
- data/lib/tomo/host.rb +57 -0
- data/lib/tomo/logger/tagged_io.rb +38 -0
- data/lib/tomo/logger.rb +70 -0
- data/lib/tomo/path.rb +19 -0
- data/lib/tomo/paths.rb +36 -0
- data/lib/tomo/plugin/bundler/helpers.rb +14 -0
- data/lib/tomo/plugin/bundler/tasks.rb +57 -0
- data/lib/tomo/plugin/bundler.rb +17 -0
- data/lib/tomo/plugin/core/helpers.rb +65 -0
- data/lib/tomo/plugin/core/tasks.rb +138 -0
- data/lib/tomo/plugin/core.rb +31 -0
- data/lib/tomo/plugin/env/tasks.rb +113 -0
- data/lib/tomo/plugin/env.rb +13 -0
- data/lib/tomo/plugin/git/helpers.rb +11 -0
- data/lib/tomo/plugin/git/tasks.rb +78 -0
- data/lib/tomo/plugin/git.rb +19 -0
- data/lib/tomo/plugin/nvm/tasks.rb +61 -0
- data/lib/tomo/plugin/nvm.rb +14 -0
- data/lib/tomo/plugin/puma/tasks.rb +38 -0
- data/lib/tomo/plugin/puma.rb +12 -0
- data/lib/tomo/plugin/rails/helpers.rb +20 -0
- data/lib/tomo/plugin/rails/tasks.rb +79 -0
- data/lib/tomo/plugin/rails.rb +11 -0
- data/lib/tomo/plugin/rbenv/tasks.rb +55 -0
- data/lib/tomo/plugin/rbenv.rb +12 -0
- data/lib/tomo/plugin/testing.rb +16 -0
- data/lib/tomo/plugin.rb +4 -0
- data/lib/tomo/plugin_dsl.rb +23 -0
- data/lib/tomo/remote.rb +55 -0
- data/lib/tomo/result.rb +28 -0
- data/lib/tomo/runtime/concurrent_ruby_load_error.rb +26 -0
- data/lib/tomo/runtime/concurrent_ruby_thread_pool.rb +50 -0
- data/lib/tomo/runtime/context.rb +21 -0
- data/lib/tomo/runtime/current.rb +41 -0
- data/lib/tomo/runtime/execution_plan.rb +107 -0
- data/lib/tomo/runtime/host_execution_step.rb +49 -0
- data/lib/tomo/runtime/inline_thread_pool.rb +27 -0
- data/lib/tomo/runtime/privileged_task.rb +6 -0
- data/lib/tomo/runtime/settings_interpolation.rb +55 -0
- data/lib/tomo/runtime/settings_required_error.rb +33 -0
- data/lib/tomo/runtime/task_aborted_error.rb +15 -0
- data/lib/tomo/runtime/task_runner.rb +56 -0
- data/lib/tomo/runtime/unknown_task_error.rb +23 -0
- data/lib/tomo/runtime.rb +82 -0
- data/lib/tomo/script.rb +44 -0
- data/lib/tomo/shell_builder.rb +108 -0
- data/lib/tomo/ssh/child_process.rb +64 -0
- data/lib/tomo/ssh/connection.rb +82 -0
- data/lib/tomo/ssh/connection_error.rb +16 -0
- data/lib/tomo/ssh/connection_validator.rb +87 -0
- data/lib/tomo/ssh/error.rb +11 -0
- data/lib/tomo/ssh/executable_error.rb +21 -0
- data/lib/tomo/ssh/options.rb +67 -0
- data/lib/tomo/ssh/permission_error.rb +18 -0
- data/lib/tomo/ssh/script_error.rb +23 -0
- data/lib/tomo/ssh/unknown_error.rb +13 -0
- data/lib/tomo/ssh/unsupported_version_error.rb +15 -0
- data/lib/tomo/ssh.rb +36 -0
- data/lib/tomo/task_library.rb +51 -0
- data/lib/tomo/templates/config.rb.erb +66 -0
- data/lib/tomo/testing/Dockerfile +10 -0
- data/lib/tomo/testing/connection.rb +34 -0
- data/lib/tomo/testing/docker_image.rb +115 -0
- data/lib/tomo/testing/docker_plugin_tester.rb +39 -0
- data/lib/tomo/testing/host_extensions.rb +27 -0
- data/lib/tomo/testing/local.rb +75 -0
- data/lib/tomo/testing/mock_plugin_tester.rb +26 -0
- data/lib/tomo/testing/mocked_exec_error.rb +6 -0
- data/lib/tomo/testing/plugin_tester.rb +49 -0
- data/lib/tomo/testing/remote_extensions.rb +10 -0
- data/lib/tomo/testing/ssh_extensions.rb +13 -0
- data/lib/tomo/testing/tomo_test_ed25519 +7 -0
- data/lib/tomo/testing/tomo_test_ed25519.pub +1 -0
- data/lib/tomo/testing/ubuntu_setup.sh +33 -0
- data/lib/tomo/testing.rb +39 -0
- data/lib/tomo/version.rb +3 -0
- data/lib/tomo.rb +45 -0
- metadata +308 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
class Tomo::CLI::Rules
|
2
|
+
class Argument
|
3
|
+
def initialize(label, multiple: false, required: false, values_proc:)
|
4
|
+
@label = label
|
5
|
+
@multiple = multiple
|
6
|
+
@required = required
|
7
|
+
@values_proc = values_proc
|
8
|
+
end
|
9
|
+
|
10
|
+
def match(arg, literal: false)
|
11
|
+
1 if literal || !arg.start_with?("-")
|
12
|
+
end
|
13
|
+
|
14
|
+
def process(arg, state:)
|
15
|
+
state.parsed_arg(arg)
|
16
|
+
end
|
17
|
+
|
18
|
+
def candidates(literal: false, state:)
|
19
|
+
values(state).reject { |val| literal && val.start_with?("-") }
|
20
|
+
end
|
21
|
+
|
22
|
+
def required?
|
23
|
+
@required
|
24
|
+
end
|
25
|
+
|
26
|
+
def multiple?
|
27
|
+
@multiple
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
@label
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :values_proc
|
37
|
+
|
38
|
+
def values(state)
|
39
|
+
values_proc.call(*state.args, state.options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Tomo::CLI::Rules
|
2
|
+
class Switch
|
3
|
+
def initialize(key,
|
4
|
+
*switches,
|
5
|
+
required: false,
|
6
|
+
callback_proc:,
|
7
|
+
&convert_proc)
|
8
|
+
@key = key
|
9
|
+
@switches = switches
|
10
|
+
@callback_proc = callback_proc
|
11
|
+
@convert_proc = convert_proc || proc { true }
|
12
|
+
@required = required
|
13
|
+
end
|
14
|
+
|
15
|
+
def match(arg, literal: false)
|
16
|
+
return nil if literal
|
17
|
+
|
18
|
+
1 if switches.include?(arg)
|
19
|
+
end
|
20
|
+
|
21
|
+
def process(arg, state:)
|
22
|
+
value = convert_proc.call(arg)
|
23
|
+
callback_proc&.call(value)
|
24
|
+
state.parsed_option(key, value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def candidates(literal: false, **_kwargs)
|
28
|
+
literal ? [] : switches
|
29
|
+
end
|
30
|
+
|
31
|
+
def required?
|
32
|
+
@required
|
33
|
+
end
|
34
|
+
|
35
|
+
def multiple?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :key, :switches, :convert_proc, :callback_proc
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Tomo::CLI::Rules
|
2
|
+
class ValueSwitch < Switch
|
3
|
+
include Tomo::Colors
|
4
|
+
|
5
|
+
def initialize(key, *switches, values_proc:, callback_proc:)
|
6
|
+
super(key, *switches, callback_proc: callback_proc)
|
7
|
+
|
8
|
+
@values_proc = values_proc
|
9
|
+
end
|
10
|
+
|
11
|
+
def match(arg, literal: false)
|
12
|
+
return nil if literal
|
13
|
+
return 2 if switches.include?(arg)
|
14
|
+
|
15
|
+
1 if arg.start_with?("--") && switches.include?(arg.split("=").first)
|
16
|
+
end
|
17
|
+
|
18
|
+
def process(switch, arg=nil, state:)
|
19
|
+
value = if switch.include?("=")
|
20
|
+
switch.split("=", 2).last
|
21
|
+
elsif !arg.to_s.start_with?("-")
|
22
|
+
arg
|
23
|
+
end
|
24
|
+
|
25
|
+
raise_missing_value(switch) if value.nil?
|
26
|
+
|
27
|
+
callback_proc&.call(value)
|
28
|
+
state.parsed_option(key, value)
|
29
|
+
end
|
30
|
+
|
31
|
+
def candidates(switch=nil, literal: false, state:)
|
32
|
+
return [] if literal
|
33
|
+
|
34
|
+
vals = values(state)
|
35
|
+
return vals.reject { |val| val.start_with?("-") } if switch
|
36
|
+
|
37
|
+
switches.each_with_object([]) do |each_switch, result|
|
38
|
+
result << each_switch
|
39
|
+
vals.each do |value|
|
40
|
+
result << "#{each_switch}=#{value}" if each_switch.start_with?("--")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :values_proc
|
48
|
+
|
49
|
+
def values(state)
|
50
|
+
values_proc.call(*state.args, state.options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def raise_missing_value(switch)
|
54
|
+
raise Tomo::CLI::Error,
|
55
|
+
"Please specify a value for the #{yellow(switch)} option."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Tomo
|
2
|
+
class CLI
|
3
|
+
class Rules
|
4
|
+
autoload :Argument, "tomo/cli/rules/argument"
|
5
|
+
autoload :Switch, "tomo/cli/rules/switch"
|
6
|
+
autoload :ValueSwitch, "tomo/cli/rules/value_switch"
|
7
|
+
|
8
|
+
ARG_PATTERNS = {
|
9
|
+
/\A\[[A-Z_]+\]\z/ => :optional_arg_rule,
|
10
|
+
/\A[A-Z_]+\z/ => :required_arg_rule,
|
11
|
+
/\A\[[A-Z_]+\.\.\.\]\z/ => :mutiple_optional_args_rule
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
OPTION_PATTERNS = {
|
15
|
+
/\A--\[no-\]([\-a-z]+)\z/ => :on_off_switch_rule,
|
16
|
+
/\A(-[a-z]), (--[\-a-z]+)\z/ => :basic_switch_rule,
|
17
|
+
/\A(-[a-z]), (--[\-a-z]+) [A-Z=_\-]+\z/ => :value_switch_rule
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
private_constant :ARG_PATTERNS, :OPTION_PATTERNS
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@rules = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_arg(spec, values_proc)
|
27
|
+
rule = ARG_PATTERNS.find do |regexp, method|
|
28
|
+
break send(method, spec, values_proc) if regexp.match?(spec)
|
29
|
+
end
|
30
|
+
raise ArgumentError, "Unrecognized arg style: #{spec}" if rule.nil?
|
31
|
+
|
32
|
+
rules << rule
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_option(key, spec, values_proc, &block)
|
36
|
+
rule = OPTION_PATTERNS.find do |regexp, method|
|
37
|
+
match = regexp.match(spec)
|
38
|
+
break send(method, key, *match.captures, values_proc, block) if match
|
39
|
+
end
|
40
|
+
raise ArgumentError, "Unrecognized option style: #{spec}" if rule.nil?
|
41
|
+
|
42
|
+
rules << rule
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_a
|
46
|
+
rules
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :rules
|
52
|
+
|
53
|
+
def optional_arg_rule(spec, values_proc)
|
54
|
+
Rules::Argument.new(
|
55
|
+
spec,
|
56
|
+
values_proc: values_proc,
|
57
|
+
required: false,
|
58
|
+
multiple: false
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def required_arg_rule(spec, values_proc)
|
63
|
+
Rules::Argument.new(
|
64
|
+
spec,
|
65
|
+
values_proc: values_proc,
|
66
|
+
required: true,
|
67
|
+
multiple: false
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def mutiple_optional_args_rule(spec, values_proc)
|
72
|
+
Rules::Argument.new(spec, multiple: true, values_proc: values_proc)
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_off_switch_rule(key, name, _values_proc, callback_proc)
|
76
|
+
Rules::Switch.new(key,
|
77
|
+
"--#{name}",
|
78
|
+
"--no-#{name}",
|
79
|
+
callback_proc: callback_proc) do |arg|
|
80
|
+
arg == "--#{name}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def basic_switch_rule(key, *switches, _values_proc, callback_proc)
|
85
|
+
Rules::Switch.new(key, *switches, callback_proc: callback_proc)
|
86
|
+
end
|
87
|
+
|
88
|
+
def value_switch_rule(key, *switches, values_proc, callback_proc)
|
89
|
+
Rules::ValueSwitch.new(
|
90
|
+
key,
|
91
|
+
*switches,
|
92
|
+
values_proc: values_proc,
|
93
|
+
callback_proc: callback_proc
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Tomo
|
2
|
+
class CLI
|
3
|
+
class RulesEvaluator
|
4
|
+
def self.evaluate(**kwargs)
|
5
|
+
new(**kwargs).call
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(rules:, argv:, state:, literal:, completions: nil)
|
9
|
+
@rules = rules
|
10
|
+
@argv = argv.dup
|
11
|
+
@state = state
|
12
|
+
@literal = literal
|
13
|
+
@completions = completions || Completions.new(literal: literal)
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
until argv.empty?
|
18
|
+
complete_if_needed(remaining_rules, *argv) if argv.length == 1
|
19
|
+
rule, matched_args = match_next_rule
|
20
|
+
complete_if_needed([rule], *matched_args) if argv.empty?
|
21
|
+
rule.process(*matched_args, state: state)
|
22
|
+
state.processed_rule(rule)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :rules, :argv, :state, :literal, :completions
|
29
|
+
|
30
|
+
def match_next_rule
|
31
|
+
matched_rule, length = remaining_rules.find do |rule|
|
32
|
+
matching_length = rule.match(argv.first, literal: literal)
|
33
|
+
break [rule, matching_length] if matching_length
|
34
|
+
end
|
35
|
+
raise_unrecognized_args if matched_rule.nil?
|
36
|
+
|
37
|
+
matched_args = argv.shift(length)
|
38
|
+
[matched_rule, matched_args]
|
39
|
+
end
|
40
|
+
|
41
|
+
def complete_if_needed(matched_rules, *matched_args)
|
42
|
+
return unless Completions.active?
|
43
|
+
|
44
|
+
completions.print_completions_and_exit(
|
45
|
+
matched_rules,
|
46
|
+
*matched_args,
|
47
|
+
state: state
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def remaining_rules
|
52
|
+
rules.reject do |rule|
|
53
|
+
state.processed?(rule) && !rule.multiple?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def raise_unrecognized_args
|
58
|
+
problem_arg = argv.first
|
59
|
+
type = if literal || !problem_arg.start_with?("-")
|
60
|
+
"arg"
|
61
|
+
else
|
62
|
+
"option"
|
63
|
+
end
|
64
|
+
|
65
|
+
raise CLI::Error,
|
66
|
+
"#{Colors.yellow(problem_arg)} is not a recognized #{type} "\
|
67
|
+
"for this tomo command."
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Tomo
|
2
|
+
class CLI
|
3
|
+
class State
|
4
|
+
attr_reader :args, :options, :processed_rules
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@args = []
|
8
|
+
@options = Options.new
|
9
|
+
@processed_rules = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def parsed_arg(arg)
|
13
|
+
args << arg
|
14
|
+
end
|
15
|
+
|
16
|
+
def parsed_option(key, value)
|
17
|
+
options.all(key) << value
|
18
|
+
end
|
19
|
+
|
20
|
+
def processed_rule(rule)
|
21
|
+
@processed_rules |= [rule]
|
22
|
+
end
|
23
|
+
|
24
|
+
def processed?(rule)
|
25
|
+
@processed_rules.include?(rule)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Tomo
|
2
|
+
class CLI
|
3
|
+
class Usage
|
4
|
+
def initialize
|
5
|
+
@options = []
|
6
|
+
@banner_proc = proc { "" }
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_option(spec, desc)
|
10
|
+
options << [
|
11
|
+
spec.start_with?("--") ? " #{spec}" : spec,
|
12
|
+
desc
|
13
|
+
]
|
14
|
+
end
|
15
|
+
|
16
|
+
def banner=(banner)
|
17
|
+
@banner_proc = banner.respond_to?(:call) ? banner : proc { banner }
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
indent([
|
22
|
+
"", banner_proc.call, "Options:", "", indent(options_help), "\n"
|
23
|
+
].join("\n"))
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :banner_proc, :options
|
29
|
+
|
30
|
+
def options_help
|
31
|
+
width = options.map(&:first).map(&:length).max
|
32
|
+
options.each_with_object([]) do |(spec, desc), help|
|
33
|
+
help << "#{Colors.yellow(spec.ljust(width))} #{desc}"
|
34
|
+
end.join("\n")
|
35
|
+
end
|
36
|
+
|
37
|
+
def indent(str)
|
38
|
+
str.gsub(/^/, " ")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/tomo/cli.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require "abbrev"
|
2
|
+
|
3
|
+
module Tomo
|
4
|
+
class CLI
|
5
|
+
autoload :Command, "tomo/cli/command"
|
6
|
+
autoload :CommonOptions, "tomo/cli/common_options"
|
7
|
+
autoload :Completions, "tomo/cli/completions"
|
8
|
+
autoload :DeployOptions, "tomo/cli/deploy_options"
|
9
|
+
autoload :Error, "tomo/cli/error"
|
10
|
+
autoload :InterruptedError, "tomo/cli/interrupted_error"
|
11
|
+
autoload :Options, "tomo/cli/options"
|
12
|
+
autoload :Parser, "tomo/cli/parser"
|
13
|
+
autoload :ProjectOptions, "tomo/cli/project_options"
|
14
|
+
autoload :Rules, "tomo/cli/rules"
|
15
|
+
autoload :RulesEvaluator, "tomo/cli/rules_evaluator"
|
16
|
+
autoload :State, "tomo/cli/state"
|
17
|
+
autoload :UnknownOptionError, "tomo/cli/unknown_option_error"
|
18
|
+
autoload :Usage, "tomo/cli/usage"
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_accessor :show_backtrace
|
22
|
+
end
|
23
|
+
|
24
|
+
COMMANDS = {
|
25
|
+
"deploy" => Tomo::Commands::Deploy,
|
26
|
+
"help" => Tomo::Commands::Help,
|
27
|
+
"init" => Tomo::Commands::Init,
|
28
|
+
"run" => Tomo::Commands::Run,
|
29
|
+
"setup" => Tomo::Commands::Setup,
|
30
|
+
"tasks" => Tomo::Commands::Tasks,
|
31
|
+
"version" => Tomo::Commands::Version,
|
32
|
+
"completion-script" => Tomo::Commands::CompletionScript
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
def call(argv)
|
36
|
+
prepare_completions(argv)
|
37
|
+
command, command_name = lookup_command(argv)
|
38
|
+
command.parse(argv)
|
39
|
+
rescue Interrupt
|
40
|
+
handle_error(InterruptedError.new, command_name)
|
41
|
+
rescue StandardError => e
|
42
|
+
handle_error(e, command_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def prepare_completions(argv)
|
48
|
+
return unless %w[--complete --complete-word].include?(argv[0])
|
49
|
+
|
50
|
+
Completions.activate
|
51
|
+
argv << "" if argv.shift == "--complete"
|
52
|
+
end
|
53
|
+
|
54
|
+
def lookup_command(argv)
|
55
|
+
command_name = argv.first unless Completions.active? && argv.length == 1
|
56
|
+
command_name = Abbrev.abbrev(COMMANDS.keys)[command_name]
|
57
|
+
argv.shift if command_name
|
58
|
+
|
59
|
+
command = COMMANDS[command_name] || Tomo::Commands::Default
|
60
|
+
[command, command_name]
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_error(error, command_name)
|
64
|
+
return if Completions.active?
|
65
|
+
raise error unless error.respond_to?(:to_console)
|
66
|
+
|
67
|
+
error.command_name = command_name if error.respond_to?(:command_name=)
|
68
|
+
Tomo.logger.error(error.to_console)
|
69
|
+
status = error.respond_to?(:exit_status) ? error.exit_status : 1
|
70
|
+
exit(status) unless Tomo::CLI.show_backtrace
|
71
|
+
|
72
|
+
raise error
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/tomo/colors.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Tomo
|
2
|
+
module Colors
|
3
|
+
ANSI_CODES = {
|
4
|
+
red: 31,
|
5
|
+
green: 32,
|
6
|
+
yellow: 33,
|
7
|
+
blue: 34,
|
8
|
+
gray: 90
|
9
|
+
}.freeze
|
10
|
+
private_constant :ANSI_CODES
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_writer :enabled
|
14
|
+
|
15
|
+
def enabled?
|
16
|
+
return @enabled if defined?(@enabled)
|
17
|
+
|
18
|
+
@enabled = determine_color_support
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def determine_color_support
|
24
|
+
if ENV["CLICOLOR_FORCE"] == "1"
|
25
|
+
true
|
26
|
+
elsif ENV["TERM"] == "dumb"
|
27
|
+
false
|
28
|
+
else
|
29
|
+
tty?($stdout) && tty?($stderr)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def tty?(io)
|
34
|
+
io.respond_to?(:tty?) && io.tty?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module_function
|
39
|
+
|
40
|
+
ANSI_CODES.each do |name, code|
|
41
|
+
define_method(name) do |str|
|
42
|
+
::Tomo::Colors.enabled? ? "\e[0;#{code};49m#{str}\e[0m" : str
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Tomo
|
2
|
+
module Commands
|
3
|
+
class CompletionScript
|
4
|
+
def self.parse(_argv)
|
5
|
+
puts <<~'SCRIPT'
|
6
|
+
# TOMO COMPLETIONS FOR BASH
|
7
|
+
#
|
8
|
+
# Assuming tomo is in your PATH, you can install tomo bash completions by
|
9
|
+
# adding this line to your .bashrc:
|
10
|
+
#
|
11
|
+
# eval "$(tomo completion-script)"
|
12
|
+
#
|
13
|
+
# The eval technique is a bit slow but ensures bash is always using the
|
14
|
+
# latest version of the tomo completion script.
|
15
|
+
#
|
16
|
+
# Alternatively, you can copy and paste the current version of the script
|
17
|
+
# into your .bashrc. The full script is listed below.
|
18
|
+
|
19
|
+
_tomo_complete() {
|
20
|
+
local cur="${COMP_WORDS[COMP_CWORD]}"
|
21
|
+
local prev="${COMP_WORDS[COMP_CWORD-1]}"
|
22
|
+
|
23
|
+
if [[ $prev == "-c" || $prev == "--config" ]]; then
|
24
|
+
COMPREPLY=($(compgen -f -- ${cur}))
|
25
|
+
return 0
|
26
|
+
fi
|
27
|
+
|
28
|
+
if [[ "${COMP_LINE: -1}" == " " ]]; then
|
29
|
+
command=${COMP_LINE/tomo/tomo --complete}
|
30
|
+
else
|
31
|
+
command=${COMP_LINE/tomo/tomo --complete-word}
|
32
|
+
fi
|
33
|
+
|
34
|
+
suggestions=$($command)
|
35
|
+
local IFS=$'\n'
|
36
|
+
COMPREPLY=($suggestions)
|
37
|
+
return 0
|
38
|
+
}
|
39
|
+
|
40
|
+
complete -o nospace -F _tomo_complete tomo
|
41
|
+
|
42
|
+
SCRIPT
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Tomo
|
2
|
+
module Commands
|
3
|
+
class Default < CLI::Command
|
4
|
+
arg "COMMAND", values: CLI::COMMANDS.keys
|
5
|
+
|
6
|
+
option :version, "-v, --version", "Display tomo’s version and exit" do
|
7
|
+
Version.parse([])
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
|
11
|
+
include CLI::CommonOptions
|
12
|
+
|
13
|
+
def banner
|
14
|
+
<<~BANNER
|
15
|
+
Usage: #{green('tomo')} #{yellow('COMMAND [options]')}
|
16
|
+
|
17
|
+
Tomo is an extensible tool for deploying projects to remote hosts via SSH.
|
18
|
+
Please specify a #{yellow('COMMAND')}, which can be:
|
19
|
+
|
20
|
+
#{commands.map { |name, help| " #{yellow(name.ljust(10))} #{help}" }.join("\n")}
|
21
|
+
|
22
|
+
Tomo accepts abbreviations for its commands. For example, #{blue('tomo deploy')}
|
23
|
+
can be shortened to #{blue('tomo d')}. You can use bash completions as well!
|
24
|
+
Run #{blue('tomo completion-script')} for installation instructions.
|
25
|
+
|
26
|
+
For help with any command, add #{blue('-h')} to the command, like this:
|
27
|
+
|
28
|
+
#{blue('tomo run -h')}
|
29
|
+
|
30
|
+
Or read the full documentation for all commands at:
|
31
|
+
|
32
|
+
#{blue('https://tomo-deploy.com/')}
|
33
|
+
BANNER
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(*args, options)
|
37
|
+
# The bare `tomo` command (i.e. without `--help` or `--version`) doesn't
|
38
|
+
# do anything, so if we got this far, something has gone wrong.
|
39
|
+
|
40
|
+
if options.any?
|
41
|
+
raise CLI::Error,
|
42
|
+
"Options must be specified after the command: " +
|
43
|
+
yellow("tomo #{args.first} [options]")
|
44
|
+
end
|
45
|
+
|
46
|
+
raise_unrecognized_command(args.first)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def raise_unrecognized_command(command)
|
52
|
+
error = "#{yellow(command)} is not a recognized tomo command."
|
53
|
+
if command.match?(/\A\S+:\S+\z/)
|
54
|
+
suggestion = "tomo run #{command}"
|
55
|
+
error << "\nMaybe you meant #{blue(suggestion)}?"
|
56
|
+
end
|
57
|
+
|
58
|
+
raise CLI::Error, error
|
59
|
+
end
|
60
|
+
|
61
|
+
def commands
|
62
|
+
CLI::COMMANDS.each_with_object({}) do |(name, klass), result|
|
63
|
+
command = klass.new
|
64
|
+
help = command.summary if command.respond_to?(:summary)
|
65
|
+
next if help.nil?
|
66
|
+
|
67
|
+
result[name] = help
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Tomo
|
2
|
+
module Commands
|
3
|
+
class Deploy < CLI::Command
|
4
|
+
include CLI::DeployOptions
|
5
|
+
include CLI::ProjectOptions
|
6
|
+
include CLI::CommonOptions
|
7
|
+
|
8
|
+
def summary
|
9
|
+
"Deploy the current project to remote host(s)"
|
10
|
+
end
|
11
|
+
|
12
|
+
def banner
|
13
|
+
<<~BANNER
|
14
|
+
Usage: #{green('tomo deploy')} #{yellow('[--dry-run] [options]')}
|
15
|
+
|
16
|
+
Sequentially run the "deploy" list of tasks specified in #{DEFAULT_CONFIG_PATH} to
|
17
|
+
deploy the project to a remote host. Use the #{blue('--dry-run')} option to quickly
|
18
|
+
simulate the entire deploy without actually connecting to the host.
|
19
|
+
|
20
|
+
For a #{DEFAULT_CONFIG_PATH} that specifies distinct environments (e.g. staging,
|
21
|
+
production), you must specify the target environment using the #{blue('-e')} option. If
|
22
|
+
you omit this option, tomo will automatically prompt for it.
|
23
|
+
|
24
|
+
Tomo will use the settings specified in #{DEFAULT_CONFIG_PATH} to configure the
|
25
|
+
deploy. You may override these on the command line using #{blue('-s')}. E.g.:
|
26
|
+
|
27
|
+
#{blue('tomo deploy -e staging -s git_branch=develop')}
|
28
|
+
|
29
|
+
Or use environment variables with the special #{blue('TOMO_')} prefix:
|
30
|
+
|
31
|
+
#{blue('TOMO_GIT_BRANCH=develop tomo deploy -e staging')}
|
32
|
+
|
33
|
+
Bash completions are provided for tomo’s options. For example, you could type
|
34
|
+
#{blue('tomo deploy -s <TAB>')} to see a list of all settings, or #{blue('tomo deploy -e pr<TAB>')}
|
35
|
+
to expand #{blue('pr')} to #{blue('production')}. For bash completion installation instructions,
|
36
|
+
run #{blue('tomo completion-script')}.
|
37
|
+
|
38
|
+
More documentation and examples can be found here:
|
39
|
+
|
40
|
+
#{blue('https://tomo-deploy.com/commands/deploy')}
|
41
|
+
BANNER
|
42
|
+
end
|
43
|
+
|
44
|
+
def call(options)
|
45
|
+
logger.info "tomo deploy v#{Tomo::VERSION}"
|
46
|
+
|
47
|
+
runtime = configure_runtime(options)
|
48
|
+
plan = runtime.deploy!
|
49
|
+
|
50
|
+
log_completion(plan)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def log_completion(plan)
|
56
|
+
app = plan.settings[:application]
|
57
|
+
target = "#{app} to #{plan.applicable_hosts_sentence}"
|
58
|
+
|
59
|
+
if dry_run?
|
60
|
+
logger.info(green("* Simulated deploy of #{target} (dry run)"))
|
61
|
+
else
|
62
|
+
logger.info(green("✔ Deployed #{target}"))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|