Commit 6b5b16fc authored by Marin Jankovski's avatar Marin Jankovski

Merge branch 'mc/feature/ci-config-render-api-endpoint' into 'master'

Add endpoint for rendering expanded CI YAML config

See merge request gitlab-org/gitlab!42380
parents 37d7704e 6b69dfb2
---
title: Show expanded CI config in CI lint API endpoint.
merge_request: 42380
author:
type: added
...@@ -4,11 +4,14 @@ group: Continuous Integration ...@@ -4,11 +4,14 @@ group: Continuous Integration
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
--- ---
# Validate the `.gitlab-ci.yml` (API) # CI Lint API
## Validate the CI YAML config
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/5953) in GitLab 8.12. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/5953) in GitLab 8.12.
Checks if your `.gitlab-ci.yml` file is valid. Checks if CI/CD YAML configuration is valid. This endpoint validates basic CI/CD
configuration syntax. It doesn't have any namespace specific context.
```plaintext ```plaintext
POST /ci/lint POST /ci/lint
...@@ -16,13 +19,15 @@ POST /ci/lint ...@@ -16,13 +19,15 @@ POST /ci/lint
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ---------- | ------- | -------- | -------- | | ---------- | ------- | -------- | -------- |
| `content` | string | yes | the `.gitlab-ci.yaml` content| | `content` | string | yes | The CI/CD configuration content. |
| `include_merged_yaml` | boolean | no | If the [expanded CI/CD configuration](#yaml-expansion) should be included in the response. |
```shell ```shell
curl --header "Content-Type: application/json" "https://gitlab.example.com/api/v4/ci/lint" --data '{"content": "{ \"image\": \"ruby:2.6\", \"services\": [\"postgres\"], \"before_script\": [\"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}' curl --header "Content-Type: application/json" "https://gitlab.example.com/api/v4/ci/lint" --data '{"content": "{ \"image\": \"ruby:2.6\", \"services\": [\"postgres\"], \"before_script\": [\"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}'
``` ```
Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky about indentation and spaces. Be sure to paste the exact contents of your GitLab CI/CD YAML config because YAML
is very sensitive about indentation and spacing.
Example responses: Example responses:
...@@ -53,3 +58,39 @@ Example responses: ...@@ -53,3 +58,39 @@ Example responses:
"error": "content is missing" "error": "content is missing"
} }
``` ```
### YAML expansion
The expansion only works for CI configurations that don't have local [includes](../ci/yaml/README.md#include).
Example contents of a `.gitlab-ci.yml` passed to the CI Lint API with
`include_merged_yaml` set as true:
```yaml
include:
remote: 'https://example.com/remote.yaml'
test:
stage: test
script:
- echo 1
```
Example contents of `https://example.com/remote.yaml`:
```yaml
another_test:
stage: test
script:
- echo 2
```
Example response:
```json
{
"status": "valid",
"errors": [],
"merged_config": "---\n:another_test:\n :stage: test\n :script: echo 2\n:test:\n :stage: test\n :script: echo 1\n"
}
```
...@@ -6,18 +6,23 @@ module API ...@@ -6,18 +6,23 @@ module API
desc 'Validation of .gitlab-ci.yml content' desc 'Validation of .gitlab-ci.yml content'
params do params do
requires :content, type: String, desc: 'Content of .gitlab-ci.yml' requires :content, type: String, desc: 'Content of .gitlab-ci.yml'
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
end end
post '/lint' do post '/lint' do
error = Gitlab::Ci::YamlProcessor.validation_message(params[:content], result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute
user: current_user) error = result.errors.first
status 200 status 200
if error.blank? response = if error.blank?
{ status: 'valid', errors: [] } { status: 'valid', errors: [] }
else else
{ status: 'invalid', errors: [error] } { status: 'invalid', errors: [error] }
end end
response.tap do |response|
response[:merged_yaml] = result.merged_yaml if params[:include_merged_yaml]
end
end end
end end
end end
......
...@@ -95,6 +95,10 @@ module Gitlab ...@@ -95,6 +95,10 @@ module Gitlab
}.compact }.compact }.compact }.compact
end end
def merged_yaml
@ci_config&.to_hash&.to_yaml
end
private private
def variables def variables
......
# frozen_string_literal: true
require 'spec_helper'
module Gitlab
module Ci
class YamlProcessor
RSpec.describe Result do
include StubRequests
let(:user) { create(:user) }
let(:ci_config) { Gitlab::Ci::Config.new(config_content, user: user) }
let(:result) { described_class.new(ci_config: ci_config, warnings: ci_config&.warnings) }
describe '#merged_yaml' do
subject(:merged_yaml) { result.merged_yaml }
let(:config_content) do
YAML.dump(
include: { remote: 'https://example.com/sample.yml' },
test: { stage: 'test', script: 'echo' }
)
end
let(:included_yml) do
YAML.dump(
another_test: { stage: 'test', script: 'echo 2' }
)
end
before do
stub_full_request('https://example.com/sample.yml').to_return(body: included_yml)
end
it 'returns expanded yaml config' do
expanded_config = YAML.safe_load(merged_yaml, [Symbol])
included_config = YAML.safe_load(included_yml, [Symbol])
expect(expanded_config).to include(*included_config.keys)
end
end
end
end
end
end
...@@ -17,24 +17,53 @@ RSpec.describe API::Lint do ...@@ -17,24 +17,53 @@ RSpec.describe API::Lint do
expect(json_response['status']).to eq('valid') expect(json_response['status']).to eq('valid')
expect(json_response['errors']).to eq([]) expect(json_response['errors']).to eq([])
end end
it 'outputs expanded yaml content' do
post api('/ci/lint'), params: { content: yaml_content, include_merged_yaml: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('merged_yaml')
end
end end
context 'with an invalid .gitlab_ci.yml' do context 'with an invalid .gitlab_ci.yml' do
context 'with invalid syntax' do
let(:yaml_content) { 'invalid content' }
it 'responds with errors about invalid syntax' do it 'responds with errors about invalid syntax' do
post api('/ci/lint'), params: { content: 'invalid content' } post api('/ci/lint'), params: { content: yaml_content }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['status']).to eq('invalid') expect(json_response['status']).to eq('invalid')
expect(json_response['errors']).to eq(['Invalid configuration format']) expect(json_response['errors']).to eq(['Invalid configuration format'])
end end
it "responds with errors about invalid configuration" do it 'outputs expanded yaml content' do
post api('/ci/lint'), params: { content: '{ image: "ruby:2.7", services: ["postgres"] }' } post api('/ci/lint'), params: { content: yaml_content, include_merged_yaml: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('merged_yaml')
end
end
context 'with invalid configuration' do
let(:yaml_content) { '{ image: "ruby:2.7", services: ["postgres"] }' }
it 'responds with errors about invalid configuration' do
post api('/ci/lint'), params: { content: yaml_content }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['status']).to eq('invalid') expect(json_response['status']).to eq('invalid')
expect(json_response['errors']).to eq(['jobs config should contain at least one visible job']) expect(json_response['errors']).to eq(['jobs config should contain at least one visible job'])
end end
it 'outputs expanded yaml content' do
post api('/ci/lint'), params: { content: yaml_content, include_merged_yaml: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('merged_yaml')
end
end
end end
context 'without the content parameter' do context 'without the content parameter' do
......
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