ey-deploy 0.2.7 → 0.3.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/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