Merge branch 'manual-actions' into 'master'
Add support for manual CI actions ## What does this MR do? This implements a `when: manual` which allows a jobs to be marked as manual actions. Manual actions have to be explicitly executed by developers. ## What are the relevant issue numbers? This is to solve: https://gitlab.com/gitlab-org/gitlab-ce/issues/17010 See merge request !5297
This commit is contained in:
commit
61e7453e04
|
@ -43,6 +43,7 @@ v 8.10.0 (unreleased)
|
|||
- Fix viewing notification settings when a project is pending deletion
|
||||
- Updated compare dropdown menus to use GL dropdown
|
||||
- Eager load award emoji on notes
|
||||
- Allow to define manual actions/builds on Pipelines and Environments
|
||||
- Fix pagination when sorting by columns with lots of ties (like priority)
|
||||
- The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
|
||||
- Updated project header design
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class Projects::BuildsController < Projects::ApplicationController
|
||||
before_action :build, except: [:index, :cancel_all]
|
||||
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
|
||||
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry, :play]
|
||||
before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
|
||||
layout 'project'
|
||||
|
||||
|
@ -49,14 +49,19 @@ class Projects::BuildsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def retry
|
||||
unless @build.retryable?
|
||||
return render_404
|
||||
end
|
||||
return render_404 unless @build.retryable?
|
||||
|
||||
build = Ci::Build.retry(@build, current_user)
|
||||
redirect_to build_path(build)
|
||||
end
|
||||
|
||||
def play
|
||||
return render_404 unless @build.playable?
|
||||
|
||||
build = @build.play(current_user)
|
||||
redirect_to build_path(build)
|
||||
end
|
||||
|
||||
def cancel
|
||||
@build.cancel
|
||||
redirect_to build_path(@build)
|
||||
|
|
|
@ -15,6 +15,7 @@ module Ci
|
|||
scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
|
||||
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
|
||||
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
|
||||
scope :manual_actions, ->() { where(when: :manual) }
|
||||
|
||||
mount_uploader :artifacts_file, ArtifactUploader
|
||||
mount_uploader :artifacts_metadata, ArtifactUploader
|
||||
|
@ -91,6 +92,29 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def manual?
|
||||
self.when == 'manual'
|
||||
end
|
||||
|
||||
def other_actions
|
||||
pipeline.manual_actions.where.not(id: self)
|
||||
end
|
||||
|
||||
def playable?
|
||||
project.builds_enabled? && commands.present? && manual?
|
||||
end
|
||||
|
||||
def play(current_user = nil)
|
||||
# Try to queue a current build
|
||||
if self.queue
|
||||
self.update(user: current_user)
|
||||
self
|
||||
else
|
||||
# Otherwise we need to create a duplicate
|
||||
Ci::Build.retry(self, current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def retryable?
|
||||
project.builds_enabled? && commands.present? && complete?
|
||||
end
|
||||
|
|
|
@ -69,6 +69,10 @@ module Ci
|
|||
!tag?
|
||||
end
|
||||
|
||||
def manual_actions
|
||||
builds.latest.manual_actions
|
||||
end
|
||||
|
||||
def retryable?
|
||||
builds.latest.any? do |build|
|
||||
build.failed? && build.retryable?
|
||||
|
|
|
@ -22,6 +22,10 @@ class CommitStatus < ActiveRecord::Base
|
|||
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
|
||||
|
||||
state_machine :status, initial: :pending do
|
||||
event :queue do
|
||||
transition skipped: :pending
|
||||
end
|
||||
|
||||
event :run do
|
||||
transition pending: :running
|
||||
end
|
||||
|
|
|
@ -16,10 +16,10 @@ module Statuseable
|
|||
|
||||
deduce_status = "(CASE
|
||||
WHEN (#{builds})=0 THEN NULL
|
||||
WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
|
||||
WHEN (#{builds})=(#{pending}) THEN 'pending'
|
||||
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored}) THEN 'canceled'
|
||||
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
|
||||
WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
|
||||
WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending'
|
||||
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
|
||||
WHEN (#{running})+(#{pending})>0 THEN 'running'
|
||||
ELSE 'failed'
|
||||
END)"
|
||||
|
|
|
@ -32,4 +32,8 @@ class Deployment < ActiveRecord::Base
|
|||
def keep_around_commit
|
||||
project.repository.keep_around(self.sha)
|
||||
end
|
||||
|
||||
def manual_actions
|
||||
deployable.try(:other_actions)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ module Ci
|
|||
status == 'success'
|
||||
when 'on_failure'
|
||||
status == 'failed'
|
||||
when 'always'
|
||||
when 'always', 'manual'
|
||||
%w(success failed).include?(status)
|
||||
end
|
||||
end
|
||||
|
@ -47,6 +47,10 @@ module Ci
|
|||
user: user,
|
||||
project: @pipeline.project)
|
||||
|
||||
# TODO: The proper implementation for this is in
|
||||
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5295
|
||||
build_attrs[:status] = 'skipped' if build_attrs[:when] == 'manual'
|
||||
|
||||
##
|
||||
# We do not persist new builds here.
|
||||
# Those will be persisted when @pipeline is saved.
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
%span.label.label-danger allowed to fail
|
||||
- if defined?(retried) && retried
|
||||
%span.label.label-warning retried
|
||||
- if build.manual?
|
||||
%span.label.label-info manual
|
||||
|
||||
|
||||
- if defined?(runner) && runner
|
||||
|
@ -79,6 +81,11 @@
|
|||
- if build.active?
|
||||
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
|
||||
= icon('remove', class: 'cred')
|
||||
- elsif defined?(allow_retry) && allow_retry && build.retryable?
|
||||
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
|
||||
= icon('repeat')
|
||||
- elsif defined?(allow_retry) && allow_retry
|
||||
- if build.retryable?
|
||||
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
|
||||
= icon('repeat')
|
||||
- elsif build.playable?
|
||||
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
|
||||
= icon('play')
|
||||
|
||||
|
|
|
@ -58,18 +58,31 @@
|
|||
%td.pipeline-actions
|
||||
.controls.hidden-xs.pull-right
|
||||
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
|
||||
- if artifacts.present?
|
||||
.inline
|
||||
.btn-group
|
||||
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
= icon("download")
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
- artifacts.each do |build|
|
||||
%li
|
||||
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
|
||||
= icon("download")
|
||||
%span Download '#{build.name}' artifacts
|
||||
- actions = pipeline.manual_actions
|
||||
- if artifacts.present? || actions.any?
|
||||
.btn-group.inline
|
||||
- if actions.any?
|
||||
.btn-group
|
||||
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
= icon("play")
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
- actions.each do |build|
|
||||
%li
|
||||
= link_to play_namespace_project_build_path(@project.namespace, @project, build), method: :post, rel: 'nofollow' do
|
||||
= icon("play")
|
||||
%span= build.name.humanize
|
||||
- if artifacts.present?
|
||||
.btn-group
|
||||
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
= icon("download")
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
- artifacts.each do |build|
|
||||
%li
|
||||
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
|
||||
= icon("download")
|
||||
%span Download '#{build.name}' artifacts
|
||||
|
||||
- if can?(current_user, :update_pipeline, @project)
|
||||
.cancel-retry-btns.inline
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
|
||||
.pull-right
|
||||
- actions = deployment.manual_actions
|
||||
- if actions.present?
|
||||
.btn-group.inline
|
||||
.btn-group
|
||||
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
= icon("play")
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
- actions.each do |action|
|
||||
%li
|
||||
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
|
||||
= icon("play")
|
||||
%span= action.name.humanize
|
||||
|
||||
- if local_assigns.fetch(:allow_rollback, false)
|
||||
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
|
||||
- if deployment.last?
|
||||
Retry
|
||||
- else
|
||||
Rollback
|
|
@ -7,17 +7,11 @@
|
|||
|
||||
%td
|
||||
- if deployment.deployable
|
||||
= link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable) do
|
||||
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
|
||||
= "#{deployment.deployable.name} (##{deployment.deployable.id})"
|
||||
|
||||
%td
|
||||
#{time_ago_with_tooltip(deployment.created_at)}
|
||||
|
||||
%td
|
||||
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
|
||||
.pull-right
|
||||
= link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do
|
||||
- if deployment.last?
|
||||
Retry
|
||||
- else
|
||||
Rollback
|
||||
= render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
|
||||
|
|
|
@ -15,3 +15,6 @@
|
|||
%td
|
||||
- if last_deployment
|
||||
#{time_ago_with_tooltip(last_deployment.created_at)}
|
||||
|
||||
%td
|
||||
= render 'projects/deployments/actions', deployment: last_deployment
|
||||
|
|
|
@ -28,4 +28,5 @@
|
|||
%th Environment
|
||||
%th Last deployment
|
||||
%th Date
|
||||
%th
|
||||
= render @environments
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
%div{ class: container_class }
|
||||
.top-area
|
||||
.col-md-9
|
||||
%h3.page-title= @environment.name.titleize
|
||||
%h3.page-title= @environment.name.capitalize
|
||||
|
||||
.col-md-3
|
||||
.nav-controls
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# Description: https://coderwall.com/p/heed_q/rails-routing-and-namespaced-models
|
||||
#
|
||||
# This allows us to use CI ActiveRecord objects in all routes and use it:
|
||||
# - [project.namespace, project, build]
|
||||
#
|
||||
# instead of:
|
||||
# - namespace_project_build_path(project.namespace, project, build)
|
||||
#
|
||||
# Without that, Ci:: namespace is used for resolving routes:
|
||||
# - namespace_project_ci_build_path(project.namespace, project, build)
|
||||
|
||||
module Ci
|
||||
def self.use_relative_model_naming?
|
||||
true
|
||||
end
|
||||
end
|
|
@ -750,6 +750,7 @@ Rails.application.routes.draw do
|
|||
get :status
|
||||
post :cancel
|
||||
post :retry
|
||||
post :play
|
||||
post :erase
|
||||
get :trace
|
||||
get :raw
|
||||
|
|
|
@ -1,13 +1,34 @@
|
|||
class Gitlab::Seeder::Builds
|
||||
STAGES = %w[build notify_build test notify_test deploy notify_deploy]
|
||||
|
||||
def initialize(project)
|
||||
@project = project
|
||||
end
|
||||
|
||||
def seed!
|
||||
ci_commits.each do |ci_commit|
|
||||
pipelines.each do |pipeline|
|
||||
begin
|
||||
build_create!(ci_commit, name: 'test build 1')
|
||||
build_create!(ci_commit, status: 'success', name: 'test build 2')
|
||||
build_create!(pipeline, name: 'build:linux', stage: 'build')
|
||||
build_create!(pipeline, name: 'build:osx', stage: 'build')
|
||||
|
||||
build_create!(pipeline, name: 'slack post build', stage: 'notify_build')
|
||||
|
||||
build_create!(pipeline, name: 'rspec:linux', stage: 'test')
|
||||
build_create!(pipeline, name: 'rspec:windows', stage: 'test')
|
||||
build_create!(pipeline, name: 'rspec:windows', stage: 'test')
|
||||
build_create!(pipeline, name: 'rspec:osx', stage: 'test')
|
||||
build_create!(pipeline, name: 'spinach:linux', stage: 'test')
|
||||
build_create!(pipeline, name: 'spinach:osx', stage: 'test')
|
||||
build_create!(pipeline, name: 'cucumber:linux', stage: 'test')
|
||||
build_create!(pipeline, name: 'cucumber:osx', stage: 'test')
|
||||
|
||||
build_create!(pipeline, name: 'slack post test', stage: 'notify_test')
|
||||
|
||||
build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging')
|
||||
build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual')
|
||||
|
||||
commit_status_create!(pipeline, name: 'jenkins')
|
||||
|
||||
print '.'
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
print 'F'
|
||||
|
@ -15,8 +36,8 @@ class Gitlab::Seeder::Builds
|
|||
end
|
||||
end
|
||||
|
||||
def ci_commits
|
||||
commits = @project.repository.commits('master', nil, 5)
|
||||
def pipelines
|
||||
commits = @project.repository.commits('master', limit: 5)
|
||||
commits_sha = commits.map { |commit| commit.raw.id }
|
||||
commits_sha.map do |sha|
|
||||
@project.ensure_pipeline(sha, 'master')
|
||||
|
@ -25,11 +46,11 @@ class Gitlab::Seeder::Builds
|
|||
[]
|
||||
end
|
||||
|
||||
def build_create!(ci_commit, opts = {})
|
||||
attributes = build_attributes_for(ci_commit).merge(opts)
|
||||
def build_create!(pipeline, opts = {})
|
||||
attributes = build_attributes_for(pipeline, opts)
|
||||
build = Ci::Build.new(attributes)
|
||||
|
||||
if %w(success failed).include?(build.status)
|
||||
if opts[:name].start_with?('build')
|
||||
artifacts_cache_file(artifacts_archive_path) do |file|
|
||||
build.artifacts_file = file
|
||||
end
|
||||
|
@ -40,19 +61,28 @@ class Gitlab::Seeder::Builds
|
|||
end
|
||||
|
||||
build.save!
|
||||
build.update(status: build_status)
|
||||
|
||||
if %w(running success failed).include?(build.status)
|
||||
# We need to set build trace after saving a build (id required)
|
||||
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
|
||||
end
|
||||
end
|
||||
|
||||
def commit_status_create!(pipeline, opts = {})
|
||||
attributes = commit_status_attributes_for(pipeline, opts)
|
||||
GenericCommitStatus.create(attributes)
|
||||
end
|
||||
|
||||
def commit_status_attributes_for(pipeline, opts)
|
||||
{ name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
|
||||
ref: 'master', user: build_user, project: @project, pipeline: pipeline,
|
||||
created_at: Time.now, updated_at: Time.now
|
||||
}.merge(opts)
|
||||
end
|
||||
|
||||
def build_attributes_for(ci_commit)
|
||||
{ name: 'test build', commands: "$ build command",
|
||||
stage: 'test', stage_idx: 1, ref: 'master',
|
||||
user_id: build_user, gl_project_id: @project.id,
|
||||
status: build_status, commit_id: ci_commit.id,
|
||||
created_at: Time.now, updated_at: Time.now }
|
||||
def build_attributes_for(pipeline, opts)
|
||||
commit_status_attributes_for(pipeline, opts).merge(commands: '$ build command')
|
||||
end
|
||||
|
||||
def build_user
|
||||
|
@ -63,13 +93,16 @@ class Gitlab::Seeder::Builds
|
|||
Ci::Build::AVAILABLE_STATUSES.sample
|
||||
end
|
||||
|
||||
def stage_index(stage)
|
||||
STAGES.index(stage) || 0
|
||||
end
|
||||
|
||||
def artifacts_archive_path
|
||||
Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
|
||||
end
|
||||
|
||||
def artifacts_metadata_path
|
||||
Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
|
||||
|
||||
end
|
||||
|
||||
def artifacts_cache_file(file_path)
|
||||
|
|
|
@ -485,6 +485,7 @@ failure.
|
|||
1. `on_failure` - execute build only when at least one build from prior stages
|
||||
fails.
|
||||
1. `always` - execute build regardless of the status of builds from prior stages.
|
||||
1. `manual` - execute build manually.
|
||||
|
||||
For example:
|
||||
|
||||
|
@ -516,6 +517,7 @@ deploy_job:
|
|||
stage: deploy
|
||||
script:
|
||||
- make deploy
|
||||
when: manual
|
||||
|
||||
cleanup_job:
|
||||
stage: cleanup
|
||||
|
@ -527,7 +529,20 @@ cleanup_job:
|
|||
The above script will:
|
||||
|
||||
1. Execute `cleanup_build_job` only when `build_job` fails
|
||||
2. Always execute `cleanup_job` as the last step in pipeline.
|
||||
2. Always execute `cleanup_job` as the last step in pipeline
|
||||
3. Allow you to manually execute `deploy_job` from GitLab
|
||||
|
||||
#### Manual actions
|
||||
|
||||
>**Note:**
|
||||
Introduced in GitLab 8.10.
|
||||
|
||||
Manual actions are special type of jobs that are not executed automatically in pipeline.
|
||||
They need to be explicitly started by the user.
|
||||
Manual actions can be started from pipelines, builds, environments and deployments views.
|
||||
You can execute the same manual action multiple times.
|
||||
|
||||
Example usage of manual actions is deployment, ex. promote a staging environment to production.
|
||||
|
||||
### environment
|
||||
|
||||
|
|
|
@ -194,8 +194,8 @@ module Ci
|
|||
raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
|
||||
end
|
||||
|
||||
if job[:when] && !job[:when].in?(%w[on_success on_failure always])
|
||||
raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
|
||||
if job[:when] && !job[:when].in?(%w[on_success on_failure always manual])
|
||||
raise ValidationError, "#{name} job: when parameter should be on_success, on_failure, always or manual"
|
||||
end
|
||||
|
||||
if job[:environment] && !validate_environment(job[:environment])
|
||||
|
|
|
@ -43,6 +43,11 @@ FactoryGirl.define do
|
|||
status 'pending'
|
||||
end
|
||||
|
||||
trait :manual do
|
||||
status 'skipped'
|
||||
self.when 'manual'
|
||||
end
|
||||
|
||||
trait :allowed_to_fail do
|
||||
allow_failure true
|
||||
end
|
||||
|
|
|
@ -13,6 +13,7 @@ feature 'Environments', feature: true do
|
|||
describe 'when showing environments' do
|
||||
given!(:environment) { }
|
||||
given!(:deployment) { }
|
||||
given!(:manual) { }
|
||||
|
||||
before do
|
||||
visit namespace_project_environments_path(project.namespace, project)
|
||||
|
@ -43,6 +44,24 @@ feature 'Environments', feature: true do
|
|||
scenario 'does show deployment SHA' do
|
||||
expect(page).to have_link(deployment.short_sha)
|
||||
end
|
||||
|
||||
context 'with build and manual actions' do
|
||||
given(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
given(:build) { create(:ci_build, pipeline: pipeline) }
|
||||
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
|
||||
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
|
||||
|
||||
scenario 'does show a play button' do
|
||||
expect(page).to have_link(manual.name.humanize)
|
||||
end
|
||||
|
||||
scenario 'does allow to play manual action' do
|
||||
expect(manual).to be_skipped
|
||||
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
|
||||
expect(page).to have_content(manual.name)
|
||||
expect(manual.reload).to be_pending
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -54,6 +73,7 @@ feature 'Environments', feature: true do
|
|||
describe 'when showing the environment' do
|
||||
given(:environment) { create(:environment, project: project) }
|
||||
given!(:deployment) { }
|
||||
given!(:manual) { }
|
||||
|
||||
before do
|
||||
visit namespace_project_environment_path(project.namespace, project, environment)
|
||||
|
@ -77,7 +97,8 @@ feature 'Environments', feature: true do
|
|||
end
|
||||
|
||||
context 'with build' do
|
||||
given(:build) { create(:ci_build, project: project) }
|
||||
given(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
given(:build) { create(:ci_build, pipeline: pipeline) }
|
||||
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
|
||||
|
||||
scenario 'does show build name' do
|
||||
|
@ -87,6 +108,21 @@ feature 'Environments', feature: true do
|
|||
scenario 'does show retry button' do
|
||||
expect(page).to have_link('Retry')
|
||||
end
|
||||
|
||||
context 'with manual action' do
|
||||
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
|
||||
|
||||
scenario 'does show a play button' do
|
||||
expect(page).to have_link(manual.name.humanize)
|
||||
end
|
||||
|
||||
scenario 'does allow to play manual action' do
|
||||
expect(manual).to be_skipped
|
||||
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
|
||||
expect(page).to have_content(manual.name)
|
||||
expect(manual.reload).to be_pending
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -62,6 +62,20 @@ describe "Pipelines" do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with manual actions' do
|
||||
let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'manual build', stage: 'test', commands: 'test') }
|
||||
|
||||
before { visit namespace_project_pipelines_path(project.namespace, project) }
|
||||
|
||||
it { expect(page).to have_link('Manual build') }
|
||||
|
||||
context 'when playing' do
|
||||
before { click_link('Manual build') }
|
||||
|
||||
it { expect(manual.reload).to be_pending }
|
||||
end
|
||||
end
|
||||
|
||||
context 'for generic statuses' do
|
||||
context 'when running' do
|
||||
let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
|
||||
|
@ -117,6 +131,7 @@ describe "Pipelines" do
|
|||
@success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
|
||||
@failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
|
||||
@running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
|
||||
@manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
|
||||
@external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
|
||||
end
|
||||
|
||||
|
@ -131,6 +146,7 @@ describe "Pipelines" do
|
|||
expect(page).to have_content(@external.id)
|
||||
expect(page).to have_content('Retry failed')
|
||||
expect(page).to have_content('Cancel running')
|
||||
expect(page).to have_link('Play')
|
||||
end
|
||||
|
||||
context 'retrying builds' do
|
||||
|
@ -154,6 +170,12 @@ describe "Pipelines" do
|
|||
it { expect(page).to have_selector('.ci-canceled') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'playing manual build' do
|
||||
before { click_link('Play') }
|
||||
|
||||
it { expect(@manual.reload).to be_pending }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /:project/pipelines' do
|
||||
|
|
|
@ -1141,7 +1141,7 @@ EOT
|
|||
config = YAML.dump({ rspec: { script: "test", when: 1 } })
|
||||
expect do
|
||||
GitlabCiYamlProcessor.new(config, path)
|
||||
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
|
||||
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure, always or manual")
|
||||
end
|
||||
|
||||
it "returns errors if job artifacts:name is not an a string" do
|
||||
|
|
|
@ -670,4 +670,55 @@ describe Ci::Build, models: true do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#manual?' do
|
||||
before do
|
||||
build.update(when: value)
|
||||
end
|
||||
|
||||
subject { build.manual? }
|
||||
|
||||
context 'when is set to manual' do
|
||||
let(:value) { 'manual' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when set to something else' do
|
||||
let(:value) { 'something else' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#other_actions' do
|
||||
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
|
||||
let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
|
||||
|
||||
subject { build.other_actions }
|
||||
|
||||
it 'returns other actions' do
|
||||
is_expected.to contain_exactly(other_build)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#play' do
|
||||
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
|
||||
|
||||
subject { build.play }
|
||||
|
||||
it 'enques a build' do
|
||||
is_expected.to be_pending
|
||||
is_expected.to eq(build)
|
||||
end
|
||||
|
||||
context 'for success build' do
|
||||
before { build.queue }
|
||||
|
||||
it 'creates a new build' do
|
||||
is_expected.to be_pending
|
||||
is_expected.not_to eq(build)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -260,6 +260,68 @@ describe Ci::Pipeline, models: true do
|
|||
expect(pipeline.reload.status).to eq('canceled')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when listing manual actions' do
|
||||
let(:yaml) do
|
||||
{
|
||||
stages: ["build", "test", "test_failure", "deploy", "cleanup"],
|
||||
build: {
|
||||
stage: "build",
|
||||
script: "BUILD",
|
||||
},
|
||||
test: {
|
||||
stage: "test",
|
||||
script: "TEST",
|
||||
},
|
||||
test_failure: {
|
||||
stage: "test_failure",
|
||||
script: "ON test failure",
|
||||
when: "on_failure",
|
||||
},
|
||||
deploy: {
|
||||
stage: "deploy",
|
||||
script: "PUBLISH",
|
||||
},
|
||||
production: {
|
||||
stage: "deploy",
|
||||
script: "PUBLISH",
|
||||
when: "manual",
|
||||
},
|
||||
cleanup: {
|
||||
stage: "cleanup",
|
||||
script: "TIDY UP",
|
||||
when: "always",
|
||||
},
|
||||
clear_cache: {
|
||||
stage: "cleanup",
|
||||
script: "CLEAR CACHE",
|
||||
when: "manual",
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns only for skipped builds' do
|
||||
# currently all builds are created
|
||||
expect(create_builds).to be_truthy
|
||||
expect(manual_actions).to be_empty
|
||||
|
||||
# succeed stage build
|
||||
pipeline.builds.running_or_pending.each(&:success)
|
||||
expect(manual_actions).to be_empty
|
||||
|
||||
# succeed stage test
|
||||
pipeline.builds.running_or_pending.each(&:success)
|
||||
expect(manual_actions).to be_one # production
|
||||
|
||||
# succeed stage deploy
|
||||
pipeline.builds.running_or_pending.each(&:success)
|
||||
expect(manual_actions).to be_many # production and clear cache
|
||||
end
|
||||
|
||||
def manual_actions
|
||||
pipeline.manual_actions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no builds created' do
|
||||
|
@ -416,4 +478,28 @@ describe Ci::Pipeline, models: true do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#manual_actions' do
|
||||
subject { pipeline.manual_actions }
|
||||
|
||||
it 'when none defined' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
|
||||
context 'when action defined' do
|
||||
let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
|
||||
|
||||
it 'returns one action' do
|
||||
is_expected.to contain_exactly(manual)
|
||||
end
|
||||
|
||||
context 'there are multiple of the same name' do
|
||||
let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
|
||||
|
||||
it 'returns latest one' do
|
||||
is_expected.to contain_exactly(manual2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,6 +11,7 @@ describe Deployment, models: true do
|
|||
it { is_expected.to delegate_method(:name).to(:environment).with_prefix }
|
||||
it { is_expected.to delegate_method(:commit).to(:project) }
|
||||
it { is_expected.to delegate_method(:commit_title).to(:commit).as(:try) }
|
||||
it { is_expected.to delegate_method(:manual_actions).to(:deployable).as(:try) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:ref) }
|
||||
it { is_expected.to validate_presence_of(:sha) }
|
||||
|
|
Loading…
Reference in New Issue