summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2016-11-21 17:26:35 +0100
committerKamil Trzcinski <ayufan@ayufan.eu>2016-11-21 17:26:35 +0100
commita36d556137116385e84eca592ec62e46ecb97e03 (patch)
tree439638d981ab25807843c19ed2b369344d27f67c
parent1d16f137d93576385e403f5caf5f64bfe0b3a647 (diff)
downloadgitlab-ce-chatops-deploy-command.tar.gz
Introduce deploy command that allows to start deployment from one environment to second onechatops-deploy-command
-rw-r--r--app/models/environment.rb4
-rw-r--r--changelogs/unreleased/chatops-deploy-command.yml4
-rw-r--r--lib/gitlab/chat_commands/command.rb1
-rw-r--r--lib/gitlab/chat_commands/deploy.rb20
-rw-r--r--spec/factories/ci/builds.rb6
-rw-r--r--spec/lib/gitlab/chat_commands/command_spec.rb43
-rw-r--r--spec/lib/gitlab/chat_commands/deploy_spec.rb75
-rw-r--r--spec/models/environment_spec.rb12
8 files changed, 156 insertions, 9 deletions
diff --git a/app/models/environment.rb b/app/models/environment.rb
index e6dd7717e46..a7f4156fc2e 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -103,8 +103,8 @@ class Environment < ActiveRecord::Base
def actions_for(environment)
return [] unless manual_actions
- manual_actions.select |action|
- action.expanded_environment_name = environment
+ manual_actions.select do |action|
+ action.expanded_environment_name == environment
end
end
end
diff --git a/changelogs/unreleased/chatops-deploy-command.yml b/changelogs/unreleased/chatops-deploy-command.yml
new file mode 100644
index 00000000000..1e5a3e8df15
--- /dev/null
+++ b/changelogs/unreleased/chatops-deploy-command.yml
@@ -0,0 +1,4 @@
+---
+title: Add deployment command to ChatOps
+merge_request: 7619
+author:
diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb
index 5f131703d40..0ec358debc7 100644
--- a/lib/gitlab/chat_commands/command.rb
+++ b/lib/gitlab/chat_commands/command.rb
@@ -4,6 +4,7 @@ module Gitlab
COMMANDS = [
Gitlab::ChatCommands::IssueShow,
Gitlab::ChatCommands::IssueCreate,
+ Gitlab::ChatCommands::Deploy,
].freeze
def execute
diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb
index 8165d52575d..c0b93cca68c 100644
--- a/lib/gitlab/chat_commands/deploy.rb
+++ b/lib/gitlab/chat_commands/deploy.rb
@@ -9,6 +9,10 @@ module Gitlab
'deploy <environment> to <target-environment>'
end
+ def self.available?(project)
+ project.builds_enabled?
+ end
+
def self.allowed?(project, user)
can?(user, :create_deployment, project)
end
@@ -17,11 +21,8 @@ module Gitlab
from = match[:from]
to = match[:to]
- environment = project.environments.find_by(name: from)
- return unless environment
-
- actions = environment.actions_for(to)
- return unless actions.any?
+ actions = find_actions(from, to)
+ return unless actions.present?
if actions.one?
actions.first.play(current_user)
@@ -29,6 +30,15 @@ module Gitlab
Result.new(:error, 'Too many actions defined')
end
end
+
+ private
+
+ def find_actions(from, to)
+ environment = project.environments.find_by(name: from)
+ return unless environment
+
+ environment.actions_for(to).select(&:starts_environment?)
+ end
end
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0c93bbdfe26..eb20bd7dd58 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -55,6 +55,12 @@ FactoryGirl.define do
self.when 'manual'
end
+ trait :teardown_environment do
+ options do
+ { environment: { action: 'stop' } }
+ end
+ end
+
trait :allowed_to_fail do
allow_failure true
end
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index 8cedbb0240f..924b4b7b101 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -4,9 +4,9 @@ describe Gitlab::ChatCommands::Command, service: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
- subject { described_class.new(project, user, params).execute }
-
describe '#execute' do
+ subject { described_class.new(project, user, params).execute }
+
context 'when no command is available' do
let(:params) { { text: 'issue show 1' } }
let(:project) { create(:project, has_external_issue_tracker: true) }
@@ -51,5 +51,44 @@ describe Gitlab::ChatCommands::Command, service: true do
expect(subject[:text]).to match(/\/issues\/\d+/)
end
end
+
+ context 'when trying to do deployment' do
+ let(:params) { { text: 'deploy staging to production' } }
+ let!(:build) { create(:ci_build, project: project) }
+ let!(:staging) { create(:environment, name: 'staging', project: project) }
+ let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
+ let!(:manual) do
+ create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'first', environment: 'production')
+ end
+
+ context 'and user can not create deployment' do
+ it 'returns action' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to start_with('Whoops! That action is not allowed')
+ end
+ end
+
+ context 'and user does have deployment permission' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ it 'returns action' do
+ expect(subject[:text]).to include(manual.name)
+ expect(subject[:response_type]).to be(:in_channel)
+ end
+
+ context 'when duplicate action exists' do
+ let!(:manual2) do
+ create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'second', environment: 'production')
+ end
+
+ it 'returns error' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to include('Too many actions defined')
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
new file mode 100644
index 00000000000..bdbaa41dd14
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Deploy, service: true do
+ describe '#execute' do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:regex_match) { described_class.match('deploy staging to production') }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ subject do
+ described_class.new(project, user).execute(regex_match)
+ end
+
+ context 'if no environment is defined' do
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'with environment' do
+ let!(:staging) { create(:environment, name: 'staging', project: project) }
+ let!(:build) { create(:ci_build, project: project) }
+ let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
+
+ context 'without actions' do
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'with action' do
+ let!(:manual1) do
+ create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'first', environment: 'production')
+ end
+
+ it 'returns action' do
+ expect(subject).to eq(manual1)
+ end
+
+ context 'when duplicate action exists' do
+ let!(:manual2) do
+ create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'second', environment: 'production')
+ end
+
+ it 'returns error' do
+ expect(subject.message).to eq('Too many actions defined')
+ end
+ end
+
+ context 'when teardown action exists' do
+ let!(:teardown) do
+ create(:ci_build, :manual, :teardown_environment, project: project, pipeline: build.pipeline,
+ name: 'teardown', environment: 'production')
+ end
+
+ it 'returns error' do
+ expect(subject).to eq(action)
+ end
+ end
+ end
+ end
+ end
+
+ describe 'self.match' do
+ it 'matches the environment' do
+ match = described_class.match('deploy staging to production')
+
+ expect(match[:from]).to eq('staging')
+ expect(match[:to]).to eq('production')
+ end
+ end
+end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 60bbe3fcd72..d06665197db 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -9,6 +9,7 @@ describe Environment, models: true do
it { is_expected.to delegate_method(:last_deployment).to(:deployments).as(:last) }
it { is_expected.to delegate_method(:stop_action).to(:last_deployment) }
+ it { is_expected.to delegate_method(:manual_actions).to(:last_deployment) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) }
@@ -187,4 +188,15 @@ describe Environment, models: true do
it { is_expected.to be false }
end
end
+
+ describe '#actions_for' do
+ let(:deployment) { create(:deployment, environment: environment) }
+ let(:pipeline) { deployment.deployable.pipeline }
+ let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_BUILD_REF_NAME' )}
+ let!(:production_action) { create(:ci_build, :manual, name: 'production', pipeline: pipeline, environment: 'production' )}
+
+ it 'returns a list of actions with matching environment' do
+ expect(environment.actions_for('review/master')).to contain_exactly(review_action)
+ end
+ end
end