ey-deploy 0.2.7 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ey-deploy.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'escape'
2
+
1
3
  $LOAD_PATH.push(File.expand_path("ey-deploy", File.dirname(__FILE__)))
2
4
 
3
5
  require 'version'
@@ -6,6 +8,7 @@ require 'strategies/git'
6
8
  require 'task'
7
9
  require 'server'
8
10
  require 'deploy'
11
+ require 'deploy_hook'
9
12
  require 'cli'
10
13
  require 'configuration'
11
14
 
@@ -13,4 +16,4 @@ module EY
13
16
  def self.node
14
17
  @node ||= JSON.parse(`sudo cat /etc/chef/dna.json`)
15
18
  end
16
- end
19
+ end
data/lib/ey-deploy/cli.rb CHANGED
@@ -26,9 +26,22 @@ module EY
26
26
 
27
27
  desc "deploy", "Deploy code from /data/<app>"
28
28
  def deploy(default_task=:deploy)
29
+ invoke :propagate
29
30
  EY::Deploy.run(options.merge("default_task" => default_task))
30
31
  end
31
32
 
33
+ method_option :app, :type => :string,
34
+ :required => true,
35
+ :desc => "Which application's hooks to run",
36
+ :aliases => ["-a"]
37
+
38
+ method_option :release_path, :type => :string,
39
+ :desc => "Value for #release_path in hooks (mostly for internal coordination)",
40
+ :aliases => ["-r"]
41
+ desc "hook [NAME]", "Run a particular deploy hook"
42
+ def hook(hook_name)
43
+ EY::DeployHook.new(options).run(hook_name)
44
+ end
32
45
 
33
46
  desc "check", "Check whether the client gem is compatible with the server gem"
34
47
  def check(client_version, server_requirement)
@@ -54,5 +67,34 @@ module EY
54
67
  exit 3
55
68
  end
56
69
  end
70
+
71
+ desc "propagate", "Propagate the ey-deploy gem to the other instances in the cluster. This will install exactly version #{VERSION} and remove other versions if found."
72
+ def propagate
73
+ config = EY::Deploy::Configuration.new
74
+ gem_filename = "ey-deploy-#{VERSION}.gem"
75
+ local_gem_file = File.join(Gem.dir, 'cache', gem_filename)
76
+ remote_gem_file = File.join(Dir.tmpdir, gem_filename)
77
+ gem_binary = File.join(Gem.default_bindir, 'gem')
78
+
79
+ EY::Server.config = config
80
+
81
+ EY::Server.all.find_all do |server|
82
+ !server.local? # of course this machine has it
83
+ end.find_all do |server|
84
+ has_gem_cmd = "/usr/local/ey_resin/ruby/bin/gem list ey-deploy | grep -q '(#{VERSION})'"
85
+ !server.run(has_gem_cmd) # doesn't have only this exact version
86
+ end.each do |server|
87
+ puts "~> Installing ey-deploy on #{server.hostname}"
88
+
89
+ system(Escape.shell_command([
90
+ 'scp', '-i', "#{ENV['HOME']}/.ssh/internal",
91
+ "-o", "StrictHostKeyChecking=no",
92
+ local_gem_file,
93
+ "#{config.user}@#{server.hostname}:#{remote_gem_file}",
94
+ ]))
95
+ server.run("sudo #{gem_binary} uninstall -a -x ey-deploy 2>/dev/null")
96
+ server.run("sudo #{gem_binary} install '#{remote_gem_file}'")
97
+ end
98
+ end
57
99
  end
58
- end
100
+ end
@@ -1,17 +1,18 @@
1
1
  require 'json'
2
+ require 'thor'
2
3
 
3
4
  module EY
4
5
  class Deploy::Configuration
5
- DEFAULT_CONFIG = {
6
+ DEFAULT_CONFIG = Thor::CoreExt::HashWithIndifferentAccess.new({
6
7
  "branch" => "master",
7
- "copy_exclude" => ".git",
8
8
  "strategy" => "Git",
9
- }
9
+ })
10
10
 
11
11
  attr_reader :configuration
12
12
  alias :c :configuration
13
13
 
14
14
  def initialize(opts={})
15
+ @release_path = opts[:release_path]
15
16
  config = JSON.parse(opts["config"] || "{}")
16
17
  @configuration = DEFAULT_CONFIG.merge(config).merge(opts)
17
18
  end
@@ -20,14 +21,35 @@ module EY
20
21
  def method_missing(meth, *args, &blk)
21
22
  c.key?(meth.to_s) ? c[meth.to_s] : super
22
23
  end
23
- def respond_to?(meth)
24
+
25
+ def respond_to?(meth, include_private=false)
24
26
  c.key?(meth.to_s) ? true : super
25
27
  end
26
28
 
29
+ def [](key)
30
+ if respond_to?(key.to_sym)
31
+ send(key.to_sym)
32
+ else
33
+ c[key]
34
+ end
35
+ end
36
+
37
+ def has_key?(key)
38
+ if respond_to?(key.to_sym)
39
+ true
40
+ else
41
+ c.has_key?(key)
42
+ end
43
+ end
44
+
27
45
  def node
28
46
  EY.node
29
47
  end
30
48
 
49
+ def revision
50
+ IO.read(File.join(latest_release, 'REVISION'))
51
+ end
52
+
31
53
  def repository_cache
32
54
  configuration['repository_cache'] || File.join(deploy_to, "/shared/cached-copy")
33
55
  end
@@ -91,7 +91,7 @@ module EY
91
91
  # task
92
92
  def copy_repository_cache
93
93
  puts "~> copying to #{c.release_path}"
94
- sudo("mkdir -p #{c.release_path} && rsync -aq #{c.exclusions} #{c.repository_cache}/* #{c.release_path}")
94
+ sudo("mkdir -p #{c.release_path} && rsync -aq #{c.exclusions} #{c.repository_cache}/ #{c.release_path}")
95
95
 
96
96
  puts "~> ensuring proper ownership"
97
97
  sudo("chown -R #{c.user}:#{c.group} #{c.deploy_to}")
@@ -126,46 +126,14 @@ module EY
126
126
  end
127
127
  end
128
128
 
129
- # before_symlink
130
- # before_restart
131
-
132
- class CallbackContext
133
- def initialize(deploy)
134
- @deploy = deploy
135
- end
136
-
137
- def method_missing(meth, *args, &blk)
138
- if @deploy.respond_to?(meth)
139
- @deploy.send(meth, *args, &blk)
140
- elsif @deploy.config.respond_to?(meth)
141
- @deploy.config.send(meth, *args, &blk)
142
- else
143
- super
144
- end
145
- end
146
-
147
- def respond_to(meth)
148
- if @deploy.respond_to?(meth) || @deploy.config.respond_to?(meth)
149
- true
150
- else
151
- super
152
- end
153
- end
154
- end
155
-
156
- def callback_context
157
- @context ||= CallbackContext.new(self)
158
- end
159
-
160
- def callback(what)
129
+ def callback(what, roles=@roles)
161
130
  if File.exist?("#{c.latest_release}/deploy/#{what}.rb")
162
- Dir.chdir(c.latest_release) do
163
- puts "~> running deploy hook: deploy/#{what}.rb"
164
- callback_context.instance_eval(IO.read("#{c.latest_release}/deploy/#{what}.rb"))
131
+ eysd_path = $0 # invoke others just like we were invoked
132
+ EY::Server.from_roles(roles).each do |server|
133
+ server.run("#{eysd_path} hook '#{what}' --app '#{config.app}' --release-path #{config.release_path}")
165
134
  end
166
135
  end
167
136
  end
168
-
169
137
  end
170
138
 
171
139
  class Deploy < DeployBase
@@ -0,0 +1,52 @@
1
+ require 'ey-deploy/verbose_system'
2
+
3
+ module EY
4
+ class DeployHook < Task
5
+ include VerboseSystem
6
+
7
+ def initialize(options)
8
+ super(EY::Deploy::Configuration.new(options))
9
+ end
10
+
11
+ def callback_context
12
+ @context ||= CallbackContext.new(self, config)
13
+ end
14
+
15
+ def run(hook)
16
+ if File.exist?("#{c.latest_release}/deploy/#{hook}.rb")
17
+ Dir.chdir(c.latest_release) do
18
+ puts "~> running deploy hook: deploy/#{hook}.rb"
19
+ callback_context.instance_eval(IO.read("#{c.latest_release}/deploy/#{hook}.rb"))
20
+ end
21
+ end
22
+ end
23
+
24
+ class CallbackContext
25
+ def initialize(hook_runner, config)
26
+ @hook_runner, @configuration = hook_runner, config
27
+ end
28
+
29
+ def method_missing(meth, *args, &blk)
30
+ if @configuration.respond_to?(meth)
31
+ @configuration.send(meth, *args, &blk)
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ def respond_to?(meth, include_private=false)
38
+ @configuration.respond_to?(meth, include_private) || super
39
+ end
40
+
41
+ def run(cmd)
42
+ system(@hook_runner.prepare_run(cmd))
43
+ end
44
+
45
+ def sudo(cmd)
46
+ system(@hook_runner.prepare_sudo(cmd))
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+
@@ -73,9 +73,9 @@ module EY
73
73
 
74
74
  def run(command)
75
75
  if local?
76
- system(command.gsub(/\\"/, '"'))
76
+ system(command)
77
77
  else
78
- system(%|#{ssh_command} #{config.user}@#{hostname} "#{command}"|)
78
+ system(ssh_command + " " + Escape.shell_command(["#{config.user}@#{hostname}", command]))
79
79
  end
80
80
  end
81
81
 
@@ -29,14 +29,22 @@ module EY
29
29
 
30
30
  def run(cmd)
31
31
  EY::Server.from_roles(@roles).each do |server|
32
- server.run %|sh -c \\"#{cmd} 2>&1\\"|
32
+ server.run prepare_run(cmd)
33
33
  end
34
34
  end
35
35
 
36
36
  def sudo(cmd)
37
37
  EY::Server.from_roles(@roles).each do |server|
38
- server.run %|sudo sh -c \\"#{cmd} 2>&1\\"|
38
+ server.run prepare_sudo(cmd)
39
39
  end
40
40
  end
41
+
42
+ def prepare_run(command)
43
+ Escape.shell_command ["sh", "-l", "-c", command]
44
+ end
45
+
46
+ def prepare_sudo(command)
47
+ Escape.shell_command ["sudo", "sh", "-l", "-c", command]
48
+ end
41
49
  end
42
50
  end
@@ -1,3 +1,3 @@
1
1
  module EY
2
- VERSION = "0.2.7"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,95 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "deploy hook's context" do
4
+ before(:each) do
5
+ @hook_runner = EY::DeployHook.new(options)
6
+ @callback_context = EY::DeployHook::CallbackContext.new(@hook_runner, @hook_runner.config)
7
+ end
8
+
9
+ def run_hook(options={}, &blk)
10
+ raise ArgumentError unless block_given?
11
+ options.each do |k, v|
12
+ @hook_runner.config.configuration[k] = v # ugh
13
+ end
14
+
15
+ # The hooks on the filesystem are run by passing a string to
16
+ # context.instance_eval, not a block. However, using a block
17
+ # should allow us to get the same degree of test coverage and
18
+ # still let us have things like syntax checking work on this spec
19
+ # file.
20
+ @callback_context.instance_eval(&blk)
21
+ end
22
+
23
+ context "the #run method" do
24
+ it "is available" do
25
+ run_hook { respond_to?(:run) }.should be_true
26
+ end
27
+
28
+ it "runs commands like the shell does" do
29
+ ENV['COUNT'] = 'Chocula'
30
+ File.unlink("/tmp/deploy_hook_spec.the_count") rescue nil
31
+
32
+ run_hook { run("echo $COUNT > /tmp/deploy_hook_spec.the_count") }
33
+
34
+ IO.read("/tmp/deploy_hook_spec.the_count").strip.should == "Chocula"
35
+ end
36
+
37
+ it "returns true/false to indicate the command's success" do
38
+ run_hook { run("true") }.should be_true
39
+ run_hook { run("false") }.should be_false
40
+ end
41
+ end
42
+
43
+ context "the #sudo method" do
44
+ it "is available" do
45
+ run_hook { respond_to?(:sudo) }.should be_true
46
+ end
47
+
48
+ it "runs things with sudo" do
49
+ @callback_context.should_receive(:system).with("sudo sh -l -c 'do it as root'").and_return(true)
50
+
51
+ run_hook { sudo("do it as root") }
52
+ end
53
+ end
54
+
55
+ context "capistrano-ish methods" do
56
+ it "has them" do
57
+ run_hook { respond_to?(:latest_release) }.should be_true
58
+ run_hook { respond_to?(:previous_release) }.should be_true
59
+ run_hook { respond_to?(:all_releases) }.should be_true
60
+ run_hook { respond_to?(:current_path) }.should be_true
61
+ run_hook { respond_to?(:shared_path) }.should be_true
62
+ run_hook { respond_to?(:release_dir) }.should be_true
63
+ run_hook { respond_to?(:release_path) }.should be_true
64
+ end
65
+ end
66
+
67
+ context "the @configuration ivar" do
68
+ it "is available" do
69
+ run_hook { @configuration.nil? }.should be_false
70
+ end
71
+
72
+ it "has the configuration in it" do
73
+ run_hook('bert' => 'ernie') { @configuration.bert }.should == 'ernie'
74
+ end
75
+
76
+ it "can be accessed with method calls, with [:symbols], or ['strings']" do
77
+ run_hook('bert' => 'ernie') { @configuration.bert }.should == 'ernie'
78
+ run_hook('bert' => 'ernie') { @configuration[:bert] }.should == 'ernie'
79
+ run_hook('bert' => 'ernie') { @configuration['bert'] }.should == 'ernie'
80
+ end
81
+
82
+ [:repository_cache,
83
+ :release_path,
84
+ :branch,
85
+ :shared_path,
86
+ :deploy_to,
87
+ :user,
88
+ :revision,
89
+ :environment].each do |attribute|
90
+ it "has the #{attribute.inspect} attribute for compatibility with chef-deploy" do
91
+ run_hook { @configuration.has_key?(attribute) }.should be_true
92
+ end
93
+ end
94
+ end
95
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
1
  $LOAD_PATH.push File.expand_path("../lib", File.dirname(__FILE__))
2
2
 
3
3
  Bundler.require :default, :test
4
+ require 'pp'
5
+ require 'ey-deploy'
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 7
9
- version: 0.2.7
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - EY Cloud Team
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-07 00:00:00 -07:00
17
+ date: 2010-05-12 00:00:00 -07:00
18
18
  default_executable: eysd
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -31,16 +31,30 @@ dependencies:
31
31
  version_requirements: *id001
32
32
  - !ruby/object:Gem::Dependency
33
33
  prerelease: false
34
- name: json
34
+ name: escape
35
35
  requirement: &id002 !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  segments:
40
40
  - 0
41
- version: "0"
41
+ - 0
42
+ - 4
43
+ version: 0.0.4
42
44
  type: :runtime
43
45
  version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ prerelease: false
48
+ name: json
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ type: :runtime
57
+ version_requirements: *id003
44
58
  description:
45
59
  email: cloud@engineyard.com
46
60
  executables:
@@ -55,6 +69,7 @@ files:
55
69
  - lib/ey-deploy/compatibility.rb
56
70
  - lib/ey-deploy/configuration.rb
57
71
  - lib/ey-deploy/deploy.rb
72
+ - lib/ey-deploy/deploy_hook.rb
58
73
  - lib/ey-deploy/server.rb
59
74
  - lib/ey-deploy/strategies/git.rb
60
75
  - lib/ey-deploy/task.rb
@@ -128,5 +143,5 @@ signing_key:
128
143
  specification_version: 3
129
144
  summary: A gem that deploys ruby applications on EY Cloud instances
130
145
  test_files:
131
- - spec/ey-deploy_spec.rb
146
+ - spec/deploy_hook_spec.rb
132
147
  - spec/spec_helper.rb
@@ -1,7 +0,0 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
2
-
3
- describe "ey-deploy" do
4
- it "should do nothing" do
5
- true.should == true
6
- end
7
- end