diff options
author | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-11-21 17:26:35 +0100 |
---|---|---|
committer | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-11-21 17:26:35 +0100 |
commit | a36d556137116385e84eca592ec62e46ecb97e03 (patch) | |
tree | 439638d981ab25807843c19ed2b369344d27f67c | |
parent | 1d16f137d93576385e403f5caf5f64bfe0b3a647 (diff) | |
download | gitlab-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.rb | 4 | ||||
-rw-r--r-- | changelogs/unreleased/chatops-deploy-command.yml | 4 | ||||
-rw-r--r-- | lib/gitlab/chat_commands/command.rb | 1 | ||||
-rw-r--r-- | lib/gitlab/chat_commands/deploy.rb | 20 | ||||
-rw-r--r-- | spec/factories/ci/builds.rb | 6 | ||||
-rw-r--r-- | spec/lib/gitlab/chat_commands/command_spec.rb | 43 | ||||
-rw-r--r-- | spec/lib/gitlab/chat_commands/deploy_spec.rb | 75 | ||||
-rw-r--r-- | spec/models/environment_spec.rb | 12 |
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 |