Merge branch 'feature/sm/34834-missing-dependency-should-fail-job-2' into 'master'
Dependency validator Closes #34834 See merge request gitlab-org/gitlab-ce!14009
This commit is contained in:
commit
af4d4e7067
|
@ -6,6 +6,8 @@ module Ci
|
|||
include Presentable
|
||||
include Importable
|
||||
|
||||
MissingDependenciesError = Class.new(StandardError)
|
||||
|
||||
belongs_to :runner
|
||||
belongs_to :trigger_request
|
||||
belongs_to :erased_by, class_name: 'User'
|
||||
|
@ -139,6 +141,10 @@ module Ci
|
|||
Ci::Build.retry(build, build.user)
|
||||
end
|
||||
end
|
||||
|
||||
before_transition any => [:running] do |build|
|
||||
build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies')
|
||||
end
|
||||
end
|
||||
|
||||
def detailed_status(current_user)
|
||||
|
@ -478,6 +484,20 @@ module Ci
|
|||
options[:dependencies]&.empty?
|
||||
end
|
||||
|
||||
def validates_dependencies!
|
||||
dependencies.each do |dependency|
|
||||
raise MissingDependenciesError unless dependency.valid_dependency?
|
||||
end
|
||||
end
|
||||
|
||||
def valid_dependency?
|
||||
return false unless complete?
|
||||
return false if artifacts_expired?
|
||||
return false if erased?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def hide_secrets(trace)
|
||||
return unless trace
|
||||
|
||||
|
|
|
@ -43,7 +43,8 @@ class CommitStatus < ActiveRecord::Base
|
|||
script_failure: 1,
|
||||
api_failure: 2,
|
||||
stuck_or_timeout_failure: 3,
|
||||
runner_system_failure: 4
|
||||
runner_system_failure: 4,
|
||||
missing_dependency_failure: 5
|
||||
}
|
||||
|
||||
##
|
||||
|
|
|
@ -54,6 +54,9 @@ module Ci
|
|||
# we still have to return 409 in the end,
|
||||
# to make sure that this is properly handled by runner.
|
||||
valid = false
|
||||
rescue Ci::Build::MissingDependenciesError
|
||||
build.drop!(:missing_dependency_failure)
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fail jobs if its dependency is missing
|
||||
merge_request: 14009
|
||||
author:
|
||||
type: fixed
|
|
@ -128,6 +128,45 @@ steps below.
|
|||
|
||||
1. Save the file and [restart GitLab][] for the changes to take effect.
|
||||
|
||||
## Validation for dependencies
|
||||
|
||||
> Introduced in GitLab 10.3.
|
||||
|
||||
To disable [the dependencies validation](../ci/yaml/README.md#when-a-dependent-job-will-fail),
|
||||
you can flip the feature flag from a Rails console.
|
||||
|
||||
---
|
||||
|
||||
**In Omnibus installations:**
|
||||
|
||||
1. Enter the Rails console:
|
||||
|
||||
```sh
|
||||
sudo gitlab-rails console
|
||||
```
|
||||
|
||||
1. Flip the switch and disable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable('ci_disable_validates_dependencies')
|
||||
```
|
||||
---
|
||||
|
||||
**In installations from source:**
|
||||
|
||||
1. Enter the Rails console:
|
||||
|
||||
```sh
|
||||
cd /home/git/gitlab
|
||||
RAILS_ENV=production sudo -u git -H bundle exec rails console
|
||||
```
|
||||
|
||||
1. Flip the switch and disable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable('ci_disable_validates_dependencies')
|
||||
```
|
||||
|
||||
## Set the maximum file size of the artifacts
|
||||
|
||||
Provided the artifacts are enabled, you can change the maximum file size of the
|
||||
|
|
|
@ -1153,6 +1153,20 @@ deploy:
|
|||
script: make deploy
|
||||
```
|
||||
|
||||
#### When a dependent job will fail
|
||||
|
||||
> Introduced in GitLab 10.3.
|
||||
|
||||
If the artifacts of the job that is set as a dependency have been
|
||||
[expired](#artifacts-expire_in) or
|
||||
[erased](../../user/project/pipelines/job_artifacts.md#erasing-artifacts), then
|
||||
the dependent job will fail.
|
||||
|
||||
NOTE: **Note:**
|
||||
You can ask your administrator to
|
||||
[flip this switch](../../administration/job_artifacts.md#validation-for-dependencies)
|
||||
and bring back the old behavior.
|
||||
|
||||
### before_script and after_script
|
||||
|
||||
It's possible to overwrite the globally defined `before_script` and `after_script`:
|
||||
|
|
|
@ -44,7 +44,7 @@ the artifacts will be kept forever.
|
|||
For more examples on artifacts, follow the [artifacts reference in
|
||||
`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifacts).
|
||||
|
||||
## Browsing job artifacts
|
||||
## Browsing artifacts
|
||||
|
||||
>**Note:**
|
||||
With GitLab 9.2, PDFs, images, videos and other formats can be previewed
|
||||
|
@ -77,7 +77,7 @@ one HTML file that you can view directly online when
|
|||
|
||||
---
|
||||
|
||||
## Downloading job artifacts
|
||||
## Downloading artifacts
|
||||
|
||||
If you need to download the whole archive, there are buttons in various places
|
||||
inside GitLab that make that possible.
|
||||
|
@ -102,7 +102,7 @@ inside GitLab that make that possible.
|
|||
|
||||
![Job artifacts browser](img/job_artifacts_browser.png)
|
||||
|
||||
## Downloading the latest job artifacts
|
||||
## Downloading the latest artifacts
|
||||
|
||||
It is possible to download the latest artifacts of a job via a well known URL
|
||||
so you can use it for scripting purposes.
|
||||
|
@ -163,6 +163,18 @@ information in the UI.
|
|||
|
||||
![Latest artifacts button](img/job_latest_artifacts_browser.png)
|
||||
|
||||
## Erasing artifacts
|
||||
|
||||
DANGER: **Warning:**
|
||||
This is a destructive action that leads to data loss. Use with caution.
|
||||
|
||||
If you have at least Developer [permissions](../../permissions.md#gitlab-ci-cd-permissions)
|
||||
on the project, you can erase a single job via the UI which will also remove the
|
||||
artifacts and the job's trace.
|
||||
|
||||
1. Navigate to a job's page.
|
||||
1. Click the trash icon at the top right of the job's trace.
|
||||
1. Confirm the deletion.
|
||||
|
||||
[expiry date]: ../../../ci/yaml/README.md#artifacts-expire_in
|
||||
[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399
|
||||
|
|
|
@ -1868,6 +1868,94 @@ describe Ci::Build do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'state transition: any => [:running]' do
|
||||
shared_examples 'validation is active' do
|
||||
context 'when depended job has not been completed yet' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
|
||||
it { expect { job.run! }.to raise_error(Ci::Build::MissingDependenciesError) }
|
||||
end
|
||||
|
||||
context 'when artifacts of depended job has been expired' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
|
||||
it { expect { job.run! }.to raise_error(Ci::Build::MissingDependenciesError) }
|
||||
end
|
||||
|
||||
context 'when artifacts of depended job has been erased' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
|
||||
|
||||
before do
|
||||
pre_stage_job.erase
|
||||
end
|
||||
|
||||
it { expect { job.run! }.to raise_error(Ci::Build::MissingDependenciesError) }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'validation is not active' do
|
||||
context 'when depended job has not been completed yet' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
|
||||
it { expect { job.run! }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when artifacts of depended job has been expired' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
|
||||
it { expect { job.run! }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when artifacts of depended job has been erased' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
|
||||
|
||||
before do
|
||||
pre_stage_job.erase
|
||||
end
|
||||
|
||||
it { expect { job.run! }.not_to raise_error }
|
||||
end
|
||||
end
|
||||
|
||||
let!(:job) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: options) }
|
||||
|
||||
context 'when validates for dependencies is enabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_disable_validates_dependencies: false)
|
||||
end
|
||||
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
|
||||
context 'when "dependencies" keyword is not defined' do
|
||||
let(:options) { {} }
|
||||
|
||||
it { expect { job.run! }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when "dependencies" keyword is empty' do
|
||||
let(:options) { { dependencies: [] } }
|
||||
|
||||
it { expect { job.run! }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when "dependencies" keyword is specified' do
|
||||
let(:options) { { dependencies: ['test'] } }
|
||||
|
||||
it_behaves_like 'validation is active'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates for dependencies is disabled' do
|
||||
let(:options) { { dependencies: ['test'] } }
|
||||
|
||||
before do
|
||||
stub_feature_flags(ci_disable_validates_dependencies: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'validation is not active'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'state transition when build fails' do
|
||||
let(:service) { MergeRequests::AddTodoWhenBuildFailsService.new(project, user) }
|
||||
|
||||
|
|
|
@ -1244,7 +1244,7 @@ describe Ci::Pipeline, :mailer do
|
|||
|
||||
describe '#execute_hooks' do
|
||||
let!(:build_a) { create_build('a', 0) }
|
||||
let!(:build_b) { create_build('b', 1) }
|
||||
let!(:build_b) { create_build('b', 0) }
|
||||
|
||||
let!(:hook) do
|
||||
create(:project_hook, project: project, pipeline_events: enabled)
|
||||
|
@ -1300,6 +1300,8 @@ describe Ci::Pipeline, :mailer do
|
|||
end
|
||||
|
||||
context 'when stage one failed' do
|
||||
let!(:build_b) { create_build('b', 1) }
|
||||
|
||||
before do
|
||||
build_a.drop
|
||||
end
|
||||
|
|
|
@ -276,6 +276,89 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
context 'when "dependencies" keyword is specified' do
|
||||
shared_examples 'not pick' do
|
||||
it 'does not pick the build and drops the build' do
|
||||
expect(subject).to be_nil
|
||||
expect(pending_job.reload).to be_failed
|
||||
expect(pending_job).to be_missing_dependency_failure
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'validation is active' do
|
||||
context 'when depended job has not been completed yet' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
|
||||
it_behaves_like 'not pick'
|
||||
end
|
||||
|
||||
context 'when artifacts of depended job has been expired' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
|
||||
it_behaves_like 'not pick'
|
||||
end
|
||||
|
||||
context 'when artifacts of depended job has been erased' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
|
||||
|
||||
before do
|
||||
pre_stage_job.erase
|
||||
end
|
||||
|
||||
it_behaves_like 'not pick'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'validation is not active' do
|
||||
context 'when depended job has not been completed yet' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
|
||||
it { expect(subject).to eq(pending_job) }
|
||||
end
|
||||
|
||||
context 'when artifacts of depended job has been expired' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
|
||||
it { expect(subject).to eq(pending_job) }
|
||||
end
|
||||
|
||||
context 'when artifacts of depended job has been erased' do
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
|
||||
|
||||
before do
|
||||
pre_stage_job.erase
|
||||
end
|
||||
|
||||
it { expect(subject).to eq(pending_job) }
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(ci_disable_validates_dependencies: false)
|
||||
end
|
||||
|
||||
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) }
|
||||
let!(:pending_job) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['test'] } ) }
|
||||
|
||||
subject { execute(specific_runner) }
|
||||
|
||||
context 'when validates for dependencies is enabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_disable_validates_dependencies: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'validation is active'
|
||||
end
|
||||
|
||||
context 'when validates for dependencies is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_disable_validates_dependencies: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'validation is not active'
|
||||
end
|
||||
end
|
||||
|
||||
def execute(runner)
|
||||
described_class.new(runner).execute.build
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue