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,14 +18,18 @@ module Resolvers
required: true,
description: "Contents of '.gitlab-ci.yml'."
def resolve(project_path:, content:)
argument :dry_run, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Run pipeline creation simulation, or only do static check.'
def resolve(project_path:, content:, dry_run: false)
project = authorized_find!(project_path: project_path)
result = ::Gitlab::Ci::YamlProcessor.new(content, project: project,
user: current_user,
sha: project.repository.commit.sha).execute
result = ::Gitlab::Ci::Lint
.new(project: project, current_user: context[:current_user])
.validate(content, dry_run: dry_run)
response = if result.errors.empty?
if result.errors.empty?
{
status: :valid,
errors: [],
......@@ -37,19 +41,26 @@ module Resolvers
errors: result.errors
}
end
response.merge(merged_yaml: result.merged_yaml)
end
private
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],
group_name: CommitStatus.new(name: job_name).group_name,
needs: job.dig(:needs, :job) || []
group_name: CommitStatus.new(name: job[:name]).group_name,
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
......
# 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
graphql_name 'CiConfigJob'
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,
description: 'Name of the job group'
description: 'Name of the job group.'
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,
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
......
---
title: Updates graphql gitlab-ci.yml linter implementation
merge_request: 50664
author:
type: changed
......@@ -2496,17 +2496,42 @@ type CiConfigGroupEdge {
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
"""
Name of the job
Name of the job.
"""
name: String
"""
Builds that must complete before the jobs run
Builds that must complete before the jobs run.
"""
needs(
"""
......@@ -2531,9 +2556,29 @@ type CiConfigJob {
): 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
"""
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 {
node: CiConfigJob
}
type CiConfigJobRestriction {
"""
The Git refs the job restriction applies to.
"""
refs: [String!]
}
type CiConfigNeed {
"""
Name of the need
......@@ -19639,6 +19691,11 @@ type Query {
"""
content: String!
"""
Run pipeline creation simulation, or only do static check.
"""
dryRun: Boolean
"""
The project of the CI config.
"""
......
......@@ -6711,9 +6711,95 @@
"name": "CiConfigJob",
"description": null,
"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",
"description": "Name of the job group",
"description": "Name of the job group.",
"args": [
],
......@@ -6727,7 +6813,7 @@
},
{
"name": "name",
"description": "Name of the job",
"description": "Name of the job.",
"args": [
],
......@@ -6741,7 +6827,7 @@
},
{
"name": "needs",
"description": "Builds that must complete before the jobs run",
"description": "Builds that must complete before the jobs run.",
"args": [
{
"name": "after",
......@@ -6792,9 +6878,81 @@
"isDeprecated": false,
"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",
"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": [
],
......@@ -6926,6 +7084,41 @@
"enumValues": 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",
"name": "CiConfigNeed",
......@@ -57308,6 +57501,16 @@
}
},
"defaultValue": null
},
{
"name": "dryRun",
"description": "Run pipeline creation simulation, or only do static check.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": null
}
],
"type": {
......@@ -413,10 +413,25 @@ Autogenerated return type of CiCdSettingsUpdate.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `groupName` | String | Name of the job group |
| `name` | String | Name of the job |
| `needs` | CiConfigNeedConnection | Builds that must complete before the jobs run |
| `stage` | String | Name of the job stage |
| `afterScript` | String! => Array | Override a set of commands that are executed after the job. |
| `allowFailure` | Boolean | Allow job to fail. |
| `beforeScript` | String! => Array | Override a set of commands that are executed before the job. |
| `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
......
......@@ -99,7 +99,8 @@ module Gitlab
except: job[:except],
environment: job[:environment],
when: job[:when],
allow_failure: job[:allow_failure]
allow_failure: job[:allow_failure],
needs: job.dig(:needs_attributes)
}
end
end
......
......@@ -7,10 +7,10 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
describe '#resolve' do
before do
yaml_processor_double = instance_double(::Gitlab::Ci::YamlProcessor)
allow(yaml_processor_double).to receive(:execute).and_return(fake_result)
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
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
let_it_be(:user) { create(:user) }
......@@ -24,8 +24,9 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
context 'with a valid .gitlab-ci.yml' do
let(:fake_result) do
::Gitlab::Ci::YamlProcessor::Result.new(
ci_config: ::Gitlab::Ci::Config.new(content),
::Gitlab::Ci::Lint::Result.new(
merged_yaml: content,
jobs: [],
errors: [],
warnings: []
)
......@@ -45,8 +46,9 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
let(:content) { 'invalid' }
let(:fake_result) do
Gitlab::Ci::YamlProcessor::Result.new(
ci_config: nil,
Gitlab::Ci::Lint::Result.new(
jobs: [],
merged_yaml: content,
errors: ['Invalid configuration format'],
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
it 'exposes the expected fields' do
expected_fields = %i[
afterScript
allowFailure
beforeScript
environment
except
script
name
only
group_name
stage
tags
needs
when
]
expect(described_class).to have_graphql_fields(*expected_fields)
......
......@@ -17,7 +17,7 @@ RSpec.describe 'Query.ciConfig' do
let(:query) do
%(
query {
ciConfig(projectPath: "#{project.full_path}", content: "#{content}") {
ciConfig(projectPath: "#{project.full_path}", content: "#{content}", dryRun: false) {
status
errors
stages {
......@@ -32,6 +32,19 @@ RSpec.describe 'Query.ciConfig' do
name
groupName
stage
script
beforeScript
afterScript
allowFailure
only {
refs
}
when
except {
refs
}
environment
tags
needs {
nodes {
name
......@@ -77,8 +90,36 @@ RSpec.describe 'Query.ciConfig' do
{
"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
{
"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
"jobs" =>
{
"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
{
"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
{
"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:
stage: build
script: 'rake spec'
needs: []
tags:
- ruby
- postgres
only:
- branches
- master
rspec 0 2:
stage: build
allow_failure: true
script: 'rake spec'
when: on_failure
needs: []
spinach:
stage: build
script: 'rake spinach'
needs: []
except:
- tags
deploy_job:
stage: deploy
script:
- echo 'done'
environment:
name: production
docker:
stage: test
script: 'curl http://dockerhub/URL'
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