Commit ea81dd8c authored by Mike Kozono's avatar Mike Kozono

Add PackageFileRegistryResolver for GraphQL

Not yet accessible via the API
parent c3e08d1c
# frozen_string_literal: true
module Resolvers
module Geo
class PackageFileRegistriesResolver < BaseResolver
include RegistriesResolver
end
end
end
# frozen_string_literal: true
module Resolvers
module Geo
module RegistriesResolver
extend ActiveSupport::Concern
included do
def self.replicator_class
Gitlab::Geo::Replicator.for_class_name(self.name)
end
delegate :registry_class, :registry_finder_class, to: :replicator_class
type replicator_class.graphql_registry_type, null: true
argument :ids,
[GraphQL::ID_TYPE],
required: false,
description: 'Filters registries by their ID'
def resolve(ids: nil)
return registry_class.none unless geo_node_is_current?
registry_finder_class.new(
context[:current_user],
ids: registry_ids(ids)
).execute
end
private
def replicator_class
self.class.replicator_class
end
def registry_ids(ids)
ids&.map { |id| GlobalID.parse(id)&.model_id }&.compact
end
# We can't query other nodes' tracking databases
def geo_node_is_current?
geo_node&.current?
end
def geo_node
object
end
end
end
end
end
# frozen_string_literal: true
module Types
module Geo
# rubocop:disable Graphql/AuthorizeTypes because it is included
class PackageFileRegistryType < BaseObject
include ::Types::Geo::RegistryType
graphql_name 'PackageFileRegistry'
description 'Represents the sync and verification state of a package file'
field :package_file_id, GraphQL::ID_TYPE, null: false, description: 'ID of the PackageFile'
end
end
end
# frozen_string_literal: true
module Types
module Geo
class RegistryStateEnum < BaseEnum
graphql_name 'RegistryState'
description 'State of a Geo registry.'
value 'PENDING', value: :pending, description: 'Registry waiting to be synced'
value 'STARTED', value: :started, description: 'Registry currently syncing'
value 'SYNCED', value: :synced, description: 'Registry that is synced'
value 'FAILED', value: :failed, description: 'Registry that failed to sync'
end
end
end
# frozen_string_literal: true
module Types
module Geo
module RegistryType
extend ActiveSupport::Concern
included do
authorize :read_geo_registry
field :id, GraphQL::ID_TYPE, null: false, description: "ID of the #{graphql_name}"
field :state, Types::Geo::RegistryStateEnum, null: true, method: :state_name, description: "Sync state of the #{graphql_name}"
field :retry_count, GraphQL::INT_TYPE, null: true, description: "Number of consecutive failed sync attempts of the #{graphql_name}"
field :last_sync_failure, GraphQL::STRING_TYPE, null: true, description: "Error message during sync of the #{graphql_name}"
field :retry_at, Types::TimeType, null: true, description: "Timestamp after which the #{graphql_name} should be resynced"
field :last_synced_at, Types::TimeType, null: true, description: "Timestamp of the most recent successful sync of the #{graphql_name}"
field :created_at, Types::TimeType, null: true, description: "Timestamp when the #{graphql_name} was created"
end
end
end
end
...@@ -5,6 +5,8 @@ class Geo::BaseRegistry < Geo::TrackingBase ...@@ -5,6 +5,8 @@ class Geo::BaseRegistry < Geo::TrackingBase
self.abstract_class = true self.abstract_class = true
include GlobalID::Identification
def self.pluck_model_ids_in_range(range) def self.pluck_model_ids_in_range(range)
where(self::MODEL_FOREIGN_KEY => range).pluck(self::MODEL_FOREIGN_KEY) where(self::MODEL_FOREIGN_KEY => range).pluck(self::MODEL_FOREIGN_KEY)
end end
......
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
class Geo::PackageFileRegistry < Geo::BaseRegistry class Geo::PackageFileRegistry < Geo::BaseRegistry
include ::Delay include ::Delay
def self.declarative_policy_class
'Geo::RegistryPolicy'
end
STATE_VALUES = { STATE_VALUES = {
pending: 0, pending: 0,
started: 1, started: 1,
......
# frozen_string_literal: true
module Geo
class RegistryPolicy < ::BasePolicy
condition(:can_read_all_geo, scope: :user) { can?(:read_all_geo, :global) }
rule { can_read_all_geo }.enable :read_geo_registry
end
end
...@@ -69,6 +69,10 @@ module Gitlab ...@@ -69,6 +69,10 @@ module Gitlab
const_get("::Geo::#{replicable_name.camelize}RegistryFinder", false) const_get("::Geo::#{replicable_name.camelize}RegistryFinder", false)
end end
def self.graphql_registry_type
const_get("::Types::Geo::#{replicable_name.camelize}RegistryType", false)
end
# Given a `replicable_name`, return the corresponding replicator # Given a `replicable_name`, return the corresponding replicator
# #
# @param [String] replicable_name the replicable slug # @param [String] replicable_name the replicable slug
......
# frozen_string_literal: true
require 'spec_helper'
describe Resolvers::Geo::PackageFileRegistriesResolver do
it_behaves_like 'a Geo registries resolver', :package_file_registry
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['PackageFileRegistry'] do
it_behaves_like 'a Geo registry type'
it 'has the expected fields (other than those included in RegistryType)' do
expected_fields = %i[package_file_id]
expect(described_class).to have_graphql_fields(*expected_fields).at_least
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['RegistryState'] do
it { expect(described_class.graphql_name).to eq('RegistryState') }
it 'exposes the correct registry states' do
expect(described_class.values.keys).to include(*%w[PENDING STARTED SYNCED FAILED])
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Geo::RegistryPolicy do
let!(:registry) { create(:package_file_registry) }
subject(:policy) { described_class.new(current_user, registry) }
context 'when the user is an admin' do
let(:current_user) { create(:user, :admin) }
it 'allows read_geo_registry for any registry' do
expect(policy).to be_allowed(:read_geo_registry)
end
end
context 'when the user is not an admin' do
let(:current_user) { create(:user) }
it 'disallows read_geo_registry for any registry' do
expect(policy).to be_disallowed(:read_geo_registry)
end
end
end
# frozen_string_literal: true
RSpec.shared_examples_for 'a Geo registries resolver' do |registry_factory_name|
include GraphqlHelpers
include EE::GeoHelpers
describe '#resolve' do
let_it_be(:secondary) { create(:geo_node) }
let_it_be(:registry1) { create(registry_factory_name) }
let_it_be(:registry2) { create(registry_factory_name) }
let_it_be(:registry3) { create(registry_factory_name) }
let(:registries) { [registry1, registry2, registry3] }
let(:gql_context) { { current_user: current_user } }
context 'when the parent object is the current node' do
before do
stub_current_geo_node(secondary)
end
context 'when the user has permission to view Geo data' do
let_it_be(:current_user) { create(:admin) }
context 'when the ids argument is null' do
it 'returns registries, in order' do
expect(resolve_registries.to_a).to eq(registries)
end
end
context 'when the ids argument is present' do
it 'returns the requested registries, in order' do
requested_ids = [registry3.to_global_id, registry1.to_global_id]
args = { ids: requested_ids }
expected = [registry1, registry3]
expect(resolve_registries(args).to_a).to eq(expected)
end
end
end
context 'when the user does not have permission to view Geo data' do
let_it_be(:current_user) { create(:user) }
it 'returns nothing' do
expect(resolve_registries).to be_empty
end
end
end
context 'when the parent object is not the current node' do
context 'when the user has permission to view Geo data' do
let_it_be(:current_user) { create(:admin) }
it "returns nothing, because we can't query other nodes' tracking databases" do
result = resolve(described_class, obj: create(:geo_node), args: {}, ctx: gql_context)
expect(result).to be_empty
end
end
end
end
def resolve_registries(args = {})
resolve(described_class, obj: secondary, args: args, ctx: gql_context)
end
end
# frozen_string_literal: true
RSpec.shared_examples_for 'a Geo registry type' do |registry_factory_name|
it { expect(described_class).to require_graphql_authorizations(:read_geo_registry) }
it 'has the expected fields' do
expected_fields = %i[
id state retry_count last_sync_failure retry_at last_synced_at created_at
]
expect(described_class).to have_graphql_fields(*expected_fields).at_least
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