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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -4
  3. data/bin/functions.sh +265 -0
  4. data/config/config.yaml +36 -39
  5. data/config/cucumber.yml +2 -0
  6. data/docman.gemspec +3 -0
  7. data/features/acquia/acquia.feature +65 -0
  8. data/features/init/init.feature +27 -0
  9. data/features/local/local.feature +88 -0
  10. data/features/local/local_deploy.feature +6 -0
  11. data/features/support/env.rb +1 -1
  12. data/lib/application.rb +48 -11
  13. data/lib/docman/builders/builder.rb +34 -34
  14. data/lib/docman/builders/dir_builder.rb +21 -0
  15. data/lib/docman/builders/drupal_drush_builder.rb +22 -0
  16. data/lib/docman/builders/git_direct_builder.rb +20 -0
  17. data/lib/docman/builders/git_strip_builder.rb +27 -0
  18. data/lib/docman/cli.rb +3 -0
  19. data/lib/docman/commands/clean_changed_cmd.rb +23 -0
  20. data/lib/docman/commands/command.rb +137 -0
  21. data/lib/docman/commands/composite_command.rb +28 -0
  22. data/lib/docman/commands/create_symlink_cmd.rb +24 -0
  23. data/lib/docman/commands/execute_script_cmd.rb +40 -0
  24. data/lib/docman/commands/git_commit_cmd.rb +25 -0
  25. data/lib/docman/commands/ssh_target_checker.rb +28 -0
  26. data/lib/docman/commands/target_checker.rb +22 -0
  27. data/lib/docman/config.rb +31 -0
  28. data/lib/docman/context.rb +11 -0
  29. data/lib/docman/deployers/common_deployer.rb +2 -1
  30. data/lib/docman/deployers/deployer.rb +121 -17
  31. data/lib/docman/deployers/git_deployer.rb +6 -3
  32. data/lib/docman/docroot_config.rb +15 -17
  33. data/lib/docman/docroot_controller.rb +23 -35
  34. data/lib/docman/exceptions/command_validation_error.rb +5 -0
  35. data/lib/docman/exceptions/no_changes_error.rb +5 -0
  36. data/lib/docman/exec.rb +1 -1
  37. data/lib/docman/git_util.rb +43 -15
  38. data/lib/docman/info.rb +63 -9
  39. data/lib/docman/logging.rb +41 -0
  40. data/lib/docman/version.rb +1 -1
  41. metadata +72 -8
  42. data/features/local.feature +0 -60
  43. data/features/local_deploy.feature +0 -37
  44. data/lib/docman/builders/common_builder.rb +0 -16
  45. 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
@@ -0,0 +1,11 @@
1
+ require 'docman/logging'
2
+
3
+ module Docman
4
+ module Context
5
+ include Docman::Logging
6
+
7
+ def describe(type = 'short')
8
+ self.class.name
9
+ end
10
+ end
11
+ end
@@ -4,8 +4,9 @@ module Docman
4
4
 
5
5
  register_deployer :common_deployer
6
6
 
7
- def push(info, state_name)
7
+ def push
8
8
  end
9
+
9
10
  end
10
11
  end
11
12
  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
- attr_reader :deploy_target
12
+ define_hooks :before_push, :after_push
6
13
 
7
- @@subclasses = {}
14
+ @@deployers = {}
8
15
 
9
- def self.create(type, deploy_target)
10
- c = @@subclasses[type]
16
+ def self.create(params, context = nil, caller = nil)
17
+ c = @@deployers[params['handler']]
11
18
  if c
12
- c.new(deploy_target)
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
- @@subclasses[name] = self
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 initialize(deploy_target)
23
- @deployed = []
24
- @deploy_target = deploy_target
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 build(root, info)
28
- return if @deployed.include? info['name']
29
- build_type = build_type(info['type'])
30
- Docman::Builders::Builder.create(build_type['handler'], root, build_type, info).do()
31
- @deployed << info['name']
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 build_type(type)
35
- @deploy_target['builders'][type]
138
+ def describe(type = 'short')
139
+ properties_info(['handler'])
36
140
  end
37
141
 
38
142
  end