Skip to content

Commit

Permalink
Merge pull request zendesk#635 from zendesk/grosser/all
Browse files Browse the repository at this point in the history
deploy all stages to a single group
  • Loading branch information
grosser committed Nov 13, 2015
2 parents 415eeca + 1db99da commit ead2eaf
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 25 deletions.
42 changes: 40 additions & 2 deletions app/controllers/admin/deploy_groups_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Admin::DeployGroupsController < ApplicationController
before_action :authorize_admin!
before_action :authorize_super_admin!, only: [ :create, :new, :update, :destroy ]
before_action :deploy_group, only: [:show, :edit, :update, :destroy]
before_action :authorize_super_admin!, only: [ :create, :new, :update, :destroy, :deploy_all, :deploy_all_now ]
before_action :deploy_group, only: [:show, :edit, :update, :destroy, :deploy_all, :deploy_all_now]

def index
@deploy_groups = DeployGroup.all
Expand Down Expand Up @@ -44,8 +44,46 @@ def destroy
redirect_to action: 'index'
end

def deploy_all
@stages = Project.all.map do |project|
stages = stages_in_same_environment(project)
next unless deploy = stages.map(&:last_successful_deploy).compact.sort_by(&:created_at).last
[stages.first, deploy]
end.compact
end

def deploy_all_now
deploys = params.require(:stages).map do |stage|
stage_id, reference = stage.split("-", 2)
stage = Stage.find(stage_id)
stage = new_stage_with_group(stage) unless only_to_current_group?(stage)
deploy_service = DeployService.new(current_user)
deploy_service.deploy!(stage, reference: reference)
end
redirect_to deploys_path(ids: deploys.map(&:id))
end

private

def only_to_current_group?(stage)
stage.deploy_groups.map(&:id) == [deploy_group.id]
end

def stages_in_same_environment(project)
project.stages.select do |stage|
stage.command.include?("$DEPLOY_GROUPS") && # is dynamic
stage.deploy_groups.where(environment: deploy_group.environment).exists? # is made to go to this environment
end.sort_by { |stage| only_to_current_group?(stage) ? 0 : 1 }
end

def new_stage_with_group(stage)
stage = Stage.build_clone(stage)
stage.deploy_groups << deploy_group
stage.name = deploy_group.name # so we can later find it in #deploy_all
stage.save!
stage
end

def deploy_group_params
params.require(:deploy_group).permit(*allowed_deploy_group_params)
end
Expand Down
7 changes: 6 additions & 1 deletion app/controllers/deploys_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ class DeploysController < ApplicationController
before_action :stage, only: :new

def index
@deploys = current_project.deploys.page(params[:page])
scope = current_project.try(:deploys) || Deploy
@deploys = if params[:ids]
Kaminari.paginate_array(scope.find(params[:ids])).page(1).per(1000)
else
scope.page(params[:page])
end

respond_to do |format|
format.html
Expand Down
4 changes: 2 additions & 2 deletions app/models/stage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ def self.reorder(new_order)
end

def self.build_clone(old_stage)
new(old_stage.attributes).tap do |new_stage|
new(old_stage.attributes.except("id")).tap do |new_stage|
Samson::Hooks.fire(:stage_clone, old_stage, new_stage)
new_stage.new_relic_applications.build(old_stage.new_relic_applications.map(&:attributes))
new_stage.new_relic_applications.build(old_stage.new_relic_applications.map { |app| app.attributes.except("id") })
new_stage.command_ids = old_stage.command_ids
end
end
Expand Down
35 changes: 35 additions & 0 deletions app/views/admin/deploy_groups/deploy_all.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<h1>Deploy to <%= @deploy_group.name %></h1>

<%= form_tag deploy_all_now_admin_deploy_group_path(@deploy_group) do %>
<section class="clearfix">
<div class="table table-hover table-condensed">
<table class="table">
<thead>
<tr>
<th></th>
<th>Project</th>
<th>Stage</th>
<th>Reference</th>
</tr>
</thead>

<tbody>
<% @stages.each do |stage, deploy| %>
<tr>
<td><%= check_box_tag 'stages[]', "#{stage.id}-#{deploy.reference}" %></td>
<td><%= link_to stage.project.name, stage.project %></td>
<td><%= link_to stage.name, [stage.project, stage] %></td>
<td><%= deploy.reference %> (<%= time_ago_in_words deploy.created_at %> ago)</td>
</tr>
<% end %>
</tbody>
</table>
</div>

<div class="admin-actions">
<div class="pull-right">
<%= submit_tag "Deploy", class: "btn btn-default" %>
</div>
</div>
</section>
<% end %>
1 change: 1 addition & 0 deletions app/views/admin/deploy_groups/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<%= link_to "Edit", edit_admin_deploy_group_path(@deploy_group), class: "btn btn-primary" %>
<%= link_to "Deploy all projects", deploy_all_admin_deploy_group_path(@deploy_group), class: "btn btn-default" if DeployGroup.enabled? %>
<%= link_to "Back", admin_deploy_groups_path, class: "btn btn-default" %>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/views/deploys/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<%= render 'projects/header', project: @project, tab: "deploys" %>
<%= render 'projects/header', project: @project, tab: "deploys" if @project %>

<%= render 'shared/deploys_table' %>
9 changes: 7 additions & 2 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
resources :streams, only: [:show]
resources :locks, only: [:create, :destroy]

resources :deploys, only: [] do
resources :deploys, only: [:index] do
collection do
get :active
get :active_count
Expand Down Expand Up @@ -95,7 +95,12 @@
resource :projects, only: [:show]
resources :commands, except: [:show]
resources :environments, except: [:show]
resources :deploy_groups
resources :deploy_groups do
member do
get :deploy_all
post :deploy_all_now
end
end
end

namespace :integrations do
Expand Down
95 changes: 82 additions & 13 deletions test/controllers/admin/deploy_groups_controller_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
require_relative '../../test_helper'

describe Admin::DeployGroupsController do
let(:deploy_group) { deploy_groups(:pod100) }
let(:stage) { stages(:test_staging) }

def self.it_renders_index
it 'get :index succeeds' do
get :index
Expand All @@ -16,6 +19,8 @@ def self.it_renders_index
unauthorized :delete, :destroy, id: 1
unauthorized :post, :update, id: 1
unauthorized :get, :new
unauthorized :get, :deploy_all, id: 1
unauthorized :post, :deploy_all, id: 1
end

as_a_admin do
Expand All @@ -24,15 +29,18 @@ def self.it_renders_index
unauthorized :delete, :destroy, id: 1
unauthorized :post, :update, id: 1
unauthorized :get, :new
unauthorized :get, :deploy_all, id: 1
unauthorized :post, :deploy_all, id: 1
end

as_a_super_admin do
it_renders_index

it 'get :new succeeds' do
get :new
assert_response :success
assert assigns(:deploy_group)
describe "#new" do
it 'renders' do
get :new
assert_response :success
end
end

describe '#create' do
Expand All @@ -54,10 +62,9 @@ def self.it_renders_index

describe '#delete' do
it 'succeeds' do
id = deploy_groups(:pod100).id
delete :destroy, id: id
delete :destroy, id: deploy_group
assert_redirected_to admin_deploy_groups_path
DeployGroup.where(id: id).must_equal []
DeployGroup.where(id: deploy_group.id).must_equal []
end

it 'fails for non-existent deploy_group' do
Expand All @@ -68,21 +75,83 @@ def self.it_renders_index
end

describe '#update' do
let(:deploy_group) { deploy_groups(:pod100) }

before { request.env["HTTP_REFERER"] = admin_deploy_groups_url }

it 'save' do
it 'saves' do
post :update, deploy_group: {name: 'Test Update', environment_id: environments(:production).id}, id: deploy_group.id
assert_redirected_to admin_deploy_groups_path
DeployGroup.find(deploy_group.id).name.must_equal 'Test Update'
end

it 'fail to edit with blank name' do
post :update, deploy_group: {name: ''}, id: deploy_group.id
it 'fail to update with blank name' do
post :update, deploy_group: {name: ''}, id: deploy_group
assert_template :edit
flash[:error].must_include "Name can't be blank"
DeployGroup.find(deploy_group.id).name.must_equal 'Pod 100'
deploy_group.reload.name.must_equal 'Pod 100'
end
end

describe "#deploy_all" do
before do
stage.commands.first.update_attributes!(command: "cap $DEPLOY_GROUPS deploy")
end

it "renders" do
get :deploy_all, id: deploy_group
assert_response :success
assigns[:stages].must_equal [[stage, stage.deploys.first]]
end

it "ignores stages that do not have $DEPLOY_GROUPS" do
stage.commands.first.update_attributes!(command: "cap pod1 deploy")
get :deploy_all, id: deploy_group
assigns[:stages].must_equal []
end

it "also lists stages that have not been deployed since they might be our last failed deploy" do
Job.update_all(status: 'running')

other_stage = stages(:test_production)
other_stage.deploy_groups << deploy_group
other_stage.commands.create!(command: "cap $DEPLOY_GROUPS deploy")
other_stage.deploys.last.job.update_attribute(:status, 'succeeded')

get :deploy_all, id: deploy_group
assigns[:stages].must_equal [[stage, other_stage.deploys.first]]
end

it "ignores stages where the whole environment never got deployed" do
Job.update_all(status: 'running')
get :deploy_all, id: deploy_group
assigns[:stages].must_equal []
end

it "ignores stages that are on different environments" do
stage.deploy_groups.clear
get :deploy_all, id: deploy_group
assigns[:stages].must_equal []
end
end

describe "#deploy_all_now" do
it "deploys matching stages" do
stage.update_attributes!(name: deploy_group.name.upcase + ' ----')
assert_no_difference "Stage.count" do
post :deploy_all_now, id: deploy_group, stages: ["#{stage.id}-master"]
end
deploy = stage.deploys.order('created_at desc').first.id
assert_redirected_to "/deploys?ids%5B%5D=#{deploy}"
end

it "deploys to a new stage when it does not match" do
stage.deploy_groups << deploy_groups(:pod2)
assert_difference "Stage.count", +1 do
post :deploy_all_now, id: deploy_group, stages: ["#{stage.id}-master"]
end
deploy = Deploy.first
new_stage = deploy.stage
new_stage.wont_equal stage
assert_redirected_to "/deploys?ids%5B%5D=#{deploy.id}"
end
end
end
Expand Down
18 changes: 18 additions & 0 deletions test/controllers/deploys_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@
assert_response :ok
assert_equal "application/json", @response.content_type
end

it "renders without a project" do
get :index
assert_template :index
assigns[:deploys].must_equal Deploy.all.to_a
end

it "renders with given ids" do
get :index, ids: [deploy.id]
assert_template :index
assigns[:deploys].must_equal [deploy]
end

it "fails when given ids do not exist" do
assert_raises ActiveRecord::RecordNotFound do
get :index, ids: [121211221]
end
end
end

describe "a GET to :recent" do
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/deploys.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ succeeded_test:

succeeded_production_test:
stage: test_production
job: succeeded_test
job: succeeded_production_test
reference: v1.0
started_at: 2014-01-02 20:00:00
updated_at: 2014-01-02 20:10:00
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ succeeded_test:
output: This worked!
commit: staging

succeeded_production_test:
command: cap production deploy
user: super_admin
project: test
status: succeeded
output: This worked!
commit: production

running_test:
command: cap staging deploy
user: super_admin
Expand Down
9 changes: 6 additions & 3 deletions test/models/stage_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -367,15 +367,18 @@
end

it "returns an unsaved copy of the given stage with exactly the same everything except id" do
assert_equal @clone.attributes, subject.attributes
@clone.attributes.except("id").must_equal subject.attributes.except("id")
@clone.id.wont_equal subject.id
end

it "copies over the flowdock flows" do
assert_equal @clone.flowdock_flows.map(&:attributes), subject.flowdock_flows.map(&:attributes)
assert_equal @clone.flowdock_flows.map { |f| f.attributes.except("stage_id") },
subject.flowdock_flows.map { |f| f.attributes.except("stage_id") }
end

it "copies over the new relic applications" do
assert_equal @clone.new_relic_applications.map(&:attributes), subject.new_relic_applications.map(&:attributes)
assert_equal @clone.new_relic_applications.map { |n| n.attributes.except("stage_id", "id") },
subject.new_relic_applications.map { |n| n.attributes.except("stage_id", "id") }
end
end

Expand Down

0 comments on commit ead2eaf

Please sign in to comment.