Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-29 03:15:22 +00:00
parent 3b11951c24
commit 4c247fac80
13 changed files with 198 additions and 20 deletions

View File

@ -4,10 +4,11 @@ module Ci
class JobsFinder
include Gitlab::Allowable
def initialize(current_user:, pipeline: nil, project: nil, params: {}, type: ::Ci::Build)
def initialize(current_user:, pipeline: nil, project: nil, runner: nil, params: {}, type: ::Ci::Build)
@pipeline = pipeline
@current_user = current_user
@project = project
@runner = runner
@params = params
@type = type
raise ArgumentError 'type must be a subclass of Ci::Processable' unless type < ::Ci::Processable
@ -22,10 +23,10 @@ module Ci
private
attr_reader :current_user, :pipeline, :project, :params, :type
attr_reader :current_user, :pipeline, :project, :runner, :params, :type
def init_collection
pipeline_jobs || project_jobs || all_jobs
pipeline_jobs || project_jobs || runner_jobs || all_jobs
end
def all_jobs
@ -34,6 +35,13 @@ module Ci
type.all
end
def runner_jobs
return unless runner
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :read_builds, runner)
jobs_by_type(runner, type).relevant
end
def project_jobs
return unless project
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :read_build, project)

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
module Resolvers
module Ci
class RunnerJobsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
include LooksAhead
type ::Types::Ci::JobType.connection_type, null: true
authorize :read_builds
authorizes_object!
argument :statuses, [::Types::Ci::JobStatusEnum],
required: false,
description: 'Filter jobs by status.'
alias_method :runner, :object
def ready?(**args)
context[self.class] ||= { executions: 0 }
context[self.class][:executions] += 1
raise GraphQL::ExecutionError, "Jobs can be requested for only one runner at a time" if context[self.class][:executions] > 1
super
end
def resolve_with_lookahead(statuses: nil)
jobs = ::Ci::JobsFinder.new(current_user: current_user, runner: runner, params: { scope: statuses }).execute
apply_lookahead(jobs)
end
private
def preloads
{
previous_stage_jobs_and_needs: [:needs, :pipeline],
artifacts: [:job_artifacts],
pipeline: [:user]
}
end
end
end
end

View File

@ -18,7 +18,8 @@ module Resolvers
def ready?(**args)
context[self.class] ||= { executions: 0 }
context[self.class][:executions] += 1
raise GraphQL::ExecutionError, "Jobs can only be requested for one project at a time" if context[self.class][:executions] > 1
raise GraphQL::ExecutionError, "Jobs can be requested for only one project at a time" if context[self.class][:executions] > 1
super
end

View File

@ -70,6 +70,10 @@ module Types
description: 'Groups the runner is associated with. For group runners only.'
field :projects, ::Types::ProjectType.connection_type, null: true,
description: 'Projects the runner is associated with. For project runners only.'
field :jobs, ::Types::Ci::JobType.connection_type, null: true,
description: 'Jobs assigned to the runner.',
authorize: :read_builds,
resolver: ::Resolvers::Ci::RunnerJobsResolver
def job_count
# We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT

View File

@ -11,6 +11,10 @@ module Ci
rule { anonymous }.prevent_all
rule { admin }.policy do
enable :read_builds
end
rule { admin | owned_runner }.policy do
enable :assign_runner
enable :read_runner

View File

@ -9085,6 +9085,22 @@ Represents the total number of issues and their weights for a particular day.
#### Fields with arguments
##### `CiRunner.jobs`
Jobs assigned to the runner.
Returns [`CiJobConnection`](#cijobconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cirunnerjobsstatuses"></a>`statuses` | [`[CiJobStatus!]`](#cijobstatus) | Filter jobs by status. |
##### `CiRunner.status`
Status of the runner.

View File

@ -31,12 +31,18 @@ module Gitlab
def find
find_with_cache do
find_object || klass.create(prepare_attributes)
find_object || create_object
end
end
protected
def create_object
klass.transaction do
klass.create(prepare_attributes)
end
end
def where_clauses
raise NotImplementedError
end

View File

@ -9,12 +9,6 @@ module Gitlab
# `Group::ObjectBuilder.build(Label, label_attributes)`
# finds or initializes a label with the given attributes.
class ObjectBuilder < Base::ObjectBuilder
def self.build(*args)
::Group.transaction do
super
end
end
def initialize(klass, attributes)
super

View File

@ -13,14 +13,6 @@ module Gitlab
#
# It also adds some logic around Group Labels/Milestones for edge cases.
class ObjectBuilder < Base::ObjectBuilder
def self.build(*args)
::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/350091') do
::Project.transaction do
super
end
end
end
def initialize(klass, attributes)
super

View File

@ -126,4 +126,41 @@ RSpec.describe Ci::JobsFinder, '#execute' do
end
end
end
context 'a runner is present' do
let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
let_it_be(:job_4) { create(:ci_build, :success, runner: runner) }
subject { described_class.new(current_user: user, runner: runner, params: params).execute }
context 'user has access to the runner', :enable_admin_mode do
let(:user) { admin }
it 'returns jobs for the specified project' do
expect(subject).to match_array([job_4])
end
end
context 'user has no access to project builds' do
let_it_be(:guest) { create(:user) }
let(:user) { guest }
before do
project.add_guest(guest)
end
it 'returns no jobs' do
expect(subject).to be_empty
end
end
context 'without user' do
let(:user) { nil }
it 'returns no jobs' do
expect(subject).to be_empty
end
end
end
end

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Ci::RunnerJobsResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:irrelevant_pipeline) { create(:ci_pipeline, project: project) }
let!(:build_one) { create(:ci_build, :success, name: 'Build One', runner: runner, pipeline: pipeline) }
let!(:build_two) { create(:ci_build, :success, name: 'Build Two', runner: runner, pipeline: pipeline) }
let!(:build_three) { create(:ci_build, :failed, name: 'Build Three', runner: runner, pipeline: pipeline) }
let!(:irrelevant_build) { create(:ci_build, name: 'Irrelevant Build', pipeline: irrelevant_pipeline)}
let(:args) { {} }
let(:runner) { create(:ci_runner, :project, projects: [project]) }
subject { resolve_jobs(args) }
describe '#resolve' do
context 'with authorized user', :enable_admin_mode do
let(:current_user) { create(:user, :admin) }
context 'with statuses argument' do
let(:args) { { statuses: [Types::Ci::JobStatusEnum.coerce_isolated_input('SUCCESS')] } }
it { is_expected.to contain_exactly(build_one, build_two) }
end
context 'without statuses argument' do
it { is_expected.to contain_exactly(build_one, build_two, build_three) }
end
end
context 'with unauthorized user' do
let(:current_user) { nil }
it { is_expected.to be_nil }
end
end
private
def resolve_jobs(args = {}, context = { current_user: current_user })
resolve(described_class, obj: runner, args: args, ctx: context)
end
end

View File

@ -12,7 +12,7 @@ RSpec.describe GitlabSchema.types['CiRunner'] do
id description created_at contacted_at maximum_timeout access_level active paused status
version short_sha revision locked run_untagged ip_address runner_type tag_list
project_count job_count admin_url edit_admin_url user_permissions executor_name
groups projects
groups projects jobs
]
expect(described_class).to include_graphql_fields(*expected_fields)

View File

@ -77,6 +77,7 @@ RSpec.describe 'Query.runner(id)' do
'runnerType' => runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE',
'executorName' => runner.executor_type&.dasherize,
'jobCount' => 0,
'jobs' => a_hash_including("count" => 0, "nodes" => [], "pageInfo" => anything),
'projectCount' => nil,
'adminUrl' => "http://localhost/admin/runners/#{runner.id}",
'userPermissions' => {
@ -308,12 +309,25 @@ RSpec.describe 'Query.runner(id)' do
let!(:job) { create(:ci_build, runner: project_runner1) }
context 'requesting projects and counts for projects and jobs' do
let(:jobs_fragment) do
%(
jobs {
count
nodes {
id
status
}
}
)
end
let(:query) do
%(
query {
projectRunner1: runner(id: "#{project_runner1.to_global_id}") {
projectCount
jobCount
#{jobs_fragment}
projects {
nodes {
id
@ -323,6 +337,7 @@ RSpec.describe 'Query.runner(id)' do
projectRunner2: runner(id: "#{project_runner2.to_global_id}") {
projectCount
jobCount
#{jobs_fragment}
projects {
nodes {
id
@ -332,6 +347,7 @@ RSpec.describe 'Query.runner(id)' do
activeInstanceRunner: runner(id: "#{active_instance_runner.to_global_id}") {
projectCount
jobCount
#{jobs_fragment}
projects {
nodes {
id
@ -355,6 +371,10 @@ RSpec.describe 'Query.runner(id)' do
expect(runner1_data).to match a_hash_including(
'jobCount' => 1,
'jobs' => a_hash_including(
"count" => 1,
"nodes" => [{ "id" => job.to_global_id.to_s, "status" => job.status.upcase }]
),
'projectCount' => 2,
'projects' => {
'nodes' => [
@ -364,12 +384,14 @@ RSpec.describe 'Query.runner(id)' do
})
expect(runner2_data).to match a_hash_including(
'jobCount' => 0,
'jobs' => nil, # returning jobs not allowed for more than 1 runner (see RunnerJobsResolver)
'projectCount' => 0,
'projects' => {
'nodes' => []
})
expect(runner3_data).to match a_hash_including(
'jobCount' => 0,
'jobs' => nil, # returning jobs not allowed for more than 1 runner (see RunnerJobsResolver)
'projectCount' => nil,
'projects' => nil)
end