deployaml 0.1
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.
- data/History.txt +4 -0
- data/Manifest.txt +19 -0
- data/README.rdoc +105 -0
- data/Rakefile +20 -0
- data/bin/deployaml +129 -0
- data/lib/deployaml.rb +120 -0
- data/lib/deployaml/deployment.rb +48 -0
- data/lib/deployaml/destination.rb +39 -0
- data/lib/deployaml/local_destination.rb +39 -0
- data/lib/deployaml/post_install/pending_migrations.rb +11 -0
- data/lib/deployaml/post_install/restart_passenger.rb +13 -0
- data/lib/deployaml/pre_install/minify.rb +22 -0
- data/lib/deployaml/pre_install/write_string_to_file.rb +13 -0
- data/lib/deployaml/remote_destination.rb +122 -0
- data/lib/deployaml/scm/filesystem.rb +13 -0
- data/lib/deployaml/scm/git.rb +33 -0
- data/lib/deployaml/scm_base.rb +17 -0
- data/vendor/yuicompressor/build.xml +66 -0
- data/vendor/yuicompressor/build/yuicompressor-2.4.2.jar +0 -0
- metadata +151 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.rdoc
|
4
|
+
Rakefile
|
5
|
+
bin/deployaml
|
6
|
+
lib/deployaml.rb
|
7
|
+
lib/deployaml/deployment.rb
|
8
|
+
lib/deployaml/destination.rb
|
9
|
+
lib/deployaml/local_destination.rb
|
10
|
+
lib/deployaml/post_install/pending_migrations.rb
|
11
|
+
lib/deployaml/post_install/restart_passenger.rb
|
12
|
+
lib/deployaml/pre_install/minify.rb
|
13
|
+
lib/deployaml/pre_install/write_string_to_file.rb
|
14
|
+
lib/deployaml/remote_destination.rb
|
15
|
+
lib/deployaml/scm/filesystem.rb
|
16
|
+
lib/deployaml/scm/git.rb
|
17
|
+
lib/deployaml/scm_base.rb
|
18
|
+
vendor/yuicompressor/build.xml
|
19
|
+
vendor/yuicompressor/build/yuicompressor-2.4.2.jar
|
data/README.rdoc
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
= deployaml
|
2
|
+
|
3
|
+
* http://www.advancethinking.com/open_source/deployaml
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
YAML based deployment framework for your code.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* If a repo is used more than once, it will be cloned more than once (annoying)
|
12
|
+
* Not compressing remote file transfer (slow)
|
13
|
+
|
14
|
+
== SYNOPSIS:
|
15
|
+
|
16
|
+
To deploy your code using the deplo.yml file in the current directory:
|
17
|
+
|
18
|
+
$ deployaml
|
19
|
+
|
20
|
+
To view help:
|
21
|
+
|
22
|
+
$ deployaml --help
|
23
|
+
|
24
|
+
Example of very simple deplo.yml (with minification of CSS & JS):
|
25
|
+
|
26
|
+
- name: my deployment
|
27
|
+
repository:
|
28
|
+
path: username@source-host.com:/path/to/git/project
|
29
|
+
scm: git
|
30
|
+
pre_install:
|
31
|
+
- task: minify
|
32
|
+
|
33
|
+
destinations:
|
34
|
+
- path: /path/to/where/your/code/is/run
|
35
|
+
host: destination-host.com
|
36
|
+
username: destination_username
|
37
|
+
|
38
|
+
This will deploy the source code from source-host.com:/path/to/git/project, minify
|
39
|
+
the CSS and JavaScript and pop the resulting code in
|
40
|
+
destination-host.com:/path/to/where/your/code/is/run/releases/YYMMDDHHSS with a
|
41
|
+
corresponding symbolic link
|
42
|
+
destination-host.com:/path/to/where/your/code/is/run/releases/current
|
43
|
+
|
44
|
+
|
45
|
+
== REQUIREMENTS:
|
46
|
+
|
47
|
+
* highline
|
48
|
+
* net-ssh
|
49
|
+
* net-scp
|
50
|
+
|
51
|
+
== INSTALL:
|
52
|
+
|
53
|
+
* sudo gem install deployaml
|
54
|
+
|
55
|
+
== DEVELOPERS:
|
56
|
+
|
57
|
+
After checking out the source, run:
|
58
|
+
|
59
|
+
$ rake newb
|
60
|
+
|
61
|
+
This task will install any missing dependencies, run the tests/specs,
|
62
|
+
and generate the RDoc.
|
63
|
+
|
64
|
+
Before you can run your tests cleanly, you will need to create two yaml
|
65
|
+
files in the "stuff_not_to_be_committed" directory. One called
|
66
|
+
"host_and_username_and_password.yml" containing the following
|
67
|
+
(replacing "PUTSOMETHINGHERE"):
|
68
|
+
|
69
|
+
host: PUTSOMETHINGHERE
|
70
|
+
username: PUTSOMETHINGHERE
|
71
|
+
password: PUTSOMETHINGHERE
|
72
|
+
|
73
|
+
And another called "host_and_username_with_ssh_keys.yml" containing the following:
|
74
|
+
|
75
|
+
host: PUTSOMETHINGHERE
|
76
|
+
username: PUTSOMETHINGHERE
|
77
|
+
|
78
|
+
For this, you will need to setup some ssh keys so you are not prompted for a password.
|
79
|
+
|
80
|
+
IMPORTANT!! Needless to say, you should never commit these files!!
|
81
|
+
|
82
|
+
== LICENSE:
|
83
|
+
|
84
|
+
(The MIT License)
|
85
|
+
|
86
|
+
Copyright (c) 2010 FIX
|
87
|
+
|
88
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
89
|
+
a copy of this software and associated documentation files (the
|
90
|
+
'Software'), to deal in the Software without restriction, including
|
91
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
92
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
93
|
+
permit persons to whom the Software is furnished to do so, subject to
|
94
|
+
the following conditions:
|
95
|
+
|
96
|
+
The above copyright notice and this permission notice shall be
|
97
|
+
included in all copies or substantial portions of the Software.
|
98
|
+
|
99
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
100
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
101
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
102
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
103
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
104
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
105
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/deployaml'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
|
9
|
+
$hoe = Hoe.spec 'deployaml' do
|
10
|
+
self.developer 'Stephen Hardisty', 'moowahaha@hotmail.com'
|
11
|
+
self.rubyforge_name = self.name
|
12
|
+
self.extra_deps = [
|
13
|
+
['net-ssh', '>= 2.0.11'],
|
14
|
+
['net-scp', '>= 1.0.3'],
|
15
|
+
['highline', '>= 1.2.9']
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'newgem/tasks'
|
20
|
+
Dir['tasks/*.rake'].each { |t| load t }
|
data/bin/deployaml
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$usage = %Q{
|
4
|
+
== Synopsis
|
5
|
+
|
6
|
+
deployaml: deploys your project using the details from a deplo.yml file in the current directory
|
7
|
+
|
8
|
+
== Usage
|
9
|
+
|
10
|
+
deployaml [OPTION]
|
11
|
+
|
12
|
+
-h, --help
|
13
|
+
show help and exit
|
14
|
+
|
15
|
+
-v VERSION, --version=VERSION
|
16
|
+
deploy a given version of the code (such as a tag)
|
17
|
+
|
18
|
+
-t, --tasks
|
19
|
+
list available pre and post install tasks and exit
|
20
|
+
|
21
|
+
-s, --scms
|
22
|
+
list available source control systems and exit
|
23
|
+
|
24
|
+
-d, --deployments
|
25
|
+
lists the deployments from your deplo.yml file and exit
|
26
|
+
|
27
|
+
-I DIRECTORY, --include=DIRECTORY
|
28
|
+
path containing custom pre/post install tasks and source control handling
|
29
|
+
}
|
30
|
+
|
31
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
32
|
+
|
33
|
+
require 'getoptlong'
|
34
|
+
require 'rdoc/usage'
|
35
|
+
require 'deployaml'
|
36
|
+
|
37
|
+
|
38
|
+
def list_tasks deployaml
|
39
|
+
%w{ pre_install post_install }.each do |thingy|
|
40
|
+
puts "All #{thingy}:"
|
41
|
+
deployaml.send('all_' + thingy).each do |elem|
|
42
|
+
puts "\t#{elem}"
|
43
|
+
end
|
44
|
+
puts "\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
exit(0)
|
48
|
+
end
|
49
|
+
|
50
|
+
def list_scms deployaml
|
51
|
+
puts "All scms:"
|
52
|
+
deployaml.all_scms.each do |elem|
|
53
|
+
puts "\t#{elem}"
|
54
|
+
end
|
55
|
+
puts "\n"
|
56
|
+
|
57
|
+
exit(0)
|
58
|
+
end
|
59
|
+
|
60
|
+
def list_deployments deployaml
|
61
|
+
puts "Available deployments:"
|
62
|
+
deployaml.available_deployments.each do |deployment|
|
63
|
+
puts "\t#{deployment}"
|
64
|
+
end
|
65
|
+
puts "\n"
|
66
|
+
|
67
|
+
exit(0)
|
68
|
+
end
|
69
|
+
|
70
|
+
def show_usage
|
71
|
+
markup = SM::SimpleMarkup.new
|
72
|
+
flow_convertor = SM::ToFlow.new
|
73
|
+
|
74
|
+
flow = markup.convert($usage, flow_convertor)
|
75
|
+
|
76
|
+
options = RI::Options.instance
|
77
|
+
formatter = options.formatter.new(options, "")
|
78
|
+
formatter.display_flow(flow)
|
79
|
+
|
80
|
+
exit(0)
|
81
|
+
end
|
82
|
+
|
83
|
+
def include_path_from_options(opts)
|
84
|
+
include_path = nil
|
85
|
+
|
86
|
+
remaining_options = []
|
87
|
+
opts.each do |opt, arg|
|
88
|
+
case opt
|
89
|
+
when '--include'
|
90
|
+
include_path = arg
|
91
|
+
else
|
92
|
+
remaining_options << [opt, arg]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
return include_path, remaining_options
|
97
|
+
end
|
98
|
+
|
99
|
+
include_path, opts = include_path_from_options(
|
100
|
+
GetoptLong.new(
|
101
|
+
['--help', '-h', GetoptLong::NO_ARGUMENT],
|
102
|
+
['--tasks', '-l', GetoptLong::NO_ARGUMENT],
|
103
|
+
['--scms', '-s', GetoptLong::NO_ARGUMENT],
|
104
|
+
['--deployments', '-d', GetoptLong::NO_ARGUMENT],
|
105
|
+
['--version', '-v', GetoptLong::REQUIRED_ARGUMENT],
|
106
|
+
['--include', '-I', GetoptLong::REQUIRED_ARGUMENT]
|
107
|
+
)
|
108
|
+
)
|
109
|
+
|
110
|
+
deployaml = Deployaml::Runner.new(include_path ? {:include => include_path} : {})
|
111
|
+
version = nil
|
112
|
+
|
113
|
+
opts.each do |opt, arg|
|
114
|
+
case opt
|
115
|
+
when '--help'
|
116
|
+
show_usage
|
117
|
+
when '--tasks'
|
118
|
+
list_tasks(deployaml)
|
119
|
+
when '--scms'
|
120
|
+
list_scms(deployaml)
|
121
|
+
when '--deployments'
|
122
|
+
list_deployments(deployaml)
|
123
|
+
when '--version'
|
124
|
+
version = arg
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
deployaml.go!(:version => version)
|
129
|
+
|
data/lib/deployaml.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'deployaml', '**', '*.rb')).each do |file|
|
4
|
+
require file
|
5
|
+
end
|
6
|
+
|
7
|
+
module Deployaml
|
8
|
+
class Runner
|
9
|
+
VERSION = '0.1'
|
10
|
+
|
11
|
+
def initialize(args = {})
|
12
|
+
load_custom_path(args[:include]) if args[:include]
|
13
|
+
load_deployments
|
14
|
+
load_scm
|
15
|
+
load_pre_install_tasks
|
16
|
+
load_post_install_tasks
|
17
|
+
end
|
18
|
+
|
19
|
+
def all_scms
|
20
|
+
@scms.keys.sort
|
21
|
+
end
|
22
|
+
|
23
|
+
def all_pre_install
|
24
|
+
@pre_install.keys.sort
|
25
|
+
end
|
26
|
+
|
27
|
+
def all_post_install
|
28
|
+
@post_install.keys.sort
|
29
|
+
end
|
30
|
+
|
31
|
+
def available_deployments
|
32
|
+
@deployments.sort { |a, b| a.name <=> b.name }.map do |deployment|
|
33
|
+
"#{deployment.name} (from #{deployment.scm})"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def go!(args = {})
|
38
|
+
@deployments.each do |deployment|
|
39
|
+
puts "Deploying #{deployment.name}"
|
40
|
+
scm = concrete_scm(deployment)
|
41
|
+
scm.stage(deployment, args[:version])
|
42
|
+
|
43
|
+
run_pre_install_tasks(deployment)
|
44
|
+
|
45
|
+
deployment.destinations.each do |destination|
|
46
|
+
destination.install_from deployment.staging_path
|
47
|
+
run_post_install_tasks(deployment, destination)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def children_of constant
|
55
|
+
classes = {}
|
56
|
+
|
57
|
+
Deployaml.const_get(constant).constants.each do |scm|
|
58
|
+
underscore_name = scm.gsub(/[A-Z]/) { |x| '_' + x.downcase }.gsub(/^_/, '')
|
59
|
+
classes[underscore_name] = Deployaml.const_get(constant).const_get(scm).new
|
60
|
+
end
|
61
|
+
|
62
|
+
classes
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_class_from_collection collection, nice_collection_type, nice_class, deployment
|
66
|
+
klass = collection.find { |x| x[0] == nice_class }
|
67
|
+
|
68
|
+
return klass[1] if klass
|
69
|
+
|
70
|
+
raise "Do not know of #{nice_collection_type} '#{nice_class}' for '#{deployment.name}'. Available: #{collection.keys.sort.join(', ')}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def concrete_scm deployment
|
74
|
+
find_class_from_collection(@scms, 'scm', deployment.scm, deployment)
|
75
|
+
end
|
76
|
+
|
77
|
+
def load_scm
|
78
|
+
@scms = children_of 'Scm'
|
79
|
+
end
|
80
|
+
|
81
|
+
def load_pre_install_tasks
|
82
|
+
@pre_install = children_of 'PreInstall'
|
83
|
+
end
|
84
|
+
|
85
|
+
def load_post_install_tasks
|
86
|
+
@post_install = children_of 'PostInstall'
|
87
|
+
end
|
88
|
+
|
89
|
+
def load_custom_path path
|
90
|
+
Dir.glob(File.join(path, '**', '*.rb')).each do |file|
|
91
|
+
require file
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def run_pre_install_tasks deployment
|
96
|
+
return if deployment.pre_install_tasks.nil? || deployment.pre_install_tasks.empty?
|
97
|
+
|
98
|
+
deployment.pre_install_tasks.each do |task|
|
99
|
+
task_klass = find_class_from_collection(@pre_install, 'pre_install', task['task'], deployment)
|
100
|
+
task_klass.run(deployment, task['parameters'])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def run_post_install_tasks deployment, destination
|
105
|
+
return if deployment.post_install_tasks.nil? || deployment.post_install_tasks.empty?
|
106
|
+
|
107
|
+
deployment.post_install_tasks.each do |task|
|
108
|
+
task_klass = find_class_from_collection(@post_install, 'post_install', task['task'], deployment)
|
109
|
+
task_klass.run(deployment, destination, task['parameters'])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def load_deployments
|
114
|
+
yaml_file = File.join(Dir.pwd, 'deplo.yml')
|
115
|
+
raise "Cannot find deployment YAML file #{yaml_file}" unless File.exists?(yaml_file)
|
116
|
+
yaml = YAML.load_file(yaml_file)
|
117
|
+
@deployments = yaml ? yaml.map { |d| Deployaml::Deployment.new(d) }.compact : []
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require File.join(File.dirname(__FILE__), 'destination')
|
3
|
+
require File.join(File.dirname(__FILE__), 'local_destination')
|
4
|
+
require File.join(File.dirname(__FILE__), 'remote_destination')
|
5
|
+
|
6
|
+
module Deployaml
|
7
|
+
class Deployment
|
8
|
+
attr_reader :scm, :name, :repository_path, :staging_path, :pre_install_tasks, :post_install_tasks, :destinations
|
9
|
+
|
10
|
+
def initialize params
|
11
|
+
validate params
|
12
|
+
|
13
|
+
@scm = params['repository']['scm'] || 'filesystem'
|
14
|
+
@name = params['name']
|
15
|
+
@repository_path = params['repository']['path']
|
16
|
+
@pre_install_tasks = params['pre_install']
|
17
|
+
@post_install_tasks = params['post_install']
|
18
|
+
|
19
|
+
initialize_destinations(params)
|
20
|
+
construct_stage
|
21
|
+
end
|
22
|
+
|
23
|
+
def construct_stage
|
24
|
+
@staging_path = File.join(Dir.tmpdir, 'deployaml', File.basename(repository_path))
|
25
|
+
FileUtils.rm_r(@staging_path) if File.directory?(@staging_path)
|
26
|
+
FileUtils.mkdir_p(@staging_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def initialize_destinations params
|
32
|
+
@destinations = []
|
33
|
+
|
34
|
+
params['destinations'].map {|d| @destinations << Deployaml::Destination.new(d)}
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate params
|
38
|
+
raise "Deployment has no name" unless param_specified?(params['name'])
|
39
|
+
raise "Deployment '#{params['name']}' has no repository specified" unless param_specified?(params['repository'])
|
40
|
+
raise "No destinations specified for '#{params['name']}'" unless param_specified?(params['destinations'])
|
41
|
+
raise "No repository path specified for '#{params['name']}'" unless param_specified?(params['repository']['path'])
|
42
|
+
end
|
43
|
+
|
44
|
+
def param_specified? param
|
45
|
+
!param.nil? && !param.empty?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Deployaml
|
2
|
+
class Destination
|
3
|
+
attr_reader :path, :live_path
|
4
|
+
|
5
|
+
def initialize params
|
6
|
+
@path = params['path'] || raise("A destination path must be specified")
|
7
|
+
@live_path = File.join(path, 'current')
|
8
|
+
|
9
|
+
@delegatee = params.has_key?('host') ?
|
10
|
+
Deployaml::RemoteDestination.new(params) :
|
11
|
+
Deployaml::LocalDestination.new(params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def install_from local_path
|
15
|
+
exec("mkdir -p #{File.join(path, 'releases')}")
|
16
|
+
|
17
|
+
destination_path = File.join(path, 'releases', Time.now.strftime('%Y%m%d%H%M%S'))
|
18
|
+
|
19
|
+
@delegatee.copy(local_path, destination_path)
|
20
|
+
|
21
|
+
exec("rm -f #{live_path}")
|
22
|
+
exec("ln -s #{destination_path} #{live_path}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def exec command
|
26
|
+
ok_message = rand.to_s
|
27
|
+
|
28
|
+
extra_command = " && echo #{ok_message}"
|
29
|
+
|
30
|
+
puts "$ #{command}"
|
31
|
+
had_error, output = @delegatee.execute_and_verify(command, extra_command, ok_message)
|
32
|
+
puts "\n"
|
33
|
+
|
34
|
+
raise "Error executing '#{command}'" if had_error
|
35
|
+
|
36
|
+
return output
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Deployaml
|
4
|
+
class LocalDestination
|
5
|
+
def initialize params
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute_and_verify(command, extra_command, ok_message)
|
9
|
+
had_error = true
|
10
|
+
output = ''
|
11
|
+
|
12
|
+
Open3.popen3(command + extra_command) do |stdin, stdout, stderr|
|
13
|
+
stdout_output, stderr_output = stdout.read, stderr.read
|
14
|
+
|
15
|
+
if !stdout_output.empty?
|
16
|
+
if stdout_output.gsub!(/#{ok_message}\n/, '')
|
17
|
+
had_error = false
|
18
|
+
end
|
19
|
+
|
20
|
+
output += stdout_output
|
21
|
+
$stdout.print stdout_output
|
22
|
+
end
|
23
|
+
|
24
|
+
if !stderr_output.empty?
|
25
|
+
output += stderr_output
|
26
|
+
$stderr.print stderr_output
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
return had_error, output
|
31
|
+
end
|
32
|
+
|
33
|
+
def copy from, to
|
34
|
+
puts "Copying #{from} => #{to}"
|
35
|
+
FileUtils.cp_r(from, to)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Deployaml
|
2
|
+
module PostInstall
|
3
|
+
class RestartPassenger
|
4
|
+
|
5
|
+
def run deployment, destination, params
|
6
|
+
tmp_dir = File.join(destination.live_path, 'tmp')
|
7
|
+
destination.exec('mkdir -p ' + tmp_dir)
|
8
|
+
destination.exec('touch ' + File.join(tmp_dir, 'restart.txt'))
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Deployaml
|
2
|
+
module PreInstall
|
3
|
+
class Minify
|
4
|
+
|
5
|
+
def run deployment, params
|
6
|
+
yui_jar = File.join(
|
7
|
+
File.dirname(__FILE__),
|
8
|
+
'..', '..', '..', 'vendor', 'yuicompressor', 'build', 'yuicompressor-2.4.2.jar'
|
9
|
+
)
|
10
|
+
|
11
|
+
base_pattern = [deployment.staging_path, '**']
|
12
|
+
|
13
|
+
[Dir.glob(File.join(base_pattern + ['*.js'])), Dir.glob(File.join(base_pattern + ['*.css']))].flatten.each do |file|
|
14
|
+
puts "Minifying #{file}"
|
15
|
+
`java -jar #{yui_jar} #{file} -o #{file}`
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/scp'
|
3
|
+
require 'highline'
|
4
|
+
|
5
|
+
module Deployaml
|
6
|
+
class RemoteDestination
|
7
|
+
def initialize params
|
8
|
+
@host = params['host'] || raise('A remote host must be specified')
|
9
|
+
@username = params['username'] || ENV['USER']
|
10
|
+
end
|
11
|
+
|
12
|
+
def session
|
13
|
+
@session_not_to_ever_be_called_directly ||= ssh_connect
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute_and_verify(command, extra_command, ok_message)
|
17
|
+
had_error = true
|
18
|
+
output = ''
|
19
|
+
|
20
|
+
session.exec!(command + extra_command) do |ch, stream, data|
|
21
|
+
if stream == :stderr
|
22
|
+
$stderr.print data
|
23
|
+
output += data
|
24
|
+
else
|
25
|
+
if data.strip == ok_message
|
26
|
+
had_error = false
|
27
|
+
else
|
28
|
+
$stdout.print data
|
29
|
+
output += data
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
return had_error, output
|
35
|
+
end
|
36
|
+
|
37
|
+
def copy from, to
|
38
|
+
compressed_file = compress(from, to)
|
39
|
+
puts "Copying #{from} #{@username}@#{@host}:#{to}"
|
40
|
+
|
41
|
+
session.scp.upload!(compressed_file, to)
|
42
|
+
|
43
|
+
decompress(to)
|
44
|
+
|
45
|
+
puts "\n"
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def compress from, to
|
52
|
+
puts "Compressing for transfer..."
|
53
|
+
root_directory, to_zip = File.dirname(from), File.basename(to)
|
54
|
+
|
55
|
+
compressed_file = to_zip + '.tgz'
|
56
|
+
`cd #{root_directory} && cp -rf #{from} #{to_zip} && tar -zcf #{compressed_file} #{to_zip}`
|
57
|
+
|
58
|
+
File.join(root_directory, compressed_file)
|
59
|
+
end
|
60
|
+
|
61
|
+
def decompress destination
|
62
|
+
puts "Inflating at destination..."
|
63
|
+
destination_root = File.dirname(destination)
|
64
|
+
compressed_file = File.basename(destination)
|
65
|
+
session.exec!("cd #{destination_root} && tar -zxf #{compressed_file}")
|
66
|
+
end
|
67
|
+
|
68
|
+
def ssh_connect
|
69
|
+
begin
|
70
|
+
connect_without_password
|
71
|
+
rescue Net::SSH::AuthenticationFailed
|
72
|
+
connect_with_password
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def connect_without_password
|
77
|
+
Net::SSH.start(@host, @username)
|
78
|
+
end
|
79
|
+
|
80
|
+
def connect_with_password
|
81
|
+
flush_stdin
|
82
|
+
Net::SSH.start(
|
83
|
+
@host,
|
84
|
+
@username,
|
85
|
+
:password => HighLine.new.ask(
|
86
|
+
"#{@username}@#{@host}'s password: "
|
87
|
+
) { |a| a.echo = false }
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def flush_stdin
|
92
|
+
begin
|
93
|
+
require 'fcntl'
|
94
|
+
STDIN.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
|
95
|
+
STDIN.read
|
96
|
+
rescue
|
97
|
+
#
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# weird string monkey patch
|
105
|
+
class String
|
106
|
+
def shellescape
|
107
|
+
# An empty argument will be skipped, so return empty quotes.
|
108
|
+
return "''" if self.empty?
|
109
|
+
|
110
|
+
str = self.dup
|
111
|
+
|
112
|
+
# Process as a single byte sequence because not all shell
|
113
|
+
# implementations are multibyte aware.
|
114
|
+
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
|
115
|
+
|
116
|
+
# A LF cannot be escaped with a backslash because a backslash + LF
|
117
|
+
# combo is regarded as line continuation and simply ignored.
|
118
|
+
str.gsub!(/\n/, "'\n'")
|
119
|
+
|
120
|
+
return str
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../scm_base'
|
2
|
+
|
3
|
+
module Deployaml
|
4
|
+
module Scm
|
5
|
+
class Filesystem < ScmBase
|
6
|
+
def fetch_files
|
7
|
+
raise "Cannot read repository #{deployment.repository_path} for '#{deployment.name}'" unless File.exists?(deployment.repository_path)
|
8
|
+
|
9
|
+
FileUtils.cp_r(deployment.repository_path, File.dirname(deployment.staging_path), :remove_destination => true)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../scm_base'
|
2
|
+
|
3
|
+
module Deployaml
|
4
|
+
module Scm
|
5
|
+
class Git < ScmBase
|
6
|
+
def fetch_files
|
7
|
+
puts "Cloning #{deployment.repository_path}"
|
8
|
+
FileUtils.rm_r(deployment.staging_path) if File.exists?(deployment.staging_path)
|
9
|
+
cloned?(`git clone #{deployment.repository_path} #{deployment.staging_path} 2>&1`)
|
10
|
+
|
11
|
+
`cd #{deployment.staging_path} && git checkout #{version}` if version
|
12
|
+
|
13
|
+
`cd #{deployment.staging_path} && git submodule init && git submodule update`
|
14
|
+
end
|
15
|
+
|
16
|
+
def cloned? result
|
17
|
+
if result =~ /fatal:/
|
18
|
+
raise "Cannot read repository #{deployment.repository_path} for '#{deployment.name}'" if result =~ /not a git archive/ || result =~ /not appear to be a git repository/ || result =~ /Could not switch to/
|
19
|
+
raise "Error cloning #{deployment.repository_path}: #{result}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def clean
|
24
|
+
# remove all the gittish files
|
25
|
+
Dir.glob(File.join(deployment.staging_path, '**', '.git*')).each do |git_file|
|
26
|
+
FileUtils.rm_r(git_file)
|
27
|
+
end
|
28
|
+
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Deployaml
|
2
|
+
class ScmBase
|
3
|
+
attr_reader :deployment, :version
|
4
|
+
|
5
|
+
def stage deployment, version = nil
|
6
|
+
@deployment = deployment
|
7
|
+
@version = version
|
8
|
+
fetch_files
|
9
|
+
clean
|
10
|
+
end
|
11
|
+
|
12
|
+
def clean
|
13
|
+
deploy_file = File.join(deployment.staging_path, 'deplo.yml')
|
14
|
+
File.unlink(deploy_file) if File.exists?(deploy_file)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
2
|
+
<project name="YUI Compressor" default="build.jar" basedir=".">
|
3
|
+
|
4
|
+
<target name="clean" depends="-load.properties">
|
5
|
+
<delete dir="${build.dir}" quiet="true"/>
|
6
|
+
</target>
|
7
|
+
|
8
|
+
<target name="-load.properties">
|
9
|
+
<property file="ant.properties"/>
|
10
|
+
</target>
|
11
|
+
|
12
|
+
<target name="-init" depends="-load.properties">
|
13
|
+
<mkdir dir="${build.dir}"/>
|
14
|
+
</target>
|
15
|
+
|
16
|
+
<target name="build.classes" depends="-init">
|
17
|
+
<mkdir dir="${build.dir}/classes"/>
|
18
|
+
<javac srcdir="${src.dir}"
|
19
|
+
destdir="${build.dir}/classes"
|
20
|
+
includes="**/*.java"
|
21
|
+
deprecation="off"
|
22
|
+
debug="on"
|
23
|
+
source="1.4">
|
24
|
+
<classpath>
|
25
|
+
<pathelement location="${lib.dir}/jargs-1.0.jar"/>
|
26
|
+
<pathelement location="${lib.dir}/rhino-1.6R7.jar"/>
|
27
|
+
</classpath>
|
28
|
+
</javac>
|
29
|
+
</target>
|
30
|
+
|
31
|
+
<target name="build.jar" depends="build.classes">
|
32
|
+
<mkdir dir="${build.dir}/jar"/>
|
33
|
+
<!-- The order is important here. Rhino MUST be unjarred first!
|
34
|
+
(some of our own classes will override the Rhino classes) -->
|
35
|
+
<unjar src="${lib.dir}/jargs-1.0.jar" dest="${build.dir}/jar"/>
|
36
|
+
<unjar src="${lib.dir}/rhino-1.6R7.jar" dest="${build.dir}/jar"/>
|
37
|
+
<copy todir="${build.dir}/jar">
|
38
|
+
<fileset dir="${build.dir}/classes" includes="**/*.class"/>
|
39
|
+
</copy>
|
40
|
+
<jar destfile="${build.dir}/${jar.name}" basedir="${build.dir}/jar">
|
41
|
+
<manifest>
|
42
|
+
<attribute name="Main-Class" value="com.yahoo.platform.yui.compressor.Bootstrap"/>
|
43
|
+
</manifest>
|
44
|
+
</jar>
|
45
|
+
</target>
|
46
|
+
|
47
|
+
<target name="build.dist.package" depends="build.jar">
|
48
|
+
<mkdir dir="${build.dir}/${dist.package.name}"/>
|
49
|
+
<mkdir dir="${build.dir}/${dist.package.name}/build"/>
|
50
|
+
<copy file="${build.dir}/${jar.name}" todir="${build.dir}/${dist.package.name}/build"/>
|
51
|
+
<copy todir="${build.dir}/${dist.package.name}">
|
52
|
+
<fileset dir=".">
|
53
|
+
<include name="ant.properties"/>
|
54
|
+
<include name="build.xml"/>
|
55
|
+
<include name="doc/**/*"/>
|
56
|
+
<include name="lib/**/*"/>
|
57
|
+
<include name="src/**/*"/>
|
58
|
+
<exclude name="**/CVS"/>
|
59
|
+
</fileset>
|
60
|
+
</copy>
|
61
|
+
<zip destfile="${build.dir}/${dist.package.name}.zip"
|
62
|
+
basedir="${build.dir}"
|
63
|
+
includes="${dist.package.name}/**/*"/>
|
64
|
+
</target>
|
65
|
+
|
66
|
+
</project>
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deployaml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Stephen Hardisty
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-09-20 00:00:00 +10:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: net-ssh
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
segments:
|
27
|
+
- 2
|
28
|
+
- 0
|
29
|
+
- 11
|
30
|
+
version: 2.0.11
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: net-scp
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 1
|
42
|
+
- 0
|
43
|
+
- 3
|
44
|
+
version: 1.0.3
|
45
|
+
type: :runtime
|
46
|
+
version_requirements: *id002
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: highline
|
49
|
+
prerelease: false
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 1
|
56
|
+
- 2
|
57
|
+
- 9
|
58
|
+
version: 1.2.9
|
59
|
+
type: :runtime
|
60
|
+
version_requirements: *id003
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rubyforge
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 2
|
70
|
+
- 0
|
71
|
+
- 4
|
72
|
+
version: 2.0.4
|
73
|
+
type: :development
|
74
|
+
version_requirements: *id004
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: hoe
|
77
|
+
prerelease: false
|
78
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 2
|
84
|
+
- 6
|
85
|
+
- 1
|
86
|
+
version: 2.6.1
|
87
|
+
type: :development
|
88
|
+
version_requirements: *id005
|
89
|
+
description: YAML based deployment framework for your code.
|
90
|
+
email:
|
91
|
+
- moowahaha@hotmail.com
|
92
|
+
executables:
|
93
|
+
- deployaml
|
94
|
+
extensions: []
|
95
|
+
|
96
|
+
extra_rdoc_files:
|
97
|
+
- History.txt
|
98
|
+
- Manifest.txt
|
99
|
+
files:
|
100
|
+
- History.txt
|
101
|
+
- Manifest.txt
|
102
|
+
- README.rdoc
|
103
|
+
- Rakefile
|
104
|
+
- bin/deployaml
|
105
|
+
- lib/deployaml.rb
|
106
|
+
- lib/deployaml/deployment.rb
|
107
|
+
- lib/deployaml/destination.rb
|
108
|
+
- lib/deployaml/local_destination.rb
|
109
|
+
- lib/deployaml/post_install/pending_migrations.rb
|
110
|
+
- lib/deployaml/post_install/restart_passenger.rb
|
111
|
+
- lib/deployaml/pre_install/minify.rb
|
112
|
+
- lib/deployaml/pre_install/write_string_to_file.rb
|
113
|
+
- lib/deployaml/remote_destination.rb
|
114
|
+
- lib/deployaml/scm/filesystem.rb
|
115
|
+
- lib/deployaml/scm/git.rb
|
116
|
+
- lib/deployaml/scm_base.rb
|
117
|
+
- vendor/yuicompressor/build.xml
|
118
|
+
- vendor/yuicompressor/build/yuicompressor-2.4.2.jar
|
119
|
+
has_rdoc: true
|
120
|
+
homepage: http://www.advancethinking.com/open_source/deployaml
|
121
|
+
licenses: []
|
122
|
+
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options:
|
125
|
+
- --main
|
126
|
+
- README.rdoc
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
version: "0"
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
segments:
|
141
|
+
- 0
|
142
|
+
version: "0"
|
143
|
+
requirements: []
|
144
|
+
|
145
|
+
rubyforge_project: deployaml
|
146
|
+
rubygems_version: 1.3.6
|
147
|
+
signing_key:
|
148
|
+
specification_version: 3
|
149
|
+
summary: YAML based deployment framework for your code.
|
150
|
+
test_files: []
|
151
|
+
|