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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +37 -0
  6. data/Rakefile +1 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +7 -0
  9. data/exe/luban +3 -0
  10. data/lib/luban/deployment/cli/application/authenticator.rb +106 -0
  11. data/lib/luban/deployment/cli/application/base.rb +179 -0
  12. data/lib/luban/deployment/cli/application/builder.rb +67 -0
  13. data/lib/luban/deployment/cli/application/publisher.rb +215 -0
  14. data/lib/luban/deployment/cli/application/repository.rb +175 -0
  15. data/lib/luban/deployment/cli/application/scm/git.rb +49 -0
  16. data/lib/luban/deployment/cli/application/scm/rsync.rb +47 -0
  17. data/lib/luban/deployment/cli/application.rb +5 -0
  18. data/lib/luban/deployment/cli/command.rb +360 -0
  19. data/lib/luban/deployment/cli/package/binary.rb +241 -0
  20. data/lib/luban/deployment/cli/package/dependency.rb +49 -0
  21. data/lib/luban/deployment/cli/package/dependency_set.rb +71 -0
  22. data/lib/luban/deployment/cli/package/installer/core.rb +98 -0
  23. data/lib/luban/deployment/cli/package/installer/install.rb +330 -0
  24. data/lib/luban/deployment/cli/package/installer/paths.rb +81 -0
  25. data/lib/luban/deployment/cli/package/installer.rb +3 -0
  26. data/lib/luban/deployment/cli/package/service.rb +17 -0
  27. data/lib/luban/deployment/cli/package/worker.rb +43 -0
  28. data/lib/luban/deployment/cli/package.rb +6 -0
  29. data/lib/luban/deployment/cli/project.rb +94 -0
  30. data/lib/luban/deployment/cli.rb +4 -0
  31. data/lib/luban/deployment/configuration/core.rb +67 -0
  32. data/lib/luban/deployment/configuration/filter.rb +54 -0
  33. data/lib/luban/deployment/configuration/question.rb +38 -0
  34. data/lib/luban/deployment/configuration/server.rb +70 -0
  35. data/lib/luban/deployment/configuration/server_set.rb +86 -0
  36. data/lib/luban/deployment/configuration.rb +5 -0
  37. data/lib/luban/deployment/error.rb +5 -0
  38. data/lib/luban/deployment/helpers/configuration.rb +159 -0
  39. data/lib/luban/deployment/helpers/utils.rb +180 -0
  40. data/lib/luban/deployment/helpers.rb +2 -0
  41. data/lib/luban/deployment/packages/bundler.rb +81 -0
  42. data/lib/luban/deployment/packages/git.rb +37 -0
  43. data/lib/luban/deployment/packages/openssl.rb +59 -0
  44. data/lib/luban/deployment/packages/ruby.rb +125 -0
  45. data/lib/luban/deployment/packages/rubygems.rb +89 -0
  46. data/lib/luban/deployment/packages/yaml.rb +33 -0
  47. data/lib/luban/deployment/parameters.rb +160 -0
  48. data/lib/luban/deployment/runner.rb +99 -0
  49. data/lib/luban/deployment/templates/envrc.erb +30 -0
  50. data/lib/luban/deployment/templates/unset_envrc.erb +28 -0
  51. data/lib/luban/deployment/version.rb +5 -0
  52. data/lib/luban/deployment/worker/base.rb +71 -0
  53. data/lib/luban/deployment/worker/controller.rb +11 -0
  54. data/lib/luban/deployment/worker/local.rb +19 -0
  55. data/lib/luban/deployment/worker/remote.rb +55 -0
  56. data/lib/luban/deployment/worker/task.rb +25 -0
  57. data/lib/luban/deployment/worker.rb +4 -0
  58. data/lib/luban/deployment.rb +8 -0
  59. data/lib/luban.rb +4 -0
  60. data/luban.gemspec +29 -0
  61. metadata +174 -0
@@ -0,0 +1,38 @@
1
+ module Luban
2
+ module Deployment
3
+ class Configuration
4
+ class Question
5
+ attr_reader :prompt, :default
6
+
7
+ def initialize(default:, prompt: nil, echo: true)
8
+ @default = default
9
+ @echo = echo
10
+ @prompt = "#{prompt.to_s}: " unless prompt.nil?
11
+ end
12
+
13
+ def echo?; @echo; end
14
+
15
+ def call
16
+ ask_question
17
+ get_response
18
+ end
19
+
20
+ protected
21
+
22
+ def ask_question
23
+ $stdout.print prompt
24
+ end
25
+
26
+ def get_response
27
+ response = if echo?
28
+ $stdin.gets.chomp
29
+ else
30
+ require 'io/console'
31
+ $stdin.noecho(&:gets).chomp.tap{ $stdout.print "\n" }
32
+ end
33
+ response.empty? ? default : response
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,70 @@
1
+ require 'set'
2
+
3
+ module Luban
4
+ module Deployment
5
+ class Configuration
6
+ class Server < SSHKit::Host
7
+ attr_reader :roles
8
+
9
+ def self.create(host, **properties)
10
+ server = host.is_a?(Server) ? host : Server.new(host)
11
+ server.add_properties(properties) unless properties.empty?
12
+ server.ssh_options ||= {}
13
+ server
14
+ end
15
+
16
+ def roles
17
+ self[:roles]
18
+ end
19
+
20
+ def add_roles(roles)
21
+ roles.each { |role| add_role(role) }
22
+ end
23
+ alias_method :roles=, :add_roles
24
+
25
+ def add_role(role)
26
+ roles.add(role.to_sym)
27
+ end
28
+
29
+ def has_role?(role)
30
+ roles.include? role.to_sym
31
+ end
32
+
33
+ def properties
34
+ @properties ||= { :roles => Set.new }
35
+ end
36
+
37
+ def [](key)
38
+ properties[key]
39
+ end
40
+ alias_method :fetch, :[]
41
+
42
+ def []=(key, value)
43
+ if respond_to?("#{key}=")
44
+ send("#{key}=", value)
45
+ else
46
+ pval = properties[key]
47
+ if pval.is_a? Hash and value.is_a? Hash
48
+ pval.merge!(value)
49
+ elsif pval.is_a? Set and value.is_a? Set
50
+ pval.merge(value)
51
+ elsif pval.is_a? Array and value.is_a? Array
52
+ pval.concat value
53
+ else
54
+ properties[key] = value
55
+ end
56
+ end
57
+ end
58
+ alias_method :set, :[]=
59
+
60
+ def add_properties(_properties)
61
+ _properties.each { |k, v| self[k] = v }
62
+ end
63
+
64
+ def primary?
65
+ self[:primary]
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,86 @@
1
+ module Luban
2
+ module Deployment
3
+ class Configuration
4
+ class ServerSet
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @servers = { :all => [] }
9
+ end
10
+
11
+ def all
12
+ @servers[:all]
13
+ end
14
+
15
+ def available_roles
16
+ @servers.keys - [:all]
17
+ end
18
+
19
+ def has_server?(server)
20
+ all.find do |s|
21
+ s.user == server.user and
22
+ s.hostname == server.hostname and
23
+ s.port == server.port
24
+ end
25
+ end
26
+
27
+ def add_host(host, **properties)
28
+ new_server = Server.create(host, properties)
29
+ if server = has_server?(new_server)
30
+ server.user = new_server.user unless new_server.user.nil?
31
+ server.port = new_server.port unless new_server.port.nil?
32
+ server.add_properties(properties)
33
+ server
34
+ else
35
+ all << new_server
36
+ new_server.roles.each do |role|
37
+ @servers[role] ||= []
38
+ @servers[role] << new_server
39
+ end
40
+ new_server
41
+ end
42
+ end
43
+
44
+ def add_hosts_for_role(role, hosts, **properties)
45
+ properties_deepcopy = Marshal.dump(properties.merge(:roles => Array(role)))
46
+ Array(hosts).each { |host| add_host(host, Marshal.load(properties_deepcopy)) }
47
+ end
48
+
49
+ def find_by_roles(roles, **opts)
50
+ roles.inject([]) do |result, role|
51
+ result.concat(find_by_role(role, opts))
52
+ end
53
+ end
54
+
55
+ def find_by_role(role, **opts)
56
+ if @servers.has_key?(role)
57
+ opts.inject(@servers[role]) do |_servers, (action, key)|
58
+ send(action, _servers, key)
59
+ end
60
+ else
61
+ []
62
+ end
63
+ end
64
+
65
+ def find_primary(role)
66
+ _servers = find_by_role(role)
67
+ _servers.find(&:primary?) || _servers.first
68
+ end
69
+
70
+ def each
71
+ all.each { |server| yield server }
72
+ end
73
+
74
+ protected
75
+
76
+ def exclude(_servers, key)
77
+ _servers.select { |server| !server[key] }
78
+ end
79
+
80
+ def filter(_servers, key)
81
+ _servers.select { |server| server[key] }
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'configuration/core'
2
+ require_relative 'configuration/filter'
3
+ require_relative 'configuration/question'
4
+ require_relative 'configuration/server'
5
+ require_relative 'configuration/server_set'
@@ -0,0 +1,5 @@
1
+ module Luban
2
+ module Deployment
3
+ class Error < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,159 @@
1
+ module Luban
2
+ module Deployment
3
+ module Helpers
4
+ module Configuration
5
+ attr_accessor :config
6
+
7
+ def config
8
+ @config ||= Luban::Deployment::Configuration.new
9
+ end
10
+
11
+ def fetch(key, *args, &blk)
12
+ config.fetch(key, *args, &blk)
13
+ end
14
+
15
+ def set(key, *args, &blk)
16
+ config.set(key, *args, &blk)
17
+ end
18
+
19
+ def set_default(key, *args, &blk)
20
+ config.set_default(key, *args, &blk)
21
+ end
22
+
23
+ def ask(key=nil, default: nil, prompt: nil, echo: true)
24
+ config.ask(key, default: default, echo: echo, prompt: prompt)
25
+ end
26
+
27
+ def role(name, hosts, **properties)
28
+ config.role(name, hosts, properties)
29
+ end
30
+
31
+ def server(name, **properties)
32
+ config.server(name, properties)
33
+ end
34
+
35
+ def roles(*names)
36
+ config.roles(*names)
37
+ end
38
+
39
+ def release_roles(*names)
40
+ config.release_roles(*names)
41
+ end
42
+
43
+ def primary(role)
44
+ config.primary(role)
45
+ end
46
+
47
+ def load_configuration_file(config_file, optional: false)
48
+ if File.file?(config_file)
49
+ if error = syntax_error?(config_file)
50
+ abort "Aborted! Syntax errors in configuration file.\n#{error}"
51
+ else
52
+ instance_eval(File.read(config_file))
53
+ end
54
+ else
55
+ unless optional
56
+ abort "Aborted! Configuration file is NOT found: #{config_file}"
57
+ end
58
+ end
59
+ end
60
+
61
+ def syntax_error?(file)
62
+ _stderr = $stderr
63
+ $stderr = StringIO.new('', 'w')
64
+ # ONLY work for MRI Ruby
65
+ RubyVM::InstructionSequence.compile_file(file)
66
+ $stderr.string.chomp.empty? ? false : $stderr.string
67
+ ensure
68
+ $stderr = _stderr
69
+ end
70
+
71
+ def find_template_file(file_name)
72
+ path = find_template_file_by_config_finder(file_name) ||
73
+ find_default_template_file(file_name)
74
+ raise RuntimeError, "Template file is NOT found: #{file_name}." if path.nil?
75
+ path
76
+ end
77
+
78
+ protected
79
+
80
+ def find_template_file_by_config_finder(file_name)
81
+ path = config_finder[:application].find_template_file(file_name) ||
82
+ config_finder[:project].find_template_file(file_name)
83
+ end
84
+
85
+ def find_default_template_file(file_name)
86
+ path = File.expand_path(File.join(File.dirname(__FILE__), '..',
87
+ 'templates', file_name))
88
+ return path if File.file?(path)
89
+ end
90
+
91
+ class Finder
92
+ class Project < Finder
93
+ def base_path; base_path ||= target.work_dir; end
94
+ end
95
+
96
+ class Application < Finder
97
+ def base_path; base_path ||= target.apps_path.join(target.application); end
98
+ end
99
+
100
+ def self.project(target); Project.new(target); end
101
+ def self.application(target); Application.new(target); end
102
+
103
+ attr_reader :target
104
+ attr_reader :base_path
105
+ attr_reader :config_root
106
+ attr_reader :config_file
107
+ attr_reader :config_path
108
+ attr_reader :templates_path
109
+ attr_reader :stage_config_file
110
+ attr_reader :stage_config_path
111
+ attr_reader :stage_templates_path
112
+
113
+ def initialize(target)
114
+ @target = target
115
+ set_config_paths
116
+ end
117
+
118
+ def deployfile; @deployfile ||= 'deploy.rb'; end
119
+ def stagefile; @stagefile ||= "#{target.stage}.rb"; end
120
+
121
+ def load_configuration
122
+ load_general_configuration
123
+ load_stage_configuration
124
+ end
125
+
126
+ def load_general_configuration
127
+ target.load_configuration_file(config_file)
128
+ end
129
+
130
+ def load_stage_configuration
131
+ target.load_configuration_file(stage_config_file)
132
+ if File.directory?(stage_config_path)
133
+ Dir[stage_config_path.join("{packages}/**/*.rb")].each do |file|
134
+ target.load_configuration_file(file)
135
+ end
136
+ end
137
+ end
138
+
139
+ def find_template_file(file_name)
140
+ return file_path if File.file?(file_path = stage_templates_path.join(file_name))
141
+ return file_path if File.file?(file_path = templates_path.join(file_name))
142
+ end
143
+
144
+ protected
145
+
146
+ def set_config_paths
147
+ @config_root = base_path.join('config')
148
+ @config_file = @config_root.join(deployfile)
149
+ @config_path = @config_root.join(deployfile.sub('.rb', ''))
150
+ @templates_path = @config_root.join('templates')
151
+ @stage_config_file = @config_path.join(stagefile)
152
+ @stage_config_path = @config_path.join(target.stage)
153
+ @stage_templates_path = @stage_config_path.join('templates')
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,180 @@
1
+ require "digest/md5"
2
+
3
+ module Luban
4
+ module Deployment
5
+ module Helpers
6
+ module Utils
7
+ LogLevels = %i(fatal error warn info debug trace)
8
+
9
+ attr_reader :backend
10
+
11
+ def check_pass?(type, *args)
12
+ send("#{type}?", *args)
13
+ end
14
+
15
+ def directory?(path)
16
+ test "[ -d #{path} ]"
17
+ end
18
+
19
+ def file?(path)
20
+ test "[ -f #{path} ]"
21
+ end
22
+
23
+ def symlink?(path)
24
+ test "[ -L #{path} ]"
25
+ end
26
+
27
+ def match?(cmd, expect)
28
+ expect = Regexp.new(Regexp.escape(expect.to_s)) unless expect.is_a?(Regexp)
29
+ output = capture(cmd)
30
+ output =~ expect
31
+ end
32
+
33
+ def assure(type, *args)
34
+ unless check_pass?(type, *args)
35
+ if block_given?
36
+ yield
37
+ else
38
+ abort "Aborted! #{type} dependency with #{args.inspect} are not met and no block is given to resolve it."
39
+ end
40
+ end
41
+ end
42
+
43
+ def assure_dirs(*dirs)
44
+ dirs.each { |dir| assure(:directory, dir) { mkdir(dir) } }
45
+ end
46
+
47
+ def assure_symlink(source_path, target_path)
48
+ unless symlink?(target_path) and readlink(target_path) == source_path.to_s
49
+ ln(source_path, target_path)
50
+ end
51
+ end
52
+
53
+ def mkdir(*opts, path)
54
+ execute(:mkdir, '-p', *opts, path)
55
+ end
56
+
57
+ def rm(*opts, path)
58
+ execute(:rm, '-fr', *opts, path)
59
+ end
60
+ alias_method :rmdir, :rm
61
+
62
+ def chmod(*opts, path)
63
+ execute(:chmod, '-R', *opts, path)
64
+ end
65
+
66
+ def ln(*opts, source_path, target_path)
67
+ execute(:ln, '-nfs', *opts, source_path, target_path)
68
+ end
69
+
70
+ def mv(*opts, source_path, target_path)
71
+ execute(:mv, *opts, source_path, target_path)
72
+ end
73
+
74
+ def cp(*opts, source_path, target_path)
75
+ execute(*opts, source_path, target_path)
76
+ end
77
+
78
+ def readlink(source_file)
79
+ capture("$(type -p readlink greadlink|head -1) #{source_file}")
80
+ end
81
+
82
+ def md5_for_file(file)
83
+ capture(:cat, "#{file} 2>/dev/null | openssl md5")
84
+ end
85
+
86
+ def sudo(*args)
87
+ execute(:sudo, *args)
88
+ end
89
+
90
+ def os_name
91
+ @os_name ||= capture("uname -s")
92
+ end
93
+
94
+ def os_release
95
+ @os_release ||= capture("uname -r")
96
+ end
97
+
98
+ def hardware_name
99
+ @hardware_name ||= capture("uname -m")
100
+ end
101
+
102
+ def user_home
103
+ @user_home ||= capture("eval echo ~")
104
+ end
105
+
106
+ def url_exists?(download_url)
107
+ # Sent HEAD request to avoid downloading the file contents
108
+ test("curl -s -L -I -o /dev/null -f #{download_url}")
109
+
110
+ # Other effective ways to check url existence with curl
111
+
112
+ # In case HEAD request is refused,
113
+ # only the first byte of the file is requested
114
+ # test("curl -s -L -o /dev/null -f -r 0-0 #{download_url}")
115
+
116
+ # Alternatively, http code (200) can be validated
117
+ # capture("curl -s -L -I -o /dev/null -w '%{http_code}' #{download_url}") == '200'
118
+ end
119
+
120
+ def upload_by_template(file_to_upload:, template_file:, auto_revision: false, **opts)
121
+ template = File.read(template_file)
122
+
123
+ if auto_revision and file?(file_to_upload)
124
+ revision = Digest::MD5.hexdigest(template)
125
+ return if revision_match?(file_to_upload, revision)
126
+ end
127
+
128
+ require 'erb'
129
+ context = opts[:binding] || binding
130
+ upload!(StringIO.new(ERB.new(template, nil, '<>').result(context)), file_to_upload)
131
+ yield file_to_upload if block_given?
132
+ end
133
+
134
+ def revision_match?(file_to_upload, revision)
135
+ match?("grep \"Revision: \" #{file_to_upload}", revision)
136
+ end
137
+
138
+ [:test, :make, :within, :with, :as, :execute,
139
+ :upload!, :download!].each do |cmd|
140
+ define_method(cmd) do |*args, &blk|
141
+ backend.send(__method__, *args, &blk)
142
+ end
143
+ end
144
+
145
+ def capture(*args, &blk)
146
+ backend.capture(*args, &blk).chomp
147
+ end
148
+
149
+ def md5_matched?(file_path, md5)
150
+ file?(file_path) and md5 == md5_for_file(file_path)
151
+ end
152
+
153
+ #def execute(*args, &blk)
154
+ # if args.last.is_a?(::Hash)
155
+ # args.last.merge(verbosity: SSHKit::Logger::DEBUG)
156
+ # else
157
+ # args.push(verbosity: SSHKit::Logger::DEBUG)
158
+ # end
159
+ # backend.execute(*args, &blk)
160
+ #end
161
+
162
+ LogLevels.each do |cmd|
163
+ define_method(cmd) { |msg| backend.send(__method__, "[#{hostname}] #{msg}") }
164
+ end
165
+
166
+ def host
167
+ @host ||= backend.host
168
+ end
169
+
170
+ def hostname
171
+ @hostname ||= host.hostname
172
+ end
173
+
174
+ def method_missing(sym, *args, &blk)
175
+ backend.respond_to?(sym) ? backend.send(sym, *args, &blk) : super
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'helpers/configuration'
2
+ require_relative 'helpers/utils'
@@ -0,0 +1,81 @@
1
+ module Luban
2
+ module Deployment
3
+ module Packages
4
+ class Bundler < Luban::Deployment::Package::Binary
5
+ protected
6
+
7
+ def setup_install_tasks
8
+ super
9
+ commands[:install].switch :install_doc, "Install Bundler document"
10
+ end
11
+
12
+ class Installer < Luban::Deployment::Package::Installer
13
+ def install_doc?
14
+ task.opts.install_doc
15
+ end
16
+
17
+ def package_version
18
+ parent.task.opts.bundler || super
19
+ end
20
+
21
+ def package_path
22
+ parent.package_path
23
+ end
24
+
25
+ def install_path
26
+ parent.install_path
27
+ end
28
+
29
+ def bundler_executable
30
+ @bundler_executable ||= bin_path.join('bundle')
31
+ end
32
+
33
+ def gem_executable
34
+ parent.gem_executable
35
+ end
36
+
37
+ def src_file_extname
38
+ @src_file_extname ||= 'gem'
39
+ end
40
+
41
+ def source_repo
42
+ #@source_repo ||= "http://production.cf.rubygems.org"
43
+ @source_repo ||= "https://rubygems.org"
44
+ end
45
+
46
+ def source_url_root
47
+ @source_url_root ||= "downloads"
48
+ end
49
+
50
+ def installed?
51
+ return false unless file?(bundler_executable)
52
+ match?("#{bundler_executable} -v", package_version)
53
+ end
54
+
55
+ protected
56
+
57
+ def validate
58
+ if parent.nil?
59
+ abort "Aborted! Parent package for Bundler MUST be provided."
60
+ end
61
+ unless parent.is_a?(Ruby::Installer)
62
+ abort "Aborted! Parent package for Bundler MUST be an instance of #{Ruby::Installer.name}"
63
+ end
64
+ end
65
+
66
+ def uncompress_package; end
67
+ def configure_package; end
68
+ def make_package; end
69
+ def update_binstubs!; end
70
+
71
+ def install_package!
72
+ install_opts = ['--local']
73
+ install_opts << "--no-document" unless install_doc?
74
+ test("#{gem_executable} uninstall bundler -a -x -I >> #{install_log_file_path} 2>&1") and
75
+ test("#{gem_executable} install #{install_opts.join(' ')} #{src_file_path} >> #{install_log_file_path} 2>&1")
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,37 @@
1
+ module Luban
2
+ module Deployment
3
+ module Packages
4
+ class Git < Luban::Deployment::Package::Binary
5
+ apply_to :all do
6
+ before_install do
7
+ depend_on 'openssl', version: '1.0.2e'
8
+ end
9
+ end
10
+
11
+ class Installer < Luban::Deployment::Package::Installer
12
+ def git_executable
13
+ @git_executable ||= bin_path.join('git')
14
+ end
15
+
16
+ def source_repo
17
+ @source_repo ||= "https://www.kernel.org"
18
+ end
19
+
20
+ def source_url_root
21
+ @source_url_root ||= "pub/software/scm/git"
22
+ end
23
+
24
+ def installed?
25
+ return false unless file?(git_executable)
26
+ pattern = "version #{package_major_version}"
27
+ match?("#{git_executable} --version", pattern)
28
+ end
29
+
30
+ def with_openssl_dir(dir)
31
+ @configure_opts << "--with-openssl=#{dir}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end