Skip to content

Commit

Permalink
Add locking - A running deployment locks the stage
Browse files Browse the repository at this point in the history
so that no other deployment ca be run. Overridable by checkbox

git-svn-id: svn+ssh://phoenix/srv/svn/webistrano/trunk@207 e1153f85-6c6c-dc11-afa8-0013d3c39b19
  • Loading branch information
jweiss committed Feb 23, 2009
1 parent 230157c commit ef87230
Show file tree
Hide file tree
Showing 17 changed files with 625 additions and 241 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

SVN
edge

* Add locking. A running deployment locks the stage, so that no other deployment ca be run. Overridable by checkbox.

* Update Net::SSH to 2.0.10 and Capistrano to 2.5.4

Expand Down
20 changes: 15 additions & 5 deletions app/controllers/deployments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,17 @@ def new
# POST /projects/1/stages/1/deployments
# POST /projects/1/stages/1/deployments.xml
def create
@deployment = @stage.deployments.build(params[:deployment])
@deployment.prompt_config = params[:deployment][:prompt_config] rescue {}
@deployment.user = current_user

@deployment = Deployment.new

respond_to do |format|
if @deployment.save
if populate_deployment_and_fire

@deployment.deploy_in_background!

format.html { redirect_to project_stage_deployment_url(@project, @stage, @deployment)}
format.xml { head :created, :location => project_stage_deployment_url(@project, @stage, @deployment) }
else
@deployment.clear_lock_error
format.html { render :action => "new" }
format.xml { render :xml => @deployment.errors.to_xml }
end
Expand Down Expand Up @@ -120,4 +119,15 @@ def set_auto_scroll
end
end

# sets @deployment
def populate_deployment_and_fire
return Deployment.lock_and_fire do |deployment|
@deployment = deployment
@deployment.attributes = params[:deployment]
@deployment.prompt_config = params[:deployment][:prompt_config] rescue {}
@deployment.stage = current_stage
@deployment.user = current_user
end
end

end
41 changes: 36 additions & 5 deletions app/models/deployment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ class Deployment < ActiveRecord::Base

serialize :excluded_host_ids

attr_accessible :task, :prompt_config, :description, :excluded_host_ids
attr_accessible :task, :prompt_config, :description, :excluded_host_ids, :override_locking

# given configuration hash on create in order to satisfy prompt configurations
attr_accessor :prompt_config
attr_accessor :prompt_config

attr_accessor :override_locking

after_create :add_stage_roles

Expand All @@ -35,10 +37,31 @@ def validate_on_create
errors.add('base', "Please fill out the parameter '#{conf.name}'") unless !prompt_config.blank? && !prompt_config[conf.name.to_sym].blank?
end

errors.add('lock', 'The stage is locked') if self.stage.locked? && !self.override_locking

ensure_not_all_hosts_excluded
end
end

def self.lock_and_fire(&block)
transaction do
d = Deployment.new
block.call(d)
return false unless d.valid?
stage = Stage.find(d.stage_id, :lock => true)
stage.lock
d.save!
stage.lock_with(d)
end
true
rescue
false
end

def override_locking?
@override_locking.to_i == 1
end

def prompt_config
@prompt_config = @prompt_config || {}
@prompt_config
Expand Down Expand Up @@ -156,6 +179,10 @@ def cancel!
complete_canceled!
end

def clear_lock_error
self.errors.instance_variable_get("@errors").delete('lock')
end

protected
def ensure_not_all_hosts_excluded
unless self.stage.blank? || self.excluded_host_ids.blank?
Expand All @@ -167,9 +194,13 @@ def ensure_not_all_hosts_excluded

def save_completed_status!(status)
raise 'cannot complete a second time' if self.completed?
self.status = status
self.completed_at = Time.now
self.save!
transaction do
stage = Stage.find(self.stage_id, :lock => true)
stage.unlock
self.status = status
self.completed_at = Time.now
self.save!
end
end

def notify_per_mail
Expand Down
23 changes: 23 additions & 0 deletions app/models/stage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ class Stage < ActiveRecord::Base
has_many :hosts, :through => :roles, :uniq => true
has_many :configuration_parameters, :dependent => :destroy, :class_name => "StageConfiguration", :order => "name ASC"
has_many :deployments, :dependent => :destroy, :order => "created_at DESC"
belongs_to :locking_deployment, :class_name => 'Deployment', :foreign_key => :locked_by_deployment_id

validates_uniqueness_of :name, :scope => :project_id
validates_length_of :name, :maximum => 250
validates_presence_of :project, :name
validates_inclusion_of :locked, :in => [0,1]

attr_accessible :name, :alert_emails

Expand Down Expand Up @@ -127,6 +129,27 @@ def list_tasks
[{:name => "Error", :description => "Could not load tasks - syntax error in recipe definition?"}]
end
end

def lock
other_self = self.class.find(self.id, :lock => true)
other_self.update_attribute(:locked, 1)
self.reload
end

def unlock
other_self = self.class.find(self.id, :lock => true)
other_self.update_attribute(:locked, 0)
other_self.update_attribute(:locked_by_deployment_id, nil)
self.reload
end

def lock_with(deployment)
raise ArgumentError, "stage #{self.id.inspect} must be locked before attaching lock_info to it" unless self.locked?
raise ArgumentError, "deployment does not belong to stage" unless deployment.stage_id == self.id
other_self = self.class.find(self.id, :lock => true)
other_self.update_attribute(:locked_by_deployment_id, deployment.id)
self.reload
end

protected
def add_deployment_problem(key, desc)
Expand Down
28 changes: 28 additions & 0 deletions app/views/deployments/_lock_info.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<p>
WARNING: The stage is locked.
</p>
<% if stage.locking_deployment %>
<p>
This stage is being deployed by <%= link_to h(stage.locking_deployment.user.login), user_path(stage.locking_deployment.user) %> who executed <%=link_to h(stage.locking_deployment.task), project_stage_deployment_path(current_project, current_stage, stage.locking_deployment) %> at <%=h stage.locking_deployment.created_at.to_s(:log) %>
</p>
<p>
Override locking and deploy anyhow? <input type="checkbox" id="override_locking_trigger" value="1">
</p>
<% end %>

<% content_for(:page_scripts) do %>
<script type="text/javascript">
Event.observe(window, 'load', function(){
var trigger = $('override_locking_trigger');
var target = $('deployment_override_locking');

Event.observe(trigger, 'change', function(){
if(trigger.checked){
target.value = 1;
}else{
target.value = 0;
}
})
});
</script>
<% end %>
7 changes: 7 additions & 0 deletions app/views/deployments/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
<% if current_stage.locked? %>
<% content_for(:flash_content) do %>
<%= error_flash( render :partial => 'lock_info', :locals => {:stage => current_stage, :deployment => @deployment}) %>
<% end %>
<% end %>

<% flashed_errors(:deployment) %>

<% form_for(:deployment, :url => project_stage_deployments_path(@project, @stage)) do |f| %>
Expand Down Expand Up @@ -29,6 +35,7 @@
<%= link_to 'Back to stage', project_stage_path(@project, @stage), :class => 'arrow_link ontheright' %>

<p>
<%= f.hidden_field :override_locking %>
<%= submit_tag "Start deployment" %>
</p>
</div>
Expand Down
11 changes: 11 additions & 0 deletions db/migrate/20090223081938_add_locking_to_stage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class AddLockingToStage < ActiveRecord::Migration
def self.up
add_column :stages, :locked_by_deployment_id, :integer
add_column :stages, :locked, :integer, :default => 0
end

def self.down
remove_column :stages, :locked_by_deployment_id
remove_column :stages, :locked
end
end
4 changes: 3 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#
# It's strongly recommended to check this file into your version control system.

ActiveRecord::Schema.define(:version => 20080925111006) do
ActiveRecord::Schema.define(:version => 20090223081938) do

create_table "configuration_parameters", :force => true do |t|
t.string "name"
Expand Down Expand Up @@ -104,6 +104,8 @@
t.datetime "created_at"
t.datetime "updated_at"
t.text "alert_emails"
t.integer "locked_by_deployment_id"
t.integer "locked", :default => 0
end

create_table "users", :force => true do |t|
Expand Down
11 changes: 11 additions & 0 deletions lib/tasks/test.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# add a test:non_transactional task that executes all tests in test/non_transactional/*_test.rb
namespace :test do

Rake::TestTask.new(:non_transactional) do |t|
t.pattern = 'test/non_transactional/*_test.rb'
t.ruby_opts << '-rubygems'
t.verbose = true
end

end

Loading

0 comments on commit ef87230

Please sign in to comment.