capistrano_deploy_lock 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -17,10 +17,11 @@ Add this line to your `config/deploy.rb`:
17
17
 
18
18
  require 'capistrano/deploy_lock'
19
19
 
20
+
20
21
  ## Usage
21
22
 
22
23
  Your deploys will now be protected by a lock. Simply run `cap deploy` as usual.
23
- However, if someone else trys to deploy at the same time, their deploy will abort
24
+ However, if someone else tries to deploy at the same time, their deploy will abort
24
25
  with an error like this:
25
26
 
26
27
  ```
@@ -30,34 +31,39 @@ with an error like this:
30
31
  .../capistrano/deploy_lock.rb:132:in `block (3 levels) in <top (required)>': Capistrano::DeployLockedError (Capistrano::DeployLockedError)
31
32
  ```
32
33
 
33
- The default 'deploy' lock will expire after 15 minutes, assuming that your deploys will not take more time than this.
34
- This is so that crashed or interrupted deploys don't leave a stale lock for the next developer to deal with.
35
- This default expiry time can be configured with:
34
+ The default deploy lock will expire after 15 minutes. This is so that crashed or interrupted deploys don't leave a stale lock behind.
36
35
 
37
- set :default_lock_expiry, (20 * 60) # Sets the default expiry to 20 minutes
36
+ The following tasks will be run before deploy:
38
37
 
39
- Anyone can remove a lock by running:
38
+ * `deploy:check_lock`
39
+ * Checks for an existing deploy lock. Aborts deploy if a lock exists and it wasn't created by you.
40
+ * `deploy:refresh_lock`
41
+ * If you previously created a lock, this task ensures that your lock won't expire before the default expiry time
42
+ * `deploy:create_lock`
43
+ * If no locks already exist, a default lock will be created with the message: `Deploying <branch>`
40
44
 
41
- cap deploy:unlock
45
+ The following task will be run after deploy:
42
46
 
43
- The lock file will be created at `#{shared_path}/capistrano.lock.yml` by default. You can configure this with:
47
+ * `deploy:unlock`
48
+ * Removes any default deploy locks. If you set a custom lock, it will not be removed at this step.
49
+ * You can remove a custom deploy lock by running `cap deploy:unlock` by itself, or by chaining `deploy:unlock:force` at the end of the command.
44
50
 
45
- set :deploy_lockfile, "path/to/deploy/lock/file"
46
51
 
52
+ ## Tasks
47
53
 
48
- ## Manual locks
54
+ ### `deploy:with_lock`
49
55
 
50
- You can explicitly set a deploy lock by running:
56
+ Deploy the latest revision with a custom deploy lock. This lock will not be removed at the end of the deploy.
51
57
 
52
- cap deploy:lock
58
+ ### `deploy:lock`
53
59
 
54
- You will receive two prompts:
60
+ Sets a custom deploy lock. You will receive two prompts for input:
55
61
 
56
- * Lock Message:
62
+ * **Lock Message:**
57
63
 
58
64
  Type the reason for the lock. This message will be displayed to any developers who attempt to deploy.
59
65
 
60
- * Expire lock at? (optional):
66
+ * **Expire lock at? (optional):**
61
67
 
62
68
  Set an expiry time for the lock. Leave this blank to make the lock last until someone removes it with `cap deploy:unlock`.
63
69
 
@@ -65,7 +71,36 @@ If the [chronic](https://github.com/mojombo/chronic) gem is available, you can t
65
71
  natural language times like `2 hours`, or `tomorrow at 6am`. If not, you must type times in a format that `DateTime.parse()` can handle,
66
72
  such as `06:30:00` or `2012-12-12 00:00:00`.
67
73
 
68
- The `cap deploy:check_lock` task will automatically delete any expired locks.
74
+ ### `deploy:unlock`
75
+
76
+ Removes the deploy lock. Will not remove a custom deploy lock when it is chained after `deploy:lock`.
77
+
78
+ ### `deploy:unlock:force`
79
+
80
+ Removes any deploy lock, even when chained after `deploy:lock`.
81
+
82
+ ### `deploy:check_lock`
83
+
84
+ Check if server is locked. If the deploy lock was not created by you, an error will be raised and the deploy will abort.
85
+ If the lock **was** created by you, the deploy will pause for 4 seconds, which gives you time to press `Ctrl+C` to cancel the deploy.
86
+
87
+ This task is also responsible for deleting any expired locks.
88
+
89
+ ### `deploy:refresh_lock`
90
+
91
+ Refreshes the current lock's expiry time if it is less than the default time.
92
+
93
+
94
+ ## Configuration
95
+
96
+ If your deploys usually take longer than 15 minutes, you can configure the default expiry time with:
97
+
98
+ set :default_lock_expiry, (20 * 60) # Sets the default expiry to 20 minutes
99
+
100
+ The lock file will be created at `#{shared_path}/capistrano.lock.yml` by default. You can configure this with:
101
+
102
+ set :deploy_lockfile, "path/to/deploy/lock/file"
103
+
69
104
 
70
105
  ## Thanks
71
106
 
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ if ARGV.size != 3
3
+ puts "Usage: cap_deploy_lock_msg <application_name> <stage> <path/to/lock_file.yml>"
4
+ else
5
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
6
+ require 'rubygems'
7
+ require 'yaml'
8
+ require 'capistrano/deploy_lock'
9
+
10
+ application, stage, lock_file = *ARGV
11
+ if File.exists?(lock_file)
12
+ deploy_lock = YAML.load_file(lock_file)
13
+
14
+ # Only show lock message if lock hasn't expired
15
+ if deploy_lock[:expire_at] && deploy_lock[:expire_at] > Time.now
16
+ puts Capistrano::DeployLock.message(application, stage, deploy_lock)
17
+ exit
18
+ end
19
+ end
20
+
21
+ puts "No deploy locks for #{application} (#{stage})"
22
+ end
@@ -0,0 +1,7 @@
1
+ module Capistrano
2
+ class DateHelper
3
+ class << self
4
+ include ActionView::Helpers::DateHelper
5
+ end
6
+ end
7
+ end
@@ -9,130 +9,39 @@ begin; require 'chronic'; rescue LoadError; end
9
9
  begin
10
10
  # Use Rails distance_of_time_in_words_to_now helper if available
11
11
  require 'action_view'
12
- module Capistrano
13
- class DateHelper
14
- class << self
15
- include ActionView::Helpers::DateHelper
16
- end
17
- end
18
- end
12
+ require 'capistrano/date_helper'
19
13
  rescue LoadError
20
14
  end
21
15
 
22
- Capistrano::DeployLockedError = Class.new(StandardError)
23
-
24
- Capistrano::Configuration.instance(:must_exist).load do
25
- before "deploy", "deploy:check_lock"
26
- before "deploy", "deploy:create_lock"
27
- after "deploy", "deploy:unlock"
28
-
29
- # Default lock expiry of 15 minutes (in case deploy crashes or is interrupted)
30
- _cset :default_lock_expiry, (15 * 60)
31
- _cset(:deploy_lockfile) { "#{shared_path}/capistrano.lock.yml" }
32
-
33
- # Show lock message as bright red
34
- log_formatter(:match => /Deploy locked/, :color => :red, :style => :bright, :priority => 20)
35
-
36
- namespace :deploy do
37
- # Set deploy lock with a custom lock message and expiry time
38
- task :lock do
39
- set :lock_message, Capistrano::CLI.ui.ask("Lock Message: ")
40
-
41
- while self[:lock_expiry].nil?
42
- expiry_str = Capistrano::CLI.ui.ask("Expire lock at? (optional): ")
43
- if expiry_str == ""
44
- # Never expire an explicit lock if no time given
45
- set :lock_expiry, false
46
- else
47
- parsed_expiry = nil
48
- if defined?(Chronic)
49
- parsed_expiry = (Chronic.parse(expiry_str) || Chronic.parse("#{expiry_str} from now"))
50
- else
51
- if dt = (DateTime.parse(expiry_str) rescue nil)
52
- parsed_expiry = dt.to_time
53
- end
54
- end
55
-
56
- if parsed_expiry
57
- set :lock_expiry, parsed_expiry.utc
58
- else
59
- logger.info "'#{expiry_str}' could not be parsed. Please try again."
60
- end
61
- end
16
+ module Capistrano
17
+ DeployLockedError = Class.new(StandardError)
18
+
19
+ module DeployLock
20
+ def self.message(application, stage, deploy_lock)
21
+ message = "#{application} (#{stage}) was locked"
22
+ if defined?(Capistrano::DateHelper)
23
+ locked_ago = Capistrano::DateHelper.distance_of_time_in_words_to_now deploy_lock[:created_at].localtime
24
+ message << " #{locked_ago} ago"
25
+ else
26
+ message << " at #{deploy_lock[:created_at].localtime}"
62
27
  end
28
+ message << " by '#{deploy_lock[:username]}'\nMessage: #{deploy_lock[:message]}"
63
29
 
64
- create_lock
65
- end
66
-
67
- desc "Creates a lock file, so that futher deploys will be prevented."
68
- task :create_lock do
69
- if self[:lock_message].nil?
70
- set :lock_message, "Deploying #{branch} branch"
71
- end
72
- if self[:lock_expiry].nil?
73
- set :lock_expiry, (Time.now + default_lock_expiry).utc
74
- end
75
-
76
- lock = {
77
- :created_at => Time.now.utc,
78
- :username => ENV['USER'],
79
- :expire_at => self[:lock_expiry],
80
- :message => self[:lock_message]
81
- }
82
- put lock.to_yaml, deploy_lockfile, :mode => 0777
83
- end
84
-
85
- desc "Unlocks the server for deployment."
86
- task :unlock do
87
- run "rm -f #{deploy_lockfile}"
88
- end
89
-
90
- desc "Checks for a deploy lock. If present, deploy is aborted and message is displayed. Any expired locks are deleted."
91
- task :check_lock do
92
- lock_file = capture("[ -e #{deploy_lockfile} ] && cat #{deploy_lockfile} || true").strip
93
-
94
- if lock_file != ""
95
- lock = YAML.load(lock_file)
96
-
97
- if lock[:expire_at] && lock[:expire_at] < Time.now
98
- logger.info "Deleting expired deploy lock..."
99
- unlock
30
+ if deploy_lock[:expire_at]
31
+ if defined?(Capistrano::DateHelper)
32
+ expires_in = Capistrano::DateHelper.distance_of_time_in_words_to_now deploy_lock[:expire_at].localtime
33
+ message << "\nExpires in #{expires_in}"
100
34
  else
101
- if defined?(Capistrano::DateHelper)
102
- locked_ago = Capistrano::DateHelper.distance_of_time_in_words_to_now lock[:created_at].localtime
103
- message = "Deploy locked #{locked_ago} ago"
104
- else
105
- message = "Deploy locked at #{lock[:created_at].localtime}"
106
- end
107
-
108
- message << " by '#{lock[:username]}'\nMessage: #{lock[:message]}"
109
-
110
- if lock[:expire_at]
111
- if defined?(Capistrano::DateHelper)
112
- expires_in = Capistrano::DateHelper.distance_of_time_in_words_to_now lock[:expire_at].localtime
113
- message << "\nExpires in #{expires_in}"
114
- else
115
- message << "\nExpires at #{lock[:expire_at].localtime.strftime("%H:%M:%S")}"
116
- end
117
- else
118
- message << "\nLock must be manually removed with: cap #{stage} deploy:unlock"
119
- end
120
-
121
- logger.important message
122
-
123
- # Don't raise exception if current user owns the lock.
124
- # Just sleep so they have a chance to Ctrl-C
125
- if lock[:username] == ENV['USER']
126
- 4.downto(1) do |i|
127
- Kernel.print "\rDeploy lock was created by you (#{ENV['USER']}). Continuing deploy in #{i}..."
128
- sleep 1
129
- end
130
- puts
131
- else
132
- raise Capistrano::DeployLockedError
133
- end
35
+ message << "\nExpires at #{deploy_lock[:expire_at].localtime.strftime("%H:%M:%S")}"
134
36
  end
37
+ else
38
+ message << "\nLock must be manually removed with: cap #{stage} deploy:unlock"
135
39
  end
136
40
  end
137
41
  end
138
42
  end
43
+
44
+ # Load recipe if required from deploy script
45
+ if defined?(Capistrano::Configuration) && Capistrano::Configuration.instance
46
+ require 'capistrano/recipes/deploy_lock'
47
+ end
@@ -0,0 +1,156 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ before "deploy", "deploy:check_lock"
3
+ before "deploy", "deploy:refresh_lock"
4
+ before "deploy", "deploy:create_lock"
5
+ after "deploy", "deploy:unlock"
6
+
7
+ # Default lock expiry of 15 minutes (in case deploy crashes or is interrupted)
8
+ _cset :default_lock_expiry, (15 * 60)
9
+ _cset(:deploy_lockfile) { "#{shared_path}/capistrano.lock.yml" }
10
+
11
+ # Show lock message as bright red
12
+ log_formatter(:match => /was locked/, :color => :red, :style => :bright, :priority => 20)
13
+
14
+ namespace :deploy do
15
+ # Fetch the deploy lock unless already cached
16
+ def fetch_deploy_lock
17
+ if self[:deploy_lock].nil?
18
+ lock_file = capture("[ -e #{deploy_lockfile} ] && cat #{deploy_lockfile} || true").strip
19
+ if lock_file != ""
20
+ set :deploy_lock, YAML.load(lock_file)
21
+ else
22
+ set :deploy_lock, false
23
+ end
24
+ end
25
+ end
26
+
27
+ def write_deploy_lock(deploy_lock)
28
+ put deploy_lock.to_yaml, deploy_lockfile, :mode => 0777
29
+ end
30
+
31
+ desc "Deploy with a custom deploy lock"
32
+ task :with_lock do
33
+ lock
34
+ deploy.default
35
+ end
36
+
37
+ desc "Set deploy lock with a custom lock message and expiry time"
38
+ task :lock do
39
+ set :lock_message, Capistrano::CLI.ui.ask("Lock Message: ")
40
+
41
+ while self[:lock_expiry].nil?
42
+ expiry_str = Capistrano::CLI.ui.ask("Expire lock at? (optional): ")
43
+ if expiry_str == ""
44
+ # Never expire an explicit lock if no time given
45
+ set :lock_expiry, false
46
+ else
47
+ parsed_expiry = nil
48
+ if defined?(Chronic)
49
+ parsed_expiry = Chronic.parse(expiry_str) || Chronic.parse("#{expiry_str} from now")
50
+ elsif dt = (DateTime.parse(expiry_str) rescue nil)
51
+ parsed_expiry = dt.to_time
52
+ end
53
+
54
+ if parsed_expiry
55
+ set :lock_expiry, parsed_expiry.utc
56
+ else
57
+ logger.info "'#{expiry_str}' could not be parsed. Please try again."
58
+ end
59
+ end
60
+ end
61
+
62
+ create_lock
63
+ set :custom_deploy_lock, true
64
+ end
65
+
66
+ desc "Creates a lock file, so that futher deploys will be prevented"
67
+ task :create_lock do
68
+ if self[:custom_deploy_lock]
69
+ logger.info 'Custom deploy lock already created.'
70
+ next
71
+ end
72
+
73
+ if self[:lock_message].nil?
74
+ set :lock_message, "Deploying #{branch} branch"
75
+ end
76
+ if self[:lock_expiry].nil?
77
+ set :lock_expiry, (Time.now + default_lock_expiry).utc
78
+ end
79
+
80
+ deploy_lock = {
81
+ :created_at => Time.now.utc,
82
+ :username => ENV['USER'],
83
+ :expire_at => self[:lock_expiry],
84
+ :message => self[:lock_message]
85
+ }
86
+ write_deploy_lock(deploy_lock)
87
+ end
88
+
89
+ namespace :unlock do
90
+ desc "Unlocks the server for deployment"
91
+ task :default do
92
+ # Don't automatically remove custom deploy locks created by deploy:lock task
93
+ if self[:custom_deploy_lock]
94
+ logger.info 'Not removing custom deploy lock.'
95
+ else
96
+ force
97
+ end
98
+ end
99
+
100
+ task :force do
101
+ run "rm -f #{deploy_lockfile}"
102
+ end
103
+ end
104
+
105
+ desc "Checks for a deploy lock. If present, deploy is aborted and message is displayed. Any expired locks are deleted."
106
+ task :check_lock do
107
+ # Don't check the lock if we just created it
108
+ next if self[:custom_deploy_lock]
109
+
110
+ fetch_deploy_lock
111
+ # Return if no lock
112
+ next unless self[:deploy_lock]
113
+
114
+ if deploy_lock[:expire_at] && deploy_lock[:expire_at] < Time.now
115
+ logger.info "Deleting expired deploy lock..."
116
+ unlock
117
+ next
118
+ end
119
+
120
+ # Unexpired lock is present, so display the lock message
121
+ logger.important Capistrano::DeployLock.message(application, stage, deploy_lock)
122
+
123
+ # Don't raise exception if current user owns the lock.
124
+ # Just sleep so they have a chance to Ctrl-C
125
+ if deploy_lock[:username] == ENV['USER']
126
+ 4.downto(1) do |i|
127
+ Kernel.print "\rDeploy lock was created by you (#{ENV['USER']}). Continuing deploy in #{i}..."
128
+ sleep 1
129
+ end
130
+ puts
131
+ else
132
+ raise Capistrano::DeployLockedError
133
+ end
134
+ end
135
+
136
+ desc "Refreshes an existing deploy lock's expiry time, if it is less than the default time"
137
+ task :refresh_lock do
138
+ # Don't refresh custom locks
139
+ next if self[:custom_deploy_lock]
140
+
141
+ fetch_deploy_lock
142
+ next unless self[:deploy_lock]
143
+
144
+ # Refresh lock expiry time if it's going to expire soon
145
+ if deploy_lock[:expire_at] && deploy_lock[:expire_at] < (Time.now + default_lock_expiry)
146
+ logger.info "Resetting lock expiry to default..."
147
+ deploy_lock[:expire_at] = (Time.now + default_lock_expiry).utc
148
+
149
+ write_deploy_lock(deploy_lock)
150
+ end
151
+
152
+ # Set the deploy_lock_created flag so that the lock isn't automatically removed after deploy
153
+ set :custom_deploy_lock, true
154
+ end
155
+ end
156
+ end
@@ -1,3 +1,3 @@
1
1
  module CapistranoDeployLock
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capistrano_deploy_lock
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,13 +9,14 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-15 00:00:00.000000000 Z
12
+ date: 2012-12-16 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Lock a server during deploy, to prevent people from deploying at the
15
15
  same time.
16
16
  email:
17
17
  - nathan.f77@gmail.com
18
- executables: []
18
+ executables:
19
+ - cap_deploy_lock_msg
19
20
  extensions: []
20
21
  extra_rdoc_files: []
21
22
  files:
@@ -24,8 +25,11 @@ files:
24
25
  - LICENSE.txt
25
26
  - README.md
26
27
  - Rakefile
28
+ - bin/cap_deploy_lock_msg
27
29
  - capistrano_deploy_lock.gemspec
30
+ - lib/capistrano/date_helper.rb
28
31
  - lib/capistrano/deploy_lock.rb
32
+ - lib/capistrano/recipes/deploy_lock.rb
29
33
  - lib/capistrano_deploy_lock.rb
30
34
  - lib/capistrano_deploy_lock/version.rb
31
35
  homepage: https://github.com/ndbroadbent/capistrano_deploy_lock
@@ -43,7 +47,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
43
47
  version: '0'
44
48
  segments:
45
49
  - 0
46
- hash: 1879672784853574210
50
+ hash: -2094697491794115315
47
51
  required_rubygems_version: !ruby/object:Gem::Requirement
48
52
  none: false
49
53
  requirements:
@@ -52,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
56
  version: '0'
53
57
  segments:
54
58
  - 0
55
- hash: 1879672784853574210
59
+ hash: -2094697491794115315
56
60
  requirements: []
57
61
  rubyforge_project:
58
62
  rubygems_version: 1.8.24