tomo 0.1.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 +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
|