Commit ecad58a0 authored by Laura Montemayor's avatar Laura Montemayor Committed by Bob Van Landuyt

Updates graphql linter implementation

* Replaces CiConfig with ::Gitlab::Ci::Lint, rather than using the
YAML Processor
* Adds job needs to the Lint processor
* Adds new fields to the CiConfigJobType
* Updates specs
* Adds docs and schema
parent 8dadea1c
...@@ -18,38 +18,49 @@ module Resolvers ...@@ -18,38 +18,49 @@ module Resolvers
required: true, required: true,
description: "Contents of '.gitlab-ci.yml'." description: "Contents of '.gitlab-ci.yml'."
def resolve(project_path:, content:) argument :dry_run, GraphQL::BOOLEAN_TYPE,
project = authorized_find!(project_path: project_path) required: false,
description: 'Run pipeline creation simulation, or only do static check.'
result = ::Gitlab::Ci::YamlProcessor.new(content, project: project, def resolve(project_path:, content:, dry_run: false)
user: current_user, project = authorized_find!(project_path: project_path)
sha: project.repository.commit.sha).execute
response = if result.errors.empty? result = ::Gitlab::Ci::Lint
{ .new(project: project, current_user: context[:current_user])
status: :valid, .validate(content, dry_run: dry_run)
errors: [],
stages: make_stages(result.jobs)
}
else
{
status: :invalid,
errors: result.errors
}
end
response.merge(merged_yaml: result.merged_yaml) if result.errors.empty?
{
status: :valid,
errors: [],
stages: make_stages(result.jobs)
}
else
{
status: :invalid,
errors: result.errors
}
end
end end
private private
def make_jobs(config_jobs) def make_jobs(config_jobs)
config_jobs.map do |job_name, job| config_jobs.map do |job|
{ {
name: job_name, name: job[:name],
stage: job[:stage], stage: job[:stage],
group_name: CommitStatus.new(name: job_name).group_name, group_name: CommitStatus.new(name: job[:name]).group_name,
needs: job.dig(:needs, :job) || [] needs: job.dig(:needs) || [],
allow_failure: job[:allow_failure],
before_script: job[:before_script],
script: job[:script],
after_script: job[:after_script],
only: job[:only],
except: job[:except],
when: job[:when],
tags: job[:tag_list],
environment: job[:environment]
} }
end end
end end
......
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
module Config
class JobRestrictionType < BaseObject
graphql_name 'CiConfigJobRestriction'
field :refs, [GraphQL::STRING_TYPE], null: true,
description: 'The Git refs the job restriction applies to.'
end
end
end
end
...@@ -8,13 +8,36 @@ module Types ...@@ -8,13 +8,36 @@ module Types
graphql_name 'CiConfigJob' graphql_name 'CiConfigJob'
field :name, GraphQL::STRING_TYPE, null: true, field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the job' description: 'Name of the job.'
field :group_name, GraphQL::STRING_TYPE, null: true, field :group_name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the job group' description: 'Name of the job group.'
field :stage, GraphQL::STRING_TYPE, null: true, field :stage, GraphQL::STRING_TYPE, null: true,
description: 'Name of the job stage' description: 'Name of the job stage.'
field :needs, Types::Ci::Config::NeedType.connection_type, null: true, field :needs, Types::Ci::Config::NeedType.connection_type, null: true,
description: 'Builds that must complete before the jobs run' description: 'Builds that must complete before the jobs run.'
field :allow_failure, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Allow job to fail.'
field :before_script, [GraphQL::STRING_TYPE], null: true,
description: 'Override a set of commands that are executed before the job.'
field :script, [GraphQL::STRING_TYPE], null: true,
description: 'Shell script that is executed by a runner.'
field :after_script, [GraphQL::STRING_TYPE], null: true,
description: 'Override a set of commands that are executed after the job.'
field :when, GraphQL::STRING_TYPE, null: true,
description: 'When to run the job.',
resolver_method: :restrict_when_to_run_jobs
field :environment, GraphQL::STRING_TYPE, null: true,
description: 'Name of an environment to which the job deploys.'
field :except, Types::Ci::Config::JobRestrictionType, null: true,
description: 'Limit when jobs are not created.'
field :only, Types::Ci::Config::JobRestrictionType, null: true,
description: 'Jobs are created when these conditions do not apply.'
field :tags, [GraphQL::STRING_TYPE], null: true,
description: 'List of tags that are used to select a runner.'
def restrict_when_to_run_jobs
object[:when]
end
end end
end end
end end
......
---
title: Updates graphql gitlab-ci.yml linter implementation
merge_request: 50664
author:
type: changed
...@@ -2496,17 +2496,42 @@ type CiConfigGroupEdge { ...@@ -2496,17 +2496,42 @@ type CiConfigGroupEdge {
type CiConfigJob { type CiConfigJob {
""" """
Name of the job group Override a set of commands that are executed after the job.
"""
afterScript: [String!]
"""
Allow job to fail.
"""
allowFailure: Boolean
"""
Override a set of commands that are executed before the job.
"""
beforeScript: [String!]
"""
Name of an environment to which the job deploys.
"""
environment: String
"""
Limit when jobs are not created.
"""
except: CiConfigJobRestriction
"""
Name of the job group.
""" """
groupName: String groupName: String
""" """
Name of the job Name of the job.
""" """
name: String name: String
""" """
Builds that must complete before the jobs run Builds that must complete before the jobs run.
""" """
needs( needs(
""" """
...@@ -2531,9 +2556,29 @@ type CiConfigJob { ...@@ -2531,9 +2556,29 @@ type CiConfigJob {
): CiConfigNeedConnection ): CiConfigNeedConnection
""" """
Name of the job stage Jobs are created when these conditions do not apply.
"""
only: CiConfigJobRestriction
"""
Shell script that is executed by a runner.
"""
script: [String!]
"""
Name of the job stage.
""" """
stage: String stage: String
"""
List of tags that are used to select a runner.
"""
tags: [String!]
"""
When to run the job.
"""
when: String
} }
""" """
...@@ -2571,6 +2616,13 @@ type CiConfigJobEdge { ...@@ -2571,6 +2616,13 @@ type CiConfigJobEdge {
node: CiConfigJob node: CiConfigJob
} }
type CiConfigJobRestriction {
"""
The Git refs the job restriction applies to.
"""
refs: [String!]
}
type CiConfigNeed { type CiConfigNeed {
""" """
Name of the need Name of the need
...@@ -19639,6 +19691,11 @@ type Query { ...@@ -19639,6 +19691,11 @@ type Query {
""" """
content: String! content: String!
"""
Run pipeline creation simulation, or only do static check.
"""
dryRun: Boolean
""" """
The project of the CI config. The project of the CI config.
""" """
......
...@@ -6711,9 +6711,95 @@ ...@@ -6711,9 +6711,95 @@
"name": "CiConfigJob", "name": "CiConfigJob",
"description": null, "description": null,
"fields": [ "fields": [
{
"name": "afterScript",
"description": "Override a set of commands that are executed after the job.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "allowFailure",
"description": "Allow job to fail.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "beforeScript",
"description": "Override a set of commands that are executed before the job.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "environment",
"description": "Name of an environment to which the job deploys.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "except",
"description": "Limit when jobs are not created.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "CiConfigJobRestriction",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "groupName", "name": "groupName",
"description": "Name of the job group", "description": "Name of the job group.",
"args": [ "args": [
], ],
...@@ -6727,7 +6813,7 @@ ...@@ -6727,7 +6813,7 @@
}, },
{ {
"name": "name", "name": "name",
"description": "Name of the job", "description": "Name of the job.",
"args": [ "args": [
], ],
...@@ -6741,7 +6827,7 @@ ...@@ -6741,7 +6827,7 @@
}, },
{ {
"name": "needs", "name": "needs",
"description": "Builds that must complete before the jobs run", "description": "Builds that must complete before the jobs run.",
"args": [ "args": [
{ {
"name": "after", "name": "after",
...@@ -6792,9 +6878,81 @@ ...@@ -6792,9 +6878,81 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "only",
"description": "Jobs are created when these conditions do not apply.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "CiConfigJobRestriction",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "script",
"description": "Shell script that is executed by a runner.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "stage", "name": "stage",
"description": "Name of the job stage", "description": "Name of the job stage.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "tags",
"description": "List of tags that are used to select a runner.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "when",
"description": "When to run the job.",
"args": [ "args": [
], ],
...@@ -6926,6 +7084,41 @@ ...@@ -6926,6 +7084,41 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "CiConfigJobRestriction",
"description": null,
"fields": [
{
"name": "refs",
"description": "The Git refs the job restriction applies to.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "CiConfigNeed", "name": "CiConfigNeed",
...@@ -57308,6 +57501,16 @@ ...@@ -57308,6 +57501,16 @@
} }
}, },
"defaultValue": null "defaultValue": null
},
{
"name": "dryRun",
"description": "Run pipeline creation simulation, or only do static check.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": null
} }
], ],
"type": { "type": {
...@@ -413,10 +413,25 @@ Autogenerated return type of CiCdSettingsUpdate. ...@@ -413,10 +413,25 @@ Autogenerated return type of CiCdSettingsUpdate.
| Field | Type | Description | | Field | Type | Description |
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `groupName` | String | Name of the job group | | `afterScript` | String! => Array | Override a set of commands that are executed after the job. |
| `name` | String | Name of the job | | `allowFailure` | Boolean | Allow job to fail. |
| `needs` | CiConfigNeedConnection | Builds that must complete before the jobs run | | `beforeScript` | String! => Array | Override a set of commands that are executed before the job. |
| `stage` | String | Name of the job stage | | `environment` | String | Name of an environment to which the job deploys. |
| `except` | CiConfigJobRestriction | Limit when jobs are not created. |
| `groupName` | String | Name of the job group. |
| `name` | String | Name of the job. |
| `needs` | CiConfigNeedConnection | Builds that must complete before the jobs run. |
| `only` | CiConfigJobRestriction | Jobs are created when these conditions do not apply. |
| `script` | String! => Array | Shell script that is executed by a runner. |
| `stage` | String | Name of the job stage. |
| `tags` | String! => Array | List of tags that are used to select a runner. |
| `when` | String | When to run the job. |
### CiConfigJobRestriction
| Field | Type | Description |
| ----- | ---- | ----------- |
| `refs` | String! => Array | The Git refs the job restriction applies to. |
### CiConfigNeed ### CiConfigNeed
......
...@@ -99,7 +99,8 @@ module Gitlab ...@@ -99,7 +99,8 @@ module Gitlab
except: job[:except], except: job[:except],
environment: job[:environment], environment: job[:environment],
when: job[:when], when: job[:when],
allow_failure: job[:allow_failure] allow_failure: job[:allow_failure],
needs: job.dig(:needs_attributes)
} }
end end
end end
......
...@@ -7,10 +7,10 @@ RSpec.describe Resolvers::Ci::ConfigResolver do ...@@ -7,10 +7,10 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
describe '#resolve' do describe '#resolve' do
before do before do
yaml_processor_double = instance_double(::Gitlab::Ci::YamlProcessor) ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(yaml_processor_double).to receive(:execute).and_return(fake_result) allow(ci_lint_double).to receive(:validate).and_return(fake_result)
allow(::Gitlab::Ci::YamlProcessor).to receive(:new).and_return(yaml_processor_double) allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint_double)
end end
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
...@@ -24,8 +24,9 @@ RSpec.describe Resolvers::Ci::ConfigResolver do ...@@ -24,8 +24,9 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
context 'with a valid .gitlab-ci.yml' do context 'with a valid .gitlab-ci.yml' do
let(:fake_result) do let(:fake_result) do
::Gitlab::Ci::YamlProcessor::Result.new( ::Gitlab::Ci::Lint::Result.new(
ci_config: ::Gitlab::Ci::Config.new(content), merged_yaml: content,
jobs: [],
errors: [], errors: [],
warnings: [] warnings: []
) )
...@@ -45,8 +46,9 @@ RSpec.describe Resolvers::Ci::ConfigResolver do ...@@ -45,8 +46,9 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
let(:content) { 'invalid' } let(:content) { 'invalid' }
let(:fake_result) do let(:fake_result) do
Gitlab::Ci::YamlProcessor::Result.new( Gitlab::Ci::Lint::Result.new(
ci_config: nil, jobs: [],
merged_yaml: content,
errors: ['Invalid configuration format'], errors: ['Invalid configuration format'],
warnings: [] warnings: []
) )
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::Config::JobRestrictionType do
specify { expect(described_class.graphql_name).to eq('CiConfigJobRestriction') }
it 'exposes the expected fields' do
expected_fields = %i[refs]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
...@@ -7,10 +7,19 @@ RSpec.describe Types::Ci::Config::JobType do ...@@ -7,10 +7,19 @@ RSpec.describe Types::Ci::Config::JobType do
it 'exposes the expected fields' do it 'exposes the expected fields' do
expected_fields = %i[ expected_fields = %i[
afterScript
allowFailure
beforeScript
environment
except
script
name name
only
group_name group_name
stage stage
tags
needs needs
when
] ]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to have_graphql_fields(*expected_fields)
......
...@@ -17,7 +17,7 @@ RSpec.describe 'Query.ciConfig' do ...@@ -17,7 +17,7 @@ RSpec.describe 'Query.ciConfig' do
let(:query) do let(:query) do
%( %(
query { query {
ciConfig(projectPath: "#{project.full_path}", content: "#{content}") { ciConfig(projectPath: "#{project.full_path}", content: "#{content}", dryRun: false) {
status status
errors errors
stages { stages {
...@@ -32,6 +32,19 @@ RSpec.describe 'Query.ciConfig' do ...@@ -32,6 +32,19 @@ RSpec.describe 'Query.ciConfig' do
name name
groupName groupName
stage stage
script
beforeScript
afterScript
allowFailure
only {
refs
}
when
except {
refs
}
environment
tags
needs { needs {
nodes { nodes {
name name
...@@ -77,8 +90,36 @@ RSpec.describe 'Query.ciConfig' do ...@@ -77,8 +90,36 @@ RSpec.describe 'Query.ciConfig' do
{ {
"nodes" => "nodes" =>
[ [
{ "name" => "rspec 0 1", "groupName" => "rspec", "stage" => "build", "needs" => { "nodes" => [] } }, {
{ "name" => "rspec 0 2", "groupName" => "rspec", "stage" => "build", "needs" => { "nodes" => [] } } "name" => "rspec 0 1",
"groupName" => "rspec",
"stage" => "build",
"script" => ["rake spec"],
"beforeScript" => ["bundle install", "bundle exec rake db:create"],
"afterScript" => ["echo 'run this after'"],
"allowFailure" => false,
"only" => { "refs" => %w[branches master] },
"when" => "on_success",
"except" => nil,
"environment" => nil,
"tags" => %w[ruby postgres],
"needs" => { "nodes" => [] }
},
{
"name" => "rspec 0 2",
"groupName" => "rspec",
"stage" => "build",
"script" => ["rake spec"],
"beforeScript" => ["bundle install", "bundle exec rake db:create"],
"afterScript" => ["echo 'run this after'"],
"allowFailure" => true,
"only" => { "refs" => %w[branches tags] },
"when" => "on_failure",
"except" => nil,
"environment" => nil,
"tags" => [],
"needs" => { "nodes" => [] }
}
] ]
} }
}, },
...@@ -87,7 +128,21 @@ RSpec.describe 'Query.ciConfig' do ...@@ -87,7 +128,21 @@ RSpec.describe 'Query.ciConfig' do
{ {
"nodes" => "nodes" =>
[ [
{ "name" => "spinach", "groupName" => "spinach", "stage" => "build", "needs" => { "nodes" => [] } } {
"name" => "spinach",
"groupName" => "spinach",
"stage" => "build",
"script" => ["rake spinach"],
"beforeScript" => ["bundle install", "bundle exec rake db:create"],
"afterScript" => ["echo 'run this after'"],
"allowFailure" => false,
"only" => { "refs" => %w[branches tags] },
"when" => "on_success",
"except" => { "refs" => ["tags"] },
"environment" => nil,
"tags" => [],
"needs" => { "nodes" => [] }
}
] ]
} }
} }
...@@ -106,7 +161,54 @@ RSpec.describe 'Query.ciConfig' do ...@@ -106,7 +161,54 @@ RSpec.describe 'Query.ciConfig' do
"jobs" => "jobs" =>
{ {
"nodes" => [ "nodes" => [
{ "name" => "docker", "groupName" => "docker", "stage" => "test", "needs" => { "nodes" => [{ "name" => "spinach" }, { "name" => "rspec 0 1" }] } } {
"name" => "docker",
"groupName" => "docker",
"stage" => "test",
"script" => ["curl http://dockerhub/URL"],
"beforeScript" => ["bundle install", "bundle exec rake db:create"],
"afterScript" => ["echo 'run this after'"],
"allowFailure" => true,
"only" => { "refs" => %w[branches tags] },
"when" => "manual",
"except" => { "refs" => ["branches"] },
"environment" => nil,
"tags" => [],
"needs" => { "nodes" => [{ "name" => "spinach" }, { "name" => "rspec 0 1" }] }
}
]
}
}
]
}
},
{
"name" => "deploy",
"groups" =>
{
"nodes" =>
[
{
"name" => "deploy_job",
"size" => 1,
"jobs" =>
{
"nodes" => [
{
"name" => "deploy_job",
"groupName" => "deploy_job",
"stage" => "deploy",
"script" => ["echo 'done'"],
"beforeScript" => ["bundle install", "bundle exec rake db:create"],
"afterScript" => ["echo 'run this after'"],
"allowFailure" => false,
"only" => { "refs" => %w[branches tags] },
"when" => "on_success",
"except" => nil,
"environment" => "production",
"tags" => [],
"needs" => { "nodes" => [] }
}
] ]
} }
} }
...@@ -165,7 +267,21 @@ RSpec.describe 'Query.ciConfig' do ...@@ -165,7 +267,21 @@ RSpec.describe 'Query.ciConfig' do
{ {
"nodes" => "nodes" =>
[ [
{ "name" => "build", "groupName" => "build", "stage" => "test", "needs" => { "nodes" => [] } } {
"name" => "build",
"stage" => "test",
"groupName" => "build",
"script" => ["build"],
"afterScript" => [],
"beforeScript" => [],
"allowFailure" => false,
"environment" => nil,
"except" => nil,
"only" => { "refs" => %w[branches tags] },
"when" => "on_success",
"tags" => [],
"needs" => { "nodes" => [] }
}
] ]
} }
}, },
...@@ -176,7 +292,19 @@ RSpec.describe 'Query.ciConfig' do ...@@ -176,7 +292,19 @@ RSpec.describe 'Query.ciConfig' do
{ {
"nodes" => "nodes" =>
[ [
{ "name" => "rspec", "groupName" => "rspec", "stage" => "test", "needs" => { "nodes" => [] } } { "name" => "rspec",
"stage" => "test",
"groupName" => "rspec",
"script" => ["rspec"],
"afterScript" => [],
"beforeScript" => [],
"allowFailure" => false,
"environment" => nil,
"except" => nil,
"only" => { "refs" => %w[branches tags] },
"when" => "on_success",
"tags" => [],
"needs" => { "nodes" => [] } }
] ]
} }
} }
......
before_script:
- bundle install
- bundle exec rake db:create
rspec 0 1: rspec 0 1:
stage: build stage: build
script: 'rake spec' script: 'rake spec'
needs: [] needs: []
tags:
- ruby
- postgres
only:
- branches
- master
rspec 0 2: rspec 0 2:
stage: build stage: build
allow_failure: true
script: 'rake spec' script: 'rake spec'
when: on_failure
needs: [] needs: []
spinach: spinach:
stage: build stage: build
script: 'rake spinach' script: 'rake spinach'
needs: [] needs: []
except:
- tags
deploy_job:
stage: deploy
script:
- echo 'done'
environment:
name: production
docker: docker:
stage: test stage: test
script: 'curl http://dockerhub/URL' script: 'curl http://dockerhub/URL'
needs: [spinach, rspec 0 1] needs: [spinach, rspec 0 1]
when: manual
except:
- branches
after_script:
- echo 'run this after'
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