luban 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +37 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/luban +3 -0
- data/lib/luban/deployment/cli/application/authenticator.rb +106 -0
- data/lib/luban/deployment/cli/application/base.rb +179 -0
- data/lib/luban/deployment/cli/application/builder.rb +67 -0
- data/lib/luban/deployment/cli/application/publisher.rb +215 -0
- data/lib/luban/deployment/cli/application/repository.rb +175 -0
- data/lib/luban/deployment/cli/application/scm/git.rb +49 -0
- data/lib/luban/deployment/cli/application/scm/rsync.rb +47 -0
- data/lib/luban/deployment/cli/application.rb +5 -0
- data/lib/luban/deployment/cli/command.rb +360 -0
- data/lib/luban/deployment/cli/package/binary.rb +241 -0
- data/lib/luban/deployment/cli/package/dependency.rb +49 -0
- data/lib/luban/deployment/cli/package/dependency_set.rb +71 -0
- data/lib/luban/deployment/cli/package/installer/core.rb +98 -0
- data/lib/luban/deployment/cli/package/installer/install.rb +330 -0
- data/lib/luban/deployment/cli/package/installer/paths.rb +81 -0
- data/lib/luban/deployment/cli/package/installer.rb +3 -0
- data/lib/luban/deployment/cli/package/service.rb +17 -0
- data/lib/luban/deployment/cli/package/worker.rb +43 -0
- data/lib/luban/deployment/cli/package.rb +6 -0
- data/lib/luban/deployment/cli/project.rb +94 -0
- data/lib/luban/deployment/cli.rb +4 -0
- data/lib/luban/deployment/configuration/core.rb +67 -0
- data/lib/luban/deployment/configuration/filter.rb +54 -0
- data/lib/luban/deployment/configuration/question.rb +38 -0
- data/lib/luban/deployment/configuration/server.rb +70 -0
- data/lib/luban/deployment/configuration/server_set.rb +86 -0
- data/lib/luban/deployment/configuration.rb +5 -0
- data/lib/luban/deployment/error.rb +5 -0
- data/lib/luban/deployment/helpers/configuration.rb +159 -0
- data/lib/luban/deployment/helpers/utils.rb +180 -0
- data/lib/luban/deployment/helpers.rb +2 -0
- data/lib/luban/deployment/packages/bundler.rb +81 -0
- data/lib/luban/deployment/packages/git.rb +37 -0
- data/lib/luban/deployment/packages/openssl.rb +59 -0
- data/lib/luban/deployment/packages/ruby.rb +125 -0
- data/lib/luban/deployment/packages/rubygems.rb +89 -0
- data/lib/luban/deployment/packages/yaml.rb +33 -0
- data/lib/luban/deployment/parameters.rb +160 -0
- data/lib/luban/deployment/runner.rb +99 -0
- data/lib/luban/deployment/templates/envrc.erb +30 -0
- data/lib/luban/deployment/templates/unset_envrc.erb +28 -0
- data/lib/luban/deployment/version.rb +5 -0
- data/lib/luban/deployment/worker/base.rb +71 -0
- data/lib/luban/deployment/worker/controller.rb +11 -0
- data/lib/luban/deployment/worker/local.rb +19 -0
- data/lib/luban/deployment/worker/remote.rb +55 -0
- data/lib/luban/deployment/worker/task.rb +25 -0
- data/lib/luban/deployment/worker.rb +4 -0
- data/lib/luban/deployment.rb +8 -0
- data/lib/luban.rb +4 -0
- data/luban.gemspec +29 -0
- metadata +174 -0
@@ -0,0 +1,175 @@
|
|
1
|
+
module Luban
|
2
|
+
module Deployment
|
3
|
+
class Application
|
4
|
+
class Repository < Luban::Deployment::Worker::Local
|
5
|
+
using Luban::CLI::CoreRefinements
|
6
|
+
|
7
|
+
DefaultRevisionSize = 12
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
attr_reader :from
|
11
|
+
attr_reader :scm
|
12
|
+
attr_reader :revision
|
13
|
+
attr_reader :rev_size
|
14
|
+
|
15
|
+
def scm_module
|
16
|
+
require_relative "scm/#{scm}"
|
17
|
+
@scm_module ||= SCM.const_get(scm.camelcase)
|
18
|
+
end
|
19
|
+
|
20
|
+
def workspace_path
|
21
|
+
@workspace_path ||= app_path.join('.luban')
|
22
|
+
end
|
23
|
+
|
24
|
+
def clone_path
|
25
|
+
@clone_path ||= workspace_path.join('repositories').join(name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def releases_path
|
29
|
+
@releases_path ||= workspace_path.join('releases').join(name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def release_package_path
|
33
|
+
@release_package_path ||= releases_path.join(release_package_file_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def release_package_file_name
|
37
|
+
@release_package_file_name ||= "#{release_package_name}.#{release_package_extname}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def release_package_name
|
41
|
+
@release_package_name ||= "#{release_prefix}-#{release_tag}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def release_package_extname
|
45
|
+
@release_package_extname ||= 'tgz'
|
46
|
+
end
|
47
|
+
|
48
|
+
def release_prefix
|
49
|
+
@release_prefix ||= "#{stage}-#{project}-#{application}-#{name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def release_tag
|
53
|
+
@release_tag ||= "#{stage}-#{revision}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def bundle_without
|
57
|
+
@bundle_without ||= %w(development test)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Description on abstract methods:
|
61
|
+
# available?: check if the remote repository is available
|
62
|
+
# cloned?: check if the remote repository is cloned locally
|
63
|
+
# fetch_revision: retrieve the signature/checksum of the commit that will be deployed
|
64
|
+
# clone: clone a new copy of the remote repository
|
65
|
+
# update: update the clone of the remote repository
|
66
|
+
# release: copy the contents of cloned repository onto the release path
|
67
|
+
[:available?, :cloned?, :fetch_revision, :clone, :update, :release].each do |method|
|
68
|
+
define_method(method) do
|
69
|
+
raise NotImplementedError, "\#{self.class.name}##{__method__} is an abstract method."
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def build
|
74
|
+
assure_dirs(clone_path, releases_path)
|
75
|
+
if cloned? and !force?
|
76
|
+
update_revision
|
77
|
+
update_result "Skipped! Local #{name} repository has been built ALREADY.", status: :skipped
|
78
|
+
else
|
79
|
+
if available?
|
80
|
+
if build!
|
81
|
+
update_revision
|
82
|
+
update_result "Successfully built local #{name} repository."
|
83
|
+
else
|
84
|
+
update_result "FAILED to build local #{name} repository!", status: :failed, level: :error
|
85
|
+
end
|
86
|
+
else
|
87
|
+
update_result "Aborted! Remote #{name} repository is NOT available.", status: :failed, level: :error
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def package
|
93
|
+
if cloned?
|
94
|
+
if package!
|
95
|
+
cleanup_releases
|
96
|
+
update_result "Successfully package local #{name} repository to #{release_package_path}.",
|
97
|
+
release: { name: name, tag: release_tag,
|
98
|
+
path: release_package_path,
|
99
|
+
md5: md5_for_file(release_package_path),
|
100
|
+
bundled_gems: bundle_gems }
|
101
|
+
else
|
102
|
+
update_result "FAILED to package local #{name} repository!", status: :failed, level: :error
|
103
|
+
end
|
104
|
+
else
|
105
|
+
update_result "Aborted! Local #{name} package is NOT built yet!", status: :failed, level: :error
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
def init
|
112
|
+
@rev_size = DefaultRevisionSize
|
113
|
+
task.opts.repository.each_pair { |name, value| instance_variable_set("@#{name}", value) }
|
114
|
+
load_scm
|
115
|
+
end
|
116
|
+
|
117
|
+
def load_scm
|
118
|
+
singleton_class.send(:prepend, scm_module)
|
119
|
+
end
|
120
|
+
|
121
|
+
def build!
|
122
|
+
rmdir(clone_path)
|
123
|
+
clone
|
124
|
+
end
|
125
|
+
|
126
|
+
def package!
|
127
|
+
if update
|
128
|
+
update_revision
|
129
|
+
release
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def update_revision
|
134
|
+
@revision = fetch_revision
|
135
|
+
end
|
136
|
+
|
137
|
+
def cleanup_releases
|
138
|
+
files = capture(:ls, '-xt', releases_path).split
|
139
|
+
if files.count > keep_releases
|
140
|
+
within(releases_path) do
|
141
|
+
files.last(files.count - keep_releases).each { |f| rm(f) }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def bundle_gems
|
147
|
+
gemfile_path = Pathname.new(release_tag).join('Gemfile')
|
148
|
+
gems_cache = Pathname.new('vendor').join('cache')
|
149
|
+
bundle_path = Pathname.new('vendor').join('bundle')
|
150
|
+
bundled_gems = {}
|
151
|
+
gems = bundled_gems[:gems] = {}
|
152
|
+
if test(:tar, "-tzf #{release_package_path} #{gemfile_path} > /dev/null 2>&1")
|
153
|
+
within(workspace_path) do
|
154
|
+
execute(:tar, "--strip-components=1 -xzf #{release_package_path} #{gemfile_path}")
|
155
|
+
unless test(:bundle, :check, "--path #{bundle_path}")
|
156
|
+
execute(:bundle, :install, "--path #{bundle_path} --without #{bundle_without.join(' ')} --quiet")
|
157
|
+
info "Package gems bundled in Gemfile"
|
158
|
+
execute(:bundle, :package, "--all --quiet")
|
159
|
+
end
|
160
|
+
gem_files = capture(:ls, '-xt', gems_cache).split
|
161
|
+
gem_files.each do |gem_file|
|
162
|
+
gems[gem_file] = md5_for_file(gems_cache.join(gem_file))
|
163
|
+
end
|
164
|
+
end
|
165
|
+
bundled_gems[:gems_cache] = workspace_path.join(gems_cache)
|
166
|
+
workspace_path.join('Gemfile.lock').tap do |p|
|
167
|
+
bundled_gems[:locked_gemfile] = { path: p, md5: md5_for_file(p) }
|
168
|
+
end
|
169
|
+
end
|
170
|
+
bundled_gems
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Luban
|
2
|
+
module Deployment
|
3
|
+
class Application
|
4
|
+
class Repository
|
5
|
+
module SCM
|
6
|
+
module Git
|
7
|
+
attr_reader :tag
|
8
|
+
attr_reader :branch
|
9
|
+
|
10
|
+
def git_cmd; :git; end
|
11
|
+
|
12
|
+
def ref
|
13
|
+
tag || branch || @ref
|
14
|
+
end
|
15
|
+
|
16
|
+
def available?
|
17
|
+
test(git_cmd, 'ls-remote --heads', from)
|
18
|
+
end
|
19
|
+
|
20
|
+
def cloned?
|
21
|
+
file?(clone_path.join("HEAD"))
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_revision
|
25
|
+
within(clone_path) { capture(git_cmd, "rev-parse --short=#{rev_size} #{ref}") }
|
26
|
+
#within(clone_path) { capture(git_cmd, "rev-list --max-count=1 --abbrev-commit --abbrev=rev_size #{ref}") }
|
27
|
+
end
|
28
|
+
|
29
|
+
def clone
|
30
|
+
test(git_cmd, :clone, '--mirror', from, clone_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update
|
34
|
+
within(clone_path) { test(git_cmd, :remote, :update, "--prune") }
|
35
|
+
end
|
36
|
+
|
37
|
+
def release
|
38
|
+
within(clone_path) { test(git_cmd, :archive, ref, "--prefix=#{release_tag}/ -o #{release_package_path}") }
|
39
|
+
end
|
40
|
+
|
41
|
+
def release_tag
|
42
|
+
@release_tag ||= "#{ref}-#{revision}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Luban
|
2
|
+
module Deployment
|
3
|
+
class Application
|
4
|
+
class Repository
|
5
|
+
module SCM
|
6
|
+
module Rsync
|
7
|
+
def init
|
8
|
+
super
|
9
|
+
@from = Pathname.new(@from) unless from.is_a?(Pathname)
|
10
|
+
end
|
11
|
+
|
12
|
+
def rsync_cmd; :rsync; end
|
13
|
+
|
14
|
+
def available?; directory?(from); end
|
15
|
+
|
16
|
+
def cloned?
|
17
|
+
directory?(clone_path) and
|
18
|
+
test("[ \"$(ls -A #{clone_path})\" ]") # Not empty
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_revision
|
22
|
+
# Use MD5 as the revision
|
23
|
+
capture(:tar, "-cf - #{dir} 2>/dev/null | openssl md5")[0, rev_size]
|
24
|
+
end
|
25
|
+
|
26
|
+
def clone
|
27
|
+
test(rsync_cmd, "-acz", "#{from}/", clone_path)
|
28
|
+
end
|
29
|
+
|
30
|
+
def update
|
31
|
+
test(rsync_cmd, "-acz", "--delete", "#{from}/", clone_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def release
|
35
|
+
within(releases_path) do
|
36
|
+
assure_dirs(release_tag)
|
37
|
+
execute(:tar, "-C #{clone_path} -cf - . | tar -C #{release_tag} -xf -")
|
38
|
+
execute(:tar, "-czf", release_package_path, release_tag)
|
39
|
+
rm(release_tag)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
module Luban
|
2
|
+
module Deployment
|
3
|
+
class Command < Luban::CLI::Command
|
4
|
+
module Tasks
|
5
|
+
module Install
|
6
|
+
%i(build destroy cleanup binstubs
|
7
|
+
show_current show_summary which whence).each do |action|
|
8
|
+
define_method(action) do |args:, opts:|
|
9
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} is an abstract method."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def installable?; true; end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def setup_install_tasks
|
18
|
+
_self = self
|
19
|
+
task :build do
|
20
|
+
desc "Build #{_self.display_name} environment"
|
21
|
+
switch :force, "Force to build", short: :f
|
22
|
+
action! :build
|
23
|
+
end
|
24
|
+
|
25
|
+
task :destroy do
|
26
|
+
desc "Destroy #{_self.display_name} environment"
|
27
|
+
switch :force, "Force to destroy", short: :f, required: true
|
28
|
+
action! :destroy
|
29
|
+
end
|
30
|
+
|
31
|
+
task :cleanup do
|
32
|
+
desc "Clean up temporary files during installation"
|
33
|
+
action! :cleanup
|
34
|
+
end
|
35
|
+
|
36
|
+
task :binstubs do
|
37
|
+
desc "Update binstubs for required packages"
|
38
|
+
switch :force, "Force to update binstubs", short: :f
|
39
|
+
action! :binstubs
|
40
|
+
end
|
41
|
+
|
42
|
+
task :version do
|
43
|
+
desc "Show current version for required packages"
|
44
|
+
action! :show_current
|
45
|
+
end
|
46
|
+
|
47
|
+
task :versions do
|
48
|
+
desc "Show package installation summary"
|
49
|
+
action! :show_summary
|
50
|
+
end
|
51
|
+
|
52
|
+
task :which do
|
53
|
+
desc "Show the real path for the given executable"
|
54
|
+
argument :executable, "Executable to which", short: :e, required: true
|
55
|
+
action! :which
|
56
|
+
end
|
57
|
+
|
58
|
+
task :whence do
|
59
|
+
desc "List packages with the given executable"
|
60
|
+
argument :executable, "Executable to whence", short: :e, required: true
|
61
|
+
action! :whence
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module Deploy
|
67
|
+
%i(deploy rollback).each do |action|
|
68
|
+
define_method(action) do |args:, opts:|
|
69
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} is an abstract method."
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def deployable?; true; end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def setup_deploy_tasks
|
78
|
+
task :deploy do
|
79
|
+
desc "Run deployment"
|
80
|
+
action! :deploy
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module Control
|
86
|
+
%i(start_process stop_process restart_process
|
87
|
+
show_process_status test_process
|
88
|
+
monitor_process unmonitor_process).each do |action|
|
89
|
+
define_method(action) do |args:, opts:|
|
90
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} is an abstract method."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def controllable?; true; end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
def setup_control_tasks
|
99
|
+
task :start do
|
100
|
+
desc "Start process"
|
101
|
+
action! :start_process
|
102
|
+
end
|
103
|
+
|
104
|
+
task :stop do
|
105
|
+
desc "Stop process"
|
106
|
+
action! :stop_process
|
107
|
+
end
|
108
|
+
|
109
|
+
task :restart do
|
110
|
+
desc "Restart process"
|
111
|
+
action! :restart_process
|
112
|
+
end
|
113
|
+
|
114
|
+
task :status do
|
115
|
+
desc "Show process status"
|
116
|
+
action! :show_process_status
|
117
|
+
end
|
118
|
+
|
119
|
+
task :test do
|
120
|
+
desc "Test process"
|
121
|
+
action! :test_process
|
122
|
+
end
|
123
|
+
|
124
|
+
task :monitor do
|
125
|
+
desc "Turn on process monitor"
|
126
|
+
action! :monitor_process
|
127
|
+
end
|
128
|
+
|
129
|
+
task :unmonitor do
|
130
|
+
desc "Turn off process monitor"
|
131
|
+
action! :unmonitor_process
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
using Luban::CLI::CoreRefinements
|
138
|
+
include Luban::Deployment::Helpers::Configuration
|
139
|
+
include Luban::Deployment::Parameters::General
|
140
|
+
|
141
|
+
def display_name; @display_name ||= name.camelcase; end
|
142
|
+
|
143
|
+
def installable?; false; end
|
144
|
+
def deployable?; false; end
|
145
|
+
def controllable?; false; end
|
146
|
+
|
147
|
+
def task(cmd, **opts, &blk)
|
148
|
+
command(cmd, **opts, &blk).tap do |c|
|
149
|
+
add_common_task_options(c)
|
150
|
+
if !c.summary.nil? and c.description.empty?
|
151
|
+
c.long_desc "#{c.summary} in #{self.class.name}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
alias_method :undef_task, :undef_command
|
157
|
+
|
158
|
+
class << self
|
159
|
+
def default_worker_class
|
160
|
+
Luban::Deployment::Worker::Base
|
161
|
+
end
|
162
|
+
|
163
|
+
def worker_class(worker)
|
164
|
+
class_name = worker.camelcase
|
165
|
+
if const_defined?(class_name)
|
166
|
+
const_get(class_name)
|
167
|
+
else
|
168
|
+
abort "Aborted! #{name}::#{class_name} is NOT defined."
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def define_task_method(method, task: method, worker:, locally: false, &blk)
|
173
|
+
define_method(method) do |args:, opts:|
|
174
|
+
run_task(cmd: task, args: args, opts: opts, locally: locally,
|
175
|
+
worker_class: self.class.worker_class(worker), &blk)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def run_task(cmd: nil, args:, opts:, locally: false,
|
181
|
+
worker_class: self.class.default_worker_class, &blk)
|
182
|
+
backtrace = opts.delete(:backtrace)
|
183
|
+
task_args = compose_task_arguments(args)
|
184
|
+
task_opts = compose_task_options(opts)
|
185
|
+
run_opts = extract_run_options(task_opts)
|
186
|
+
run_opts[:hosts] = :local if locally
|
187
|
+
task_msg = { cmd: cmd, config: config, local: locally,
|
188
|
+
args: task_args, opts: task_opts}
|
189
|
+
result = []
|
190
|
+
mutex = Mutex.new
|
191
|
+
run(**run_opts) do |backend|
|
192
|
+
begin
|
193
|
+
r = worker_class.new(task_msg.merge(backend: backend), &blk).run
|
194
|
+
rescue StandardError => e
|
195
|
+
r = {
|
196
|
+
status: :failed,
|
197
|
+
message: backtrace ? "#{e.message}\n#{e.backtrace.join("\n")}" : e.message,
|
198
|
+
error: e
|
199
|
+
}
|
200
|
+
end
|
201
|
+
mutex.synchronize { result << r }
|
202
|
+
end
|
203
|
+
print_task_result result
|
204
|
+
locally ? result.first[:__return__] : result
|
205
|
+
end
|
206
|
+
|
207
|
+
protected
|
208
|
+
|
209
|
+
def on_configure
|
210
|
+
super
|
211
|
+
set_parameters
|
212
|
+
set_default_parameters
|
213
|
+
load_configuration
|
214
|
+
validate_parameters
|
215
|
+
load_libraries
|
216
|
+
setup_cli
|
217
|
+
end
|
218
|
+
|
219
|
+
def set_parameters
|
220
|
+
copy_parameters_from_parent(
|
221
|
+
:luban_roles, :luban_root_path,
|
222
|
+
:stages, :production_stages, :applications,
|
223
|
+
:work_dir, :apps_path, :user
|
224
|
+
)
|
225
|
+
end
|
226
|
+
|
227
|
+
def copy_parameters_from_parent(*parameters)
|
228
|
+
parameters.each do |p|
|
229
|
+
if parent.respond_to?(p)
|
230
|
+
send(p, parent.send(p))
|
231
|
+
else
|
232
|
+
abort "Aborted! #{self.class.name} failed to copy parameter #{p.inspect} from #{parent.class.name}."
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def set_default_parameters
|
238
|
+
set_default_general_parameters
|
239
|
+
end
|
240
|
+
|
241
|
+
def load_configuration; end
|
242
|
+
|
243
|
+
def validate_parameters
|
244
|
+
validate_general_parameters
|
245
|
+
end
|
246
|
+
|
247
|
+
def load_libraries; end
|
248
|
+
|
249
|
+
def setup_cli
|
250
|
+
setup_descriptions
|
251
|
+
setup_tasks
|
252
|
+
end
|
253
|
+
|
254
|
+
def setup_descriptions; end
|
255
|
+
|
256
|
+
def setup_tasks
|
257
|
+
setup_install_tasks if installable?
|
258
|
+
setup_deploy_tasks if deployable?
|
259
|
+
setup_control_tasks if controllable?
|
260
|
+
end
|
261
|
+
|
262
|
+
%i(install deploy control).each do |operation|
|
263
|
+
define_method("setup_#{operation}_tasks") do
|
264
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} is an abstract method."
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def add_common_task_options(task)
|
269
|
+
task.switch :dry_run, "Run as a simulation", short: :d
|
270
|
+
task.switch :once, "Run ONLY once", short: :o
|
271
|
+
task.option :roles, "Run with given roles",
|
272
|
+
type: :symbol, multiple: true, default: luban_roles
|
273
|
+
task.option :hosts, "Run with given hosts", multiple: true, default: []
|
274
|
+
task.option :in, "Run in parallel, sequence or group", short: :i,
|
275
|
+
type: :symbol, within: [:parallel, :sequence, :groups], default: :parallel
|
276
|
+
task.option :wait, "Wait interval for every run in sequence or groups", short: :w,
|
277
|
+
type: :integer, assure: ->(v){ v > 0 }, default: 2
|
278
|
+
task.option :limit, "Number of hosts per group", short: :n,
|
279
|
+
type: :integer, assure: ->(v){ v > 0 }, default: 2
|
280
|
+
task.option :format, "Set output format", short: :F,
|
281
|
+
type: :symbol, within: %i(pretty dot simpletext blackhole airbrussh),
|
282
|
+
default: :blackhole
|
283
|
+
task.option :verbosity, "Set verbosity level", short: :V,
|
284
|
+
type: :symbol, within: Luban::Deployment::Helpers::Utils::LogLevels,
|
285
|
+
default: :info
|
286
|
+
task.switch :backtrace, "Enable backtrace for exceptions", short: :B
|
287
|
+
end
|
288
|
+
|
289
|
+
def extract_run_options(task_opts)
|
290
|
+
%i(once roles hosts in wait limit
|
291
|
+
dry_run format verbosity).inject({}) do |opts, n|
|
292
|
+
opts[n] = task_opts.delete(n) if task_opts.has_key?(n)
|
293
|
+
opts
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def compose_task_arguments(args); args.clone; end
|
298
|
+
def compose_task_options(opts); opts.clone; end
|
299
|
+
|
300
|
+
def run(roles: luban_roles, hosts: nil, once: false,
|
301
|
+
dry_run: false, format: log_format, verbosity: log_level, **opts)
|
302
|
+
configure_backend(dry_run: dry_run, format: format, verbosity: verbosity)
|
303
|
+
hosts = Array(hosts)
|
304
|
+
servers = select_servers(roles, hosts)
|
305
|
+
servers = servers.first if once and !servers.empty?
|
306
|
+
on(servers, **opts) { |backend| yield backend }
|
307
|
+
end
|
308
|
+
|
309
|
+
def select_servers(roles, hosts)
|
310
|
+
hosts.empty? ? release_roles(*roles) : hosts
|
311
|
+
end
|
312
|
+
|
313
|
+
def on(hosts, **opts, &blk)
|
314
|
+
SSHKit::Coordinator.new(hosts).each(opts) { blk.call(self) }
|
315
|
+
end
|
316
|
+
|
317
|
+
def print_task_result(result)
|
318
|
+
result.each do |entry|
|
319
|
+
next if entry[:message].to_s.empty?
|
320
|
+
puts " [#{entry[:hostname]}] #{entry[:message]}"
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def backend_configured?; @@backend_configured ||= false; end
|
325
|
+
|
326
|
+
def configure_backend(dry_run:, format:, verbosity:)
|
327
|
+
return if backend_configured?
|
328
|
+
enable_dry_run if dry_run
|
329
|
+
|
330
|
+
SSHKit.configure do |sshkit|
|
331
|
+
sshkit.format = format
|
332
|
+
sshkit.output_verbosity = verbosity
|
333
|
+
sshkit.default_env = default_env
|
334
|
+
sshkit.backend = sshkit_backend
|
335
|
+
sshkit.backend.configure do |backend|
|
336
|
+
backend.pty = pty
|
337
|
+
backend.connection_timeout = connection_timeout
|
338
|
+
backend.ssh_options =
|
339
|
+
backend.ssh_options.merge(user: user).merge!(ssh_options)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
configure_airbrussh if format == :airbrussh
|
344
|
+
@@backend_configured = true
|
345
|
+
end
|
346
|
+
|
347
|
+
def configure_airbrussh
|
348
|
+
require 'airbrussh'
|
349
|
+
Airbrussh.configure do |config|
|
350
|
+
config.command_output = [:stdout, :stderr]
|
351
|
+
end
|
352
|
+
SSHKit.config.output = Airbrussh::Formatter.new($stdout)
|
353
|
+
end
|
354
|
+
|
355
|
+
def enable_dry_run
|
356
|
+
sshkit_backend SSHKit::Backend::Printer
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|