tomo 0.19.0 → 1.2.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -6
  3. data/lib/tomo/cli.rb +0 -2
  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/switch.rb +1 -5
  10. data/lib/tomo/cli/rules/value_switch.rb +1 -2
  11. data/lib/tomo/cli/rules_evaluator.rb +3 -13
  12. data/lib/tomo/cli/usage.rb +1 -3
  13. data/lib/tomo/commands/default.rb +1 -3
  14. data/lib/tomo/commands/init.rb +24 -1
  15. data/lib/tomo/commands/run.rb +1 -3
  16. data/lib/tomo/configuration.rb +5 -11
  17. data/lib/tomo/configuration/dsl/hosts_and_settings.rb +1 -2
  18. data/lib/tomo/configuration/plugin_resolver.rb +1 -1
  19. data/lib/tomo/configuration/plugins_registry.rb +1 -2
  20. data/lib/tomo/configuration/plugins_registry/gem_resolver.rb +1 -1
  21. data/lib/tomo/configuration/unknown_environment_error.rb +1 -4
  22. data/lib/tomo/console.rb +2 -8
  23. data/lib/tomo/console/menu.rb +1 -2
  24. data/lib/tomo/host.rb +1 -2
  25. data/lib/tomo/plugin/bundler/tasks.rb +2 -7
  26. data/lib/tomo/plugin/core/tasks.rb +3 -12
  27. data/lib/tomo/plugin/env/tasks.rb +2 -5
  28. data/lib/tomo/plugin/git.rb +1 -4
  29. data/lib/tomo/plugin/git/tasks.rb +4 -14
  30. data/lib/tomo/plugin/nodenv/tasks.rb +1 -3
  31. data/lib/tomo/plugin/puma.rb +0 -3
  32. data/lib/tomo/plugin/puma/systemd/service.erb +1 -1
  33. data/lib/tomo/plugin/puma/tasks.rb +6 -15
  34. data/lib/tomo/plugin/rails/helpers.rb +1 -1
  35. data/lib/tomo/plugin/rails/tasks.rb +6 -4
  36. data/lib/tomo/plugin/testing.rb +1 -3
  37. data/lib/tomo/remote.rb +1 -3
  38. data/lib/tomo/runtime.rb +3 -6
  39. data/lib/tomo/runtime/concurrent_ruby_thread_pool.rb +1 -4
  40. data/lib/tomo/runtime/execution_plan.rb +1 -4
  41. data/lib/tomo/runtime/explanation.rb +1 -7
  42. data/lib/tomo/runtime/settings_interpolation.rb +1 -3
  43. data/lib/tomo/runtime/settings_required_error.rb +1 -3
  44. data/lib/tomo/runtime/task_runner.rb +2 -7
  45. data/lib/tomo/runtime/unknown_task_error.rb +1 -4
  46. data/lib/tomo/script.rb +1 -5
  47. data/lib/tomo/shell_builder.rb +5 -10
  48. data/lib/tomo/ssh/child_process.rb +6 -13
  49. data/lib/tomo/ssh/connection.rb +3 -16
  50. data/lib/tomo/ssh/connection_validator.rb +1 -4
  51. data/lib/tomo/ssh/executable_error.rb +1 -2
  52. data/lib/tomo/ssh/options.rb +2 -5
  53. data/lib/tomo/task_api.rb +4 -15
  54. data/lib/tomo/templates/config.rb.erb +7 -1
  55. data/lib/tomo/testing.rb +0 -2
  56. data/lib/tomo/testing/Dockerfile +1 -3
  57. data/lib/tomo/testing/connection.rb +1 -6
  58. data/lib/tomo/testing/docker_image.rb +4 -17
  59. data/lib/tomo/testing/local.rb +1 -3
  60. data/lib/tomo/testing/mock_plugin_tester.rb +27 -4
  61. data/lib/tomo/testing/ubuntu_setup.sh +1 -2
  62. data/lib/tomo/version.rb +1 -1
  63. metadata +9 -151
  64. data/lib/tomo/testing/docker_plugin_tester.rb +0 -39
  65. data/lib/tomo/testing/plugin_tester.rb +0 -33
@@ -12,8 +12,7 @@ module Tomo
12
12
  extend Forwardable
13
13
  include Colors
14
14
 
15
- def initialize(question, options, key_reader: KeyReader.new,
16
- output: $stdout)
15
+ def initialize(question, options, key_reader: KeyReader.new, output: $stdout)
17
16
  @question = question
18
17
  @options = options
19
18
  @position = 0
@@ -13,8 +13,7 @@ module Tomo
13
13
  new(**{ user: user, address: address }.merge(kwargs))
14
14
  end
15
15
 
16
- def initialize(address:, port: nil, log_prefix: nil, roles: nil,
17
- user: nil, privileged_user: "root")
16
+ def initialize(address:, port: nil, log_prefix: nil, roles: nil, user: nil, privileged_user: "root")
18
17
  @user = user.freeze
19
18
  @port = (port || 22).to_i.freeze
20
19
  @address = address.freeze
@@ -30,12 +30,7 @@ module Tomo::Plugin::Bundler
30
30
 
31
31
  def upgrade_bundler
32
32
  needed_bundler_ver = version_setting || extract_bundler_ver_from_lockfile
33
-
34
- remote.run(
35
- "gem", "install", "bundler",
36
- "--conservative", "--no-document",
37
- "-v", needed_bundler_ver
38
- )
33
+ remote.run("gem", "install", "bundler", "--conservative", "--no-document", "-v", needed_bundler_ver)
39
34
  end
40
35
 
41
36
  private
@@ -61,7 +56,7 @@ module Tomo::Plugin::Bundler
61
56
  raise_on_error: false
62
57
  )
63
58
  version = lockfile_tail[/BUNDLED WITH\n (\S+)$/, 1]
64
- return version if version
59
+ return version if version || dry_run?
65
60
 
66
61
  die <<~REASON
67
62
  Could not guess bundler version from Gemfile.lock.
@@ -34,8 +34,7 @@ module Tomo::Plugin::Core
34
34
  remote.run "mv", "-fT", tmp_link, paths.current
35
35
  end
36
36
 
37
- # rubocop:disable Metrics/AbcSize
38
- def clean_releases
37
+ def clean_releases # rubocop:disable Metrics/AbcSize
39
38
  desired_count = settings[:keep_releases].to_i
40
39
  return if desired_count < 1
41
40
 
@@ -49,15 +48,13 @@ module Tomo::Plugin::Core
49
48
  remote.rm_rf(*releases.take(releases.length - desired_count))
50
49
  end
51
50
  end
52
- # rubocop:enable Metrics/AbcSize
53
51
 
54
52
  def write_release_json
55
53
  json = JSON.pretty_generate(remote.release)
56
54
  remote.write(text: "#{json}\n", to: paths.release_json)
57
55
  end
58
56
 
59
- # rubocop:disable Metrics/AbcSize
60
- def log_revision
57
+ def log_revision # rubocop:disable Metrics/AbcSize
61
58
  ref = remote.release[:ref]
62
59
  revision = remote.release[:revision]
63
60
 
@@ -69,7 +66,6 @@ module Tomo::Plugin::Core
69
66
 
70
67
  remote.write(text: message, to: paths.revision_log, append: true)
71
68
  end
72
- # rubocop:enable Metrics/AbcSize
73
69
 
74
70
  private
75
71
 
@@ -123,12 +119,7 @@ module Tomo::Plugin::Core
123
119
  end
124
120
 
125
121
  def read_current_release
126
- result = remote.run(
127
- "readlink",
128
- paths.current,
129
- raise_on_error: false,
130
- silent: true
131
- )
122
+ result = remote.run("readlink", paths.current, raise_on_error: false, silent: true)
132
123
  return nil if result.failure?
133
124
 
134
125
  result.stdout.strip[%r{/(#{RELEASE_REGEXP})$}, 1]
@@ -65,10 +65,7 @@ module Tomo::Plugin::Env
65
65
  end
66
66
 
67
67
  def read_existing
68
- remote.capture(
69
- "cat", paths.env,
70
- raise_on_error: false, echo: false, silent: true
71
- )
68
+ remote.capture("cat", paths.env, raise_on_error: false, echo: false, silent: true)
72
69
  end
73
70
 
74
71
  def replace_entry(text, name, value)
@@ -82,7 +79,7 @@ module Tomo::Plugin::Env
82
79
 
83
80
  def prepend_entry(text, name, value)
84
81
  text.prepend("\n") unless text.start_with?("\n")
85
- text.prepend("export #{name.to_s.shellescape}=#{value.shellescape}")
82
+ text.prepend("export #{name.to_s.shellescape}=#{value.to_s.shellescape}")
86
83
  end
87
84
 
88
85
  def contains_entry?(text, name)
@@ -7,14 +7,11 @@ module Tomo::Plugin
7
7
 
8
8
  helpers Tomo::Plugin::Git::Helpers
9
9
  tasks Tomo::Plugin::Git::Tasks
10
-
11
- # rubocop:disable Layout/LineLength
12
- defaults git_branch: "master",
10
+ defaults git_branch: nil,
13
11
  git_repo_path: "%{deploy_to}/git_repo",
14
12
  git_exclusions: [],
15
13
  git_env: { GIT_SSH_COMMAND: "ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no" },
16
14
  git_ref: nil,
17
15
  git_url: nil
18
- # rubocop:enable Layout/LineLength
19
16
  end
20
17
  end
@@ -3,7 +3,6 @@ require "time"
3
3
 
4
4
  module Tomo::Plugin::Git
5
5
  class Tasks < Tomo::TaskLibrary
6
- # rubocop:disable Metrics/AbcSize
7
6
  def clone
8
7
  require_setting :git_url
9
8
 
@@ -15,7 +14,7 @@ module Tomo::Plugin::Git
15
14
  end
16
15
  end
17
16
 
18
- def create_release
17
+ def create_release # rubocop:disable Metrics/AbcSize
19
18
  remote.chdir(paths.git_repo) do
20
19
  remote.git("remote update --prune")
21
20
  end
@@ -31,7 +30,6 @@ module Tomo::Plugin::Git
31
30
  )
32
31
  end
33
32
  end
34
- # rubocop:enable Metrics/AbcSize
35
33
 
36
34
  private
37
35
 
@@ -64,19 +62,13 @@ module Tomo::Plugin::Git
64
62
  exclusions = settings[:git_exclusions] || []
65
63
  attributes = exclusions.map { |excl| "#{excl} export-ignore" }.join("\n")
66
64
 
67
- remote.write(
68
- text: attributes,
69
- to: paths.git_repo.join("info/attributes")
70
- )
65
+ remote.write(text: attributes, to: paths.git_repo.join("info/attributes"))
71
66
  end
72
67
 
73
- # rubocop:disable Metrics/AbcSize
74
- # rubocop:disable Metrics/MethodLength
75
- def store_release_info
68
+ def store_release_info # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
76
69
  log = remote.chdir(paths.git_repo) do
77
70
  remote.git(
78
- 'log -n1 --date=iso --pretty=format:"%H/%cd/%ae" '\
79
- "#{ref.shellescape} --",
71
+ %Q(log -n1 --date=iso --pretty=format:"%H/%cd/%ae" #{ref.shellescape} --),
80
72
  silent: true
81
73
  ).stdout.strip
82
74
  end
@@ -90,7 +82,5 @@ module Tomo::Plugin::Git
90
82
  remote.release[:deploy_date] = Time.now.to_s
91
83
  remote.release[:deploy_user] = settings.fetch(:local_user)
92
84
  end
93
- # rubocop:enable Metrics/MethodLength
94
- # rubocop:enable Metrics/AbcSize
95
85
  end
96
86
  end
@@ -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
@@ -19,4 +19,4 @@ WorkingDirectory=<%= paths.current %>
19
19
  # Environment=PUMA_DEBUG=1
20
20
 
21
21
  [Install]
22
- WantedBy=multi-user.target
22
+ WantedBy=default.target
@@ -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
@@ -7,7 +7,7 @@ module Tomo::Plugin::Rails
7
7
  end
8
8
 
9
9
  def rake(*args, **opts)
10
- prepend("exec", "rails") do
10
+ prepend("exec", "rake") do
11
11
  bundle(*args, **opts)
12
12
  end
13
13
  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
@@ -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
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
 
@@ -35,9 +35,7 @@ module Tomo
35
35
  end
36
36
 
37
37
  def symbolize(hash)
38
- hash.each_with_object({}) do |(key, value), symbolized|
39
- symbolized[key.to_sym] = value
40
- end
38
+ hash.transform_keys(&:to_sym)
41
39
  end
42
40
 
43
41
  def dump_settings(hash)
@@ -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:)
@@ -53,8 +49,7 @@ module Tomo
53
49
  attr_reader :helper_modules, :tasks_by_name
54
50
 
55
51
  def ssh_options
56
- # TODO: replace with Hash#slice after dropping Ruby 2.4 support
57
- settings.select { |key| SSH::Options::DEFAULTS.key?(key) }
52
+ settings.slice(*SSH::Options::DEFAULTS.keys)
58
53
  end
59
54
  end
60
55
  end