capistrano_deploy_lock 1.0.0 → 1.1.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/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