tomo 1.1.0 → 1.3.1

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  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 +24 -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 +32 -10
  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/helpers.rb +1 -1
  37. data/lib/tomo/plugin/rails/tasks.rb +6 -4
  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 +1 -4
  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 +7 -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 +2 -8
  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
@@ -39,7 +39,7 @@ module Tomo
39
39
  def constantize(path)
40
40
  parts = path.split("/")
41
41
  parts.reduce(Object) do |parent, part|
42
- child = part.gsub(/^[a-z]|_[a-z]/) { |str| str.chars.last.upcase }
42
+ child = part.gsub(/^[a-z]|_[a-z]/) { |str| str[-1].upcase }
43
43
  parent.const_get(child, false)
44
44
  end
45
45
  end
@@ -36,10 +36,7 @@ module Tomo
36
36
  end
37
37
 
38
38
  def suggestions
39
- @_suggestions ||= Error::Suggestions.new(
40
- dictionary: known_environments,
41
- word: name
42
- )
39
+ @_suggestions ||= Error::Suggestions.new(dictionary: known_environments, word: name)
43
40
  end
44
41
  end
45
42
  end
@@ -12,22 +12,20 @@ module Tomo
12
12
  def_delegators :@instance, :interactive?, :prompt, :menu
13
13
  end
14
14
 
15
- def initialize(env=ENV, input=$stdin)
15
+ def initialize(env=ENV, input=$stdin, output=$stdout)
16
16
  @env = env
17
17
  @input = input
18
+ @output = output
18
19
  end
19
20
 
20
21
  def interactive?
21
- input.respond_to?(:raw) &&
22
- input.respond_to?(:tty?) &&
23
- input.tty? &&
24
- !ci?
22
+ input.respond_to?(:raw) && input.respond_to?(:tty?) && input.tty? && !ci?
25
23
  end
26
24
 
27
25
  def prompt(question)
28
26
  assert_interactive
29
27
 
30
- print question
28
+ output.print question
31
29
  line = input.gets
32
30
  raise_non_interactive if line.nil?
33
31
 
@@ -42,7 +40,7 @@ module Tomo
42
40
 
43
41
  private
44
42
 
45
- attr_reader :env, :input
43
+ attr_reader :env, :input, :output
46
44
 
47
45
  CI_VARS = %w[
48
46
  JENKINS_HOME
@@ -66,10 +64,7 @@ module Tomo
66
64
  end
67
65
 
68
66
  def raise_non_interactive
69
- NonInteractiveError.raise_with(
70
- task: Runtime::Current.task,
71
- ci_var: (env.keys & CI_VARS).first
72
- )
67
+ NonInteractiveError.raise_with(task: Runtime::Current.task, ci_var: (env.keys & CI_VARS).first)
73
68
  end
74
69
  end
75
70
  end
@@ -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
@@ -20,7 +20,7 @@ module Tomo
20
20
  path(:"#{method}_path")
21
21
  end
22
22
 
23
- def respond_to_missing?(method, include_private=false)
23
+ def respond_to_missing?(method, include_private)
24
24
  setting?(method) || super
25
25
  end
26
26
 
@@ -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.
@@ -12,7 +12,7 @@ module Tomo::Plugin::Core
12
12
  result.success?
13
13
  end
14
14
 
15
- def write(text: nil, template: nil, to:, append: false, **run_opts)
15
+ def write(to:, text: nil, template: nil, append: false, **run_opts)
16
16
  assert_text_or_template_required!(text, template)
17
17
  text = merge_template(template) unless template.nil?
18
18
  message = "Writing #{text.bytesize} bytes to #{to}"
@@ -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]
@@ -1,7 +1,7 @@
1
1
  require "monitor"
2
2
 
3
3
  module Tomo::Plugin::Env
4
- class Tasks < Tomo::TaskLibrary
4
+ class Tasks < Tomo::TaskLibrary # rubocop:disable Metrics/ClassLength
5
5
  include MonitorMixin
6
6
 
7
7
  def show
@@ -10,8 +10,8 @@ module Tomo::Plugin::Env
10
10
  end
11
11
 
12
12
  def setup
13
- update
14
13
  modify_bashrc
14
+ update
15
15
  end
16
16
 
17
17
  def update
@@ -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)
@@ -106,12 +103,37 @@ module Tomo::Plugin::Env
106
103
  existing_rc = remote.capture("cat", paths.bashrc, raise_on_error: false)
107
104
  return if existing_rc.include?(". #{env_path}")
108
105
 
106
+ fail_if_different_app_already_configured!(existing_rc)
107
+
109
108
  remote.write(text: <<~BASHRC + existing_rc, to: paths.bashrc)
110
- if [ -f #{env_path} ]; then
111
- . #{env_path}
112
- fi
109
+ if [ -f #{env_path} ]; then # DO NOT MODIFY THESE LINES
110
+ . #{env_path} # ENV MAINTAINED BY TOMO
111
+ fi #{' ' * env_path.to_s.length}# END TOMO ENV
113
112
 
114
113
  BASHRC
115
114
  end
115
+
116
+ def fail_if_different_app_already_configured!(bashrc)
117
+ existing_env_path = bashrc[/\s*\.\s+(.+)\s+# ENV MAINTAINED BY TOMO/, 1]
118
+ return if existing_env_path.nil?
119
+
120
+ die <<~REASON
121
+ Based on the contents of #{paths.bashrc}, it looks like another application
122
+ is already being deployed via tomo to this host, using the following envrc
123
+ path:
124
+
125
+ #{existing_env_path}
126
+
127
+ Tomo is designed such that only one application can be deployed to a given
128
+ user@host. To deploy multiple applications to the same host, use a separate
129
+ deployer user per app. Refer to the tomo FAQ for details:
130
+
131
+ https://tomo-deploy.com/#faq
132
+
133
+ You may be receiving this message in error if you recently renamed or
134
+ reconfigured your application. In this case, remove the references to the
135
+ old envrc path in the host's #{paths.bashrc} and re-run env:setup.
136
+ REASON
137
+ end
116
138
  end
117
139
  end
@@ -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
@@ -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