Commit e2db7924 authored by Dmitriy Zaporozhets (DZ)'s avatar Dmitriy Zaporozhets (DZ)

Merge branch '322839-dependency-proxy-graphql' into 'master'

Add dependency proxy to graphql

See merge request gitlab-org/gitlab!68605
parents e1abd2a9 2fa3ac77
# frozen_string_literal: true
module Types
class DependencyProxy::BlobType < BaseObject
graphql_name 'DependencyProxyBlob'
description 'Dependency proxy blob'
authorize :read_dependency_proxy
field :created_at, Types::TimeType, null: false, description: 'Date of creation.'
field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.'
field :file_name, GraphQL::Types::String, null: false, description: 'Name of the blob.'
field :size, GraphQL::Types::String, null: false, description: 'Size of the blob file.'
end
end
# frozen_string_literal: true
module Types
class DependencyProxy::GroupSettingType < BaseObject
graphql_name 'DependencyProxySetting'
description 'Group-level Dependency Proxy settings'
authorize :read_dependency_proxy
field :enabled, GraphQL::Types::Boolean, null: false, description: 'Indicates whether the dependency proxy is enabled for the group.'
end
end
# frozen_string_literal: true
module Types
class DependencyProxy::ManifestType < BaseObject
graphql_name 'DependencyProxyManifest'
description 'Dependency proxy manifest'
authorize :read_dependency_proxy
field :created_at, Types::TimeType, null: false, description: 'Date of creation.'
field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.'
field :file_name, GraphQL::Types::String, null: false, description: 'Name of the manifest.'
field :image_name, GraphQL::Types::String, null: false, description: 'Name of the image.'
field :size, GraphQL::Types::String, null: false, description: 'Size of the manifest file.'
field :digest, GraphQL::Types::String, null: false, description: 'Digest of the manifest.'
def image_name
object.file_name.chomp(File.extname(object.file_name))
end
end
end
......@@ -128,6 +128,36 @@ module Types
description: 'Packages of the group.',
resolver: Resolvers::GroupPackagesResolver
field :dependency_proxy_setting,
Types::DependencyProxy::GroupSettingType,
null: true,
description: 'Dependency Proxy settings for the group.'
field :dependency_proxy_manifests,
Types::DependencyProxy::ManifestType.connection_type,
null: true,
description: 'Dependency Proxy manifests.'
field :dependency_proxy_blobs,
Types::DependencyProxy::BlobType.connection_type,
null: true,
description: 'Dependency Proxy blobs.'
field :dependency_proxy_image_count,
GraphQL::Types::Int,
null: false,
description: 'Number of dependency proxy images cached in the group.'
field :dependency_proxy_blob_count,
GraphQL::Types::Int,
null: false,
description: 'Number of dependency proxy blobs cached in the group.'
field :dependency_proxy_total_size,
GraphQL::Types::String,
null: false,
description: 'Total size of the dependency proxy cached images.'
def label(title:)
BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args|
LabelsFinder
......@@ -172,6 +202,20 @@ module Types
group.container_repositories.size
end
def dependency_proxy_image_count
group.dependency_proxy_manifests.count
end
def dependency_proxy_blob_count
group.dependency_proxy_blobs.count
end
def dependency_proxy_total_size
ActiveSupport::NumberHelper.number_to_human_size(
group.dependency_proxy_manifests.sum(:size) + group.dependency_proxy_blobs.sum(:size)
)
end
private
def group
......
# frozen_string_literal: true
module DependencyProxy
class BlobPolicy < BasePolicy
delegate { @subject.group }
end
end
# frozen_string_literal: true
module DependencyProxy
class GroupSettingPolicy < BasePolicy
delegate { @subject.group }
end
end
# frozen_string_literal: true
module DependencyProxy
class ManifestPolicy < BasePolicy
delegate { @subject.group }
end
end
......@@ -5408,6 +5408,52 @@ The edge type for [`DastSiteValidation`](#dastsitevalidation).
| <a id="dastsitevalidationedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="dastsitevalidationedgenode"></a>`node` | [`DastSiteValidation`](#dastsitevalidation) | The item at the end of the edge. |
#### `DependencyProxyBlobConnection`
The connection type for [`DependencyProxyBlob`](#dependencyproxyblob).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="dependencyproxyblobconnectionedges"></a>`edges` | [`[DependencyProxyBlobEdge]`](#dependencyproxyblobedge) | A list of edges. |
| <a id="dependencyproxyblobconnectionnodes"></a>`nodes` | [`[DependencyProxyBlob]`](#dependencyproxyblob) | A list of nodes. |
| <a id="dependencyproxyblobconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `DependencyProxyBlobEdge`
The edge type for [`DependencyProxyBlob`](#dependencyproxyblob).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="dependencyproxyblobedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="dependencyproxyblobedgenode"></a>`node` | [`DependencyProxyBlob`](#dependencyproxyblob) | The item at the end of the edge. |
#### `DependencyProxyManifestConnection`
The connection type for [`DependencyProxyManifest`](#dependencyproxymanifest).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="dependencyproxymanifestconnectionedges"></a>`edges` | [`[DependencyProxyManifestEdge]`](#dependencyproxymanifestedge) | A list of edges. |
| <a id="dependencyproxymanifestconnectionnodes"></a>`nodes` | [`[DependencyProxyManifest]`](#dependencyproxymanifest) | A list of nodes. |
| <a id="dependencyproxymanifestconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `DependencyProxyManifestEdge`
The edge type for [`DependencyProxyManifest`](#dependencyproxymanifest).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="dependencyproxymanifestedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="dependencyproxymanifestedgenode"></a>`node` | [`DependencyProxyManifest`](#dependencyproxymanifest) | The item at the end of the edge. |
#### `DesignAtVersionConnection`
The connection type for [`DesignAtVersion`](#designatversion).
......@@ -8565,6 +8611,44 @@ The response from the AdminSidekiqQueuesDeleteJobs mutation.
| <a id="deletejobsresponsedeletedjobs"></a>`deletedJobs` | [`Int`](#int) | Number of matching jobs deleted. |
| <a id="deletejobsresponsequeuesize"></a>`queueSize` | [`Int`](#int) | Queue size after processing. |
### `DependencyProxyBlob`
Dependency proxy blob.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="dependencyproxyblobcreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. |
| <a id="dependencyproxyblobfilename"></a>`fileName` | [`String!`](#string) | Name of the blob. |
| <a id="dependencyproxyblobsize"></a>`size` | [`String!`](#string) | Size of the blob file. |
| <a id="dependencyproxyblobupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. |
### `DependencyProxyManifest`
Dependency proxy manifest.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="dependencyproxymanifestcreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. |
| <a id="dependencyproxymanifestdigest"></a>`digest` | [`String!`](#string) | Digest of the manifest. |
| <a id="dependencyproxymanifestfilename"></a>`fileName` | [`String!`](#string) | Name of the manifest. |
| <a id="dependencyproxymanifestimagename"></a>`imageName` | [`String!`](#string) | Name of the image. |
| <a id="dependencyproxymanifestsize"></a>`size` | [`String!`](#string) | Size of the manifest file. |
| <a id="dependencyproxymanifestupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. |
### `DependencyProxySetting`
Group-level Dependency Proxy settings.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="dependencyproxysettingenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether the dependency proxy is enabled for the group. |
### `Design`
A single design.
......@@ -9596,6 +9680,12 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the group. |
| <a id="groupcontainslockedprojects"></a>`containsLockedProjects` | [`Boolean!`](#boolean) | Includes at least one project where the repository size exceeds the limit. |
| <a id="groupcustomemoji"></a>`customEmoji` | [`CustomEmojiConnection`](#customemojiconnection) | Custom emoji within this namespace. Available only when feature flag `custom_emoji` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
| <a id="groupdependencyproxyblobcount"></a>`dependencyProxyBlobCount` | [`Int!`](#int) | Number of dependency proxy blobs cached in the group. |
| <a id="groupdependencyproxyblobs"></a>`dependencyProxyBlobs` | [`DependencyProxyBlobConnection`](#dependencyproxyblobconnection) | Dependency Proxy blobs. (see [Connections](#connections)) |
| <a id="groupdependencyproxyimagecount"></a>`dependencyProxyImageCount` | [`Int!`](#int) | Number of dependency proxy images cached in the group. |
| <a id="groupdependencyproxymanifests"></a>`dependencyProxyManifests` | [`DependencyProxyManifestConnection`](#dependencyproxymanifestconnection) | Dependency Proxy manifests. (see [Connections](#connections)) |
| <a id="groupdependencyproxysetting"></a>`dependencyProxySetting` | [`DependencyProxySetting`](#dependencyproxysetting) | Dependency Proxy settings for the group. |
| <a id="groupdependencyproxytotalsize"></a>`dependencyProxyTotalSize` | [`String!`](#string) | Total size of the dependency proxy cached images. |
| <a id="groupdescription"></a>`description` | [`String`](#string) | Description of the namespace. |
| <a id="groupdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
| <a id="groupdora"></a>`dora` | [`Dora`](#dora) | The group's DORA metrics. |
......
......@@ -3,12 +3,14 @@
FactoryBot.define do
factory :dependency_proxy_blob, class: 'DependencyProxy::Blob' do
group
size { 1234 }
file { fixture_file_upload('spec/fixtures/dependency_proxy/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz') }
file_name { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz' }
end
factory :dependency_proxy_manifest, class: 'DependencyProxy::Manifest' do
group
size { 1234 }
file { fixture_file_upload('spec/fixtures/dependency_proxy/manifest') }
digest { 'sha256:d0710affa17fad5f466a70159cc458227bd25d4afb39514ef662ead3e6c99515' }
file_name { 'alpine:latest.json' }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['DependencyProxyBlob'] do
it 'includes dependency proxy blob fields' do
expected_fields = %w[
file_name size created_at updated_at
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['DependencyProxySetting'] do
it 'includes dependency proxy blob fields' do
expected_fields = %w[
enabled
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['DependencyProxyManifest'] do
it 'includes dependency proxy manifest fields' do
expected_fields = %w[
file_name image_name size created_at updated_at digest
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
......@@ -18,7 +18,10 @@ RSpec.describe GitlabSchema.types['Group'] do
two_factor_grace_period auto_devops_enabled emails_disabled
mentions_disabled parent boards milestones group_members
merge_requests container_repositories container_repositories_count
packages shared_runners_setting timelogs
packages dependency_proxy_setting dependency_proxy_manifests
dependency_proxy_blobs dependency_proxy_image_count
dependency_proxy_blob_count dependency_proxy_total_size
shared_runners_setting timelogs
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting dependency proxy blobs in a group' do
using RSpec::Parameterized::TableSyntax
include GraphqlHelpers
let_it_be(:owner) { create(:user) }
let_it_be_with_reload(:group) { create(:group) }
let_it_be(:blob) { create(:dependency_proxy_blob, group: group) }
let_it_be(:blob2) { create(:dependency_proxy_blob, file_name: 'blob2.json', group: group) }
let_it_be(:blobs) { [blob, blob2].flatten }
let(:dependency_proxy_blob_fields) do
<<~GQL
edges {
node {
#{all_graphql_fields_for('dependency_proxy_blobs'.classify, max_depth: 1)}
}
}
GQL
end
let(:fields) do
<<~GQL
#{query_graphql_field('dependency_proxy_blobs', {}, dependency_proxy_blob_fields)}
dependencyProxyBlobCount
dependencyProxyTotalSize
GQL
end
let(:query) do
graphql_query_for(
'group',
{ 'fullPath' => group.full_path },
fields
)
end
let(:user) { owner }
let(:variables) { {} }
let(:dependency_proxy_blobs_response) { graphql_data.dig('group', 'dependencyProxyBlobs', 'edges') }
let(:dependency_proxy_blob_count_response) { graphql_data.dig('group', 'dependencyProxyBlobCount') }
let(:dependency_proxy_total_size_response) { graphql_data.dig('group', 'dependencyProxyTotalSize') }
before do
stub_config(dependency_proxy: { enabled: true })
group.add_owner(owner)
end
subject { post_graphql(query, current_user: user, variables: variables) }
it_behaves_like 'a working graphql query' do
before do
subject
end
end
context 'with different permissions' do
let_it_be(:user) { create(:user) }
where(:group_visibility, :role, :access_granted) do
:private | :maintainer | true
:private | :developer | true
:private | :reporter | true
:private | :guest | true
:private | :anonymous | false
:public | :maintainer | true
:public | :developer | true
:public | :reporter | true
:public | :guest | true
:public | :anonymous | false
end
with_them do
before do
group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false))
group.add_user(user, role) unless role == :anonymous
end
it 'return the proper response' do
subject
if access_granted
expect(dependency_proxy_blobs_response.size).to eq(blobs.size)
else
expect(dependency_proxy_blobs_response).to be_blank
end
end
end
end
context 'limiting the number of blobs' do
let(:limit) { 1 }
let(:variables) do
{ path: group.full_path, n: limit }
end
let(:query) do
<<~GQL
query($path: ID!, $n: Int) {
group(fullPath: $path) {
dependencyProxyBlobs(first: $n) { #{dependency_proxy_blob_fields} }
}
}
GQL
end
it 'only returns N blobs' do
subject
expect(dependency_proxy_blobs_response.size).to eq(limit)
end
end
it 'returns the total count of blobs' do
subject
expect(dependency_proxy_blob_count_response).to eq(blobs.size)
end
it 'returns the total size' do
subject
expected_size = blobs.inject(0) { |sum, blob| sum + blob.size }
expect(dependency_proxy_total_size_response).to eq(ActiveSupport::NumberHelper.number_to_human_size(expected_size))
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting dependency proxy settings for a group' do
using RSpec::Parameterized::TableSyntax
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:group) { create(:group) }
let(:dependency_proxy_group_setting_fields) do
<<~GQL
#{all_graphql_fields_for('dependency_proxy_setting'.classify, max_depth: 1)}
GQL
end
let(:fields) do
<<~GQL
#{query_graphql_field('dependency_proxy_setting', {}, dependency_proxy_group_setting_fields)}
GQL
end
let(:query) do
graphql_query_for(
'group',
{ 'fullPath' => group.full_path },
fields
)
end
let(:variables) { {} }
let(:dependency_proxy_group_setting_response) { graphql_data.dig('group', 'dependencyProxySetting') }
before do
stub_config(dependency_proxy: { enabled: true })
group.create_dependency_proxy_setting!(enabled: true)
end
subject { post_graphql(query, current_user: user, variables: variables) }
it_behaves_like 'a working graphql query' do
before do
subject
end
end
context 'with different permissions' do
where(:group_visibility, :role, :access_granted) do
:private | :maintainer | true
:private | :developer | true
:private | :reporter | true
:private | :guest | true
:private | :anonymous | false
:public | :maintainer | true
:public | :developer | true
:public | :reporter | true
:public | :guest | true
:public | :anonymous | false
end
with_them do
before do
group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false))
group.add_user(user, role) unless role == :anonymous
end
it 'return the proper response' do
subject
if access_granted
expect(dependency_proxy_group_setting_response).to eq('enabled' => true)
else
expect(dependency_proxy_group_setting_response).to be_blank
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting dependency proxy manifests in a group' do
using RSpec::Parameterized::TableSyntax
include GraphqlHelpers
let_it_be(:owner) { create(:user) }
let_it_be_with_reload(:group) { create(:group) }
let_it_be(:manifest) { create(:dependency_proxy_manifest, group: group) }
let_it_be(:manifest2) { create(:dependency_proxy_manifest, file_name: 'image2.json', group: group) }
let_it_be(:manifests) { [manifest, manifest2].flatten }
let(:dependency_proxy_manifest_fields) do
<<~GQL
edges {
node {
#{all_graphql_fields_for('dependency_proxy_manifests'.classify, max_depth: 1)}
}
}
GQL
end
let(:fields) do
<<~GQL
#{query_graphql_field('dependency_proxy_manifests', {}, dependency_proxy_manifest_fields)}
dependencyProxyImageCount
GQL
end
let(:query) do
graphql_query_for(
'group',
{ 'fullPath' => group.full_path },
fields
)
end
let(:user) { owner }
let(:variables) { {} }
let(:dependency_proxy_manifests_response) { graphql_data.dig('group', 'dependencyProxyManifests', 'edges') }
let(:dependency_proxy_image_count_response) { graphql_data.dig('group', 'dependencyProxyImageCount') }
before do
stub_config(dependency_proxy: { enabled: true })
group.add_owner(owner)
end
subject { post_graphql(query, current_user: user, variables: variables) }
it_behaves_like 'a working graphql query' do
before do
subject
end
end
context 'with different permissions' do
let_it_be(:user) { create(:user) }
where(:group_visibility, :role, :access_granted) do
:private | :maintainer | true
:private | :developer | true
:private | :reporter | true
:private | :guest | true
:private | :anonymous | false
:public | :maintainer | true
:public | :developer | true
:public | :reporter | true
:public | :guest | true
:public | :anonymous | false
end
with_them do
before do
group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false))
group.add_user(user, role) unless role == :anonymous
end
it 'return the proper response' do
subject
if access_granted
expect(dependency_proxy_manifests_response.size).to eq(manifests.size)
else
expect(dependency_proxy_manifests_response).to be_blank
end
end
end
end
context 'limiting the number of manifests' do
let(:limit) { 1 }
let(:variables) do
{ path: group.full_path, n: limit }
end
let(:query) do
<<~GQL
query($path: ID!, $n: Int) {
group(fullPath: $path) {
dependencyProxyManifests(first: $n) { #{dependency_proxy_manifest_fields} }
}
}
GQL
end
it 'only returns N manifests' do
subject
expect(dependency_proxy_manifests_response.size).to eq(limit)
end
end
it 'returns the total count of manifests' do
subject
expect(dependency_proxy_image_count_response).to eq(manifests.size)
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