Commit ef0f1584 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'use-dry-run-on-ci-lint' into 'master'

Optionally use new dry_run mode on CI Lint page

See merge request gitlab-org/gitlab!37828
parents 2f94aec4 bac1b6b8
...@@ -8,6 +8,18 @@ class Projects::Ci::LintsController < Projects::ApplicationController ...@@ -8,6 +8,18 @@ class Projects::Ci::LintsController < Projects::ApplicationController
def create def create
@content = params[:content] @content = params[:content]
@dry_run = params[:dry_run]
if @dry_run && Gitlab::Ci::Features.lint_creates_pipeline_with_dry_run?(@project)
pipeline = Ci::CreatePipelineService
.new(@project, current_user, ref: @project.default_branch)
.execute(:push, dry_run: true, content: @content)
@status = pipeline.error_messages.empty?
@stages = pipeline.stages
@errors = pipeline.error_messages.map(&:content)
@warnings = pipeline.warning_messages.map(&:content)
else
result = Gitlab::Ci::YamlProcessor.new_with_validation_errors(@content, yaml_processor_options) result = Gitlab::Ci::YamlProcessor.new_with_validation_errors(@content, yaml_processor_options)
@status = result.valid? @status = result.valid?
...@@ -20,6 +32,7 @@ class Projects::Ci::LintsController < Projects::ApplicationController ...@@ -20,6 +32,7 @@ class Projects::Ci::LintsController < Projects::ApplicationController
@builds = @config_processor.builds @builds = @config_processor.builds
@jobs = @config_processor.jobs @jobs = @config_processor.jobs
end end
end
render :show render :show
end end
......
...@@ -13,6 +13,28 @@ ...@@ -13,6 +13,28 @@
%th= _("Parameter") %th= _("Parameter")
%th= _("Value") %th= _("Value")
%tbody %tbody
- if @dry_run
- @stages.each do |stage|
- stage.statuses.each do |job|
%tr
%td #{stage.name.capitalize} Job - #{job.name}
%td
%pre= job.options[:before_script].to_a.join('\n')
%pre= job.options[:script].to_a.join('\n')
%pre= job.options[:after_script].to_a.join('\n')
%br
%b= _("Tag list:")
= job.tag_list.to_a.join(", ") if job.is_a?(Ci::Build)
%br
%b= _("Environment:")
= job.options.dig(:environment, :name)
%br
%b= _("When:")
= job.when
- if job.allow_failure
%b= _("Allowed to fail")
- else
- @stages.each do |stage| - @stages.each do |stage|
- @builds.select { |build| build[:stage] == stage }.each do |build| - @builds.select { |build| build[:stage] == stage }.each do |build|
- job = @jobs[build[:name].to_sym] - job = @jobs[build[:name].to_sym]
...@@ -22,7 +44,6 @@ ...@@ -22,7 +44,6 @@
%pre= job[:before_script].to_a.join('\n') %pre= job[:before_script].to_a.join('\n')
%pre= job[:script].to_a.join('\n') %pre= job[:script].to_a.join('\n')
%pre= job[:after_script].to_a.join('\n') %pre= job[:after_script].to_a.join('\n')
%br %br
%b= _("Tag list:") %b= _("Tag list:")
= build[:tag_list].to_a.join(", ") = build[:tag_list].to_a.join(", ")
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- content_for :library_javascripts do - content_for :library_javascripts do
= page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('lib/ace.js')
%h2.pt-3.pb-3= _("Check your .gitlab-ci.yml") %h2.pt-3.pb-3= _("Validate your GitLab CI configuration")
.project-ci-linter .project-ci-linter
= form_tag project_ci_lint_path(@project), method: :post do = form_tag project_ci_lint_path(@project), method: :post do
...@@ -17,7 +17,11 @@ ...@@ -17,7 +17,11 @@
.col-sm-12 .col-sm-12
.float-left.gl-mt-3 .float-left.gl-mt-3
= submit_tag(_('Validate'), class: 'btn btn-success submit-yml') = submit_tag(_('Validate'), class: 'btn btn-success submit-yml')
.float-right.gl-mt-3 - if Gitlab::Ci::Features.lint_creates_pipeline_with_dry_run?(@project)
= check_box_tag(:dry_run, 'true', params[:dry_run])
= label_tag(:dry_run, _('Simulate a pipeline created for the default branch'))
= link_to icon('question-circle'), help_page_path('ci/lint', anchor: 'pipeline-simulation'), target: '_blank', rel: 'noopener noreferrer'
.float-right.prepend-top-10
= button_tag(_('Clear'), type: 'button', class: 'btn btn-default clear-yml') = button_tag(_('Clear'), type: 'button', class: 'btn btn-default clear-yml')
.row.prepend-top-20 .row.prepend-top-20
......
---
title: Allow user to simulate pipeline creation via CI Lint and go beyond syntax checks
merge_request: 37828
author:
type: added
# CI Lint
If you want to test the validity of your GitLab CI/CD configuration before committing
the changes, you can use the CI Lint tool. This tool checks for syntax and logical
errors by default, and can simulate pipeline creation to try to find more complicated
issues as well.
To access the CI Lint tool, navigate to **CI/CD > Pipelines** or **CI/CD > Jobs**
in your project and click **CI lint**.
## Validate basic logic and syntax
By default, the CI lint checks the syntax of your CI YAML configuration and also runs
some basic logical validations.
To use the CI lint, paste a complete CI configuration (`.gitlab-ci.yml` for example)
into the text box and click **Validate**:
![CI Lint](img/ci_lint.png)
## Pipeline simulation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229794) in GitLab 13.3.
Not all pipeline configuration issues can be found by the [basic CI lint validation](#validate-basic-logic-and-syntax).
You can simulate the creation of a pipeline for deeper validation that can discover
more complicated issues.
To validate the configuration by running a pipeline simulation:
1. Paste the GitLab CI configuration to verify into the text box.
1. Click the **Simulate pipeline creation for the default branch** checkbox.
1. Click **Validate**.
![Dry run](img/ci_lint_dry_run.png)
### Pipeline simulation limitations
Simulations run as `git push` events against the default branch. You must have
[permissions](../user/permissions.md#project-members-permissions) to create pipelines
on this branch to validate with a simulation.
...@@ -114,9 +114,7 @@ Jobs are used to create jobs, which are then picked by ...@@ -114,9 +114,7 @@ Jobs are used to create jobs, which are then picked by
What is important is that each job is run independently from each other. What is important is that each job is run independently from each other.
If you want to check whether the `.gitlab-ci.yml` of your project is valid, there is a If you want to check whether the `.gitlab-ci.yml` of your project is valid, there is a
Lint tool under the page `/-/ci/lint` of your project namespace. You can also find [CI Lint tool](../lint.md) available in every project.
a "CI Lint" button to go to this page under **CI/CD ➔ Pipelines** and
**Pipelines ➔ Jobs** in your project.
For more information and a complete `.gitlab-ci.yml` syntax, please read For more information and a complete `.gitlab-ci.yml` syntax, please read
[the reference documentation on `.gitlab-ci.yml`](../yaml/README.md). [the reference documentation on `.gitlab-ci.yml`](../yaml/README.md).
......
...@@ -76,6 +76,10 @@ module Gitlab ...@@ -76,6 +76,10 @@ module Gitlab
::Feature.enabled?(:ci_job_entry_matches_all_keys) ::Feature.enabled?(:ci_job_entry_matches_all_keys)
end end
def self.lint_creates_pipeline_with_dry_run?(project)
::Feature.enabled?(:ci_lint_creates_pipeline_with_dry_run, project)
end
def self.reset_ci_minutes_for_all_namespaces? def self.reset_ci_minutes_for_all_namespaces?
::Feature.enabled?(:reset_ci_minutes_for_all_namespaces, default_enabled: false) ::Feature.enabled?(:reset_ci_minutes_for_all_namespaces, default_enabled: false)
end end
......
...@@ -45,6 +45,15 @@ module Gitlab ...@@ -45,6 +45,15 @@ module Gitlab
Gitlab::Metrics.counter(name, comment) Gitlab::Metrics.counter(name, comment)
end end
end end
def pipelines_created_counter
strong_memoize(:pipelines_created_count) do
name = :pipelines_created_total
comment = 'Counter of pipelines created'
Gitlab::Metrics.counter(name, comment)
end
end
end end
end end
end end
......
...@@ -4550,9 +4550,6 @@ msgstr "" ...@@ -4550,9 +4550,6 @@ msgstr ""
msgid "Check the %{docs_link_start}documentation%{docs_link_end}." msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "" msgstr ""
msgid "Check your .gitlab-ci.yml"
msgstr ""
msgid "Check your Docker images for known vulnerabilities." msgid "Check your Docker images for known vulnerabilities."
msgstr "" msgstr ""
...@@ -22459,6 +22456,9 @@ msgstr "" ...@@ -22459,6 +22456,9 @@ msgstr ""
msgid "Similar issues" msgid "Similar issues"
msgstr "" msgstr ""
msgid "Simulate a pipeline created for the default branch"
msgstr ""
msgid "Single or combined queries" msgid "Single or combined queries"
msgstr "" msgstr ""
...@@ -26754,6 +26754,9 @@ msgstr "" ...@@ -26754,6 +26754,9 @@ msgstr ""
msgid "Validate" msgid "Validate"
msgstr "" msgstr ""
msgid "Validate your GitLab CI configuration"
msgstr ""
msgid "Validate your GitLab CI configuration file" msgid "Validate your GitLab CI configuration file"
msgstr "" msgstr ""
......
...@@ -45,6 +45,9 @@ RSpec.describe Projects::Ci::LintsController do ...@@ -45,6 +45,9 @@ RSpec.describe Projects::Ci::LintsController do
end end
describe 'POST #create' do describe 'POST #create' do
subject { post :create, params: params }
let(:params) { { namespace_id: project.namespace, project_id: project, content: content } }
let(:remote_file_path) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' } let(:remote_file_path) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:remote_file_content) do let(:remote_file_content) do
...@@ -72,21 +75,65 @@ RSpec.describe Projects::Ci::LintsController do ...@@ -72,21 +75,65 @@ RSpec.describe Projects::Ci::LintsController do
before do before do
stub_full_request(remote_file_path).to_return(body: remote_file_content) stub_full_request(remote_file_path).to_return(body: remote_file_content)
project.add_developer(user) project.add_developer(user)
post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
end end
it { expect(response).to be_successful } shared_examples 'returns a successful validation' do
it 'returns successfully' do
subject
expect(response).to be_successful
end
it 'render show page' do it 'render show page' do
subject
expect(response).to render_template :show expect(response).to render_template :show
end end
it 'retrieves project' do it 'retrieves project' do
subject
expect(assigns(:project)).to eq(project) expect(assigns(:project)).to eq(project)
end end
end end
context 'using legacy validation (YamlProcessor)' do
it_behaves_like 'returns a successful validation'
it 'runs validations through YamlProcessor' do
expect(Gitlab::Ci::YamlProcessor).to receive(:new_with_validation_errors).and_call_original
subject
end
end
context 'using dry_run mode' do
subject { post :create, params: params.merge(dry_run: 'true') }
it_behaves_like 'returns a successful validation'
it 'runs validations through Ci::CreatePipelineService' do
expect(Ci::CreatePipelineService)
.to receive(:new)
.with(project, user, ref: 'master')
.and_call_original
subject
end
context 'when dry_run feature flag is disabled' do
before do
stub_feature_flags(ci_lint_creates_pipeline_with_dry_run: false)
end
it_behaves_like 'returns a successful validation'
it 'runs validations through YamlProcessor' do
expect(Gitlab::Ci::YamlProcessor).to receive(:new_with_validation_errors).and_call_original
subject
end
end
end
end
context 'with an invalid gitlab-ci.yml' do context 'with an invalid gitlab-ci.yml' do
let(:content) do let(:content) do
<<~HEREDOC <<~HEREDOC
...@@ -98,14 +145,24 @@ RSpec.describe Projects::Ci::LintsController do ...@@ -98,14 +145,24 @@ RSpec.describe Projects::Ci::LintsController do
before do before do
project.add_developer(user) project.add_developer(user)
end
post :create, params: { namespace_id: project.namespace, project_id: project, content: content } it 'assigns errors' do
subject
expect(assigns[:errors]).to eq(['root config contains unknown keys: rubocop'])
end end
context 'with dry_run mode' do
subject { post :create, params: params.merge(dry_run: 'true') }
it 'assigns errors' do it 'assigns errors' do
subject
expect(assigns[:errors]).to eq(['root config contains unknown keys: rubocop']) expect(assigns[:errors]).to eq(['root config contains unknown keys: rubocop'])
end end
end end
end
context 'without enough privileges' do context 'without enough privileges' do
before do before do
......
...@@ -21,6 +21,7 @@ RSpec.describe 'CI Lint', :js do ...@@ -21,6 +21,7 @@ RSpec.describe 'CI Lint', :js do
end end
describe 'YAML parsing' do describe 'YAML parsing' do
shared_examples 'validates the YAML' do
before do before do
click_on 'Validate' click_on 'Validate'
end end
...@@ -30,8 +31,11 @@ RSpec.describe 'CI Lint', :js do ...@@ -30,8 +31,11 @@ RSpec.describe 'CI Lint', :js do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
end end
it 'parses Yaml' do it 'parses Yaml and displays the jobs' do
expect(page).to have_content('Status: syntax is correct')
within "table" do within "table" do
aggregate_failures do
expect(page).to have_content('Job - rspec') expect(page).to have_content('Job - rspec')
expect(page).to have_content('Job - spinach') expect(page).to have_content('Job - spinach')
expect(page).to have_content('Deploy Job - staging') expect(page).to have_content('Deploy Job - staging')
...@@ -39,6 +43,7 @@ RSpec.describe 'CI Lint', :js do ...@@ -39,6 +43,7 @@ RSpec.describe 'CI Lint', :js do
end end
end end
end end
end
context 'YAML is incorrect' do context 'YAML is incorrect' do
let(:yaml_content) { 'value: cannot have :' } let(:yaml_content) { 'value: cannot have :' }
...@@ -48,6 +53,17 @@ RSpec.describe 'CI Lint', :js do ...@@ -48,6 +53,17 @@ RSpec.describe 'CI Lint', :js do
expect(page).to have_selector('.ace_content', text: yaml_content) expect(page).to have_selector('.ace_content', text: yaml_content)
end end
end end
end
it_behaves_like 'validates the YAML'
context 'when Dry Run is checked' do
before do
check 'Simulate a pipeline created for the default branch'
end
it_behaves_like 'validates the YAML'
end
describe 'YAML revalidate' do describe 'YAML revalidate' do
let(:yaml_content) { 'my yaml content' } let(:yaml_content) { 'my yaml content' }
......
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