Commit 6ad3814e authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'feature/gb/manual-actions-protected-branches-permissions' into 'master'

Check access to a branch when user triggers manual action

Closes #20261

See merge request !10494
parents 2e6201b1 fc121cca
No related merge requests found
...@@ -40,13 +40,15 @@ class Projects::ApplicationController < ApplicationController ...@@ -40,13 +40,15 @@ class Projects::ApplicationController < ApplicationController
(current_user && current_user.already_forked?(project)) (current_user && current_user.already_forked?(project))
end end
def authorize_project!(action) def authorize_action!(action)
return access_denied! unless can?(current_user, action, project) unless can?(current_user, action, project)
return access_denied!
end
end end
def method_missing(method_sym, *arguments, &block) def method_missing(method_sym, *arguments, &block)
if method_sym.to_s =~ /\Aauthorize_(.*)!\z/ if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
authorize_project!($1.to_sym) authorize_action!($1.to_sym)
else else
super super
end end
......
class Projects::BuildsController < Projects::ApplicationController class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all] before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, only: [:index, :show, :status, :raw, :trace]
before_action :authorize_update_build!, except: [:index, :show, :status, :raw, :trace] before_action :authorize_read_build!,
only: [:index, :show, :status, :raw, :trace]
before_action :authorize_update_build!,
except: [:index, :show, :status, :raw, :trace, :cancel_all]
layout 'project' layout 'project'
def index def index
...@@ -28,7 +32,12 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -28,7 +32,12 @@ class Projects::BuildsController < Projects::ApplicationController
end end
def cancel_all def cancel_all
@project.builds.running_or_pending.each(&:cancel) return access_denied! unless can?(current_user, :update_build, project)
@project.builds.running_or_pending.each do |build|
build.cancel if can?(current_user, :update_build, build)
end
redirect_to namespace_project_builds_path(project.namespace, project) redirect_to namespace_project_builds_path(project.namespace, project)
end end
...@@ -107,8 +116,13 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -107,8 +116,13 @@ class Projects::BuildsController < Projects::ApplicationController
private private
def authorize_update_build!
return access_denied! unless can?(current_user, :update_build, build)
end
def build def build
@build ||= project.builds.find_by!(id: params[:id]).present(current_user: current_user) @build ||= project.builds.find(params[:id])
.present(current_user: current_user)
end end
def build_path(build) def build_path(build)
......
...@@ -111,14 +111,9 @@ module Ci ...@@ -111,14 +111,9 @@ module Ci
end end
def play(current_user) def play(current_user)
# Try to queue a current build Ci::PlayBuildService
if self.enqueue .new(project, current_user)
self.update(user: current_user) .execute(self)
self
else
# Otherwise we need to create a duplicate
Ci::Build.retry(self, current_user)
end
end end
def cancelable? def cancelable?
......
...@@ -85,8 +85,8 @@ class Deployment < ActiveRecord::Base ...@@ -85,8 +85,8 @@ class Deployment < ActiveRecord::Base
end end
def stop_action def stop_action
return nil unless on_stop.present? return unless on_stop.present?
return nil unless manual_actions return unless manual_actions
@stop_action ||= manual_actions.find_by(name: on_stop) @stop_action ||= manual_actions.find_by(name: on_stop)
end end
......
...@@ -97,6 +97,10 @@ class BasePolicy ...@@ -97,6 +97,10 @@ class BasePolicy
rules rules
end end
def rules
raise NotImplementedError
end
def delegate!(new_subject) def delegate!(new_subject)
@rule_set.merge(Ability.allowed(@user, new_subject)) @rule_set.merge(Ability.allowed(@user, new_subject))
end end
......
module Ci module Ci
class BuildPolicy < CommitStatusPolicy class BuildPolicy < CommitStatusPolicy
alias_method :build, :subject
def rules def rules
super super
...@@ -8,6 +10,20 @@ module Ci ...@@ -8,6 +10,20 @@ module Ci
%w[read create update admin].each do |rule| %w[read create update admin].each do |rule|
cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build" cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build"
end end
if can?(:update_build) && protected_action?
cannot! :update_build
end
end
private
def protected_action?
return false unless build.action?
!::Gitlab::UserAccess
.new(user, project: build.project)
.can_push_to_branch?(build.ref)
end end
end end
end end
module Ci module Ci
class PipelinePolicy < BuildPolicy class PipelinePolicy < BasePolicy
def rules
delegate! @subject.project
end
end end
end end
class EnvironmentPolicy < BasePolicy class EnvironmentPolicy < BasePolicy
alias_method :environment, :subject
def rules def rules
delegate! @subject.project delegate! environment.project
if can?(:create_deployment) && environment.stop_action?
can! :stop_environment if can_play_stop_action?
end
end
private
def can_play_stop_action?
Ability.allowed?(user, :update_build, environment.stop_action)
end end
end end
...@@ -13,4 +13,12 @@ class BuildActionEntity < Grape::Entity ...@@ -13,4 +13,12 @@ class BuildActionEntity < Grape::Entity
end end
expose :playable?, as: :playable expose :playable?, as: :playable
private
alias_method :build, :object
def playable?
build.playable? && can?(request.user, :update_build, build)
end
end end
...@@ -12,7 +12,7 @@ class BuildEntity < Grape::Entity ...@@ -12,7 +12,7 @@ class BuildEntity < Grape::Entity
path_to(:retry_namespace_project_build, build) path_to(:retry_namespace_project_build, build)
end end
expose :play_path, if: ->(build, _) { build.playable? } do |build| expose :play_path, if: -> (*) { playable? } do |build|
path_to(:play_namespace_project_build, build) path_to(:play_namespace_project_build, build)
end end
...@@ -25,11 +25,15 @@ class BuildEntity < Grape::Entity ...@@ -25,11 +25,15 @@ class BuildEntity < Grape::Entity
alias_method :build, :object alias_method :build, :object
def path_to(route, build) def playable?
send("#{route}_path", build.project.namespace, build.project, build) build.playable? && can?(request.user, :update_build, build)
end end
def detailed_status def detailed_status
build.detailed_status(request.user) build.detailed_status(request.user)
end end
def path_to(route, build)
send("#{route}_path", build.project.namespace, build.project, build)
end
end end
...@@ -48,15 +48,15 @@ class PipelineEntity < Grape::Entity ...@@ -48,15 +48,15 @@ class PipelineEntity < Grape::Entity
end end
expose :commit, using: CommitEntity expose :commit, using: CommitEntity
expose :yaml_errors, if: ->(pipeline, _) { pipeline.has_yaml_errors? } expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
expose :retry_path, if: proc { can_retry? } do |pipeline| expose :retry_path, if: -> (*) { can_retry? } do |pipeline|
retry_namespace_project_pipeline_path(pipeline.project.namespace, retry_namespace_project_pipeline_path(pipeline.project.namespace,
pipeline.project, pipeline.project,
pipeline.id) pipeline.id)
end end
expose :cancel_path, if: proc { can_cancel? } do |pipeline| expose :cancel_path, if: -> (*) { can_cancel? } do |pipeline|
cancel_namespace_project_pipeline_path(pipeline.project.namespace, cancel_namespace_project_pipeline_path(pipeline.project.namespace,
pipeline.project, pipeline.project,
pipeline.id) pipeline.id)
......
module Ci
class PlayBuildService < ::BaseService
def execute(build)
unless can?(current_user, :update_build, build)
raise Gitlab::Access::AccessDeniedError
end
# Try to enqueue the build, otherwise create a duplicate.
#
if build.enqueue
build.tap { |action| action.update(user: current_user) }
else
Ci::Build.retry(build, current_user)
end
end
end
end
...@@ -8,6 +8,8 @@ module Ci ...@@ -8,6 +8,8 @@ module Ci
end end
pipeline.retryable_builds.find_each do |build| pipeline.retryable_builds.find_each do |build|
next unless can?(current_user, :update_build, build)
Ci::RetryBuildService.new(project, current_user) Ci::RetryBuildService.new(project, current_user)
.reprocess(build) .reprocess(build)
end end
......
...@@ -5,10 +5,11 @@ module Ci ...@@ -5,10 +5,11 @@ module Ci
def execute(branch_name) def execute(branch_name)
@ref = branch_name @ref = branch_name
return unless has_ref? return unless @ref.present?
environments.each do |environment| environments.each do |environment|
next unless can?(current_user, :create_deployment, project) next unless environment.stop_action?
next unless can?(current_user, :stop_environment, environment)
environment.stop_with_action!(current_user) environment.stop_with_action!(current_user)
end end
...@@ -16,13 +17,10 @@ module Ci ...@@ -16,13 +17,10 @@ module Ci
private private
def has_ref?
@ref.present?
end
def environments def environments
@environments ||= @environments ||= EnvironmentsFinder
EnvironmentsFinder.new(project, current_user, ref: @ref, recently_updated: true).execute .new(project, current_user, ref: @ref, recently_updated: true)
.execute
end end
end end
end end
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
= link_to cancel_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = link_to cancel_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred') = icon('remove', class: 'cred')
- elsif allow_retry - elsif allow_retry
- if job.playable? && !admin - if job.playable? && !admin && can?(current_user, :update_build, job)
= link_to play_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = link_to play_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= custom_icon('icon_play') = custom_icon('icon_play')
- elsif job.retryable? - elsif job.retryable?
......
---
title: Implement protected manual actions
merge_request: 10494
author:
...@@ -553,6 +553,8 @@ The above script will: ...@@ -553,6 +553,8 @@ The above script will:
#### Manual actions #### Manual actions
> Introduced in GitLab 8.10. > Introduced in GitLab 8.10.
> Blocking manual actions were introduced in GitLab 9.0
> Protected actions were introduced in GitLab 9.2
Manual actions are a special type of job that are not executed automatically; Manual actions are a special type of job that are not executed automatically;
they need to be explicitly started by a user. Manual actions can be started they need to be explicitly started by a user. Manual actions can be started
...@@ -578,7 +580,10 @@ Optional manual actions have `allow_failure: true` set by default. ...@@ -578,7 +580,10 @@ Optional manual actions have `allow_failure: true` set by default.
**Statuses of optional actions do not contribute to overall pipeline status.** **Statuses of optional actions do not contribute to overall pipeline status.**
> Blocking manual actions were introduced in GitLab 9.0 **Manual actions are considered to be write actions, so permissions for
protected branches are used when user wants to trigger an action. In other
words, in order to trigger a manual action assigned to a branch that the
pipeline is running for, user needs to have ability to push to this branch.**
### environment ### environment
......
...@@ -132,6 +132,7 @@ module API ...@@ -132,6 +132,7 @@ module API
authorize_update_builds! authorize_update_builds!
build = get_build!(params[:job_id]) build = get_build!(params[:job_id])
authorize!(:update_build, build)
build.cancel build.cancel
...@@ -148,6 +149,7 @@ module API ...@@ -148,6 +149,7 @@ module API
authorize_update_builds! authorize_update_builds!
build = get_build!(params[:job_id]) build = get_build!(params[:job_id])
authorize!(:update_build, build)
return forbidden!('Job is not retryable') unless build.retryable? return forbidden!('Job is not retryable') unless build.retryable?
build = Ci::Build.retry(build, current_user) build = Ci::Build.retry(build, current_user)
...@@ -165,6 +167,7 @@ module API ...@@ -165,6 +167,7 @@ module API
authorize_update_builds! authorize_update_builds!
build = get_build!(params[:job_id]) build = get_build!(params[:job_id])
authorize!(:update_build, build)
return forbidden!('Job is not erasable!') unless build.erasable? return forbidden!('Job is not erasable!') unless build.erasable?
build.erase(erased_by: current_user) build.erase(erased_by: current_user)
...@@ -181,6 +184,7 @@ module API ...@@ -181,6 +184,7 @@ module API
authorize_update_builds! authorize_update_builds!
build = get_build!(params[:job_id]) build = get_build!(params[:job_id])
authorize!(:update_build, build)
return not_found!(build) unless build.artifacts? return not_found!(build) unless build.artifacts?
build.keep_artifacts! build.keep_artifacts!
...@@ -201,6 +205,7 @@ module API ...@@ -201,6 +205,7 @@ module API
build = get_build!(params[:job_id]) build = get_build!(params[:job_id])
authorize!(:update_build, build)
bad_request!("Unplayable Job") unless build.playable? bad_request!("Unplayable Job") unless build.playable?
build.play(current_user) build.play(current_user)
...@@ -211,12 +216,12 @@ module API ...@@ -211,12 +216,12 @@ module API
end end
helpers do helpers do
def get_build(id) def find_build(id)
user_project.builds.find_by(id: id.to_i) user_project.builds.find_by(id: id.to_i)
end end
def get_build!(id) def get_build!(id)
get_build(id) || not_found! find_build(id) || not_found!
end end
def present_artifacts!(artifacts_file) def present_artifacts!(artifacts_file)
......
...@@ -134,6 +134,7 @@ module API ...@@ -134,6 +134,7 @@ module API
authorize_update_builds! authorize_update_builds!
build = get_build!(params[:build_id]) build = get_build!(params[:build_id])
authorize!(:update_build, build)
build.cancel build.cancel
...@@ -150,6 +151,7 @@ module API ...@@ -150,6 +151,7 @@ module API
authorize_update_builds! authorize_update_builds!
build = get_build!(params[:build_id]) build = get_build!(params[:build_id])
authorize!(:update_build, build)
return forbidden!('Build is not retryable') unless build.retryable? return forbidden!('Build is not retryable') unless build.retryable?
build = Ci::Build.retry(build, current_user) build = Ci::Build.retry(build, current_user)
...@@ -167,6 +169,7 @@ module API ...@@ -167,6 +169,7 @@ module API
authorize_update_builds! authorize_update_builds!
build = get_build!(params[:build_id]) build = get_build!(params[:build_id])
authorize!(:update_build, build)
return forbidden!('Build is not erasable!') unless build.erasable? return forbidden!('Build is not erasable!') unless build.erasable?
build.erase(erased_by: current_user) build.erase(erased_by: current_user)
...@@ -183,6 +186,7 @@ module API ...@@ -183,6 +186,7 @@ module API
authorize_update_builds! authorize_update_builds!
build = get_build!(params[:build_id]) build = get_build!(params[:build_id])
authorize!(:update_build, build)
return not_found!(build) unless build.artifacts? return not_found!(build) unless build.artifacts?
build.keep_artifacts! build.keep_artifacts!
...@@ -202,7 +206,7 @@ module API ...@@ -202,7 +206,7 @@ module API
authorize_read_builds! authorize_read_builds!
build = get_build!(params[:build_id]) build = get_build!(params[:build_id])
authorize!(:update_build, build)
bad_request!("Unplayable Job") unless build.playable? bad_request!("Unplayable Job") unless build.playable?
build.play(current_user) build.play(current_user)
...@@ -213,12 +217,12 @@ module API ...@@ -213,12 +217,12 @@ module API
end end
helpers do helpers do
def get_build(id) def find_build(id)
user_project.builds.find_by(id: id.to_i) user_project.builds.find_by(id: id.to_i)
end end
def get_build!(id) def get_build!(id)
get_build(id) || not_found! find_build(id) || not_found!
end end
def present_artifacts!(artifacts_file) def present_artifacts!(artifacts_file)
......
module Gitlab
module Ci
module Status
module Build
class Action < Status::Extended
def label
if has_action?
@status.label
else
"#{@status.label} (not allowed)"
end
end
def self.matches?(build, user)
build.action?
end
end
end
end
end
end
...@@ -2,9 +2,7 @@ module Gitlab ...@@ -2,9 +2,7 @@ module Gitlab
module Ci module Ci
module Status module Status
module Build module Build
class Cancelable < SimpleDelegator class Cancelable < Status::Extended
include Status::Extended
def has_action? def has_action?
can?(user, :update_build, subject) can?(user, :update_build, subject)
end end
......
...@@ -8,7 +8,8 @@ module Gitlab ...@@ -8,7 +8,8 @@ module Gitlab
Status::Build::Retryable], Status::Build::Retryable],
[Status::Build::FailedAllowed, [Status::Build::FailedAllowed,
Status::Build::Play, Status::Build::Play,
Status::Build::Stop]] Status::Build::Stop],
[Status::Build::Action]]
end end
def self.common_helpers def self.common_helpers
......
...@@ -2,9 +2,7 @@ module Gitlab ...@@ -2,9 +2,7 @@ module Gitlab
module Ci module Ci
module Status module Status
module Build module Build
class FailedAllowed < SimpleDelegator class FailedAllowed < Status::Extended
include Status::Extended
def label def label
'failed (allowed to fail)' 'failed (allowed to fail)'
end end
......
...@@ -2,9 +2,7 @@ module Gitlab ...@@ -2,9 +2,7 @@ module Gitlab
module Ci module Ci
module Status module Status
module Build module Build
class Play < SimpleDelegator class Play < Status::Extended
include Status::Extended
def label def label
'manual play action' 'manual play action'
end end
......
...@@ -2,9 +2,7 @@ module Gitlab ...@@ -2,9 +2,7 @@ module Gitlab
module Ci module Ci
module Status module Status
module Build module Build
class Retryable < SimpleDelegator class Retryable < Status::Extended
include Status::Extended
def has_action? def has_action?
can?(user, :update_build, subject) can?(user, :update_build, subject)
end end
......
...@@ -2,9 +2,7 @@ module Gitlab ...@@ -2,9 +2,7 @@ module Gitlab
module Ci module Ci
module Status module Status
module Build module Build
class Stop < SimpleDelegator class Stop < Status::Extended
include Status::Extended
def label def label
'manual stop action' 'manual stop action'
end end
......
module Gitlab module Gitlab
module Ci module Ci
module Status module Status
module Extended class Extended < SimpleDelegator
extend ActiveSupport::Concern def initialize(status)
super(@status = status)
end
class_methods do def self.matches?(_subject, _user)
def matches?(_subject, _user) raise NotImplementedError
raise NotImplementedError
end
end end
end end
end end
......
...@@ -2,9 +2,7 @@ module Gitlab ...@@ -2,9 +2,7 @@ module Gitlab
module Ci module Ci
module Status module Status
module Pipeline module Pipeline
class Blocked < SimpleDelegator class Blocked < Status::Extended
include Status::Extended
def text def text
'blocked' 'blocked'
end end
......
...@@ -5,9 +5,7 @@ module Gitlab ...@@ -5,9 +5,7 @@ module Gitlab
# Extended status used when pipeline or stage passed conditionally. # Extended status used when pipeline or stage passed conditionally.
# This means that failed jobs that are allowed to fail were present. # This means that failed jobs that are allowed to fail were present.
# #
class SuccessWarning < SimpleDelegator class SuccessWarning < Status::Extended
include Status::Extended
def text def text
'passed' 'passed'
end end
......
...@@ -261,7 +261,7 @@ describe Projects::BuildsController do ...@@ -261,7 +261,7 @@ describe Projects::BuildsController do
describe 'POST play' do describe 'POST play' do
before do before do
project.add_developer(user) project.add_master(user)
sign_in(user) sign_in(user)
post_play post_play
......
...@@ -18,15 +18,21 @@ FactoryGirl.define do ...@@ -18,15 +18,21 @@ FactoryGirl.define do
# interconnected objects to simulate a review app. # interconnected objects to simulate a review app.
# #
after(:create) do |environment, evaluator| after(:create) do |environment, evaluator|
pipeline = create(:ci_pipeline, project: environment.project)
deployable = create(:ci_build, name: "#{environment.name}:deploy",
pipeline: pipeline)
deployment = create(:deployment, deployment = create(:deployment,
environment: environment, environment: environment,
project: environment.project, project: environment.project,
deployable: deployable,
ref: evaluator.ref, ref: evaluator.ref,
sha: environment.project.commit(evaluator.ref).id) sha: environment.project.commit(evaluator.ref).id)
teardown_build = create(:ci_build, :manual, teardown_build = create(:ci_build, :manual,
name: "#{deployment.environment.name}:teardown", name: "#{environment.name}:teardown",
pipeline: deployment.deployable.pipeline) pipeline: pipeline)
deployment.update_column(:on_stop, teardown_build.name) deployment.update_column(:on_stop, teardown_build.name)
environment.update_attribute(:deployments, [deployment]) environment.update_attribute(:deployments, [deployment])
......
...@@ -62,6 +62,8 @@ feature 'Environment', :feature do ...@@ -62,6 +62,8 @@ feature 'Environment', :feature do
name: 'deploy to production') name: 'deploy to production')
end end
given(:role) { :master }
scenario 'does show a play button' do scenario 'does show a play button' do
expect(page).to have_link(action.name.humanize) expect(page).to have_link(action.name.humanize)
end end
...@@ -132,6 +134,8 @@ feature 'Environment', :feature do ...@@ -132,6 +134,8 @@ feature 'Environment', :feature do
on_stop: 'close_app') on_stop: 'close_app')
end end
given(:role) { :master }
scenario 'does allow to stop environment' do scenario 'does allow to stop environment' do
click_link('Stop') click_link('Stop')
......
...@@ -40,11 +40,15 @@ describe Gitlab::ChatCommands::Command, service: true do ...@@ -40,11 +40,15 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'when trying to do deployment' do context 'when trying to do deployment' do
let(:params) { { text: 'deploy staging to production' } } let(:params) { { text: 'deploy staging to production' } }
let!(:build) { create(:ci_build, project: project) } let!(:build) { create(:ci_build, pipeline: pipeline) }
let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:staging) { create(:environment, name: 'staging', project: project) } let!(:staging) { create(:environment, name: 'staging', project: project) }
let!(:deployment) { create(:deployment, environment: staging, deployable: build) } let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
let!(:manual) do let!(:manual) do
create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'first', environment: 'production') create(:ci_build, :manual, pipeline: pipeline,
name: 'first',
environment: 'production')
end end
context 'and user can not create deployment' do context 'and user can not create deployment' do
...@@ -56,7 +60,7 @@ describe Gitlab::ChatCommands::Command, service: true do ...@@ -56,7 +60,7 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'and user does have deployment permission' do context 'and user does have deployment permission' do
before do before do
project.team << [user, :developer] build.project.add_master(user)
end end
it 'returns action' do it 'returns action' do
...@@ -66,7 +70,9 @@ describe Gitlab::ChatCommands::Command, service: true do ...@@ -66,7 +70,9 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'when duplicate action exists' do context 'when duplicate action exists' do
let!(:manual2) do let!(:manual2) do
create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'second', environment: 'production') create(:ci_build, :manual, pipeline: pipeline,
name: 'second',
environment: 'production')
end end
it 'returns error' do it 'returns error' do
......
...@@ -7,7 +7,7 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -7,7 +7,7 @@ describe Gitlab::ChatCommands::Deploy, service: true do
let(:regex_match) { described_class.match('deploy staging to production') } let(:regex_match) { described_class.match('deploy staging to production') }
before do before do
project.team << [user, :master] project.add_master(user)
end end
subject do subject do
...@@ -23,7 +23,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -23,7 +23,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
context 'with environment' do context 'with environment' do
let!(:staging) { create(:environment, name: 'staging', project: project) } let!(:staging) { create(:environment, name: 'staging', project: project) }
let!(:build) { create(:ci_build, project: project) } let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:build) { create(:ci_build, pipeline: pipeline) }
let!(:deployment) { create(:deployment, environment: staging, deployable: build) } let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
context 'without actions' do context 'without actions' do
...@@ -35,7 +36,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -35,7 +36,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
context 'with action' do context 'with action' do
let!(:manual1) do let!(:manual1) do
create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'first', environment: 'production') create(:ci_build, :manual, pipeline: pipeline,
name: 'first',
environment: 'production')
end end
it 'returns success result' do it 'returns success result' do
...@@ -45,7 +48,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -45,7 +48,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
context 'when duplicate action exists' do context 'when duplicate action exists' do
let!(:manual2) do let!(:manual2) do
create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'second', environment: 'production') create(:ci_build, :manual, pipeline: pipeline,
name: 'second',
environment: 'production')
end end
it 'returns error' do it 'returns error' do
...@@ -57,8 +62,7 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -57,8 +62,7 @@ describe Gitlab::ChatCommands::Deploy, service: true do
context 'when teardown action exists' do context 'when teardown action exists' do
let!(:teardown) do let!(:teardown) do
create(:ci_build, :manual, :teardown_environment, create(:ci_build, :manual, :teardown_environment,
project: project, pipeline: build.pipeline, pipeline: pipeline, name: 'teardown', environment: 'production')
name: 'teardown', environment: 'production')
end end
it 'returns the success message' do it 'returns the success message' do
......
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Action do
let(:status) { double('core status') }
let(:user) { double('user') }
subject do
described_class.new(status)
end
describe '#label' do
before do
allow(status).to receive(:label).and_return('label')
end
context 'when status has action' do
before do
allow(status).to receive(:has_action?).and_return(true)
end
it 'does not append text' do
expect(subject.label).to eq 'label'
end
end
context 'when status does not have action' do
before do
allow(status).to receive(:has_action?).and_return(false)
end
it 'appends text about action not allowed' do
expect(subject.label).to eq 'label (not allowed)'
end
end
end
describe '.matches?' do
subject { described_class.matches?(build, user) }
context 'when build is an action' do
let(:build) { create(:ci_build, :manual) }
it 'is a correct match' do
expect(subject).to be true
end
end
context 'when build is not manual' do
let(:build) { create(:ci_build) }
it 'does not match' do
expect(subject).to be false
end
end
end
end
...@@ -204,11 +204,12 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -204,11 +204,12 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'matches correct extended statuses' do it 'matches correct extended statuses' do
expect(factory.extended_statuses) expect(factory.extended_statuses)
.to eq [Gitlab::Ci::Status::Build::Play] .to eq [Gitlab::Ci::Status::Build::Play,
Gitlab::Ci::Status::Build::Action]
end end
it 'fabricates a play detailed status' do it 'fabricates action detailed status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Play expect(status).to be_a Gitlab::Ci::Status::Build::Action
end end
it 'fabricates status with correct details' do it 'fabricates status with correct details' do
...@@ -216,11 +217,26 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -216,11 +217,26 @@ describe Gitlab::Ci::Status::Build::Factory do
expect(status.group).to eq 'manual' expect(status.group).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual' expect(status.icon).to eq 'icon_status_manual'
expect(status.favicon).to eq 'favicon_status_manual' expect(status.favicon).to eq 'favicon_status_manual'
expect(status.label).to eq 'manual play action' expect(status.label).to include 'manual play action'
expect(status).to have_details expect(status).to have_details
expect(status).to have_action
expect(status.action_path).to include 'play' expect(status.action_path).to include 'play'
end end
context 'when user has ability to play action' do
before do
build.project.add_master(user)
end
it 'fabricates status that has action' do
expect(status).to have_action
end
end
context 'when user does not have ability to play action' do
it 'fabricates status that has no action' do
expect(status).not_to have_action
end
end
end end
context 'when build is an environment stop action' do context 'when build is an environment stop action' do
...@@ -232,21 +248,24 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -232,21 +248,24 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'matches correct extended statuses' do it 'matches correct extended statuses' do
expect(factory.extended_statuses) expect(factory.extended_statuses)
.to eq [Gitlab::Ci::Status::Build::Stop] .to eq [Gitlab::Ci::Status::Build::Stop,
Gitlab::Ci::Status::Build::Action]
end end
it 'fabricates a stop detailed status' do it 'fabricates action detailed status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Stop expect(status).to be_a Gitlab::Ci::Status::Build::Action
end end
it 'fabricates status with correct details' do context 'when user is not allowed to execute manual action' do
expect(status.text).to eq 'manual' it 'fabricates status with correct details' do
expect(status.group).to eq 'manual' expect(status.text).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual' expect(status.group).to eq 'manual'
expect(status.favicon).to eq 'favicon_status_manual' expect(status.icon).to eq 'icon_status_manual'
expect(status.label).to eq 'manual stop action' expect(status.favicon).to eq 'favicon_status_manual'
expect(status).to have_details expect(status.label).to eq 'manual stop action (not allowed)'
expect(status).to have_action expect(status).to have_details
expect(status).not_to have_action
end
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Build::Play do describe Gitlab::Ci::Status::Build::Play do
let(:status) { double('core') } let(:user) { create(:user) }
let(:user) { double('user') } let(:build) { create(:ci_build, :manual) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
subject { described_class.new(status) } subject { described_class.new(status) }
describe '#label' do describe '#label' do
it { expect(subject.label).to eq 'manual play action' } it 'has a label that says it is a manual action' do
expect(subject.label).to eq 'manual play action'
end
end end
describe 'action details' do describe '#has_action?' do
let(:user) { create(:user) } context 'when user is allowed to update build' do
let(:build) { create(:ci_build) } context 'when user can push to branch' do
let(:status) { Gitlab::Ci::Status::Core.new(build, user) } before { build.project.add_master(user) }
describe '#has_action?' do
context 'when user is allowed to update build' do
before { build.project.team << [user, :developer] }
it { is_expected.to have_action } it { is_expected.to have_action }
end end
context 'when user is not allowed to update build' do context 'when user can not push to the branch' do
before { build.project.add_developer(user) }
it { is_expected.not_to have_action } it { is_expected.not_to have_action }
end end
end end
describe '#action_path' do context 'when user is not allowed to update build' do
it { expect(subject.action_path).to include "#{build.id}/play" } it { is_expected.not_to have_action }
end end
end
describe '#action_icon' do describe '#action_path' do
it { expect(subject.action_icon).to eq 'icon_action_play' } it { expect(subject.action_path).to include "#{build.id}/play" }
end end
describe '#action_title' do describe '#action_icon' do
it { expect(subject.action_title).to eq 'Play' } it { expect(subject.action_icon).to eq 'icon_action_play' }
end end
describe '#action_title' do
it { expect(subject.action_title).to eq 'Play' }
end end
describe '.matches?' do describe '.matches?' do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Extended do describe Gitlab::Ci::Status::Extended do
subject do
Class.new.include(described_class)
end
it 'requires subclass to implement matcher' do it 'requires subclass to implement matcher' do
expect { subject.matches?(double, double) } expect { described_class.matches?(double, double) }
.to raise_error(NotImplementedError) .to raise_error(NotImplementedError)
end end
end end
...@@ -897,22 +897,26 @@ describe Ci::Build, :models do ...@@ -897,22 +897,26 @@ describe Ci::Build, :models do
end end
describe '#persisted_environment' do describe '#persisted_environment' do
before do let!(:environment) do
@environment = create(:environment, project: project, name: "foo-#{project.default_branch}") create(:environment, project: project, name: "foo-#{project.default_branch}")
end end
subject { build.persisted_environment } subject { build.persisted_environment }
context 'referenced literally' do context 'when referenced literally' do
let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}") } let(:build) do
create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}")
end
it { is_expected.to eq(@environment) } it { is_expected.to eq(environment) }
end end
context 'referenced with a variable' do context 'when referenced with a variable' do
let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_COMMIT_REF_NAME") } let(:build) do
create(:ci_build, pipeline: pipeline, environment: "foo-$CI_COMMIT_REF_NAME")
end
it { is_expected.to eq(@environment) } it { is_expected.to eq(environment) }
end end
end end
...@@ -923,26 +927,8 @@ describe Ci::Build, :models do ...@@ -923,26 +927,8 @@ describe Ci::Build, :models do
project.add_developer(user) project.add_developer(user)
end end
context 'when build is manual' do it 'enqueues the build' do
it 'enqueues a build' do expect(build.play(user)).to be_pending
new_build = build.play(user)
expect(new_build).to be_pending
expect(new_build).to eq(build)
end
end
context 'when build is passed' do
before do
build.update(status: 'success')
end
it 'creates a new build' do
new_build = build.play(user)
expect(new_build).to be_pending
expect(new_build).not_to eq(build)
end
end end
end end
......
...@@ -206,25 +206,52 @@ describe Environment, models: true do ...@@ -206,25 +206,52 @@ describe Environment, models: true do
end end
context 'when matching action is defined' do context 'when matching action is defined' do
let(:build) { create(:ci_build) } let(:pipeline) { create(:ci_pipeline, project: project) }
let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } let(:build) { create(:ci_build, pipeline: pipeline) }
let!(:deployment) do
create(:deployment, environment: environment,
deployable: build,
on_stop: 'close_app')
end
context 'when action did not yet finish' do context 'when user is not allowed to stop environment' do
let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } let!(:close_action) do
create(:ci_build, :manual, pipeline: pipeline, name: 'close_app')
end
it 'returns the same action' do it 'raises an exception' do
expect(subject).to eq(close_action) expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
expect(subject.user).to eq(user)
end end
end end
context 'if action did finish' do context 'when user is allowed to stop environment' do
let!(:close_action) { create(:ci_build, :manual, :success, pipeline: build.pipeline, name: 'close_app') } before do
project.add_master(user)
end
context 'when action did not yet finish' do
let!(:close_action) do
create(:ci_build, :manual, pipeline: pipeline, name: 'close_app')
end
it 'returns the same action' do
expect(subject).to eq(close_action)
expect(subject.user).to eq(user)
end
end
it 'returns a new action of the same type' do context 'if action did finish' do
is_expected.to be_persisted let!(:close_action) do
expect(subject.name).to eq(close_action.name) create(:ci_build, :manual, :success,
expect(subject.user).to eq(user) pipeline: pipeline, name: 'close_app')
end
it 'returns a new action of the same type' do
expect(subject).to be_persisted
expect(subject.name).to eq(close_action.name)
expect(subject.user).to eq(user)
end
end end
end end
end end
......
...@@ -89,5 +89,58 @@ describe Ci::BuildPolicy, :models do ...@@ -89,5 +89,58 @@ describe Ci::BuildPolicy, :models do
end end
end end
end end
describe 'rules for manual actions' do
let(:project) { create(:project) }
before do
project.add_developer(user)
end
context 'when branch build is assigned to is protected' do
before do
create(:protected_branch, :no_one_can_push,
name: 'some-ref', project: project)
end
context 'when build is a manual action' do
let(:build) do
create(:ci_build, :manual, ref: 'some-ref', pipeline: pipeline)
end
it 'does not include ability to update build' do
expect(policies).not_to include :update_build
end
end
context 'when build is not a manual action' do
let(:build) do
create(:ci_build, ref: 'some-ref', pipeline: pipeline)
end
it 'includes ability to update build' do
expect(policies).to include :update_build
end
end
end
context 'when branch build is assigned to is not protected' do
context 'when build is a manual action' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
it 'includes ability to update build' do
expect(policies).to include :update_build
end
end
context 'when build is not a manual action' do
let(:build) { create(:ci_build, pipeline: pipeline) }
it 'includes ability to update build' do
expect(policies).to include :update_build
end
end
end
end
end end
end end
require 'spec_helper'
describe EnvironmentPolicy do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:environment) do
create(:environment, :with_review_app, project: project)
end
let(:policies) do
described_class.abilities(user, environment).to_set
end
describe '#rules' do
context 'when user does not have access to the project' do
let(:project) { create(:project, :private) }
it 'does not include ability to stop environment' do
expect(policies).not_to include :stop_environment
end
end
context 'when anonymous user has access to the project' do
let(:project) { create(:project, :public) }
it 'does not include ability to stop environment' do
expect(policies).not_to include :stop_environment
end
end
context 'when team member has access to the project' do
let(:project) { create(:project, :public) }
before do
project.add_master(user)
end
context 'when team member has ability to stop environment' do
it 'does includes ability to stop environment' do
expect(policies).to include :stop_environment
end
end
context 'when team member has no ability to stop environment' do
before do
create(:protected_branch, :no_one_can_push,
name: 'master', project: project)
end
it 'does not include ability to stop environment' do
expect(policies).not_to include :stop_environment
end
end
end
end
end
require 'spec_helper' require 'spec_helper'
describe API::Jobs do describe API::Jobs, :api do
let!(:project) do
create(:project, :repository, public_builds: false)
end
let!(:pipeline) do
create(:ci_empty_pipeline, project: project,
sha: project.commit.id,
ref: project.default_branch)
end
let!(:build) { create(:ci_build, pipeline: pipeline) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:api_user) { user } let(:api_user) { user }
let!(:project) { create(:project, :repository, creator: user, public_builds: false) } let(:reporter) { create(:project_member, :reporter, project: project).user }
let!(:developer) { create(:project_member, :developer, user: user, project: project) } let(:guest) { create(:project_member, :guest, project: project).user }
let(:reporter) { create(:project_member, :reporter, project: project) }
let(:guest) { create(:project_member, :guest, project: project) } before do
let!(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) } project.add_developer(user)
let!(:build) { create(:ci_build, pipeline: pipeline) } end
describe 'GET /projects/:id/jobs' do describe 'GET /projects/:id/jobs' do
let(:query) { Hash.new } let(:query) { Hash.new }
...@@ -211,7 +223,7 @@ describe API::Jobs do ...@@ -211,7 +223,7 @@ describe API::Jobs do
end end
describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
let(:api_user) { reporter.user } let(:api_user) { reporter }
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
before do before do
...@@ -235,7 +247,7 @@ describe API::Jobs do ...@@ -235,7 +247,7 @@ describe API::Jobs do
end end
context 'when logging as guest' do context 'when logging as guest' do
let(:api_user) { guest.user } let(:api_user) { guest }
before do before do
get_for_ref get_for_ref
...@@ -345,7 +357,7 @@ describe API::Jobs do ...@@ -345,7 +357,7 @@ describe API::Jobs do
end end
context 'user without :update_build permission' do context 'user without :update_build permission' do
let(:api_user) { reporter.user } let(:api_user) { reporter }
it 'does not cancel job' do it 'does not cancel job' do
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
...@@ -379,7 +391,7 @@ describe API::Jobs do ...@@ -379,7 +391,7 @@ describe API::Jobs do
end end
context 'user without :update_build permission' do context 'user without :update_build permission' do
let(:api_user) { reporter.user } let(:api_user) { reporter }
it 'does not retry job' do it 'does not retry job' do
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
...@@ -455,16 +467,39 @@ describe API::Jobs do ...@@ -455,16 +467,39 @@ describe API::Jobs do
describe 'POST /projects/:id/jobs/:job_id/play' do describe 'POST /projects/:id/jobs/:job_id/play' do
before do before do
post api("/projects/#{project.id}/jobs/#{build.id}/play", user) post api("/projects/#{project.id}/jobs/#{build.id}/play", api_user)
end end
context 'on an playable job' do context 'on an playable job' do
let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) } let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) }
it 'plays the job' do context 'when user is authorized to trigger a manual action' do
expect(response).to have_http_status(200) it 'plays the job' do
expect(json_response['user']['id']).to eq(user.id) expect(response).to have_http_status(200)
expect(json_response['id']).to eq(build.id) expect(json_response['user']['id']).to eq(user.id)
expect(json_response['id']).to eq(build.id)
expect(build.reload).to be_pending
end
end
context 'when user is not authorized to trigger a manual action' do
context 'when user does not have access to the project' do
let(:api_user) { create(:user) }
it 'does not trigger a manual action' do
expect(build.reload).to be_manual
expect(response).to have_http_status(404)
end
end
context 'when user is not allowed to trigger the manual action' do
let(:api_user) { reporter }
it 'does not trigger a manual action' do
expect(build.reload).to be_manual
expect(response).to have_http_status(403)
end
end
end end
end end
......
...@@ -2,9 +2,10 @@ require 'spec_helper' ...@@ -2,9 +2,10 @@ require 'spec_helper'
describe BuildActionEntity do describe BuildActionEntity do
let(:build) { create(:ci_build, name: 'test_build') } let(:build) { create(:ci_build, name: 'test_build') }
let(:request) { double('request') }
let(:entity) do let(:entity) do
described_class.new(build, request: double) described_class.new(build, request: spy('request'))
end end
describe '#as_json' do describe '#as_json' do
......
...@@ -41,13 +41,37 @@ describe BuildEntity do ...@@ -41,13 +41,37 @@ describe BuildEntity do
it 'does not contain path to play action' do it 'does not contain path to play action' do
expect(subject).not_to include(:play_path) expect(subject).not_to include(:play_path)
end end
it 'is not a playable job' do
expect(subject[:playable]).to be false
end
end end
context 'when build is a manual action' do context 'when build is a manual action' do
let(:build) { create(:ci_build, :manual) } let(:build) { create(:ci_build, :manual) }
it 'contains path to play action' do context 'when user is allowed to trigger action' do
expect(subject).to include(:play_path) before do
build.project.add_master(user)
end
it 'contains path to play action' do
expect(subject).to include(:play_path)
end
it 'is a playable action' do
expect(subject[:playable]).to be true
end
end
context 'when user is not allowed to trigger action' do
it 'does not contain path to play action' do
expect(subject).not_to include(:play_path)
end
it 'is not a playable action' do
expect(subject[:playable]).to be false
end
end end
end end
end end
require 'spec_helper'
describe Ci::PlayBuildService, '#execute', :services do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
let(:service) do
described_class.new(project, user)
end
context 'when project does not have repository yet' do
let(:project) { create(:empty_project) }
it 'allows user with master role to play build' do
project.add_master(user)
service.execute(build)
expect(build.reload).to be_pending
end
it 'does not allow user with developer role to play build' do
project.add_developer(user)
expect { service.execute(build) }
.to raise_error Gitlab::Access::AccessDeniedError
end
end
context 'when project has repository' do
let(:project) { create(:project) }
it 'allows user with developer role to play a build' do
project.add_developer(user)
service.execute(build)
expect(build.reload).to be_pending
end
end
context 'when build is a playable manual action' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
before do
project.add_master(user)
end
it 'enqueues the build' do
expect(service.execute(build)).to eq build
expect(build.reload).to be_pending
end
it 'reassignes build user correctly' do
service.execute(build)
expect(build.reload.user).to eq user
end
end
context 'when build is not a playable manual action' do
let(:build) { create(:ci_build, when: :manual, pipeline: pipeline) }
before do
project.add_master(user)
end
it 'duplicates the build' do
duplicate = service.execute(build)
expect(duplicate).not_to eq build
expect(duplicate).to be_pending
end
it 'assigns users correctly' do
duplicate = service.execute(build)
expect(build.user).not_to eq user
expect(duplicate.user).to eq user
end
end
context 'when build is not action' do
let(:build) { create(:ci_build, :success, pipeline: pipeline) }
it 'raises an error' do
expect { service.execute(build) }
.to raise_error Gitlab::Access::AccessDeniedError
end
end
context 'when user does not have ability to trigger action' do
before do
create(:protected_branch, :no_one_can_push,
name: build.ref, project: project)
end
it 'raises an error' do
expect { service.execute(build) }
.to raise_error Gitlab::Access::AccessDeniedError
end
end
end
...@@ -314,6 +314,13 @@ describe Ci::ProcessPipelineService, '#execute', :services do ...@@ -314,6 +314,13 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end end
context 'when pipeline is promoted sequentially up to the end' do context 'when pipeline is promoted sequentially up to the end' do
before do
# We are using create(:empty_project), and users has to be master in
# order to execute manual action when repository does not exist.
#
project.add_master(user)
end
it 'properly processes entire pipeline' do it 'properly processes entire pipeline' do
process_pipeline process_pipeline
......
...@@ -7,7 +7,9 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -7,7 +7,9 @@ describe Ci::RetryPipelineService, '#execute', :services do
let(:service) { described_class.new(project, user) } let(:service) { described_class.new(project, user) }
context 'when user has ability to modify pipeline' do context 'when user has ability to modify pipeline' do
let(:user) { create(:admin) } before do
project.add_master(user)
end
context 'when there are already retried jobs present' do context 'when there are already retried jobs present' do
before do before do
...@@ -227,6 +229,46 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -227,6 +229,46 @@ describe Ci::RetryPipelineService, '#execute', :services do
end end
end end
context 'when user is not allowed to trigger manual action' do
before do
project.add_developer(user)
end
context 'when there is a failed manual action present' do
before do
create_build('test', :failed, 0)
create_build('deploy', :failed, 0, when: :manual)
create_build('verify', :canceled, 1)
end
it 'does not reprocess manual action' do
service.execute(pipeline)
expect(build('test')).to be_pending
expect(build('deploy')).to be_failed
expect(build('verify')).to be_created
expect(pipeline.reload).to be_running
end
end
context 'when there is a failed manual action in later stage' do
before do
create_build('test', :failed, 0)
create_build('deploy', :failed, 1, when: :manual)
create_build('verify', :canceled, 2)
end
it 'does not reprocess manual action' do
service.execute(pipeline)
expect(build('test')).to be_pending
expect(build('deploy')).to be_failed
expect(build('verify')).to be_created
expect(pipeline.reload).to be_running
end
end
end
def statuses def statuses
pipeline.reload.statuses pipeline.reload.statuses
end end
......
...@@ -55,8 +55,22 @@ describe Ci::StopEnvironmentsService, services: true do ...@@ -55,8 +55,22 @@ describe Ci::StopEnvironmentsService, services: true do
end end
context 'when user does not have permission to stop environment' do context 'when user does not have permission to stop environment' do
context 'when user has no access to manage deployments' do
before do
project.team << [user, :guest]
end
it 'does not stop environment' do
expect_environment_not_stopped_on('master')
end
end
end
context 'when branch for stop action is protected' do
before do before do
project.team << [user, :guest] project.add_developer(user)
create(:protected_branch, :no_one_can_push,
name: 'master', project: project)
end end
it 'does not stop environment' do it 'does not stop environment' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment