docman 0.0.5 → 0.0.6
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 +4 -4
- data/README.md +17 -4
- data/bin/functions.sh +265 -0
- data/config/config.yaml +36 -39
- data/config/cucumber.yml +2 -0
- data/docman.gemspec +3 -0
- data/features/acquia/acquia.feature +65 -0
- data/features/init/init.feature +27 -0
- data/features/local/local.feature +88 -0
- data/features/local/local_deploy.feature +6 -0
- data/features/support/env.rb +1 -1
- data/lib/application.rb +48 -11
- data/lib/docman/builders/builder.rb +34 -34
- data/lib/docman/builders/dir_builder.rb +21 -0
- data/lib/docman/builders/drupal_drush_builder.rb +22 -0
- data/lib/docman/builders/git_direct_builder.rb +20 -0
- data/lib/docman/builders/git_strip_builder.rb +27 -0
- data/lib/docman/cli.rb +3 -0
- data/lib/docman/commands/clean_changed_cmd.rb +23 -0
- data/lib/docman/commands/command.rb +137 -0
- data/lib/docman/commands/composite_command.rb +28 -0
- data/lib/docman/commands/create_symlink_cmd.rb +24 -0
- data/lib/docman/commands/execute_script_cmd.rb +40 -0
- data/lib/docman/commands/git_commit_cmd.rb +25 -0
- data/lib/docman/commands/ssh_target_checker.rb +28 -0
- data/lib/docman/commands/target_checker.rb +22 -0
- data/lib/docman/config.rb +31 -0
- data/lib/docman/context.rb +11 -0
- data/lib/docman/deployers/common_deployer.rb +2 -1
- data/lib/docman/deployers/deployer.rb +121 -17
- data/lib/docman/deployers/git_deployer.rb +6 -3
- data/lib/docman/docroot_config.rb +15 -17
- data/lib/docman/docroot_controller.rb +23 -35
- data/lib/docman/exceptions/command_validation_error.rb +5 -0
- data/lib/docman/exceptions/no_changes_error.rb +5 -0
- data/lib/docman/exec.rb +1 -1
- data/lib/docman/git_util.rb +43 -15
- data/lib/docman/info.rb +63 -9
- data/lib/docman/logging.rb +41 -0
- data/lib/docman/version.rb +1 -1
- metadata +72 -8
- data/features/local.feature +0 -60
- data/features/local_deploy.feature +0 -37
- data/lib/docman/builders/common_builder.rb +0 -16
- data/lib/docman/builders/git_builder.rb +0 -26
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'docman/logging'
|
2
|
+
require 'docman/exceptions/command_validation_error'
|
3
|
+
require 'docman/exceptions/no_changes_error'
|
4
|
+
require 'hooks'
|
5
|
+
|
6
|
+
module Docman
|
7
|
+
|
8
|
+
class Command < Hash
|
9
|
+
|
10
|
+
include Hooks
|
11
|
+
include Docman::Logging
|
12
|
+
|
13
|
+
attr_reader :type
|
14
|
+
|
15
|
+
@@subclasses = {}
|
16
|
+
|
17
|
+
define_hooks :before_execute, :after_execute
|
18
|
+
|
19
|
+
def self.create(params, context = nil, caller = nil)
|
20
|
+
c = @@subclasses[params['type']]
|
21
|
+
if c
|
22
|
+
c.new(params, context, caller)
|
23
|
+
else
|
24
|
+
raise "Bad command type: #{params['type']}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.register_command(name)
|
29
|
+
@@subclasses[name] = self
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(params = nil, context = nil, caller = nil, type = 'command')
|
33
|
+
unless params.nil?
|
34
|
+
params.each_pair do |k, v|
|
35
|
+
self[k] = v
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@context = context
|
39
|
+
@caller = caller
|
40
|
+
@type = type
|
41
|
+
@log = self.has_key?('log') ? self['log'] : true
|
42
|
+
@hooks = {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def config
|
46
|
+
add_actions self
|
47
|
+
add_actions @context unless @context.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_actions(obj)
|
51
|
+
if obj.has_key? 'hooks' and obj['hooks'].has_key? @type
|
52
|
+
obj['hooks'][@type].each_pair do |name, hook|
|
53
|
+
if @hooks[name].nil?
|
54
|
+
@hooks[name] = hook
|
55
|
+
else
|
56
|
+
@hooks[name].concat(hook)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_action(name, hook)
|
63
|
+
if @hooks.has_key? name
|
64
|
+
@hooks[name] << {'type' => hook}
|
65
|
+
else
|
66
|
+
@hooks[name] = [hook]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def run_actions(name)
|
71
|
+
if @hooks.has_key? name
|
72
|
+
@hooks[name].each do |hook|
|
73
|
+
Docman::Command.create(hook, @context, self).perform
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def run_with_hooks(method)
|
79
|
+
with_logging(method) do
|
80
|
+
run_hook "before_#{method}".to_sym
|
81
|
+
result = self.send(method)
|
82
|
+
@execute_result = result if method == 'execute'
|
83
|
+
run_hook "after_#{method}".to_sym
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
before_execute do
|
88
|
+
run_actions('before_execute')
|
89
|
+
end
|
90
|
+
|
91
|
+
# @abstract
|
92
|
+
def execute
|
93
|
+
raise NoMethodError.new("Please define #execute for #{self.class.name}", '')
|
94
|
+
end
|
95
|
+
|
96
|
+
after_execute do
|
97
|
+
run_actions('after_execute')
|
98
|
+
end
|
99
|
+
|
100
|
+
def perform
|
101
|
+
config if self.respond_to? :config
|
102
|
+
validate_command if self.respond_to? :validate_command
|
103
|
+
@execute_result = run_with_hooks('execute')
|
104
|
+
rescue CommandValidationError => e
|
105
|
+
logger.error "Command validation error: #{e.message}"
|
106
|
+
return false
|
107
|
+
rescue NoChangesError => e
|
108
|
+
logger.info "No changes: #{e.message}"
|
109
|
+
return false
|
110
|
+
rescue StandardError => e
|
111
|
+
logger.error e.message
|
112
|
+
raise
|
113
|
+
ensure
|
114
|
+
@execute_result
|
115
|
+
end
|
116
|
+
|
117
|
+
def describe(type = 'short')
|
118
|
+
"Command: #{properties_info}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def prefix
|
122
|
+
prefix = []
|
123
|
+
prefix << @caller.prefix if not @caller.nil? and @caller.respond_to? :prefix
|
124
|
+
prefix << self.class.name
|
125
|
+
prefix.join(' - ')
|
126
|
+
end
|
127
|
+
|
128
|
+
def replace_placeholder(value)
|
129
|
+
value.gsub! '$ROOT$', @context['docroot_config'].root['full_build_path']
|
130
|
+
value.gsub! '$DOCROOT$', @context['docroot_config'].docroot_dir
|
131
|
+
value.gsub! '$PROJECT$', @context['full_build_path']
|
132
|
+
value.gsub! '$INFO$', @context['full_path']
|
133
|
+
value.gsub! '$ENVIRONMENT$', @context['docroot_config'].deploy_target['environment']
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Docman
|
2
|
+
class CompositeCommand
|
3
|
+
def initialize(caller = nil)
|
4
|
+
@caller = caller
|
5
|
+
@commands = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_command(cmd)
|
9
|
+
@commands << cmd
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_commands(cmds, context = nil)
|
13
|
+
return if cmds.nil?
|
14
|
+
cmds.each do |k, v|
|
15
|
+
@commands << Docman::Command.create(k, v, context)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform
|
20
|
+
@commands.each { |cmd| cmd.perform }
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_commands?
|
24
|
+
@commands.any?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Docman
|
4
|
+
class CreateSymlinkCmd < Docman::Command
|
5
|
+
|
6
|
+
register_command :create_symlink
|
7
|
+
|
8
|
+
def validate_command
|
9
|
+
raise "Please provide 'target_dir' param" if self['target_dir'].nil?
|
10
|
+
raise "Please provide 'context'" if @context.nil?
|
11
|
+
raise "Context should be of type 'Info'" unless @context.is_a? Docman::Info
|
12
|
+
raise "Directory #{File.join(@context['docroot_config'].docroot_dir, self['target_dir'])} not exists" unless File.directory? File.join(@context['docroot_config'].docroot_dir, self['target_dir'])
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
source_path = File.join(@context['docroot_config'].docroot_dir, self['target_dir'])
|
17
|
+
Dir.chdir source_path
|
18
|
+
source_pathname = Pathname.new source_path
|
19
|
+
target_pathname = Pathname.new @context['full_build_path']
|
20
|
+
relative_path = target_pathname.relative_path_from source_pathname
|
21
|
+
puts `ln -s #{relative_path} #{@context['name']}`
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
module Docman
|
3
|
+
class ExecuteScriptCmd < Docman::Command
|
4
|
+
|
5
|
+
register_command :script
|
6
|
+
|
7
|
+
|
8
|
+
def validate_command
|
9
|
+
raise Docman::CommandValidationError.new("Please provide 'execution_dir' param") if self['execution_dir'].nil?
|
10
|
+
raise Docman::CommandValidationError.new("Please provide 'location' param") if self['location'].nil?
|
11
|
+
raise Docman::CommandValidationError.new("Please provide 'context'") if @context.nil?
|
12
|
+
raise Docman::CommandValidationError.new("Context should be of type 'Info'") unless @context.is_a? Docman::Info
|
13
|
+
replace_placeholder(self['execution_dir'])
|
14
|
+
replace_placeholder(self['location'])
|
15
|
+
raise Docman::CommandValidationError.new("Directory #{self['execution_dir']} not exists") unless File.directory? self['execution_dir']
|
16
|
+
raise Docman::CommandValidationError.new("Script #{self['location']} not exists") unless File.file? self['location']
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute
|
20
|
+
Dir.chdir self['execution_dir']
|
21
|
+
logger.info "Script execution: #{self['location']}"
|
22
|
+
params = self['params'].nil? ? '' : prepare_params(self['params'])
|
23
|
+
`chmod a+x #{self['location']}`
|
24
|
+
logger.info `#{self['location']} #{params}`
|
25
|
+
$?.exitstatus
|
26
|
+
end
|
27
|
+
|
28
|
+
def prepare_params(params)
|
29
|
+
result = []
|
30
|
+
params.each do |param|
|
31
|
+
case param
|
32
|
+
when 'environment'
|
33
|
+
result << @context['docroot_config'].deploy_target['environment']
|
34
|
+
end
|
35
|
+
end
|
36
|
+
result.join(' ')
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Docman
|
2
|
+
class GitCommitCmd < Docman::Command
|
3
|
+
|
4
|
+
register_command :git_commit
|
5
|
+
|
6
|
+
def validate_command
|
7
|
+
raise "Please provide 'context'" if @context.nil?
|
8
|
+
raise "Context should be of type 'Info'" unless @context.is_a? Docman::Info
|
9
|
+
end
|
10
|
+
|
11
|
+
before_execute do
|
12
|
+
unless GitUtil.repo_changed? @context['root']['full_build_path']
|
13
|
+
raise NoChangesError, "Repo not changed needed, commit not needed"
|
14
|
+
end
|
15
|
+
# @not_execute = true unless GitUtil.repo_changed? @context['root']['full_build_path']
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute
|
19
|
+
message = "name: #{@context['name']} updated, state: #{@context['state']}"
|
20
|
+
with_logging(message) do
|
21
|
+
GitUtil.commit(@context['root']['full_build_path'], @context['full_build_path'], message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'net/sftp'
|
2
|
+
|
3
|
+
module Docman
|
4
|
+
|
5
|
+
class SSHTargetChecker < Docman::TargetChecker
|
6
|
+
register_checker :ssh
|
7
|
+
|
8
|
+
def execute
|
9
|
+
filename = File.join(self['file_path'], @context['docroot_name'] + @context['environment'], self['filename'])
|
10
|
+
Net::SFTP.start(self['ssh_host'], self['ssh_user']) do |sftp|
|
11
|
+
n = 0
|
12
|
+
begin
|
13
|
+
n+=1
|
14
|
+
log "Checking if files deployed, retry ##{n}, filename: #{filename}"
|
15
|
+
sftp.stat!(filename) do |response|
|
16
|
+
unless response.ok?
|
17
|
+
sleep 15
|
18
|
+
end
|
19
|
+
end
|
20
|
+
data = YAML.load sftp.download!(filename)
|
21
|
+
sleep 30
|
22
|
+
end until data['random'] == self['version']
|
23
|
+
end
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'net/sftp'
|
2
|
+
|
3
|
+
module Docman
|
4
|
+
|
5
|
+
class TargetChecker < Docman::Command
|
6
|
+
@@checkers = {}
|
7
|
+
|
8
|
+
def self.create(params = nil, context = nil)
|
9
|
+
c = @@checkers[params['handler']]
|
10
|
+
if c
|
11
|
+
c.new(params, context)
|
12
|
+
else
|
13
|
+
raise "Bad checker type: #{type}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.register_checker(name)
|
18
|
+
@@checkers[name] = self
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'hash_deep_merge'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
module Docman
|
6
|
+
class Config < Hash
|
7
|
+
|
8
|
+
def initialize(file)
|
9
|
+
super
|
10
|
+
@config = YAML::load_file(file)
|
11
|
+
assign_to_self
|
12
|
+
end
|
13
|
+
|
14
|
+
def assign_to_self
|
15
|
+
@config.each_pair do |k, v|
|
16
|
+
self[k] = v
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def merge_config_from_file(file)
|
21
|
+
config = YAML::load_file(file)
|
22
|
+
@config.deep_merge(config)
|
23
|
+
assign_to_self
|
24
|
+
end
|
25
|
+
|
26
|
+
def config_hash
|
27
|
+
Digest::MD5.hexdigest(Marshal::dump(self))
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -1,38 +1,142 @@
|
|
1
|
+
require 'docman/commands/target_checker'
|
2
|
+
require 'docman/commands/ssh_target_checker'
|
3
|
+
require 'docman/exceptions/no_changes_error'
|
4
|
+
require 'securerandom'
|
5
|
+
|
1
6
|
module Docman
|
2
7
|
module Deployers
|
3
|
-
class Deployer
|
8
|
+
class Deployer < Docman::Command
|
9
|
+
|
10
|
+
attr_accessor :before, :after
|
4
11
|
|
5
|
-
|
12
|
+
define_hooks :before_push, :after_push
|
6
13
|
|
7
|
-
@@
|
14
|
+
@@deployers = {}
|
8
15
|
|
9
|
-
def self.create(
|
10
|
-
c = @@
|
16
|
+
def self.create(params, context = nil, caller = nil)
|
17
|
+
c = @@deployers[params['handler']]
|
11
18
|
if c
|
12
|
-
c.new(
|
19
|
+
c.new(params, context, caller, 'deployer')
|
13
20
|
else
|
14
21
|
raise "Bad deployer type: #{type}"
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
18
25
|
def self.register_deployer(name)
|
19
|
-
@@
|
26
|
+
@@deployers[name] = self
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(params, context = nil, caller = nil, type = nil)
|
30
|
+
super(params, context, caller, type)
|
31
|
+
@docroot_config = caller.docroot_config
|
32
|
+
@builded = []
|
33
|
+
@build_results = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def config
|
37
|
+
unless self['name'].nil?
|
38
|
+
@docroot_config.chain(@docroot_config.info_by(self['name'])).values.each do |info|
|
39
|
+
add_actions(info)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
before_execute do
|
45
|
+
stored_config_hash = read_version_file_param('config_hash')
|
46
|
+
@config_hash = Docman::Application.instance.config.config_hash
|
47
|
+
stored_docroot_config_hash = read_version_file_param('docroot_config_hash')
|
48
|
+
@docroot_config_hash = @docroot_config.config_hash
|
49
|
+
# config = Docman::Application.instance.config
|
50
|
+
# log(config.to_yaml)
|
51
|
+
if stored_config_hash != @config_hash or stored_docroot_config_hash != @docroot_config_hash
|
52
|
+
logger.info 'Forced rebuild as configuration was changed'
|
53
|
+
Docman::Application.instance.force = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def execute
|
58
|
+
logger.info "Deploy started"
|
59
|
+
if self['name'].nil?
|
60
|
+
build_recursive
|
61
|
+
else
|
62
|
+
build_dir_chain(@docroot_config.info_by(self['name']))
|
63
|
+
end
|
64
|
+
|
65
|
+
if @changed
|
66
|
+
filename = 'version.yaml'
|
67
|
+
path = File.join(@docroot_config.root['full_build_path'], filename)
|
68
|
+
version = SecureRandom.hex
|
69
|
+
write_version_file version, path
|
70
|
+
run_with_hooks('push')
|
71
|
+
raise 'Files are not deployed' unless files_deployed? version, filename
|
72
|
+
else
|
73
|
+
logger.info 'No changes in docroot'
|
74
|
+
end
|
75
|
+
logger.debug 'Deploy results:'
|
76
|
+
logger.debug @build_results.to_yaml
|
77
|
+
logger.info 'Deploy finished'
|
78
|
+
end
|
79
|
+
|
80
|
+
def read_version_file_param(param)
|
81
|
+
path = File.join(@docroot_config.root['full_build_path'], 'version.yaml')
|
82
|
+
return false unless File.file?(path)
|
83
|
+
content = YAML::load_file(path)
|
84
|
+
content[param] if content.has_key? param
|
20
85
|
end
|
21
86
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
87
|
+
def write_version_file(version, path)
|
88
|
+
to_write = Hash.new
|
89
|
+
to_write['random'] = version
|
90
|
+
# config = Docman::Application.instance.config.raw_config
|
91
|
+
# log(config.to_yaml)
|
92
|
+
to_write['config_hash'] = @config_hash
|
93
|
+
to_write['docroot_config_hash'] = @docroot_config_hash
|
94
|
+
File.open(path, 'w') {|f| f.write to_write.to_yaml}
|
95
|
+
end
|
96
|
+
|
97
|
+
def build_dir_chain(info)
|
98
|
+
@docroot_config.chain(info).values.each do |item|
|
99
|
+
item.state = self['state']
|
100
|
+
if item.need_rebuild?
|
101
|
+
build_recursive(item)
|
102
|
+
return
|
103
|
+
elsif
|
104
|
+
build_dir(item)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_dir(info)
|
110
|
+
return if @builded.include? info['name']
|
111
|
+
info.state = self['state']
|
112
|
+
|
113
|
+
build_result = Docman::Builders::Builder.create(self['builders'][info['type']], info, self).perform
|
114
|
+
logger.info '-------------------------------------------------------'
|
115
|
+
@changed = true if build_result
|
116
|
+
@build_results[info['name']] = build_result
|
117
|
+
|
118
|
+
@builded << info['name']
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_recursive(info = nil)
|
122
|
+
info = info ? info : @docroot_config.structure
|
123
|
+
build_dir(info)
|
124
|
+
|
125
|
+
info['children'].each do |child|
|
126
|
+
build_recursive(child)
|
127
|
+
end
|
25
128
|
end
|
26
129
|
|
27
|
-
def
|
28
|
-
return
|
29
|
-
|
30
|
-
|
31
|
-
|
130
|
+
def files_deployed?(version, filename)
|
131
|
+
return true unless self.has_key? 'target_checker'
|
132
|
+
params = self['target_checker']
|
133
|
+
params['version'] = version
|
134
|
+
params['filename'] = filename
|
135
|
+
Docman::TargetChecker.create(params, self).perform
|
32
136
|
end
|
33
137
|
|
34
|
-
def
|
35
|
-
|
138
|
+
def describe(type = 'short')
|
139
|
+
properties_info(['handler'])
|
36
140
|
end
|
37
141
|
|
38
142
|
end
|