From 557dbeaa03d3443201ee7f1f6b3f65216d9960e9 Mon Sep 17 00:00:00 2001 From: Nicholas Wittstruck Date: Tue, 9 Jul 2024 11:38:35 +0000 Subject: [PATCH 1/2] Protected containers: Add PATCH REST API for container protection rules Adds the PATCH route to the REST API for container protection rules to allow updating existing container protection rules. Issue https://gitlab.com/gitlab-org/gitlab/-/issues/457518 Changelog: added --- ...ect_container_registry_protection_rules.md | 43 ++++++ ...ect_container_registry_protection_rules.rb | 42 ++++++ ...ontainer_registry_protection_rules_spec.rb | 142 ++++++++++++++++++ 3 files changed, 227 insertions(+) diff --git a/doc/api/project_container_registry_protection_rules.md b/doc/api/project_container_registry_protection_rules.md index cdbd8be23ee383..80475da85e75bc 100644 --- a/doc/api/project_container_registry_protection_rules.md +++ b/doc/api/project_container_registry_protection_rules.md @@ -115,3 +115,46 @@ curl --request POST \ "minimum_access_level_for_delete": "maintainer" }' ``` + +## Update a container registry protection rule + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/457518) in GitLab 17.2. + +Update a container registry protection rule for a project. + +```plaintext +PATCH /api/v4/projects/:id/registry/protection/rules/:protection_rule_id +``` + +Supported attributes: + +| Attribute | Type | Required | Description | +|---------------------------------------|-----------------|----------|--------------------------------| +| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. | +| `protection_rule_id` | integer | Yes | ID of the protection rule to be updated. | +| `repository_path_pattern` | string | No | Container repository path pattern protected by the protection rule. For example `flight/flight-*`. Wildcard character `*` allowed. | +| `minimum_access_level_for_push` | string | No | Minimum GitLab access level to allow to push container images to the container registry. For example `maintainer`, `owner` or `admin`. Must be provided when `minimum_access_level_for_delete` is not set. To unset the value, use an empty string `""`. | +| `minimum_access_level_for_delete` | string | No | Minimum GitLab access level to allow to delete container images in the container registry. For example `maintainer`, `owner`, `admin`. Must be provided when `minimum_access_level_for_push` is not set. To unset the value, use an empty string `""`. | + +If successful, returns [`200`](rest/index.md#status-codes) and the updated protection rule. + +Can return the following status codes: + +- `200 OK`: The protection rule was patched successfully. +- `400 Bad Request`: The patch is invalid. +- `401 Unauthorized`: The access token is invalid. +- `403 Forbidden`: The user does not have permission to patch the protection rule. +- `404 Not Found`: The project was not found. +- `422 Unprocessable Entity`: The protection rule could not be patched, for example, because the `repository_path_pattern` is already taken. + +Example request: + +```shell +curl --request PATCH \ + --header "PRIVATE-TOKEN: " \ + --header "Content-Type: application/json" \ + --url "https://gitlab.example.com/api/v4/projects/7/registry/protection/rules/32" \ + --data '{ + "repository_path_pattern": "flight/flight-*" + }' +``` diff --git a/lib/api/project_container_registry_protection_rules.rb b/lib/api/project_container_registry_protection_rules.rb index 55520481e120e3..4bac0cefde28fc 100644 --- a/lib/api/project_container_registry_protection_rules.rb +++ b/lib/api/project_container_registry_protection_rules.rb @@ -69,6 +69,48 @@ class ProjectContainerRegistryProtectionRules < ::API::Base present response[:container_registry_protection_rule], with: Entities::Projects::ContainerRegistry::Protection::Rule end + + params do + requires :protection_rule_id, type: Integer, + desc: 'The ID of the container protection rule' + end + resource ':protection_rule_id' do + desc 'Update a container protection rule for a project' do + success Entities::Projects::ContainerRegistry::Protection::Rule + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' }, + { code: 422, message: 'Unprocessable Entity' } + ] + tags %w[projects] + hidden true + end + params do + optional :repository_path_pattern, type: String, + desc: 'Container repository path pattern protected by the protection rule. + For example `flight/flight-*`. Wildcard character `*` allowed.' + optional :minimum_access_level_for_push, type: String, + values: ContainerRegistry::Protection::Rule.minimum_access_level_for_pushes.keys << "", + desc: 'Minimum GitLab access level to allow to push container images to the container registry. + For example maintainer, owner or admin. To unset the value, use an empty string `""`.' + optional :minimum_access_level_for_delete, type: String, + values: ContainerRegistry::Protection::Rule.minimum_access_level_for_deletes.keys << "", + desc: 'Minimum GitLab access level to allow to delete container images in the container registry. + For example maintainer, owner or admin. To unset the value, use an empty string `""`.' + end + patch do + protection_rule = user_project.container_registry_protection_rules.find(params[:protection_rule_id]) + response = ::ContainerRegistry::Protection::UpdateRuleService.new(protection_rule, + current_user: current_user, params: declared_params(include_missing: false)).execute + + render_api_error!({ error: response.message }, :unprocessable_entity) if response.error? + + present response[:container_registry_protection_rule], + with: Entities::Projects::ContainerRegistry::Protection::Rule + end + end end end end diff --git a/spec/requests/api/project_container_registry_protection_rules_spec.rb b/spec/requests/api/project_container_registry_protection_rules_spec.rb index 4c29daf73ec594..1aba9958b384c8 100644 --- a/spec/requests/api/project_container_registry_protection_rules_spec.rb +++ b/spec/requests/api/project_container_registry_protection_rules_spec.rb @@ -62,6 +62,26 @@ end end + shared_examples 'rejecting container registry protection rules request when handling rule ids' do + context 'when the rule id is invalid' do + let(:url) { "/projects/#{project.id}/registry/protection/rules/invalid" } + + it_behaves_like 'returning response status', :bad_request + end + + context 'when the rule id does not exist' do + let(:url) { "/projects/#{project.id}/registry/protection/rules/#{non_existing_record_id}" } + + it_behaves_like 'returning response status', :not_found + end + + context 'when the container registry protection rule does belong to another project' do + let(:url) { "/projects/#{other_project.id}/registry/protection/rules/#{container_registry_protection_rule.id}" } + + it_behaves_like 'returning response status', :not_found + end + end + describe 'GET /projects/:id/registry/protection/rules' do let(:url) { "/projects/#{project.id}/registry/protection/rules" } @@ -201,4 +221,126 @@ it_behaves_like 'returning response status', :unauthorized end end + + describe 'PATCH /projects/:id/registry/protection/rules/:protection_rule_id' do + let(:url) { "/projects/#{project.id}/registry/protection/rules/#{container_registry_protection_rule.id}" } + + subject(:patch_container_registry_protection_rule) { patch(api(url, api_user), params: params) } + + it_behaves_like 'rejecting project container protection rules request when not enough permissions' + + context 'for maintainer' do + let(:api_user) { maintainer } + let_it_be(:changed_repository_path_pattern) do + "#{container_registry_protection_rule.repository_path_pattern}-changed" + end + + context 'with full changeset' do + before do + params[:repository_path_pattern] = changed_repository_path_pattern + end + + it 'updates a container registry protection rule' do + patch_container_registry_protection_rule + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to match(hash_including({ + 'project_id' => container_registry_protection_rule.project.id, + 'repository_path_pattern' => changed_repository_path_pattern, + 'minimum_access_level_for_push' => container_registry_protection_rule.minimum_access_level_for_push, + 'minimum_access_level_for_delete' => container_registry_protection_rule.minimum_access_level_for_delete + })) + end + end + + context 'with a single change' do + let(:params) { { repository_path_pattern: changed_repository_path_pattern } } + + it 'updates a container registry protection rule' do + patch_container_registry_protection_rule + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response["repository_path_pattern"]).to eq(changed_repository_path_pattern) + end + end + + context 'with minimum_access_level_to_push set to nil' do + before do + params[:minimum_access_level_for_push] = "" + end + + it 'clears the minimum_access_level_to_push' do + patch_container_registry_protection_rule + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response["minimum_access_level_for_push"]).to be_nil + end + + context 'with minimum_access_level_to_delete set to nil as well' do + before do + params[:minimum_access_level_for_delete] = "" + end + + it_behaves_like 'returning response status', :unprocessable_entity + end + end + + context 'with minimum_access_level_to_delete set to nil' do + before do + params[:minimum_access_level_for_delete] = "" + end + + it 'clears the minimum_access_level_to_delete' do + patch_container_registry_protection_rule + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response["minimum_access_level_for_delete"]).to be_nil + end + + context 'with minimum_access_level_to_push set to nil as well' do + before do + params[:minimum_access_level_for_push] = "" + end + + it_behaves_like 'returning response status', :unprocessable_entity + end + end + + context 'with invalid repository_path_pattern' do + before do + params[:repository_path_pattern] = "not in enum" + end + + it_behaves_like 'returning response status', :unprocessable_entity + end + + context 'with invalid minimum_access_level_for_push' do + before do + params[:minimum_access_level_for_push] = "not in enum" + end + + it_behaves_like 'returning response status', :bad_request + end + + context 'with already existing repository_path_pattern' do + before do + other_protection_rule = create(:container_registry_protection_rule, project: project, + repository_path_pattern: "#{project.full_path}/path") + params[:repository_path_pattern] = other_protection_rule.repository_path_pattern + end + + it_behaves_like 'returning response status', :unprocessable_entity + end + + it_behaves_like 'rejecting container registry protection rules request when handling rule ids' + it_behaves_like 'rejecting container registry protection rules request when enough permissions' + end + + context 'with invalid token' do + subject(:patch_container_registry_protection_rule) do + patch(api(url), headers: headers_with_invalid_token, params: params) + end + + it_behaves_like 'returning response status', :unauthorized + end + end end -- GitLab From 21fddaeb71b718c6cfbebd6eeace79fe585dd482 Mon Sep 17 00:00:00 2001 From: Nicholas <1494491-nwittstruck@users.noreply.gitlab.com> Date: Mon, 15 Jul 2024 09:18:55 +0000 Subject: [PATCH 2/2] Protected containers: Add PATCH REST API for container protection rules Adds the PATCH route to the REST API for container protection rules to allow updating existing container protection rules. Includes feedback regarding whitespace. Issue https://gitlab.com/gitlab-org/gitlab/-/issues/457518 Changelog: added --- .../project_container_registry_protection_rules.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/api/project_container_registry_protection_rules.md b/doc/api/project_container_registry_protection_rules.md index 80475da85e75bc..5aa07210481ae6 100644 --- a/doc/api/project_container_registry_protection_rules.md +++ b/doc/api/project_container_registry_protection_rules.md @@ -128,13 +128,13 @@ PATCH /api/v4/projects/:id/registry/protection/rules/:protection_rule_id Supported attributes: -| Attribute | Type | Required | Description | -|---------------------------------------|-----------------|----------|--------------------------------| -| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. | -| `protection_rule_id` | integer | Yes | ID of the protection rule to be updated. | -| `repository_path_pattern` | string | No | Container repository path pattern protected by the protection rule. For example `flight/flight-*`. Wildcard character `*` allowed. | -| `minimum_access_level_for_push` | string | No | Minimum GitLab access level to allow to push container images to the container registry. For example `maintainer`, `owner` or `admin`. Must be provided when `minimum_access_level_for_delete` is not set. To unset the value, use an empty string `""`. | -| `minimum_access_level_for_delete` | string | No | Minimum GitLab access level to allow to delete container images in the container registry. For example `maintainer`, `owner`, `admin`. Must be provided when `minimum_access_level_for_push` is not set. To unset the value, use an empty string `""`. | +| Attribute | Type | Required | Description | +|-----------------------------------|----------------|----------|-------------| +| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. | +| `protection_rule_id` | integer | Yes | ID of the protection rule to be updated. | +| `repository_path_pattern` | string | No | Container repository path pattern protected by the protection rule. For example `flight/flight-*`. Wildcard character `*` allowed. | +| `minimum_access_level_for_push` | string | No | Minimum GitLab access level to allow to push container images to the container registry. For example `maintainer`, `owner` or `admin`. Must be provided when `minimum_access_level_for_delete` is not set. To unset the value, use an empty string `""`. | +| `minimum_access_level_for_delete` | string | No | Minimum GitLab access level to allow to delete container images in the container registry. For example `maintainer`, `owner`, `admin`. Must be provided when `minimum_access_level_for_push` is not set. To unset the value, use an empty string `""`. | If successful, returns [`200`](rest/index.md#status-codes) and the updated protection rule. -- GitLab