luban 0.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.
- 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
|