tomo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|