tomo 1.1.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +47 -29
  3. data/lib/tomo/cli.rb +1 -3
  4. data/lib/tomo/cli/common_options.rb +4 -12
  5. data/lib/tomo/cli/deploy_options.rb +2 -7
  6. data/lib/tomo/cli/parser.rb +1 -6
  7. data/lib/tomo/cli/project_options.rb +1 -3
  8. data/lib/tomo/cli/rules.rb +4 -22
  9. data/lib/tomo/cli/rules/argument.rb +2 -2
  10. data/lib/tomo/cli/rules/switch.rb +1 -5
  11. data/lib/tomo/cli/rules/value_switch.rb +2 -3
  12. data/lib/tomo/cli/rules_evaluator.rb +3 -13
  13. data/lib/tomo/cli/usage.rb +1 -3
  14. data/lib/tomo/commands/default.rb +1 -3
  15. data/lib/tomo/commands/init.rb +28 -1
  16. data/lib/tomo/commands/run.rb +1 -3
  17. data/lib/tomo/configuration.rb +6 -12
  18. data/lib/tomo/configuration/dsl/error_formatter.rb +1 -1
  19. data/lib/tomo/configuration/dsl/hosts_and_settings.rb +1 -2
  20. data/lib/tomo/configuration/plugins_registry.rb +1 -2
  21. data/lib/tomo/configuration/plugins_registry/gem_resolver.rb +1 -1
  22. data/lib/tomo/configuration/unknown_environment_error.rb +1 -4
  23. data/lib/tomo/console.rb +6 -11
  24. data/lib/tomo/console/menu.rb +1 -2
  25. data/lib/tomo/host.rb +1 -2
  26. data/lib/tomo/paths.rb +1 -1
  27. data/lib/tomo/plugin/bundler/tasks.rb +2 -7
  28. data/lib/tomo/plugin/core/helpers.rb +1 -1
  29. data/lib/tomo/plugin/core/tasks.rb +3 -12
  30. data/lib/tomo/plugin/env/tasks.rb +31 -9
  31. data/lib/tomo/plugin/git.rb +1 -4
  32. data/lib/tomo/plugin/git/tasks.rb +4 -14
  33. data/lib/tomo/plugin/nodenv/tasks.rb +1 -3
  34. data/lib/tomo/plugin/puma.rb +0 -3
  35. data/lib/tomo/plugin/puma/tasks.rb +6 -15
  36. data/lib/tomo/plugin/rails/tasks.rb +6 -4
  37. data/lib/tomo/plugin/rbenv/tasks.rb +16 -2
  38. data/lib/tomo/plugin/testing.rb +1 -3
  39. data/lib/tomo/remote.rb +1 -3
  40. data/lib/tomo/runtime.rb +3 -6
  41. data/lib/tomo/runtime/concurrent_ruby_thread_pool.rb +1 -4
  42. data/lib/tomo/runtime/execution_plan.rb +1 -4
  43. data/lib/tomo/runtime/explanation.rb +1 -7
  44. data/lib/tomo/runtime/settings_required_error.rb +1 -3
  45. data/lib/tomo/runtime/task_runner.rb +1 -5
  46. data/lib/tomo/runtime/unknown_task_error.rb +1 -4
  47. data/lib/tomo/script.rb +1 -5
  48. data/lib/tomo/shell_builder.rb +5 -10
  49. data/lib/tomo/ssh/child_process.rb +2 -7
  50. data/lib/tomo/ssh/connection.rb +5 -18
  51. data/lib/tomo/ssh/connection_validator.rb +5 -8
  52. data/lib/tomo/ssh/executable_error.rb +1 -2
  53. data/lib/tomo/ssh/options.rb +2 -5
  54. data/lib/tomo/task_api.rb +4 -15
  55. data/lib/tomo/templates/config.rb.erb +9 -1
  56. data/lib/tomo/testing/cli_extensions.rb +1 -1
  57. data/lib/tomo/testing/connection.rb +1 -6
  58. data/lib/tomo/testing/docker_image.rb +5 -11
  59. data/lib/tomo/testing/local.rb +1 -3
  60. data/lib/tomo/testing/ubuntu_setup.sh +1 -2
  61. data/lib/tomo/version.rb +1 -1
  62. metadata +8 -135
  63. data/lib/tomo/configuration/plugin_resolver.rb +0 -63
@@ -34,9 +34,7 @@ module Tomo::Plugin::Nodenv
34
34
  require_setting :nodenv_node_version
35
35
  node_version = settings[:nodenv_node_version]
36
36
 
37
- unless node_installed?(node_version)
38
- remote.run "nodenv install #{node_version.shellescape}"
39
- end
37
+ remote.run "nodenv install #{node_version.shellescape}" unless node_installed?(node_version)
40
38
  remote.run "nodenv global #{node_version.shellescape}"
41
39
  end
42
40
 
@@ -5,8 +5,6 @@ module Tomo::Plugin
5
5
  extend Tomo::PluginDSL
6
6
 
7
7
  tasks Tomo::Plugin::Puma::Tasks
8
-
9
- # rubocop:disable Layout/LineLength
10
8
  defaults puma_check_timeout: 15,
11
9
  puma_host: "0.0.0.0",
12
10
  puma_port: "3000",
@@ -16,6 +14,5 @@ module Tomo::Plugin
16
14
  puma_systemd_socket_path: ".config/systemd/user/%{puma_systemd_socket}",
17
15
  puma_systemd_service_template_path: File.expand_path("puma/systemd/service.erb", __dir__),
18
16
  puma_systemd_socket_template_path: File.expand_path("puma/systemd/socket.erb", __dir__)
19
- # rubocop:enable Layout/LineLength
20
17
  end
21
18
  end
@@ -2,8 +2,7 @@ module Tomo::Plugin::Puma
2
2
  class Tasks < Tomo::TaskLibrary
3
3
  SystemdUnit = Struct.new(:name, :template, :path)
4
4
 
5
- # rubocop:disable Metrics/AbcSize
6
- def setup_systemd
5
+ def setup_systemd # rubocop:disable Metrics/AbcSize
7
6
  linger_must_be_enabled!
8
7
 
9
8
  setup_directories
@@ -13,7 +12,6 @@ module Tomo::Plugin::Puma
13
12
  remote.run "systemctl --user daemon-reload"
14
13
  remote.run "systemctl", "--user", "enable", service.name, socket.name
15
14
  end
16
- # rubocop:enable Metrics/AbcSize
17
15
 
18
16
  %i[start stop status].each do |action|
19
17
  define_method(action) do
@@ -37,9 +35,7 @@ module Tomo::Plugin::Puma
37
35
  end
38
36
 
39
37
  def log
40
- remote.attach "journalctl", "-q",
41
- raw("--user-unit=#{service.name.shellescape}"),
42
- *settings[:run_args]
38
+ remote.attach "journalctl", "-q", raw("--user-unit=#{service.name.shellescape}"), *settings[:run_args]
43
39
  end
44
40
 
45
41
  private
@@ -102,22 +98,17 @@ module Tomo::Plugin::Puma
102
98
  end
103
99
 
104
100
  def assert_active!
105
- return true if remote.run? "systemctl", "--user", "is-active",
106
- service.name,
107
- silent: true, raise_on_error: false
101
+ return true if remote.run? "systemctl", "--user", "is-active", service.name, silent: true, raise_on_error: false
108
102
 
109
- remote.run "systemctl", "--user", "status", service.name,
110
- raise_on_error: false
111
- remote.run "journalctl -q -n 50 --user-unit=#{service.name.shellescape}",
112
- raise_on_error: false
103
+ remote.run "systemctl", "--user", "status", service.name, raise_on_error: false
104
+ remote.run "journalctl -q -n 50 --user-unit=#{service.name.shellescape}", raise_on_error: false
113
105
 
114
106
  die "puma failed to start (see previous systemctl and journalctl output)"
115
107
  end
116
108
 
117
109
  def listening?
118
110
  test_url = "http://localhost:#{port}"
119
- remote.run? "curl -sS --connect-timeout 1 --max-time 10 #{test_url}"\
120
- " > /dev/null"
111
+ remote.run? "curl -sS --connect-timeout 1 --max-time 10 #{test_url} > /dev/null"
121
112
  end
122
113
  end
123
114
  end
@@ -8,6 +8,10 @@ module Tomo::Plugin::Rails
8
8
  remote.rails("console", settings[:run_args], attach: true)
9
9
  end
10
10
 
11
+ def db_console
12
+ remote.rails("dbconsole", "--include-password", settings[:run_args], attach: true)
13
+ end
14
+
11
15
  def db_migrate
12
16
  remote.rake("db:migrate")
13
17
  end
@@ -40,11 +44,9 @@ module Tomo::Plugin::Rails
40
44
 
41
45
  def db_structure_load
42
46
  if !structure_sql_present?
43
- logger.warn "db/structure.sql is not present; "\
44
- "skipping db:structure:load."
47
+ logger.warn "db/structure.sql is not present; skipping db:structure:load."
45
48
  elsif database_schema_loaded?
46
- logger.info "Database structure already loaded; "\
47
- "skipping db:structure:load."
49
+ logger.info "Database structure already loaded; skipping db:structure:load."
48
50
  else
49
51
  remote.rake("db:structure:load")
50
52
  end
@@ -31,8 +31,7 @@ module Tomo::Plugin::Rbenv
31
31
  end
32
32
 
33
33
  def compile_ruby
34
- require_setting :rbenv_ruby_version
35
- ruby_version = settings[:rbenv_ruby_version]
34
+ ruby_version = version_setting || extract_ruby_ver_from_version_file
36
35
 
37
36
  unless ruby_installed?(ruby_version)
38
37
  logger.info(
@@ -51,5 +50,20 @@ module Tomo::Plugin::Rbenv
51
50
  end
52
51
  false
53
52
  end
53
+
54
+ def version_setting
55
+ settings[:rbenv_ruby_version]
56
+ end
57
+
58
+ def extract_ruby_ver_from_version_file
59
+ path = paths.release.join(".ruby-version")
60
+ version = remote.capture("cat", path, raise_on_error: false).strip
61
+ return version unless version.empty?
62
+
63
+ die <<~REASON
64
+ Could not guess ruby version from .ruby-version file.
65
+ Use the :rbenv_ruby_version setting to specify the version of ruby to install.
66
+ REASON
67
+ end
54
68
  end
55
69
  end
@@ -1,6 +1,4 @@
1
- unless defined?(Tomo::Testing)
2
- raise "The testing plugin cannot be used outside of unit tests"
3
- end
1
+ raise "The testing plugin cannot be used outside of unit tests" unless defined?(Tomo::Testing)
4
2
 
5
3
  module Tomo::Plugin
6
4
  class Testing < Tomo::TaskLibrary
@@ -21,9 +21,7 @@ module Tomo
21
21
 
22
22
  def attach(*command, default_chdir: nil, **command_opts)
23
23
  full_command = shell_builder.build(*command, default_chdir: default_chdir)
24
- ssh.ssh_exec(
25
- Script.new(full_command, **{ pty: true }.merge(command_opts))
26
- )
24
+ ssh.ssh_exec(Script.new(full_command, **{ pty: true }.merge(command_opts)))
27
25
  end
28
26
 
29
27
  def run(*command, attach: false, default_chdir: nil, **command_opts)
@@ -3,8 +3,7 @@ require "time"
3
3
  module Tomo
4
4
  class Runtime
5
5
  autoload :ConcurrentRubyLoadError, "tomo/runtime/concurrent_ruby_load_error"
6
- autoload :ConcurrentRubyThreadPool,
7
- "tomo/runtime/concurrent_ruby_thread_pool"
6
+ autoload :ConcurrentRubyThreadPool, "tomo/runtime/concurrent_ruby_thread_pool"
8
7
  autoload :Context, "tomo/runtime/context"
9
8
  autoload :Current, "tomo/runtime/current"
10
9
  autoload :ExecutionPlan, "tomo/runtime/execution_plan"
@@ -28,8 +27,7 @@ module Tomo
28
27
 
29
28
  attr_reader :tasks
30
29
 
31
- def initialize(deploy_tasks:, setup_tasks:, hosts:, task_filter:,
32
- settings:, plugins_registry:)
30
+ def initialize(deploy_tasks:, setup_tasks:, hosts:, task_filter:, settings:, plugins_registry:)
33
31
  @deploy_tasks = deploy_tasks.freeze
34
32
  @setup_tasks = setup_tasks.freeze
35
33
  @hosts = hosts.freeze
@@ -68,8 +66,7 @@ module Tomo
68
66
 
69
67
  private
70
68
 
71
- attr_reader :deploy_tasks, :setup_tasks, :hosts, :task_filter, :settings,
72
- :plugins_registry
69
+ attr_reader :deploy_tasks, :setup_tasks, :hosts, :task_filter, :settings, :plugins_registry
73
70
 
74
71
  def new_task_runner(release_type, args)
75
72
  run_settings = { release_path: release_path_for(release_type) }
@@ -4,10 +4,7 @@ begin
4
4
  gem "concurrent-ruby", concurrent_ver
5
5
  require "concurrent"
6
6
  rescue LoadError => e
7
- Tomo::Runtime::ConcurrentRubyLoadError.raise_with(
8
- e.message,
9
- version: concurrent_ver
10
- )
7
+ Tomo::Runtime::ConcurrentRubyLoadError.raise_with(e.message, version: concurrent_ver)
11
8
  end
12
9
 
13
10
  module Tomo
@@ -77,10 +77,7 @@ module Tomo
77
77
  def build_plan(tasks, task_filter)
78
78
  tasks.each_with_object([]) do |task, result|
79
79
  steps = hosts.map do |host|
80
- HostExecutionStep.new(
81
- tasks: task, host: host,
82
- task_filter: task_filter, task_runner: task_runner
83
- )
80
+ HostExecutionStep.new(tasks: task, host: host, task_filter: task_filter, task_runner: task_runner)
84
81
  end
85
82
  steps.reject!(&:empty?)
86
83
  result << steps unless steps.empty?
@@ -7,10 +7,7 @@ module Tomo
7
7
  @concurrency = concurrency
8
8
  end
9
9
 
10
- # rubocop:disable Metrics/MethodLength
11
- # rubocop:disable Metrics/AbcSize
12
- # rubocop:disable Metrics/CyclomaticComplexity
13
- def to_s
10
+ def to_s # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
14
11
  desc = []
15
12
  threads = [applicable_hosts.length, concurrency].min
16
13
  desc << "CONCURRENTLY (#{threads} THREADS):" if threads > 1
@@ -32,9 +29,6 @@ module Tomo
32
29
  end
33
30
  desc.join("\n")
34
31
  end
35
- # rubocop:enable Metrics/MethodLength
36
- # rubocop:enable Metrics/AbcSize
37
- # rubocop:enable Metrics/CyclomaticComplexity
38
32
 
39
33
  private
40
34
 
@@ -21,9 +21,7 @@ module Tomo
21
21
  private
22
22
 
23
23
  def settings_sentence
24
- if settings.length == 1
25
- return "a value for the #{yellow(settings.first.to_s)} setting."
26
- end
24
+ return "a value for the #{yellow(settings.first.to_s)} setting." if settings.length == 1
27
25
 
28
26
  sentence = "values for these settings:\n\n "
29
27
  sentence << settings.map { |s| yellow(s.to_s) }.join("\n ")
@@ -19,11 +19,7 @@ module Tomo
19
19
  def validate_task!(name)
20
20
  return if tasks_by_name.key?(name)
21
21
 
22
- UnknownTaskError.raise_with(
23
- name,
24
- unknown_task: name,
25
- known_tasks: tasks_by_name.keys
26
- )
22
+ UnknownTaskError.raise_with(name, unknown_task: name, known_tasks: tasks_by_name.keys)
27
23
  end
28
24
 
29
25
  def run(task:, remote:)
@@ -17,10 +17,7 @@ module Tomo
17
17
  private
18
18
 
19
19
  def spelling_suggestion
20
- sugg = Error::Suggestions.new(
21
- dictionary: known_tasks,
22
- word: unknown_task
23
- )
20
+ sugg = Error::Suggestions.new(dictionary: known_tasks, word: unknown_task)
24
21
  sugg.to_console if sugg.any?
25
22
  end
26
23
 
@@ -2,11 +2,7 @@ module Tomo
2
2
  class Script
3
3
  attr_reader :script
4
4
 
5
- def initialize(script,
6
- echo: true,
7
- pty: false,
8
- raise_on_error: true,
9
- silent: false)
5
+ def initialize(script, echo: true, pty: false, raise_on_error: true, silent: false)
10
6
  @script = script
11
7
  @echo = echo
12
8
  @pty = pty
@@ -3,8 +3,9 @@ require "shellwords"
3
3
  module Tomo
4
4
  class ShellBuilder
5
5
  def self.raw(string)
6
- string.define_singleton_method(:shellescape) { string }
7
- string
6
+ string.dup.tap do |raw_string|
7
+ raw_string.define_singleton_method(:shellescape) { string }
8
+ end
8
9
  end
9
10
 
10
11
  def initialize
@@ -45,9 +46,7 @@ module Tomo
45
46
  end
46
47
 
47
48
  def build(*command, default_chdir: nil)
48
- if @chdir.empty? && default_chdir
49
- return chdir(default_chdir) { build(*command) }
50
- end
49
+ return chdir(default_chdir) { build(*command) } if @chdir.empty? && default_chdir
51
50
 
52
51
  command_string = command_to_string(*command)
53
52
  modifiers = [cd_chdir, unset_env, export_env, set_umask].compact.flatten
@@ -97,11 +96,7 @@ module Tomo
97
96
  def set_umask
98
97
  return if @umask.nil?
99
98
 
100
- umask_value = if @umask.is_a?(Integer)
101
- @umask.to_s(8).rjust(4, "0")
102
- else
103
- @umask
104
- end
99
+ umask_value = @umask.is_a?(Integer) ? @umask.to_s(8).rjust(4, "0") : @umask
105
100
  "umask #{umask_value.to_s.shellescape}"
106
101
  end
107
102
  end
@@ -30,17 +30,12 @@ module Tomo
30
30
  end
31
31
 
32
32
  def result
33
- Result.new(
34
- exit_status: exit_status,
35
- stdout: stdout_buffer.string,
36
- stderr: stderr_buffer.string
37
- )
33
+ Result.new(exit_status: exit_status, stdout: stdout_buffer.string, stderr: stderr_buffer.string)
38
34
  end
39
35
 
40
36
  private
41
37
 
42
- attr_reader :command, :exit_status, :on_data,
43
- :stdout_buffer, :stderr_buffer
38
+ attr_reader :command, :exit_status, :on_data, :stdout_buffer, :stderr_buffer
44
39
 
45
40
  def start_io_thread(source, buffer)
46
41
  new_thread_inheriting_current_vars do
@@ -6,12 +6,7 @@ module Tomo
6
6
  module SSH
7
7
  class Connection
8
8
  def self.dry_run(host, options)
9
- new(
10
- host,
11
- options,
12
- exec_proc: proc { CLI.exit },
13
- child_proc: proc { Result.empty_success }
14
- )
9
+ new(host, options, exec_proc: proc { CLI.exit }, child_proc: proc { Result.empty_success })
15
10
  end
16
11
 
17
12
  attr_reader :host
@@ -31,16 +26,14 @@ module Tomo
31
26
  end
32
27
 
33
28
  def ssh_subprocess(script, verbose: false)
34
- ssh_args = build_args(script, verbose)
29
+ ssh_args = build_args(script, verbose: verbose)
35
30
  handle_data = ->(data) { logger.script_output(script, data) }
36
31
 
37
32
  logger.script_start(script)
38
33
  result = child_proc.call(*ssh_args, on_data: handle_data)
39
34
  logger.script_end(script, result)
40
35
 
41
- if result.failure? && script.raise_on_error?
42
- raise_run_error(script, ssh_args, result)
43
- end
36
+ raise_run_error(script, ssh_args, result) if result.failure? && script.raise_on_error?
44
37
 
45
38
  result
46
39
  end
@@ -57,7 +50,7 @@ module Tomo
57
50
  Tomo.logger
58
51
  end
59
52
 
60
- def build_args(script, verbose=false)
53
+ def build_args(script, verbose: false)
61
54
  options.build_args(host, script, control_path, verbose)
62
55
  end
63
56
 
@@ -69,13 +62,7 @@ module Tomo
69
62
  end
70
63
 
71
64
  def raise_run_error(script, ssh_args, result)
72
- ScriptError.raise_with(
73
- result.output,
74
- host: host,
75
- result: result,
76
- script: script,
77
- ssh_args: ssh_args
78
- )
65
+ ScriptError.raise_with(result.output, host: host, result: result, script: script, ssh_args: ssh_args)
79
66
  end
80
67
  end
81
68
  end
@@ -15,10 +15,10 @@ module Tomo
15
15
 
16
16
  def assert_valid_executable!
17
17
  result = begin
18
- ChildProcess.execute(executable, "-V")
19
- rescue StandardError => e
20
- handle_bad_executable(e)
21
- end
18
+ ChildProcess.execute(executable, "-V")
19
+ rescue StandardError => e
20
+ handle_bad_executable(e)
21
+ end
22
22
 
23
23
  Tomo.logger.debug(result.output)
24
24
  return if result.success? && supported?(result.output)
@@ -27,10 +27,7 @@ module Tomo
27
27
  end
28
28
 
29
29
  def assert_valid_connection!
30
- script = Script.new(
31
- "echo hi",
32
- silent: !Tomo.debug?, echo: false, raise_on_error: false
33
- )
30
+ script = Script.new("echo hi", silent: !Tomo.debug?, echo: false, raise_on_error: false)
34
31
  res = connection.ssh_subprocess(script, verbose: Tomo.debug?)
35
32
  raise_connection_failure(res) if res.exit_status == 255
36
33
  raise_unknown_error(res) if res.failure? || res.stdout.chomp != "hi"
@@ -7,8 +7,7 @@ module Tomo
7
7
  hint = if executable.to_s.include?("/")
8
8
  "Is the ssh binary properly installed in this location?"
9
9
  else
10
- "Is #{yellow(executable)} installed and in your "\
11
- "#{blue('$PATH')}?"
10
+ "Is #{yellow(executable)} installed and in your #{blue('$PATH')}?"
12
11
  end
13
12
 
14
13
  <<~ERROR
@@ -20,8 +20,7 @@ module Tomo
20
20
  freeze
21
21
  end
22
22
 
23
- # rubocop:disable Metrics/AbcSize
24
- def build_args(host, script, control_path, verbose)
23
+ def build_args(host, script, control_path, verbose) # rubocop:disable Metrics/AbcSize
25
24
  args = [verbose ? "-v" : ["-o", "LogLevel=ERROR"]]
26
25
  args << "-A" if forward_agent
27
26
  args << connect_timeout_option
@@ -34,13 +33,11 @@ module Tomo
34
33
 
35
34
  [executable, args, script.to_s].flatten
36
35
  end
37
- # rubocop:enable Metrics/AbcSize
38
36
 
39
37
  private
40
38
 
41
39
  attr_writer :executable
42
- attr_accessor :connect_timeout, :extra_opts, :forward_agent,
43
- :reuse_connections, :strict_host_key_checking
40
+ attr_accessor :connect_timeout, :extra_opts, :forward_agent, :reuse_connections, :strict_host_key_checking
44
41
 
45
42
  def control_opts(path, verbose)
46
43
  opts = [