diff --git a/app/graphql/resolvers/project_container_registry_protection_rules_resolver.rb b/app/graphql/resolvers/project_container_registry_protection_rules_resolver.rb new file mode 100644 index 0000000000000000000000000000000000000000..e051943ee5b96e05651623b01b36de5c325615e6 --- /dev/null +++ b/app/graphql/resolvers/project_container_registry_protection_rules_resolver.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Resolvers + class ProjectContainerRegistryProtectionRulesResolver < BaseResolver + type Types::ContainerRegistry::Protection::RuleType.connection_type, null: true + + alias_method :project, :object + + def resolve(**_args) + return [] if Feature.disabled?(:container_registry_protected_containers, project) + + project.container_registry_protection_rules + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 4ee6e2429b7e4d6aaaee16707bbc355b7e6c2fc6..05add3af1819e836cb957844de690466d5df6813 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -496,6 +496,13 @@ class ProjectType < BaseObject null: true, description: 'Container expiration policy of the project.' + field :container_registry_protection_rules, + Types::ContainerRegistry::Protection::RuleType.connection_type, + null: true, + description: 'Container protection rules for the project.', + alpha: { milestone: '16.10' }, + resolver: Resolvers::ProjectContainerRegistryProtectionRulesResolver + field :container_repositories, Types::ContainerRepositoryType.connection_type, null: true, description: 'Container repositories of the project.', diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e90412c9e84e8d5b80953f569fd87fa4f9fc19c2..722deb02095612589602163f0377c131f812da57 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -10450,6 +10450,29 @@ The edge type for [`ConnectedAgent`](#connectedagent). | `cursor` | [`String!`](#string) | A cursor for use in pagination. | | `node` | [`ConnectedAgent`](#connectedagent) | The item at the end of the edge. | +#### `ContainerRegistryProtectionRuleConnection` + +The connection type for [`ContainerRegistryProtectionRule`](#containerregistryprotectionrule). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `edges` | [`[ContainerRegistryProtectionRuleEdge]`](#containerregistryprotectionruleedge) | A list of edges. | +| `nodes` | [`[ContainerRegistryProtectionRule]`](#containerregistryprotectionrule) | A list of nodes. | +| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `ContainerRegistryProtectionRuleEdge` + +The edge type for [`ContainerRegistryProtectionRule`](#containerregistryprotectionrule). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`ContainerRegistryProtectionRule`](#containerregistryprotectionrule) | The item at the end of the edge. | + #### `ContainerRepositoryConnection` The connection type for [`ContainerRepository`](#containerrepository). @@ -25268,6 +25291,7 @@ Check permissions for the current user on a vulnerability finding. | `complianceFrameworks` | [`ComplianceFrameworkConnection`](#complianceframeworkconnection) | Compliance frameworks associated with the project. (see [Connections](#connections)) | | `containerExpirationPolicy` | [`ContainerExpirationPolicy`](#containerexpirationpolicy) | Container expiration policy of the project. | | `containerRegistryEnabled` | [`Boolean`](#boolean) | Indicates if Container Registry is enabled for the current user. | +| `containerRegistryProtectionRules` **{warning-solid}** | [`ContainerRegistryProtectionRuleConnection`](#containerregistryprotectionruleconnection) | **Introduced** in GitLab 16.10. **Status**: Experiment. Container protection rules for the project. | | `containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the project. | | `corpuses` | [`CoverageFuzzingCorpusConnection`](#coveragefuzzingcorpusconnection) | Find corpuses of the project. (see [Connections](#connections)) | | `createdAt` | [`Time`](#time) | Timestamp of the project creation. | diff --git a/spec/requests/api/graphql/project/container_registry_protection_rules_spec.rb b/spec/requests/api/graphql/project/container_registry_protection_rules_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..57c50e4c33bc240db9f5b2cc0bfedd182907ffb0 --- /dev/null +++ b/spec/requests/api/graphql/project/container_registry_protection_rules_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting the containers protection rules linked to a project', :aggregate_failures, feature_category: :container_registry do + include GraphqlHelpers + + let_it_be_with_reload(:project) { create(:project) } + let_it_be(:user) { project.owner } + + let(:query) do + graphql_query_for( + :project, + { full_path: project.full_path }, + query_nodes(:containerRegistryProtectionRules, of: 'ContainerRegistryProtectionRule') + ) + end + + let(:protection_rules) { graphql_data_at(:project, :containerRegistryProtectionRules, :nodes) } + + subject(:send_graqhql_query) { post_graphql(query, current_user: user) } + + context 'with authorized user owner' do + before do + send_graqhql_query + end + + context 'with container protection rule' do + let_it_be(:container_protection_rule) { create(:container_registry_protection_rule, project: project) } + + it_behaves_like 'a working graphql query' + + it 'returns only on containersProtectionRule' do + expect(protection_rules.count).to eq 1 + end + + it 'returns all containers protection rule fields' do + expect(protection_rules).to include( + hash_including( + 'repositoryPathPattern' => container_protection_rule.repository_path_pattern, + 'pushProtectedUpToAccessLevel' => 'DEVELOPER', + 'deleteProtectedUpToAccessLevel' => 'DEVELOPER' + ) + ) + end + end + + context 'without container protection rule' do + it_behaves_like 'a working graphql query' + + it 'returns no containersProtectionRule' do + expect(protection_rules).to be_empty + end + end + end + + context 'with unauthorized user' do + let_it_be(:user) { create(:user).tap { |u| project.add_developer(u) } } + + before do + send_graqhql_query + end + + it_behaves_like 'a working graphql query' + + it 'returns no container protection rules' do + expect(protection_rules).to be_empty + end + end + + context "when feature flag ':containers_protected_containers' disabled" do + let_it_be(:container_protection_rule) { create(:container_registry_protection_rule, project: project) } + + before do + stub_feature_flags(container_registry_protected_containers: false) + + send_graqhql_query + end + + it_behaves_like 'a working graphql query' + + it 'returns no container protection rules' do + expect(protection_rules).to be_empty + end + end +end