engineyard-serverside 1.3.7 → 1.4.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.
- data/bin/engineyard-serverside +1 -1
- data/lib/engineyard-serverside.rb +37 -33
- data/lib/engineyard-serverside/bundle_installer.rb +3 -1
- data/lib/engineyard-serverside/cli.rb +196 -194
- data/lib/engineyard-serverside/configuration.rb +109 -107
- data/lib/engineyard-serverside/deploy.rb +273 -271
- data/lib/engineyard-serverside/deploy_hook.rb +57 -55
- data/lib/engineyard-serverside/deprecation.rb +27 -0
- data/lib/engineyard-serverside/lockfile_parser.rb +80 -78
- data/lib/engineyard-serverside/logged_output.rb +56 -54
- data/lib/engineyard-serverside/server.rb +67 -64
- data/lib/engineyard-serverside/strategies/git.rb +110 -108
- data/lib/engineyard-serverside/task.rb +48 -45
- data/lib/engineyard-serverside/version.rb +3 -1
- data/spec/custom_deploy_spec.rb +5 -5
- data/spec/deploy_hook_spec.rb +3 -3
- data/spec/deprecation_spec.rb +25 -0
- data/spec/git_strategy_spec.rb +1 -1
- data/spec/lockfile_parser_spec.rb +4 -4
- data/spec/real_deploy_spec.rb +13 -7
- data/spec/restart_spec.rb +4 -4
- data/spec/server_spec.rb +24 -24
- data/spec/spec_helper.rb +15 -13
- metadata +8 -5
@@ -2,92 +2,95 @@ require 'open-uri'
|
|
2
2
|
require 'engineyard-serverside/logged_output'
|
3
3
|
|
4
4
|
module EY
|
5
|
-
|
6
|
-
|
5
|
+
module Serverside
|
6
|
+
class Server < Struct.new(:hostname, :roles, :name, :user)
|
7
|
+
include LoggedOutput
|
8
|
+
|
9
|
+
class DuplicateHostname < StandardError
|
10
|
+
def initialize(hostname)
|
11
|
+
super "There is already an EY::Serverside::Server with hostname '#{hostname}'"
|
12
|
+
end
|
13
|
+
end
|
7
14
|
|
8
|
-
|
9
|
-
|
10
|
-
|
15
|
+
def initialize(*fields)
|
16
|
+
super
|
17
|
+
self.roles = self.roles.map { |r| r.to_sym } if self.roles
|
11
18
|
end
|
12
|
-
end
|
13
19
|
|
14
|
-
|
15
|
-
super
|
16
|
-
self.roles = self.roles.map { |r| r.to_sym } if self.roles
|
17
|
-
end
|
20
|
+
attr_writer :default_task
|
18
21
|
|
19
|
-
|
22
|
+
def self.from_roles(*want_roles)
|
23
|
+
want_roles = want_roles.flatten.compact.map{|r| r.to_sym}
|
24
|
+
return all if !want_roles || want_roles.include?(:all) || want_roles.empty?
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
all.select do |s|
|
27
|
+
!(s.roles & want_roles).empty?
|
28
|
+
end
|
29
|
+
end
|
24
30
|
|
25
|
-
|
26
|
-
|
31
|
+
def role
|
32
|
+
roles.first
|
27
33
|
end
|
28
|
-
end
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
def self.load_all_from_array(server_hashes)
|
36
|
+
server_hashes.each do |instance_hash|
|
37
|
+
add(instance_hash)
|
38
|
+
end
|
39
|
+
end
|
33
40
|
|
34
|
-
|
35
|
-
|
36
|
-
add(instance_hash)
|
41
|
+
def self.all
|
42
|
+
@all
|
37
43
|
end
|
38
|
-
end
|
39
44
|
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
def self.by_hostname(hostname)
|
46
|
+
all.find{|s| s.hostname == hostname}
|
47
|
+
end
|
43
48
|
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
def self.add(server_hash)
|
50
|
+
hostname = server_hash[:hostname]
|
51
|
+
if by_hostname(hostname)
|
52
|
+
raise DuplicateHostname.new(hostname)
|
53
|
+
end
|
54
|
+
server = new(hostname, server_hash[:roles], server_hash[:name], server_hash[:user])
|
55
|
+
@all << server
|
56
|
+
server
|
57
|
+
end
|
47
58
|
|
48
|
-
|
49
|
-
|
50
|
-
if by_hostname(hostname)
|
51
|
-
raise DuplicateHostname.new(hostname)
|
59
|
+
def self.current
|
60
|
+
all.find {|s| s.local? }
|
52
61
|
end
|
53
|
-
server = new(hostname, server_hash[:roles], server_hash[:name], server_hash[:user])
|
54
|
-
@all << server
|
55
|
-
server
|
56
|
-
end
|
57
62
|
|
58
|
-
|
59
|
-
|
60
|
-
|
63
|
+
def self.reset
|
64
|
+
@all = []
|
65
|
+
end
|
66
|
+
reset
|
61
67
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
reset
|
68
|
+
def roles=(roles)
|
69
|
+
super(roles.map{|r| r.to_sym})
|
70
|
+
end
|
66
71
|
|
67
|
-
|
68
|
-
|
69
|
-
|
72
|
+
def local?
|
73
|
+
hostname == 'localhost'
|
74
|
+
end
|
70
75
|
|
71
|
-
|
72
|
-
|
73
|
-
|
76
|
+
def sync_directory(directory)
|
77
|
+
return if local?
|
78
|
+
run "mkdir -p #{directory}"
|
79
|
+
logged_system(%|rsync --delete -aq -e "#{ssh_command}" #{directory}/ #{user}@#{hostname}:#{directory}|)
|
80
|
+
end
|
74
81
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
82
|
+
def run(command)
|
83
|
+
if local?
|
84
|
+
logged_system(command)
|
85
|
+
else
|
86
|
+
logged_system(ssh_command + " " + Escape.shell_command(["#{user}@#{hostname}", command]))
|
87
|
+
end
|
88
|
+
end
|
80
89
|
|
81
|
-
|
82
|
-
|
83
|
-
logged_system(command)
|
84
|
-
else
|
85
|
-
logged_system(ssh_command + " " + Escape.shell_command(["#{user}@#{hostname}", command]))
|
90
|
+
def ssh_command
|
91
|
+
"ssh -i #{ENV['HOME']}/.ssh/internal -o StrictHostKeyChecking=no -o PasswordAuthentication=no"
|
86
92
|
end
|
87
|
-
end
|
88
93
|
|
89
|
-
def ssh_command
|
90
|
-
"ssh -i #{ENV['HOME']}/.ssh/internal -o StrictHostKeyChecking=no -o PasswordAuthentication=no"
|
91
94
|
end
|
92
95
|
end
|
93
96
|
end
|
@@ -1,138 +1,140 @@
|
|
1
1
|
require 'engineyard-serverside/logged_output'
|
2
2
|
|
3
3
|
module EY
|
4
|
-
module
|
5
|
-
|
6
|
-
|
4
|
+
module Serverside
|
5
|
+
module Strategies
|
6
|
+
class Git
|
7
|
+
module Helpers
|
8
|
+
|
9
|
+
def update_repository_cache
|
10
|
+
unless strategy.fetch && strategy.checkout
|
11
|
+
abort "*** [Error] Git could not checkout (#{strategy.to_checkout}) ***"
|
12
|
+
end
|
13
|
+
end
|
7
14
|
|
8
|
-
|
9
|
-
|
10
|
-
abort "*** [Error] Git could not checkout (#{strategy.to_checkout}) ***"
|
15
|
+
def create_revision_file_command
|
16
|
+
strategy.create_revision_file_command(c.release_path)
|
11
17
|
end
|
12
|
-
end
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
def short_log_message(revision)
|
20
|
+
strategy.short_log_message(revision)
|
21
|
+
end
|
17
22
|
|
18
|
-
|
19
|
-
|
23
|
+
def strategy
|
24
|
+
klass = Module.nesting[1]
|
25
|
+
# Use [] to access attributes instead of calling methods so
|
26
|
+
# that we get nils instead of NoMethodError.
|
27
|
+
#
|
28
|
+
# Rollback doesn't know about the repository location (nor
|
29
|
+
# should it need to), but it would like to use #short_log_message.
|
30
|
+
klass.new(
|
31
|
+
:repository_cache => c[:repository_cache],
|
32
|
+
:app => c[:app],
|
33
|
+
:repo => c[:repo],
|
34
|
+
:ref => c[:branch]
|
35
|
+
)
|
36
|
+
end
|
20
37
|
end
|
21
38
|
|
22
|
-
|
23
|
-
klass = Module.nesting[1]
|
24
|
-
# Use [] to access attributes instead of calling methods so
|
25
|
-
# that we get nils instead of NoMethodError.
|
26
|
-
#
|
27
|
-
# Rollback doesn't know about the repository location (nor
|
28
|
-
# should it need to), but it would like to use #short_log_message.
|
29
|
-
klass.new(
|
30
|
-
:repository_cache => c[:repository_cache],
|
31
|
-
:app => c[:app],
|
32
|
-
:repo => c[:repo],
|
33
|
-
:ref => c[:branch]
|
34
|
-
)
|
35
|
-
end
|
36
|
-
end
|
39
|
+
include LoggedOutput
|
37
40
|
|
38
|
-
|
41
|
+
attr_reader :opts
|
39
42
|
|
40
|
-
|
43
|
+
def initialize(opts)
|
44
|
+
@opts = opts
|
45
|
+
set_up_git_ssh(@opts[:app])
|
46
|
+
end
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
48
|
+
def usable_repository?
|
49
|
+
File.directory?(opts[:repository_cache]) && `#{git} remote -v | grep origin`[opts[:repo]]
|
50
|
+
end
|
46
51
|
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
def fetch
|
53
|
+
if usable_repository?
|
54
|
+
logged_system("#{git} fetch -q origin 2>&1")
|
55
|
+
else
|
56
|
+
FileUtils.rm_rf(opts[:repository_cache])
|
57
|
+
logged_system("git clone -q #{opts[:repo]} #{opts[:repository_cache]} 2>&1")
|
58
|
+
end
|
59
|
+
end
|
50
60
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
61
|
+
def checkout
|
62
|
+
info "~> Deploying revision #{short_log_message(to_checkout)}"
|
63
|
+
in_git_work_tree do
|
64
|
+
(logged_system("git checkout -q '#{to_checkout}'") ||
|
65
|
+
logged_system("git reset -q --hard '#{to_checkout}'")) &&
|
66
|
+
logged_system("git submodule sync") &&
|
67
|
+
logged_system("git submodule update --init") &&
|
68
|
+
logged_system("git clean -dfq")
|
69
|
+
end
|
57
70
|
end
|
58
|
-
end
|
59
71
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
72
|
+
def to_checkout
|
73
|
+
return @to_checkout if @opts_ref == opts[:ref]
|
74
|
+
@opts_ref = opts[:ref]
|
75
|
+
@to_checkout = if branch?(opts[:ref])
|
76
|
+
"origin/#{opts[:ref]}"
|
77
|
+
else
|
78
|
+
opts[:ref]
|
79
|
+
end
|
68
80
|
end
|
69
|
-
end
|
70
81
|
|
71
|
-
|
72
|
-
|
73
|
-
@opts_ref = opts[:ref]
|
74
|
-
@to_checkout = if branch?(opts[:ref])
|
75
|
-
"origin/#{opts[:ref]}"
|
76
|
-
else
|
77
|
-
opts[:ref]
|
82
|
+
def create_revision_file_command(dir)
|
83
|
+
%Q{#{git} show --pretty=format:"%H" | head -1 > "#{dir}/REVISION"}
|
78
84
|
end
|
79
|
-
end
|
80
85
|
|
81
|
-
|
82
|
-
|
83
|
-
|
86
|
+
def short_log_message(rev)
|
87
|
+
`#{git} log --pretty=oneline --abbrev-commit '#{rev}^..#{rev}'`.strip
|
88
|
+
end
|
84
89
|
|
85
|
-
|
86
|
-
|
87
|
-
|
90
|
+
private
|
91
|
+
def in_git_work_tree
|
92
|
+
Dir.chdir(git_work_tree) { yield }
|
93
|
+
end
|
88
94
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
end
|
95
|
+
def git_work_tree
|
96
|
+
opts[:repository_cache]
|
97
|
+
end
|
93
98
|
|
94
|
-
|
95
|
-
|
96
|
-
|
99
|
+
def git
|
100
|
+
"git --git-dir #{git_work_tree}/.git --work-tree #{git_work_tree}"
|
101
|
+
end
|
97
102
|
|
98
|
-
|
99
|
-
|
100
|
-
|
103
|
+
def branch?(ref)
|
104
|
+
`#{git} branch -r`.map { |x| x.strip }.include?("origin/#{ref}")
|
105
|
+
end
|
101
106
|
|
102
|
-
|
103
|
-
|
104
|
-
|
107
|
+
def set_up_git_ssh(app)
|
108
|
+
# hold references to the tempfiles so they don't get finalized
|
109
|
+
# unexpectedly; tempfile finalization unlinks the files
|
110
|
+
@git_ssh = Tempfile.open("git-ssh")
|
111
|
+
@config = Tempfile.open("git-ssh-config")
|
112
|
+
|
113
|
+
@config.write "StrictHostKeyChecking no\n"
|
114
|
+
@config.write "CheckHostIP no\n"
|
115
|
+
@config.write "PasswordAuthentication no\n"
|
116
|
+
@config.write "LogLevel DEBUG\n"
|
117
|
+
@config.write "IdentityFile ~/.ssh/#{app}-deploy-key\n"
|
118
|
+
@config.chmod(0600)
|
119
|
+
@config.close
|
120
|
+
|
121
|
+
@git_ssh.write "#!/bin/sh\n"
|
122
|
+
@git_ssh.write "unset SSH_AUTH_SOCK\n"
|
123
|
+
@git_ssh.write "ssh -F \"#{@config.path}\" $*\n"
|
124
|
+
@git_ssh.chmod(0700)
|
125
|
+
# NB: this file _must_ be closed before git looks at it.
|
126
|
+
#
|
127
|
+
# Linux won't let you execve a file that's open for writing,
|
128
|
+
# so if this file stays open, then git will complain about
|
129
|
+
# being unable to exec it and will exit with a message like
|
130
|
+
#
|
131
|
+
# fatal: exec /tmp/git-ssh20100417-21417-d040rm-0 failed.
|
132
|
+
@git_ssh.close
|
105
133
|
|
106
|
-
|
107
|
-
|
108
|
-
# unexpectedly; tempfile finalization unlinks the files
|
109
|
-
@git_ssh = Tempfile.open("git-ssh")
|
110
|
-
@config = Tempfile.open("git-ssh-config")
|
111
|
-
|
112
|
-
@config.write "StrictHostKeyChecking no\n"
|
113
|
-
@config.write "CheckHostIP no\n"
|
114
|
-
@config.write "PasswordAuthentication no\n"
|
115
|
-
@config.write "LogLevel DEBUG\n"
|
116
|
-
@config.write "IdentityFile ~/.ssh/#{app}-deploy-key\n"
|
117
|
-
@config.chmod(0600)
|
118
|
-
@config.close
|
119
|
-
|
120
|
-
@git_ssh.write "#!/bin/sh\n"
|
121
|
-
@git_ssh.write "unset SSH_AUTH_SOCK\n"
|
122
|
-
@git_ssh.write "ssh -F \"#{@config.path}\" $*\n"
|
123
|
-
@git_ssh.chmod(0700)
|
124
|
-
# NB: this file _must_ be closed before git looks at it.
|
125
|
-
#
|
126
|
-
# Linux won't let you execve a file that's open for writing,
|
127
|
-
# so if this file stays open, then git will complain about
|
128
|
-
# being unable to exec it and will exit with a message like
|
129
|
-
#
|
130
|
-
# fatal: exec /tmp/git-ssh20100417-21417-d040rm-0 failed.
|
131
|
-
@git_ssh.close
|
132
|
-
|
133
|
-
ENV['GIT_SSH'] = @git_ssh.path
|
134
|
-
end
|
134
|
+
ENV['GIT_SSH'] = @git_ssh.path
|
135
|
+
end
|
135
136
|
|
137
|
+
end
|
136
138
|
end
|
137
139
|
end
|
138
140
|
end
|
@@ -1,62 +1,65 @@
|
|
1
1
|
module EY
|
2
|
-
|
3
|
-
|
2
|
+
module Serverside
|
3
|
+
class Task
|
4
|
+
include Dataflow
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
attr_reader :config
|
7
|
+
alias :c :config
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
def require_custom_tasks
|
14
|
-
deploy_file = ["config/eydeploy.rb", "eydeploy.rb"].map do |short_file|
|
15
|
-
File.join(c.repository_cache, short_file)
|
16
|
-
end.detect do |file|
|
17
|
-
File.exist?(file)
|
9
|
+
def initialize(conf)
|
10
|
+
@config = conf
|
11
|
+
@roles = :all
|
18
12
|
end
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
14
|
+
def require_custom_tasks
|
15
|
+
deploy_file = ["config/eydeploy.rb", "eydeploy.rb"].map do |short_file|
|
16
|
+
File.join(c.repository_cache, short_file)
|
17
|
+
end.detect do |file|
|
18
|
+
File.exist?(file)
|
19
|
+
end
|
20
|
+
|
21
|
+
if deploy_file
|
22
|
+
puts "~> Loading deployment task overrides from #{deploy_file}"
|
23
|
+
instance_eval(File.read(deploy_file))
|
24
|
+
true
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
26
28
|
end
|
27
|
-
end
|
28
29
|
|
29
|
-
|
30
|
-
|
30
|
+
def roles(*task_roles)
|
31
|
+
raise "Roles must be passed a block" unless block_given?
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
begin
|
34
|
+
@roles = task_roles
|
35
|
+
yield
|
36
|
+
ensure
|
37
|
+
@roles = :all
|
38
|
+
end
|
37
39
|
end
|
38
|
-
end
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
def run(cmd, &blk)
|
42
|
+
run_on_roles(cmd, &blk)
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
def sudo(cmd, &blk)
|
46
|
+
run_on_roles(cmd, %w[sudo sh -l -c], &blk)
|
47
|
+
end
|
47
48
|
|
48
|
-
|
49
|
+
private
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
def run_on_roles(cmd, wrapper=%w[sh -l -c])
|
52
|
+
results = EY::Serverside::Server.from_roles(@roles).map do |server|
|
53
|
+
to_run = block_given? ? yield(server, cmd.dup) : cmd
|
54
|
+
need_later { server.run(Escape.shell_command(wrapper + [to_run])) }
|
55
|
+
end
|
56
|
+
barrier *results
|
57
|
+
# MRI's truthiness check is an internal C thing that does not call
|
58
|
+
# any methods... so Dataflow cannot proxy it & we must "x == true"
|
59
|
+
# Rubinius, wherefore art thou!?
|
60
|
+
results.all?{|x| x == true } || raise(EY::Serverside::RemoteFailure.new(cmd))
|
54
61
|
end
|
55
|
-
|
56
|
-
# MRI's truthiness check is an internal C thing that does not call
|
57
|
-
# any methods... so Dataflow cannot proxy it & we must "x == true"
|
58
|
-
# Rubinius, wherefore art thou!?
|
59
|
-
results.all?{|x| x == true } || raise(EY::RemoteFailure.new(cmd))
|
62
|
+
|
60
63
|
end
|
61
64
|
end
|
62
65
|
end
|