Commit 1201f46a authored by Furkan Ayhan's avatar Furkan Ayhan Committed by Alper Akgun

Add project scope to ci clint graphql endpoint

We need project and user info in order to execute yaml processor
effectively.
parent d9958bf6
#import "~/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql" #import "~/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql"
query getCiConfigData($content: String!) { query getCiConfigData($projectPath: ID!, $content: String!) {
ciConfig(content: $content) { ciConfig(projectPath: $projectPath, content: $content) {
errors errors
status status
stages { stages {
......
...@@ -106,6 +106,7 @@ export default { ...@@ -106,6 +106,7 @@ export default {
}, },
variables() { variables() {
return { return {
projectPath: this.projectPath,
content: this.contentModel, content: this.contentModel,
}; };
}, },
......
...@@ -3,14 +3,27 @@ ...@@ -3,14 +3,27 @@
module Resolvers module Resolvers
module Ci module Ci
class ConfigResolver < BaseResolver class ConfigResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
include ResolvesProject
type Types::Ci::Config::ConfigType, null: true type Types::Ci::Config::ConfigType, null: true
authorize :read_pipeline
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project of the CI config'
argument :content, GraphQL::STRING_TYPE, argument :content, GraphQL::STRING_TYPE,
required: true, required: true,
description: 'Contents of .gitlab-ci.yml' description: 'Contents of .gitlab-ci.yml'
def resolve(content:) def resolve(project_path:, content:)
result = ::Gitlab::Ci::YamlProcessor.new(content).execute project = authorized_find!(project_path: project_path)
result = ::Gitlab::Ci::YamlProcessor.new(content, project: project,
user: current_user,
sha: project.repository.commit.sha).execute
response = if result.errors.empty? response = if result.errors.empty?
{ {
...@@ -55,6 +68,10 @@ module Resolvers ...@@ -55,6 +68,10 @@ module Resolvers
.group_by { |group| group[:stage] } .group_by { |group| group[:stage] }
.map { |name, groups| { name: name, groups: groups } } .map { |name, groups| { name: name, groups: groups } }
end end
def find_object(project_path:)
resolve_project(full_path: project_path)
end
end end
end end
end end
---
title: Add project scope to ci clint graphql endpoint
merge_request: 50418
author:
type: fixed
...@@ -19371,6 +19371,11 @@ type Query { ...@@ -19371,6 +19371,11 @@ type Query {
Contents of .gitlab-ci.yml Contents of .gitlab-ci.yml
""" """
content: String! content: String!
"""
The project of the CI config
"""
projectPath: ID!
): CiConfig ): CiConfig
""" """
......
...@@ -56487,6 +56487,20 @@ ...@@ -56487,6 +56487,20 @@
"name": "ciConfig", "name": "ciConfig",
"description": "Get linted and processed contents of a CI config. Should not be requested more than once per request.", "description": "Get linted and processed contents of a CI config. Should not be requested more than once per request.",
"args": [ "args": [
{
"name": "projectPath",
"description": "The project of the CI config",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{ {
"name": "content", "name": "content",
"description": "Contents of .gitlab-ci.yml", "description": "Contents of .gitlab-ci.yml",
...@@ -149,7 +149,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { ...@@ -149,7 +149,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
beforeEach(() => { beforeEach(() => {
mockBlobContentData = jest.fn(); mockBlobContentData = jest.fn();
mockCiConfigData = jest.fn().mockResolvedValue(mockCiConfigQueryResponse); mockCiConfigData = jest.fn();
}); });
afterEach(() => { afterEach(() => {
...@@ -413,9 +413,13 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { ...@@ -413,9 +413,13 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
}); });
}); });
describe('displays fetch content errors', () => { describe('when queries are called', () => {
it('no error is shown when data is set', async () => { beforeEach(() => {
mockBlobContentData.mockResolvedValue(mockCiYml); mockBlobContentData.mockResolvedValue(mockCiYml);
mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse);
});
it('no error is shown when data is set', async () => {
createComponentWithApollo(); createComponentWithApollo();
await waitForPromises(); await waitForPromises();
...@@ -424,6 +428,17 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { ...@@ -424,6 +428,17 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
expect(findEditorLite().attributes('value')).toBe(mockCiYml); expect(findEditorLite().attributes('value')).toBe(mockCiYml);
}); });
it('ci config query is called with correct variables', async () => {
createComponentWithApollo();
await waitForPromises();
expect(mockCiConfigData).toHaveBeenCalledWith({
content: mockCiYml,
projectPath: mockProjectPath,
});
});
it('shows a 404 error message', async () => { it('shows a 404 error message', async () => {
mockBlobContentData.mockRejectedValueOnce({ mockBlobContentData.mockRejectedValueOnce({
response: { response: {
......
...@@ -13,6 +13,15 @@ RSpec.describe Resolvers::Ci::ConfigResolver do ...@@ -13,6 +13,15 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
allow(::Gitlab::Ci::YamlProcessor).to receive(:new).and_return(yaml_processor_double) allow(::Gitlab::Ci::YamlProcessor).to receive(:new).and_return(yaml_processor_double)
end end
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
subject(:response) do
resolve(described_class,
args: { project_path: project.full_path, content: content },
ctx: { current_user: user })
end
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::YamlProcessor::Result.new(
...@@ -27,8 +36,6 @@ RSpec.describe Resolvers::Ci::ConfigResolver do ...@@ -27,8 +36,6 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
end end
it 'lints the ci config file' do it 'lints the ci config file' do
response = resolve(described_class, args: { content: content }, ctx: {})
expect(response[:status]).to eq(:valid) expect(response[:status]).to eq(:valid)
expect(response[:errors]).to be_empty expect(response[:errors]).to be_empty
end end
...@@ -46,8 +53,6 @@ RSpec.describe Resolvers::Ci::ConfigResolver do ...@@ -46,8 +53,6 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
end end
it 'responds with errors about invalid syntax' do it 'responds with errors about invalid syntax' do
response = resolve(described_class, args: { content: content }, ctx: {})
expect(response[:status]).to eq(:invalid) expect(response[:status]).to eq(:invalid)
expect(response[:errors]).to eq(['Invalid configuration format']) expect(response[:errors]).to eq(['Invalid configuration format'])
end end
......
...@@ -7,7 +7,8 @@ RSpec.describe 'Query.ciConfig' do ...@@ -7,7 +7,8 @@ RSpec.describe 'Query.ciConfig' do
subject(:post_graphql_query) { post_graphql(query, current_user: user) } subject(:post_graphql_query) { post_graphql(query, current_user: user) }
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
let_it_be(:content) do let_it_be(:content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml')) File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
...@@ -16,7 +17,7 @@ RSpec.describe 'Query.ciConfig' do ...@@ -16,7 +17,7 @@ RSpec.describe 'Query.ciConfig' do
let(:query) do let(:query) do
%( %(
query { query {
ciConfig(content: "#{content}") { ciConfig(projectPath: "#{project.full_path}", content: "#{content}") {
status status
errors errors
stages { stages {
...@@ -47,13 +48,15 @@ RSpec.describe 'Query.ciConfig' do ...@@ -47,13 +48,15 @@ RSpec.describe 'Query.ciConfig' do
) )
end end
before do it_behaves_like 'a working graphql query' do
post_graphql_query before do
post_graphql_query
end
end end
it_behaves_like 'a working graphql query'
it 'returns the correct structure' do it 'returns the correct structure' do
post_graphql_query
expect(graphql_data['ciConfig']).to eq( expect(graphql_data['ciConfig']).to eq(
"status" => "VALID", "status" => "VALID",
"errors" => [], "errors" => [],
...@@ -114,4 +117,75 @@ RSpec.describe 'Query.ciConfig' do ...@@ -114,4 +117,75 @@ RSpec.describe 'Query.ciConfig' do
} }
) )
end end
context 'when the config file includes other files' do
let_it_be(:content) do
YAML.dump(
include: 'other_file.yml',
rspec: {
script: 'rspec'
}
)
end
before do
allow_next_instance_of(Repository) do |repository|
allow(repository).to receive(:blob_data_at).with(an_instance_of(String), 'other_file.yml') do
YAML.dump(
build: {
script: 'build'
}
)
end
end
post_graphql_query
end
it_behaves_like 'a working graphql query'
it 'returns the correct structure with included files' do
expect(graphql_data['ciConfig']).to eq(
"status" => "VALID",
"errors" => [],
"stages" =>
{
"nodes" =>
[
{
"name" => "test",
"groups" =>
{
"nodes" =>
[
{
"name" => "build",
"size" => 1,
"jobs" =>
{
"nodes" =>
[
{ "name" => "build", "groupName" => "build", "stage" => "test", "needs" => { "nodes" => [] } }
]
}
},
{
"name" => "rspec",
"size" => 1,
"jobs" =>
{
"nodes" =>
[
{ "name" => "rspec", "groupName" => "rspec", "stage" => "test", "needs" => { "nodes" => [] } }
]
}
}
]
}
}
]
}
)
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