Commit d238bf57 authored by Matija Čupić's avatar Matija Čupić Committed by Markus Koller

Add RunnerSetup to GraphQL API

Implements a resolver that generates the runner install script and
register command, with support for passing a project or group.
parent 0a13f744
# frozen_string_literal: true
module Resolvers
module Ci
class RunnerSetupResolver < BaseResolver
type Types::Ci::RunnerSetupType, null: true
argument :platform, GraphQL::STRING_TYPE,
required: true,
description: 'Platform to generate the instructions for'
argument :architecture, GraphQL::STRING_TYPE,
required: true,
description: 'Architecture to generate the instructions for'
argument :project_id, ::Types::GlobalIDType[::Project],
required: false,
description: 'Project to register the runner for'
argument :group_id, ::Types::GlobalIDType[::Group],
required: false,
description: 'Group to register the runner for'
def resolve(platform:, architecture:, **args)
instructions = Gitlab::Ci::RunnerInstructions.new(
{ current_user: current_user, os: platform, arch: architecture }.merge(target_param(args))
)
{
install_instructions: instructions.install_script,
register_instructions: instructions.register_command
}
ensure
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'User is not authorized to register a runner for the specified resource!' if instructions.errors.include?('Gitlab::Access::AccessDeniedError')
end
private
def target_param(args)
project_param(args[:project_id]) || group_param(args[:group_id]) || {}
end
def project_param(project_id)
return unless project_id
{ project: find_object(project_id) }
end
def group_param(group_id)
return unless group_id
{ group: find_object(group_id) }
end
def find_object(gid)
GlobalID::Locator.locate(gid)
end
end
end
end
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class RunnerSetupType < BaseObject
graphql_name 'RunnerSetup'
field :install_instructions, GraphQL::STRING_TYPE, null: false,
description: 'Instructions for installing the runner on the specified architecture'
field :register_instructions, GraphQL::STRING_TYPE, null: false,
description: 'Instructions for registering the runner'
end
end
end
......@@ -84,6 +84,10 @@ module Types
null: true, description: 'Supported runner platforms',
resolver: Resolvers::Ci::RunnerPlatformsResolver
field :runner_setup, Types::Ci::RunnerSetupType, null: true,
description: 'Get runner setup instructions',
resolver: Resolvers::Ci::RunnerSetupResolver
def design_management
DesignManagementObject.new(nil)
end
......
......@@ -8571,6 +8571,11 @@ type Group {
webUrl: String!
}
"""
Identifier of Group
"""
scalar GroupID
"""
Represents a Group Membership
"""
......@@ -15629,6 +15634,31 @@ type Query {
last: Int
): RunnerPlatformConnection
"""
Get runner setup instructions
"""
runnerSetup(
"""
Architecture to generate the instructions for
"""
architecture: String!
"""
Group to register the runner for
"""
groupId: GroupID
"""
Platform to generate the instructions for
"""
platform: String!
"""
Project to register the runner for
"""
projectId: ProjectID
): RunnerSetup
"""
Find Snippets visible to the current user
"""
......@@ -16922,6 +16952,18 @@ type RunnerPlatformEdge {
node: RunnerPlatform
}
type RunnerSetup {
"""
Instructions for installing the runner on the specified architecture
"""
installInstructions: String!
"""
Instructions for registering the runner
"""
registerInstructions: String!
}
"""
Represents a CI configuration of SAST
"""
......
......@@ -23240,6 +23240,16 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "GroupID",
"description": "Identifier of Group",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "GroupMember",
......@@ -45234,6 +45244,67 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "runnerSetup",
"description": "Get runner setup instructions",
"args": [
{
"name": "platform",
"description": "Platform to generate the instructions for",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "architecture",
"description": "Architecture to generate the instructions for",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "projectId",
"description": "Project to register the runner for",
"type": {
"kind": "SCALAR",
"name": "ProjectID",
"ofType": null
},
"defaultValue": null
},
{
"name": "groupId",
"description": "Group to register the runner for",
"type": {
"kind": "SCALAR",
"name": "GroupID",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "RunnerSetup",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "snippets",
"description": "Find Snippets visible to the current user",
......@@ -48854,6 +48925,55 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "RunnerSetup",
"description": null,
"fields": [
{
"name": "installInstructions",
"description": "Instructions for installing the runner on the specified architecture",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "registerInstructions",
"description": "Instructions for registering the runner",
"args": [
],
"type": {
"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": "SastCiConfiguration",
......@@ -2276,6 +2276,13 @@ Autogenerated return type of RunDASTScan.
| `humanReadableName` | String! | Human readable name of the runner platform |
| `name` | String! | Name slug of the runner platform |
### RunnerSetup
| Field | Type | Description |
| ----- | ---- | ----------- |
| `installInstructions` | String! | Instructions for installing the runner on the specified architecture |
| `registerInstructions` | String! | Instructions for registering the runner |
### SastCiConfigurationAnalyzersEntity
Represents an analyzer entity in SAST CI configuration.
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Ci::RunnerSetupResolver do
include GraphqlHelpers
describe '#resolve' do
let(:user) { create(:user) }
subject(:resolve_subject) { resolve(described_class, ctx: { current_user: user }, args: { platform: 'linux', architecture: 'amd64' }.merge(target_param)) }
context 'without target parameter' do
let(:target_param) { {} }
context 'when user is not admin' do
it 'returns access error' do
expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when user is admin' do
before do
user.update!(admin: true)
end
it 'returns install and register instructions' do
expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions)
expect(resolve_subject.values).not_to include(nil)
end
end
end
context 'with project target parameter' do
let(:project) { create(:project) }
let(:target_param) { { project_id: project.to_global_id } }
context 'when user has access to admin builds on project' do
before do
project.add_maintainer(user)
end
it 'returns install and register instructions' do
expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions)
expect(resolve_subject.values).not_to include(nil)
end
end
context 'when user does not have access to admin builds on project' do
before do
project.add_developer(user)
end
it 'returns access error' do
expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
context 'with group target parameter' do
let(:group) { create(:group) }
let(:target_param) { { group_id: group.to_global_id } }
context 'when user has access to admin builds on group' do
before do
group.add_owner(user)
end
it 'returns install and register instructions' do
expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions)
expect(resolve_subject.values).not_to include(nil)
end
end
context 'when user does not have access to admin builds on group' do
before do
group.add_developer(user)
end
it 'returns access error' do
expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::RunnerSetupType do
specify { expect(described_class.graphql_name).to eq('RunnerSetup') }
it 'exposes the expected fields' do
expected_fields = %i[
install_instructions
register_instructions
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end
......@@ -80,4 +80,12 @@ RSpec.describe GitlabSchema.types['Query'] do
is_expected.to have_graphql_type(Types::Ci::RunnerPlatformType.connection_type)
end
end
describe 'runner_setup field' do
subject { described_class.fields['runnerSetup'] }
it 'returns runner setup instructions' do
is_expected.to have_graphql_type(Types::Ci::RunnerSetupType)
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