Commit 3aaac9db authored by allison.browne's avatar allison.browne

Add json api response to ci lints controller

Add new lint result serializer and allow this controller
to respond to requests for json formatted data
parent c0db5a84
......@@ -14,6 +14,11 @@ class Projects::Ci::LintsController < Projects::ApplicationController
.new(project: @project, current_user: current_user)
.validate(@content, dry_run: @dry_run)
render :show
respond_to do |format|
format.html { render :show }
format.json do
render json: ::Ci::Lint::ResultSerializer.new.represent(@result)
end
end
end
end
# frozen_string_literal: true
class Ci::Lint::JobEntity < Grape::Entity
expose :name
expose :stage
expose :before_script
expose :script
expose :after_script
expose :tag_list
expose :environment
expose :when
expose :allow_failure
expose :only
expose :except
end
# frozen_string_literal: true
class Ci::Lint::ResultEntity < Grape::Entity
expose :valid?, as: :valid
expose :errors
expose :warnings
expose :jobs, using: Ci::Lint::JobEntity do |result, options|
next [] unless result.valid?
result.jobs
end
end
# frozen_string_literal: true
class Ci::Lint::ResultSerializer < BaseSerializer
entity ::Ci::Lint::ResultEntity
end
---
title: Add json api endpoint that provides CI linting
merge_request: 37344
author:
type: added
......@@ -65,10 +65,10 @@ module Gitlab
{
name: job.name,
stage: stage.name,
before_script: job.options[:before_script],
script: job.options[:script],
after_script: job.options[:after_script],
tag_list: (job.tag_list if job.is_a?(::Ci::Build)),
before_script: job.options[:before_script].to_a,
script: job.options[:script].to_a,
after_script: job.options[:after_script].to_a,
tag_list: (job.tag_list if job.is_a?(::Ci::Build)).to_a,
environment: job.options.dig(:environment, :name),
when: job.when,
allow_failure: job.allow_failure
......@@ -88,9 +88,9 @@ module Gitlab
jobs << {
name: job[:name],
stage: stage_name,
before_script: job.dig(:options, :before_script),
script: job.dig(:options, :script),
after_script: job.dig(:options, :after_script),
before_script: job.dig(:options, :before_script).to_a,
script: job.dig(:options, :script).to_a,
after_script: job.dig(:options, :after_script).to_a,
tag_list: job[:tag_list].to_a,
only: job[:only],
except: job[:except],
......
......@@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe Projects::Ci::LintsController do
include StubRequests
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
before do
sign_in(user)
......@@ -20,7 +20,7 @@ RSpec.describe Projects::Ci::LintsController do
get :show, params: { namespace_id: project.namespace, project_id: project }
end
it { expect(response).to be_successful }
it { expect(response).to have_gitlab_http_status(:ok) }
it 'renders show page' do
expect(response).to render_template :show
......@@ -47,7 +47,8 @@ RSpec.describe Projects::Ci::LintsController do
describe 'POST #create' do
subject { post :create, params: params }
let(:params) { { namespace_id: project.namespace, project_id: project, content: content } }
let(:format) { :html }
let(:params) { { namespace_id: project.namespace, project_id: project, content: content, format: format } }
let(:remote_file_path) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:remote_file_content) do
......@@ -71,6 +72,20 @@ RSpec.describe Projects::Ci::LintsController do
HEREDOC
end
shared_examples 'successful request with format json' do
context 'with format json' do
let(:format) { :json }
let(:parsed_body) { Gitlab::Json.parse(response.body) }
it 'renders json' do
expect(response).to have_gitlab_http_status :ok
expect(response.content_type).to eq 'application/json'
expect(parsed_body).to include('errors', 'warnings', 'jobs', 'valid')
expect(parsed_body).to match_schema('entities/lint_result_entity')
end
end
end
context 'with a valid gitlab-ci.yml' do
before do
stub_full_request(remote_file_path).to_return(body: remote_file_content)
......@@ -78,20 +93,23 @@ RSpec.describe Projects::Ci::LintsController do
end
shared_examples 'returns a successful validation' do
it 'returns successfully' do
before do
subject
expect(response).to be_successful
end
it 'render show page' do
subject
it 'returns successfully' do
expect(response).to have_gitlab_http_status :ok
end
it 'renders show page' do
expect(response).to render_template :show
end
it 'retrieves project' do
subject
expect(assigns(:project)).to eq(project)
end
it_behaves_like 'successful request with format json'
end
context 'using legacy validation (YamlProcessor)' do
......@@ -145,25 +163,30 @@ RSpec.describe Projects::Ci::LintsController do
before do
project.add_developer(user)
subject
end
it 'assigns result with errors' do
subject
expect(assigns[:result].errors).to match_array([
'jobs rubocop config should implement a script: or a trigger: keyword',
'jobs config should contain at least one visible job'
])
end
it 'render show page' do
expect(response).to render_template :show
end
it_behaves_like 'successful request with format json'
context 'with dry_run mode' do
subject { post :create, params: params.merge(dry_run: 'true') }
it 'assigns result with errors' do
subject
expect(assigns[:result].errors).to eq(['jobs rubocop config should implement a script: or a trigger: keyword'])
end
it_behaves_like 'successful request with format json'
end
end
......@@ -177,6 +200,14 @@ RSpec.describe Projects::Ci::LintsController do
it 'responds with 404' do
expect(response).to have_gitlab_http_status(:not_found)
end
context 'with format json' do
let(:format) { :json }
it 'responds with 404' do
expect(response).to have_gitlab_http_status :not_found
end
end
end
end
end
{
"type": "object",
"required": [
"name",
"stage",
"before_script",
"script",
"after_script",
"tag_list",
"environment",
"when",
"allow_failure",
"only",
"except"
],
"properties": {
"name": {
"type": ["string"]
},
"stage": {
"type": ["string"]
},
"before_script": {
"type": ["array"],
"items": { "type": "string" }
},
"script": {
"type": ["array"],
"items": { "type": "string" }
},
"after_script": {
"type": ["array"],
"items": { "type": "string" }
},
"when": {
"items": { "type": ["string"] }
},
"allow_failure": {
"type": ["boolean"]
},
"environment": {
"type": ["string", null]
},
"tag_list": {
"type": ["array"],
"items": { "type": "string" }
},
"only": {
"type": ["array", "object", null],
"items": { "type": ["string", "array"]}
},
"except": {
"type": ["array", "object", null],
"items": { "type": ["string", "array"]}
}
},
"additionalProperties": false
}
{
"type": "object",
"required": ["valid", "errors", "jobs", "warnings"],
"properties": {
"errors": {
"type": "array",
"items": { "type": "string" }
},
"warnings": {
"type": "array",
"items": { "type": "string" }
},
"valid": {
"type": "boolean"
},
"jobs": {
"type": ["array", null],
"items": {
"type": "object",
"$ref": "lint_job_entity.json"
}
}
},
"additionalProperties": false
}
......@@ -42,7 +42,7 @@ RSpec.describe Gitlab::Ci::Lint do
expect(build_job[:stage]).to eq('build')
expect(build_job[:before_script]).to eq(['before_build'])
expect(build_job[:script]).to eq(['echo'])
expect(build_job.fetch(:after_script)).to be_nil
expect(build_job.fetch(:after_script)).to eq([])
expect(build_job[:tag_list]).to eq([])
expect(build_job[:environment]).to eq('staging')
expect(build_job[:when]).to eq('manual')
......@@ -51,7 +51,7 @@ RSpec.describe Gitlab::Ci::Lint do
rspec_job = subject.jobs.last
expect(rspec_job[:name]).to eq('rspec')
expect(rspec_job[:stage]).to eq('test')
expect(rspec_job.fetch(:before_script)).to be_nil
expect(rspec_job.fetch(:before_script)).to eq([])
expect(rspec_job[:script]).to eq(['rspec'])
expect(rspec_job[:after_script]).to eq(['after_rspec'])
expect(rspec_job[:tag_list]).to eq(['docker'])
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::Lint::JobEntity, :aggregate_failures do
describe '#represent' do
let(:job) do
{
name: 'rspec',
stage: 'test',
before_script: ['bundle install', 'bundle exec rake db:create'],
script: ["rake spec"],
after_script: ["rake spec"],
tag_list: %w[ruby postgres],
environment: { name: 'hello', url: 'world' },
when: 'on_success',
allow_failure: false,
except: { refs: ["branches"] },
only: { refs: ["branches"] },
variables: { hello: 'world' }
}
end
subject(:serialized_job_result) { described_class.new(job).as_json }
it 'exposes job data' do
expect(serialized_job_result.keys).to contain_exactly(
:name,
:stage,
:before_script,
:script,
:after_script,
:tag_list,
:environment,
:when,
:allow_failure,
:only,
:except
)
expect(serialized_job_result.keys).not_to include(:variables)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::Lint::ResultEntity do
describe '#represent' do
let(:yaml_content) { YAML.dump({ rspec: { script: 'test', tags: 'mysql' } }) }
let(:result) { Gitlab::Ci::YamlProcessor.new(yaml_content).execute }
subject(:serialized_linting_result) { described_class.new(result).as_json }
it 'serializes with lint result entity' do
expect(serialized_linting_result.keys).to include(:valid, :errors, :jobs, :warnings)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::Lint::ResultSerializer, :aggregate_failures do
let_it_be(:project) { create(:project, :repository) }
let(:result) do
Gitlab::Ci::Lint
.new(project: project, current_user: project.owner)
.validate(yaml_content, dry_run: false)
end
let(:first_job) { linting_result[:jobs].first }
let(:serialized_linting_result) { linting_result.to_json }
subject(:linting_result) { described_class.new.represent(result) }
shared_examples 'matches schema' do
it { expect(serialized_linting_result).to match_schema('entities/lint_result_entity') }
end
context 'when config is invalid' do
let(:yaml_content) { YAML.dump({ rspec: { script: 'test', tags: 'mysql' } }) }
it_behaves_like 'matches schema'
it 'returns expected validity' do
expect(linting_result[:valid]).to eq(false)
expect(linting_result[:errors]).to eq(['jobs:rspec:tags config should be an array of strings'])
expect(linting_result[:warnings]).to eq([])
end
it 'returns job data' do
expect(linting_result[:jobs]).to eq([])
end
end
context 'when config is valid' do
let(:yaml_content) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
it_behaves_like 'matches schema'
it 'returns expected validity' do
expect(linting_result[:valid]).to eq(true)
expect(linting_result[:errors]).to eq([])
expect(linting_result[:warnings]).to eq([])
end
it 'returns job data' do
expect(first_job[:name]).to eq('rspec')
expect(first_job[:stage]).to eq('test')
expect(first_job[:before_script]).to eq(['bundle install', 'bundle exec rake db:create'])
expect(first_job[:script]).to eq(['rake spec'])
expect(first_job[:after_script]).to eq([])
expect(first_job[:tag_list]).to eq(%w[ruby postgres])
expect(first_job[:environment]).to eq(nil)
expect(first_job[:when]).to eq('on_success')
expect(first_job[:allow_failure]).to eq(false)
expect(first_job[:only]).to eq(refs: ['branches'])
expect(first_job[:except]).to eq(nil)
end
context 'when dry run is enabled' do
let(:result) do
Gitlab::Ci::Lint
.new(project: project, current_user: project.owner)
.validate(yaml_content, dry_run: true)
end
it_behaves_like 'matches schema'
it 'returns expected validity' do
expect(linting_result[:valid]).to eq(true)
expect(linting_result[:errors]).to eq([])
expect(linting_result[:warnings]).to eq([])
end
it 'returns job data' do
expect(first_job[:name]).to eq('rspec')
expect(first_job[:stage]).to eq('test')
expect(first_job[:before_script]).to eq(['bundle install', 'bundle exec rake db:create'])
expect(first_job[:script]).to eq(['rake spec'])
expect(first_job[:after_script]).to eq([])
expect(first_job[:tag_list]).to eq(%w[ruby postgres])
expect(first_job[:environment]).to eq(nil)
expect(first_job[:when]).to eq('on_success')
expect(first_job[:allow_failure]).to eq(false)
expect(first_job[:only]).to eq(nil)
expect(first_job[:except]).to eq(nil)
end
end
context 'when only is not nil in the yaml' do
context 'when only: is hash' do
let(:yaml_content) do
<<~YAML
build:
stage: build
script: echo
only:
refs:
- branches
YAML
end
it_behaves_like 'matches schema'
it 'renders only:refs as hash' do
expect(first_job[:only]).to eq(refs: ['branches'])
end
end
context 'when only is an array of strings in the yaml' do
let(:yaml_content) do
<<~YAML
build:
stage: build
script: echo
only:
- pushes
YAML
end
it_behaves_like 'matches schema'
it 'renders only: list as hash' do
expect(first_job[:only]).to eq(refs: ['pushes'])
end
end
end
context 'when except is not nil in the yaml' do
context 'when except: is hash' do
let(:yaml_content) do
<<~YAML
build:
stage: build
script: echo
except:
refs:
- branches
YAML
end
it_behaves_like 'matches schema'
it 'renders except as hash' do
expect(first_job[:except]).to eq(refs: ['branches'])
end
end
context 'when except is an array of strings in the yaml' do
let(:yaml_content) do
<<~YAML
build:
stage: build
script: echo
except:
- pushes
YAML
end
it_behaves_like 'matches schema'
it 'renders only: list as hash' do
expect(first_job[:except]).to eq(refs: ['pushes'])
end
end
context 'with minimal job configuration' do
let(:yaml_content) do
<<~YAML
build:
stage: build
script: echo
YAML
end
it_behaves_like 'matches schema'
it 'renders the job with defaults' do
expect(first_job[:name]).to eq('build')
expect(first_job[:stage]).to eq('build')
expect(first_job[:before_script]).to eq([])
expect(first_job[:script]).to eq(['echo'])
expect(first_job[:after_script]).to eq([])
expect(first_job[:tag_list]).to eq([])
expect(first_job[:environment]).to eq(nil)
expect(first_job[:when]).to eq('on_success')
expect(first_job[:allow_failure]).to eq(false)
expect(first_job[:only]).to eq(refs: %w[branches tags])
expect(first_job[:except]).to eq(nil)
end
end
context 'with environment defined' do
context 'when formatted as a hash in yaml' do
let(:yaml_content) do
<<~YAML
build:
stage: build
script: echo
environment:
name: production
url: https://example.com
YAML
end
it_behaves_like 'matches schema'
it 'renders the environment as a string' do
expect(first_job[:environment]).to eq('production')
end
end
context 'when formatted as a string in yaml' do
let(:yaml_content) do
<<~YAML
build:
stage: build
script: echo
environment: production
YAML
end
it_behaves_like 'matches schema'
it 'renders the environment as a string' do
expect(first_job[:environment]).to eq('production')
end
end
end
context 'when script values are formatted as arrays in the yaml' do
let(:yaml_content) do
<<~YAML
build:
stage: build
before_script:
- echo
- cat '~/.zshrc'
script:
- echo
- cat '~/.zshrc'
after_script:
- echo
- cat '~/.zshrc'
YAML
end
it_behaves_like 'matches schema'
it 'renders the scripts as arrays' do
expect(first_job[:before_script]).to eq(['echo', "cat '~/.zshrc'"])
expect(first_job[:script]).to eq(['echo', "cat '~/.zshrc'"])
expect(first_job[:after_script]).to eq(['echo', "cat '~/.zshrc'"])
end
end
end
end
end
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