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,78 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
require "time"
|
|
3
|
+
|
|
4
|
+
module Tomo::Plugin::Git
|
|
5
|
+
class Tasks < Tomo::TaskLibrary
|
|
6
|
+
# rubocop:disable Metrics/AbcSize
|
|
7
|
+
def clone
|
|
8
|
+
require_setting :git_url
|
|
9
|
+
|
|
10
|
+
if remote.directory?(paths.git_repo) && !dry_run?
|
|
11
|
+
set_origin_url
|
|
12
|
+
else
|
|
13
|
+
remote.mkdir_p(paths.git_repo.dirname)
|
|
14
|
+
remote.git(
|
|
15
|
+
"clone", "--mirror", settings[:git_url], paths.git_repo
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create_release
|
|
21
|
+
configure_git_attributes
|
|
22
|
+
remote.chdir(paths.git_repo) do
|
|
23
|
+
remote.git("remote update --prune")
|
|
24
|
+
remote.mkdir_p(paths.release)
|
|
25
|
+
remote.git(
|
|
26
|
+
"archive #{branch.shellescape} | "\
|
|
27
|
+
"tar -x -f - -C #{paths.release.shellescape}"
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
store_release_info
|
|
31
|
+
end
|
|
32
|
+
# rubocop:enable Metrics/AbcSize
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def branch
|
|
37
|
+
settings[:git_branch]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_origin_url
|
|
41
|
+
remote.chdir(paths.git_repo) do
|
|
42
|
+
remote.git("remote", "set-url", "origin", settings[:git_url])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def configure_git_attributes
|
|
47
|
+
exclusions = settings[:git_exclusions] || []
|
|
48
|
+
attributes = exclusions.map { |excl| "#{excl} export-ignore" }.join("\n")
|
|
49
|
+
|
|
50
|
+
remote.write(
|
|
51
|
+
text: attributes,
|
|
52
|
+
to: paths.git_repo.join("info/attributes")
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# rubocop:disable Metrics/AbcSize
|
|
57
|
+
# rubocop:disable Metrics/MethodLength
|
|
58
|
+
def store_release_info
|
|
59
|
+
log = remote.chdir(paths.git_repo) do
|
|
60
|
+
remote.git(
|
|
61
|
+
'log -n1 --date=iso --pretty=format:"%H/%cd/%ae" '\
|
|
62
|
+
"#{branch.shellescape}",
|
|
63
|
+
silent: true
|
|
64
|
+
).stdout.strip
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
sha, date, email = log.split("/", 3)
|
|
68
|
+
remote.release[:branch] = branch
|
|
69
|
+
remote.release[:author] = email
|
|
70
|
+
remote.release[:revision] = sha
|
|
71
|
+
remote.release[:revision_date] = date
|
|
72
|
+
remote.release[:deploy_date] = Time.now.to_s
|
|
73
|
+
remote.release[:deploy_user] = ENV["USER"] || ENV["USERNAME"]
|
|
74
|
+
end
|
|
75
|
+
# rubocop:enable Metrics/MethodLength
|
|
76
|
+
# rubocop:enable Metrics/AbcSize
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative "git/helpers"
|
|
2
|
+
require_relative "git/tasks"
|
|
3
|
+
|
|
4
|
+
module Tomo::Plugin
|
|
5
|
+
module Git
|
|
6
|
+
extend Tomo::PluginDSL
|
|
7
|
+
|
|
8
|
+
helpers Tomo::Plugin::Git::Helpers
|
|
9
|
+
tasks Tomo::Plugin::Git::Tasks
|
|
10
|
+
|
|
11
|
+
# rubocop:disable Metrics/LineLength
|
|
12
|
+
defaults git_branch: "master",
|
|
13
|
+
git_repo_path: "%<deploy_to>/git_repo",
|
|
14
|
+
git_exclusions: [],
|
|
15
|
+
git_env: { GIT_SSH_COMMAND: "ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no" },
|
|
16
|
+
git_url: nil
|
|
17
|
+
# rubocop:enable Metrics/LineLength
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
|
|
3
|
+
module Tomo::Plugin::Nvm
|
|
4
|
+
class Tasks < Tomo::TaskLibrary
|
|
5
|
+
def install
|
|
6
|
+
remote.mkdir_p raw("$HOME/.nvm")
|
|
7
|
+
modify_bashrc
|
|
8
|
+
run_installer
|
|
9
|
+
install_node
|
|
10
|
+
install_yarn
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def run_installer
|
|
16
|
+
require_setting :nvm_version
|
|
17
|
+
|
|
18
|
+
nvm_version = settings[:nvm_version]
|
|
19
|
+
install_url = "https://raw.githubusercontent.com/creationix/nvm/"\
|
|
20
|
+
"v#{nvm_version}/install.sh"
|
|
21
|
+
remote.run("curl -o- #{install_url.shellescape} | bash")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def modify_bashrc
|
|
25
|
+
existing_rc = remote.capture("cat", paths.bashrc, raise_on_error: false)
|
|
26
|
+
return if existing_rc.include?("nvm.sh")
|
|
27
|
+
|
|
28
|
+
remote.write(text: <<~BASHRC + existing_rc, to: paths.bashrc)
|
|
29
|
+
export NVM_DIR="$HOME/.nvm"
|
|
30
|
+
[ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"
|
|
31
|
+
|
|
32
|
+
BASHRC
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def install_node
|
|
36
|
+
require_setting :nvm_node_version
|
|
37
|
+
node_version = settings[:nvm_node_version]
|
|
38
|
+
|
|
39
|
+
unless node_installed?(node_version)
|
|
40
|
+
remote.run "nvm", "install", node_version
|
|
41
|
+
end
|
|
42
|
+
remote.run "nvm", "alias", "default", node_version
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def install_yarn
|
|
46
|
+
version = settings[:nvm_yarn_version]
|
|
47
|
+
return remote.run "npm i -g yarn@#{version.shellescape}" if version
|
|
48
|
+
|
|
49
|
+
logger.info "No :nvm_yarn_version specified; skipping yarn installation."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def node_installed?(version)
|
|
53
|
+
versions = remote.capture("nvm ls", raise_on_error: false)
|
|
54
|
+
if versions.include?("v#{version}")
|
|
55
|
+
logger.info("Node #{version} is already installed.")
|
|
56
|
+
return true
|
|
57
|
+
end
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require_relative "nvm/tasks"
|
|
2
|
+
|
|
3
|
+
module Tomo::Plugin
|
|
4
|
+
module Nvm
|
|
5
|
+
extend Tomo::PluginDSL
|
|
6
|
+
|
|
7
|
+
defaults bashrc_path: ".bashrc",
|
|
8
|
+
nvm_version: "0.34.0",
|
|
9
|
+
nvm_node_version: nil,
|
|
10
|
+
nvm_yarn_version: nil
|
|
11
|
+
|
|
12
|
+
tasks Tomo::Plugin::Nvm::Tasks
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Tomo::Plugin::Puma
|
|
2
|
+
class Tasks < Tomo::TaskLibrary
|
|
3
|
+
def restart
|
|
4
|
+
return if try_restart
|
|
5
|
+
|
|
6
|
+
remote.chdir(paths.current) do
|
|
7
|
+
logger.info "Puma is not running. Starting it now."
|
|
8
|
+
remote.bundle("exec", "puma", "--daemon", *control_options)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def try_restart
|
|
15
|
+
ctl_result = remote.chdir(paths.current) do
|
|
16
|
+
ctl_result = remote.bundle(
|
|
17
|
+
"exec", "pumactl", *control_options, "restart",
|
|
18
|
+
raise_on_error: false,
|
|
19
|
+
silent: true
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
return false if dry_run? || ctl_result.failure?
|
|
24
|
+
|
|
25
|
+
logger.info(ctl_result.output)
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def control_options
|
|
30
|
+
require_settings :puma_control_token, :puma_control_url
|
|
31
|
+
|
|
32
|
+
[
|
|
33
|
+
"--control-url", settings[:puma_control_url],
|
|
34
|
+
"--control-token", settings[:puma_control_token]
|
|
35
|
+
]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Tomo::Plugin::Rails
|
|
2
|
+
module Helpers
|
|
3
|
+
def rails(*args, **opts)
|
|
4
|
+
prepend("exec", "rails") do
|
|
5
|
+
bundle(*args, **opts)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def rake(*args, **opts)
|
|
10
|
+
prepend("exec", "rails") do
|
|
11
|
+
bundle(*args, **opts)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def rake?(*args, **opts)
|
|
16
|
+
result = rake(*args, **opts.merge(raise_on_error: false))
|
|
17
|
+
result.success?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module Tomo::Plugin::Rails
|
|
2
|
+
class Tasks < Tomo::TaskLibrary
|
|
3
|
+
def assets_precompile
|
|
4
|
+
remote.rake("assets:precompile")
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def console
|
|
8
|
+
remote.rails("console", settings[:run_args], attach: true)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def db_migrate
|
|
12
|
+
remote.rake("db:migrate")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def db_seed
|
|
16
|
+
remote.rake("db:seed")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def db_create
|
|
20
|
+
return remote.rake("db:create") unless database_exists?
|
|
21
|
+
|
|
22
|
+
logger.info "Database already exists; skipping db:create."
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def db_setup
|
|
26
|
+
return remote.rake("db:setup") unless database_exists?
|
|
27
|
+
|
|
28
|
+
logger.info "Database already exists; skipping db:setup."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def db_schema_load
|
|
32
|
+
if !schema_rb_present?
|
|
33
|
+
logger.warn "db/schema.rb is not present; skipping schema:load."
|
|
34
|
+
elsif database_schema_loaded?
|
|
35
|
+
logger.info "Database schema already loaded; skipping db:schema:load."
|
|
36
|
+
else
|
|
37
|
+
remote.rake("db:schema:load")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def db_structure_load
|
|
42
|
+
if !structure_sql_present?
|
|
43
|
+
logger.warn "db/structure.sql is not present; "\
|
|
44
|
+
"skipping db:structure:load."
|
|
45
|
+
elsif database_schema_loaded?
|
|
46
|
+
logger.info "Database structure already loaded; "\
|
|
47
|
+
"skipping db:structure:load."
|
|
48
|
+
else
|
|
49
|
+
remote.rake("db:structure:load")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def log_tail
|
|
54
|
+
log_path = raw("#{paths.release.to_s.shellescape}/log/${RAILS_ENV}.log")
|
|
55
|
+
remote.run("tail", settings[:run_args], log_path)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def database_exists?
|
|
61
|
+
remote.rake?("db:version", silent: true) && !dry_run?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def database_schema_loaded?
|
|
65
|
+
result = remote.rake("db:version", silent: true, raise_on_error: false)
|
|
66
|
+
schema_version = result.output[/version:\s*(\d+)$/i, 1].to_i
|
|
67
|
+
|
|
68
|
+
result.success? && schema_version.positive? && !dry_run?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def schema_rb_present?
|
|
72
|
+
remote.file?(paths.release.join("db/schema.rb"))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def structure_sql_present?
|
|
76
|
+
remote.file?(paths.release.join("db/structure.sql"))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
|
|
3
|
+
module Tomo::Plugin::Rbenv
|
|
4
|
+
class Tasks < Tomo::TaskLibrary
|
|
5
|
+
def install
|
|
6
|
+
run_installer
|
|
7
|
+
modify_bashrc
|
|
8
|
+
compile_ruby
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def run_installer
|
|
14
|
+
install_url = "https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer"
|
|
15
|
+
remote.env PATH: raw("$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH") do
|
|
16
|
+
remote.run("curl -fsSL #{install_url.shellescape} | bash")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def modify_bashrc
|
|
21
|
+
existing_rc = remote.capture("cat", paths.bashrc, raise_on_error: false)
|
|
22
|
+
return if existing_rc.include?("rbenv init")
|
|
23
|
+
|
|
24
|
+
remote.write(text: <<~BASHRC + existing_rc, to: paths.bashrc)
|
|
25
|
+
if [ -d $HOME/.rbenv ]; then
|
|
26
|
+
export PATH="$HOME/.rbenv/bin:$PATH"
|
|
27
|
+
eval "$(rbenv init -)"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
BASHRC
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def compile_ruby
|
|
34
|
+
require_setting :rbenv_ruby_version
|
|
35
|
+
ruby_version = settings[:rbenv_ruby_version]
|
|
36
|
+
|
|
37
|
+
unless ruby_installed?(ruby_version)
|
|
38
|
+
logger.info(
|
|
39
|
+
"Installing ruby #{ruby_version} -- this may take several minutes"
|
|
40
|
+
)
|
|
41
|
+
remote.run "CFLAGS=-O3 rbenv install #{ruby_version.shellescape}"
|
|
42
|
+
end
|
|
43
|
+
remote.run "rbenv global #{ruby_version.shellescape}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ruby_installed?(version)
|
|
47
|
+
versions = remote.capture("rbenv versions", raise_on_error: false)
|
|
48
|
+
if versions.include?(version)
|
|
49
|
+
logger.info("Ruby #{version} is already installed.")
|
|
50
|
+
return true
|
|
51
|
+
end
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
unless defined?(Tomo::Testing)
|
|
2
|
+
raise "The testing plugin cannot be used outside of unit tests"
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
module Tomo::Plugin
|
|
6
|
+
class Testing < Tomo::TaskLibrary
|
|
7
|
+
extend Tomo::PluginDSL
|
|
8
|
+
tasks self
|
|
9
|
+
|
|
10
|
+
def call_helper
|
|
11
|
+
helper, args, kwargs = settings[:run_args]
|
|
12
|
+
value = remote.public_send(helper, *args, **(kwargs || {}))
|
|
13
|
+
remote.host.helper_values << value
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/tomo/plugin.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Tomo
|
|
2
|
+
module PluginDSL
|
|
3
|
+
def self.extended(mod)
|
|
4
|
+
mod.instance_variable_set(:@helper_modules, [])
|
|
5
|
+
mod.instance_variable_set(:@default_settings, {})
|
|
6
|
+
mod.instance_variable_set(:@tasks_classes, [])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
attr_reader :helper_modules, :default_settings, :tasks_classes
|
|
10
|
+
|
|
11
|
+
def helpers(mod, *more_mods)
|
|
12
|
+
@helper_modules.push(mod, *more_mods)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def defaults(settings)
|
|
16
|
+
@default_settings.merge!(settings)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tasks(tasks_class, *more_tasks_classes)
|
|
20
|
+
@tasks_classes.push(tasks_class, *more_tasks_classes)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/tomo/remote.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
|
|
3
|
+
module Tomo
|
|
4
|
+
class Remote
|
|
5
|
+
extend Forwardable
|
|
6
|
+
def_delegators :ssh, :close, :host
|
|
7
|
+
def_delegators :shell_builder, :chdir, :env, :prepend, :umask
|
|
8
|
+
|
|
9
|
+
attr_reader :release
|
|
10
|
+
|
|
11
|
+
def initialize(ssh, context, helper_modules)
|
|
12
|
+
@ssh = ssh
|
|
13
|
+
@context = context
|
|
14
|
+
@release = {}
|
|
15
|
+
@shell_builder = ShellBuilder.new
|
|
16
|
+
helper_modules.each { |mod| extend(mod) }
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def attach(*command, default_chdir: nil, **command_opts)
|
|
21
|
+
full_command = shell_builder.build(*command, default_chdir: default_chdir)
|
|
22
|
+
ssh.ssh_exec(
|
|
23
|
+
Script.new(full_command, { pty: true }.merge(**command_opts))
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run(*command, attach: false, default_chdir: nil, **command_opts)
|
|
28
|
+
attach(*command, default_chdir: default_chdir, **command_opts) if attach
|
|
29
|
+
|
|
30
|
+
full_command = shell_builder.build(*command, default_chdir: default_chdir)
|
|
31
|
+
ssh.ssh_subprocess(Script.new(full_command, **command_opts))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def_delegators :context, :paths, :settings
|
|
37
|
+
attr_reader :context, :ssh, :shell_builder
|
|
38
|
+
|
|
39
|
+
def dry_run?
|
|
40
|
+
Tomo.dry_run?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def logger
|
|
44
|
+
Tomo.logger
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def raw(str)
|
|
48
|
+
ShellBuilder.raw(str)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def remote
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/tomo/result.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Tomo
|
|
2
|
+
class Result
|
|
3
|
+
def self.empty_success
|
|
4
|
+
new(stdout: "", stderr: "", exit_status: 0)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
attr_reader :stdout, :stderr, :exit_status
|
|
8
|
+
|
|
9
|
+
def initialize(stdout:, stderr:, exit_status:)
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
@stderr = stderr
|
|
12
|
+
@exit_status = exit_status
|
|
13
|
+
freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def success?
|
|
17
|
+
exit_status.zero?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def failure?
|
|
21
|
+
!success?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def output
|
|
25
|
+
[stdout, stderr].compact.join
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Tomo
|
|
2
|
+
class Runtime
|
|
3
|
+
class ConcurrentRubyLoadError < Tomo::Error
|
|
4
|
+
attr_accessor :version
|
|
5
|
+
|
|
6
|
+
def to_console
|
|
7
|
+
<<~ERROR
|
|
8
|
+
Running tasks on multiple hosts requires the #{yellow('concurrent-ruby')} gem.
|
|
9
|
+
To install it, #{install_instructions}
|
|
10
|
+
ERROR
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def install_instructions
|
|
16
|
+
if Tomo.bundled?
|
|
17
|
+
gem_entry = %Q(gem "concurrent-ruby", "#{version}")
|
|
18
|
+
"add this entry to your Gemfile:\n\n #{blue(gem_entry)}"
|
|
19
|
+
else
|
|
20
|
+
gem_install = "gem install concurrent-ruby -v '#{version}'"
|
|
21
|
+
"run:\n\n #{blue(gem_install)}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
concurrent_ver = "~> 1.1"
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
gem "concurrent-ruby", concurrent_ver
|
|
5
|
+
require "concurrent"
|
|
6
|
+
rescue LoadError => e
|
|
7
|
+
Tomo::Runtime::ConcurrentRubyLoadError.raise_with(
|
|
8
|
+
e.message,
|
|
9
|
+
version: concurrent_ver
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module Tomo
|
|
14
|
+
class Runtime
|
|
15
|
+
class ConcurrentRubyThreadPool
|
|
16
|
+
include ::Concurrent::Promises::FactoryMethods
|
|
17
|
+
|
|
18
|
+
def initialize(size)
|
|
19
|
+
@executor = ::Concurrent::FixedThreadPool.new(size)
|
|
20
|
+
@promises = []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def post(*args, &block)
|
|
24
|
+
return if failure?
|
|
25
|
+
|
|
26
|
+
promises << future_on(executor, *args, &block)
|
|
27
|
+
.on_rejection_using(executor) do |reason|
|
|
28
|
+
self.failure = reason
|
|
29
|
+
end
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def run_to_completion
|
|
34
|
+
promises_to_wait = promises.dup
|
|
35
|
+
promises.clear
|
|
36
|
+
zip_futures_on(executor, *promises_to_wait).value
|
|
37
|
+
raise failure if failure?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def failure?
|
|
41
|
+
!!failure
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
attr_accessor :failure
|
|
47
|
+
attr_reader :executor, :promises
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Tomo
|
|
2
|
+
class Runtime
|
|
3
|
+
class Context
|
|
4
|
+
attr_reader :paths, :settings
|
|
5
|
+
|
|
6
|
+
def initialize(settings)
|
|
7
|
+
@paths = Paths.new(settings)
|
|
8
|
+
@settings = settings.freeze
|
|
9
|
+
freeze
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def current_remote
|
|
13
|
+
Current.remote
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def current_task
|
|
17
|
+
Current.task
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Tomo
|
|
2
|
+
class Runtime
|
|
3
|
+
module Current
|
|
4
|
+
class << self
|
|
5
|
+
def host
|
|
6
|
+
fiber_locals[:host] || remote&.host
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def remote
|
|
10
|
+
fiber_locals[:remote]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def task
|
|
14
|
+
fiber_locals[:task]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def with(new_locals)
|
|
18
|
+
old_locals = slice(*new_locals.keys)
|
|
19
|
+
fiber_locals.merge!(new_locals)
|
|
20
|
+
yield
|
|
21
|
+
ensure
|
|
22
|
+
fiber_locals.merge!(old_locals)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def variables
|
|
26
|
+
fiber_locals.dup.freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def slice(*keys)
|
|
32
|
+
Hash[keys.map { |key| [key, fiber_locals[key]] }]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fiber_locals
|
|
36
|
+
Thread.current["Tomo::Runtime::Current"] ||= {}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|