Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-06-21 03:14:26 +00:00
parent 9aca3d8ca6
commit 267bb63c52
14 changed files with 377 additions and 149 deletions

View File

@ -1,9 +1,7 @@
@import 'mixins_and_variables_and_functions';
@import '@gitlab/ui/src/scss/bootstrap';
@import 'bootstrap-vue/src/index';
@import '@gitlab/ui/src/scss/utilities';
@import '@gitlab/ui/src/scss/bootstrap_vue';
// We should only import styles that we actually use.
@import '@gitlab/ui/src/components/base/alert/alert';

View File

@ -8,6 +8,10 @@ module Issuable
end
def execute(issuable)
# load sync object before destroy otherwise we cannot access it for
# deletion of label links in delete_label_links
@synced_object_to_delete = issuable.try(:sync_object)
before_destroy(issuable)
after_destroy(issuable) if issuable.destroy
end
@ -29,14 +33,32 @@ module Issuable
end
def delete_todos(issuable)
synced_object_to_delete = @synced_object_to_delete
issuable.run_after_commit_or_now do
TodosDestroyer::DestroyedIssuableWorker.perform_async(issuable.id, issuable.class.name)
TodosDestroyer::DestroyedIssuableWorker.perform_async(issuable.id, issuable.class.base_class.name)
# if there is a sync object, we need to cleanup its todos as well
next unless synced_object_to_delete
TodosDestroyer::DestroyedIssuableWorker.perform_async(
synced_object_to_delete.id, synced_object_to_delete.class.base_class.name
)
end
end
def delete_label_links(issuable)
synced_object_to_delete = @synced_object_to_delete
issuable.run_after_commit_or_now do
Issuable::LabelLinksDestroyWorker.perform_async(issuable.id, issuable.class.name)
Issuable::LabelLinksDestroyWorker.perform_async(issuable.id, issuable.class.base_class.name)
# if there is a sync object, we need to cleanup its label links as well
next unless synced_object_to_delete
Issuable::LabelLinksDestroyWorker.perform_async(
synced_object_to_delete.id, synced_object_to_delete.class.base_class.name
)
end
end
end

View File

@ -3,3 +3,4 @@
= webpack_bundle_tag 'jira_connect_app'
- add_page_specific_style 'page_bundles/jira_connect', defer: false
- add_page_specific_style 'tailwind'

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class RemoveProjectStatisticsRepositorySizeAndProjectIdIndex < Gitlab::Database::Migration[2.2]
milestone '17.1'
INDEX_NAME = 'index_project_statistics_on_repository_size_and_project_id'
COLUMNS = %i[repository_size project_id]
# TODO: Index to be destroyed synchronously in https://gitlab.com/gitlab-org/gitlab/-/issues/466691
def up
return unless should_run?
prepare_async_index_removal :project_statistics, COLUMNS, name: INDEX_NAME
end
def down
return unless should_run?
unprepare_async_index :project_statistics, COLUMNS, name: INDEX_NAME
end
def should_run?
Gitlab.com_except_jh?
end
end

View File

@ -0,0 +1 @@
572b8d57ea60ff7cf45598081d29d82a7134221d7e27a1be1c562fc7f081536e

View File

@ -114,6 +114,47 @@ curl --request POST \
}'
```
### Update a package protection rule
Update a package protection rule for a project.
```plaintext
PATCH /api/v4/projects/:id/packages/protection/rules/:package_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. |
| `package_protection_rule_id` | integer | Yes | ID of the package protection rule to be updated. |
| `package_name_pattern` | string | No | Package name protected by the protection rule. For example `@my-scope/my-package-*`. Wildcard character `*` allowed. |
| `package_type` | string | No | Package type protected by the protection rule. For example `npm`. |
| `minimum_access_level_for_push` | string | No | Minimum GitLab access level able to push a package. For example `developer`, `maintainer`, `owner`. |
If successful, returns [`200`](rest/index.md#status-codes) and the updated package protection rule.
Can return the following status codes:
- `200 OK`: The package 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 a package protection rule.
- `404 Not Found`: The project was not found.
- `422 Unprocessable Entity`: The package protection rule could not be patched, for example, because the `package_name_pattern` is already taken.
Example request:
```shell
curl --request PATCH \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-Type: application/json" \
--url "https://gitlab.example.com/api/v4/projects/7/packages/protection/rules/32" \
--data '{
"package_name_pattern": "new-package-name-pattern-*"
}'
```
## Delete a package protection rule
Deletes a package protection rule from a project.

View File

@ -18,70 +18,111 @@ module API
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get list of package protection rules for a project' do
success Entities::Projects::Packages::Protection::Rule
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[projects]
is_array true
end
get ':id/packages/protection/rules' do
present user_project.package_protection_rules, with: Entities::Projects::Packages::Protection::Rule
end
resource ':id/packages/protection/rules' do
desc 'Get list of package protection rules for a project' do
success Entities::Projects::Packages::Protection::Rule
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[projects]
is_array true
hidden true
end
get do
present user_project.package_protection_rules, with: Entities::Projects::Packages::Protection::Rule
end
desc 'Create a package protection rule for a project' do
success Entities::Projects::Packages::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]
end
params do
requires :package_name_pattern, type: String,
desc: 'Package name protected by the rule. For example @my-scope/my-package-*. Wildcard character * allowed.'
requires :package_type, type: String, values: Packages::Protection::Rule.package_types.keys,
desc: 'Package type protected by the rule. For example npm.'
requires :minimum_access_level_for_push, type: String,
values: Packages::Protection::Rule.minimum_access_level_for_pushes.keys,
desc: 'Minimum GitLab access level able to push a package. For example developer, maintainer, owner.'
end
post ':id/packages/protection/rules' do
response = ::Packages::Protection::CreateRuleService.new(project: user_project, current_user: current_user,
params: declared_params(params)).execute
desc 'Create a package protection rule for a project' do
success Entities::Projects::Packages::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
requires :package_name_pattern, type: String,
desc: 'Package name protected by the rule. For example @my-scope/my-package-*.
Wildcard character * allowed.'
requires :package_type, type: String, values: Packages::Protection::Rule.package_types.keys,
desc: 'Package type protected by the rule. For example npm.'
requires :minimum_access_level_for_push, type: String,
values: Packages::Protection::Rule.minimum_access_level_for_pushes.keys,
desc: 'Minimum GitLab access level able to push a package. For example developer, maintainer, owner.'
end
post do
response = ::Packages::Protection::CreateRuleService.new(project: user_project, current_user: current_user,
params: declared_params).execute
render_api_error!({ error: response.message }, :unprocessable_entity) if response.error?
render_api_error!({ error: response.message }, :unprocessable_entity) if response.error?
present response[:package_protection_rule], with: Entities::Projects::Packages::Protection::Rule
end
present response[:package_protection_rule], with: Entities::Projects::Packages::Protection::Rule
end
desc 'Delete package protection rule' do
success code: 204, message: '204 No Content'
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[projects]
end
params do
requires :package_protection_rule_id, type: Integer, desc: 'The ID of the package protection rule'
end
delete ':id/packages/protection/rules/:package_protection_rule_id' do
package_protection_rule = user_project.package_protection_rules.find(params[:package_protection_rule_id])
params do
requires :package_protection_rule_id, type: Integer, desc: 'The ID of the package protection rule'
end
resource ':package_protection_rule_id' do
desc 'Update a package protection rule for a project' do
success Entities::Projects::Packages::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 :package_name_pattern, type: String,
desc: 'Package name protected by the rule. For example @my-scope/my-package-*.
Wildcard character * allowed.'
optional :package_type, type: String, values: Packages::Protection::Rule.package_types.keys,
desc: 'Package type protected by the rule. For example npm.'
optional :minimum_access_level_for_push, type: String,
values: Packages::Protection::Rule.minimum_access_level_for_pushes.keys,
desc: 'Minimum GitLab access level able to push a package. For example developer, maintainer, owner.'
end
patch do
package_protection_rule = user_project.package_protection_rules.find(params[:package_protection_rule_id])
destroy_conditionally!(package_protection_rule) do |package_protection_rule|
response = ::Packages::Protection::DeleteRuleService.new(package_protection_rule,
current_user: current_user).execute
response = ::Packages::Protection::UpdateRuleService.new(package_protection_rule,
current_user: current_user, params: declared_params(include_missing: false)).execute
render_api_error!({ error: response.message }, :bad_request) if response.error?
render_api_error!({ error: response.message }, :unprocessable_entity) if response.error?
present response[:package_protection_rule], with: Entities::Projects::Packages::Protection::Rule
end
desc 'Delete package protection rule' do
success code: 204, message: '204 No Content'
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[projects]
hidden true
end
delete do
package_protection_rule = user_project.package_protection_rules.find(params[:package_protection_rule_id])
destroy_conditionally!(package_protection_rule) do |package_protection_rule|
response = ::Packages::Protection::DeleteRuleService.new(package_protection_rule,
current_user: current_user).execute
render_api_error!({ error: response.message }, :bad_request) if response.error?
end
end
end
end
end

View File

@ -11,7 +11,7 @@ gem 'capybara', '~> 3.40.0'
gem 'capybara-screenshot', '~> 1.0.26'
gem 'rake', '~> 13', '>= 13.2.1'
gem 'rspec', '~> 3.13'
gem 'selenium-webdriver', '= 4.21.1'
gem 'selenium-webdriver', '= 4.22.0'
gem 'airborne', '~> 0.3.7', require: false # airborne is messing with rspec sandboxed mode so not requiring by default
gem 'rest-client', '~> 2.1.0'
gem 'rspec_junit_formatter', '~> 0.6.0'

View File

@ -210,6 +210,7 @@ GEM
llhttp-ffi (0.4.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
logger (1.6.0)
loofah (2.21.3)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@ -318,8 +319,9 @@ GEM
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
selenium-webdriver (4.21.1)
selenium-webdriver (4.22.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
@ -409,11 +411,11 @@ DEPENDENCIES
rspec-parameterized (~> 1.0.2)
rspec_junit_formatter (~> 0.6.0)
ruby-debug-ide (~> 0.7.3)
selenium-webdriver (= 4.21.1)
selenium-webdriver (= 4.22.0)
slack-notifier (~> 2.4)
terminal-table (~> 3.0.2)
warning (~> 1.4)
zeitwerk (~> 2.6, >= 2.6.15)
BUNDLED WITH
2.5.11
2.5.13

View File

@ -11,7 +11,7 @@ module QA
end
it(
'user registers a new group runner',
'user registers a new group runner', :blocking,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/388740'
) do
Flow::Login.sign_in

View File

@ -10,7 +10,7 @@ module QA
runner.remove_via_api!
end
it 'user registers a new project runner', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348025' do
it 'user registers a new project runner', :blocking, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348025' do
Flow::Login.sign_in
runner.project.visit!

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe RemoveProjectStatisticsRepositorySizeAndProjectIdIndex, feature_category: :consumables_cost_management do
let(:migration) { described_class.new }
let(:postgres_async_indexes) { table(:postgres_async_indexes) }
describe '#up' do
subject(:up) { migration.up }
it 'does nothing when not on gitlab.com' do
expect { up }.not_to change { postgres_async_indexes.count }
end
it 'prepares async index removal when on gitlab.com', :saas do
expect { up }.to change { postgres_async_indexes.count }.from(0).to(1)
end
end
describe '#down' do
subject(:down) { migration.down }
before do
postgres_async_indexes.create!(
name: 'index_project_statistics_on_repository_size_and_project_id',
table_name: 'project_statistics',
definition: 'test index'
)
end
it 'does nothing when not on gitlab.com' do
expect { down }.not_to change { postgres_async_indexes.count }
end
it 'unprepares async index removal when on gitlab.com', :saas do
expect { down }.to change { postgres_async_indexes.count }.from(1).to(0)
end
end
end

View File

@ -15,6 +15,12 @@ RSpec.describe API::ProjectPackagesProtectionRules, :aggregate_failures, feature
let_it_be(:invalid_token) { 'invalid-token123' }
let_it_be(:headers_with_invalid_token) { { Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER => invalid_token } }
let(:params) do
{ package_name_pattern: '@my-new-scope/my-package-*',
package_type: package_protection_rule.package_type,
minimum_access_level_for_push: package_protection_rule.minimum_access_level_for_push }
end
shared_examples 'rejecting project packages protection rules request when not enough permissions' do
using RSpec::Parameterized::TableSyntax
@ -34,6 +40,48 @@ RSpec.describe API::ProjectPackagesProtectionRules, :aggregate_failures, feature
end
end
shared_examples 'rejecting project packages protection rules request when enough permissions' do
context 'when feature flag is disabled' do
before do
stub_feature_flags(packages_protected_packages: false)
end
it_behaves_like 'returning response status', :not_found
end
context 'when the project id is invalid' do
let(:url) { "/projects/invalid/packages/protection/rules" }
it_behaves_like 'returning response status', :not_found
end
context 'when the project id does not exist' do
let(:url) { "/projects/#{non_existing_record_id}/packages/protection/rules" }
it_behaves_like 'returning response status', :not_found
end
end
shared_examples 'rejecting project packages protection rules request when handling rule ids' do
context 'when the rule id is invalid' do
let(:url) { "/projects/#{project.id}/packages/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}/packages/protection/rules/#{non_existing_record_id}" }
it_behaves_like 'returning response status', :not_found
end
context 'when the package protection rule does belong to another project' do
let(:url) { "/projects/#{other_project.id}/packages/protection/rules/#{package_protection_rule.id}" }
it_behaves_like 'returning response status', :not_found
end
end
describe 'GET /projects/:id/packages/protection/rules' do
let(:url) { "/projects/#{project.id}/packages/protection/rules" }
@ -55,25 +103,7 @@ RSpec.describe API::ProjectPackagesProtectionRules, :aggregate_failures, feature
expect(json_response.count).to eq(2)
end
context 'when the project id is invalid' do
let(:url) { "/projects/invalid/packages/protection/rules" }
it_behaves_like 'returning response status', :not_found
end
context 'when the project id does not exist' do
let(:url) { "/projects/#{non_existing_record_id}/packages/protection/rules" }
it_behaves_like 'returning response status', :not_found
end
context 'when packages_protected_packages is disabled' do
before do
stub_feature_flags(packages_protected_packages: false)
end
it_behaves_like 'returning response status', :not_found
end
it_behaves_like 'rejecting project packages protection rules request when enough permissions'
end
context 'with invalid token' do
@ -85,11 +115,6 @@ RSpec.describe API::ProjectPackagesProtectionRules, :aggregate_failures, feature
describe 'POST /projects/:id/packages/protection/rules' do
let(:url) { "/projects/#{project.id}/packages/protection/rules" }
let(:params) do
{ package_name_pattern: '@my-new-scope/my-package-*',
package_type: package_protection_rule.package_type,
minimum_access_level_for_push: package_protection_rule.minimum_access_level_for_push }
end
subject(:post_package_rule) { post(api(url, api_user), params: params) }
@ -136,25 +161,86 @@ RSpec.describe API::ProjectPackagesProtectionRules, :aggregate_failures, feature
end
end
context 'when the project id is invalid' do
let(:url) { "/projects/invalid/packages/protection/rules" }
it_behaves_like 'rejecting project packages protection rules request when enough permissions'
end
it_behaves_like 'returning response status', :not_found
end
context 'with invalid token' do
subject(:post_package_rules) { post(api(url), headers: headers_with_invalid_token, params: params) }
context 'when the project id does not exist' do
let(:url) { "/projects/#{non_existing_record_id}/packages/protection/rules" }
it_behaves_like 'returning response status', :unauthorized
end
end
it_behaves_like 'returning response status', :not_found
end
describe 'PATCH /projects/:id/packages/protection/rules/:package_protection_rule_id' do
let(:url) { "/projects/#{project.id}/packages/protection/rules/#{package_protection_rule.id}" }
context 'when packages_protected_packages is disabled' do
subject(:patch_package_rule) { patch(api(url, api_user), params: params) }
it_behaves_like 'rejecting project packages protection rules request when not enough permissions'
context 'for maintainer' do
let(:api_user) { maintainer }
let_it_be(:changed_scope) { '@my-changed-scope/my-package-*' }
context 'with full changeset' do
before do
stub_feature_flags(packages_protected_packages: false)
params[:package_name_pattern] = changed_scope
end
it_behaves_like 'returning response status', :not_found
it 'updates a package protection rule' do
patch_package_rule
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["package_name_pattern"]).to eq(changed_scope)
expect(json_response["package_type"]).to eq(package_protection_rule.package_type)
end
end
context 'with a single change' do
let(:params) { { package_name_pattern: changed_scope } }
it 'updates a package protection rule' do
patch_package_rule
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["package_name_pattern"]).to eq(changed_scope)
end
end
context 'with invalid package_type' do
before do
params[:package_type] = "not in enum"
end
it_behaves_like 'returning response status', :bad_request
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 package_name_pattern' do
before do
other_package_protection_rule = create(:package_protection_rule, project: project,
package_name_pattern: "@my-scope/my-package-*")
params[:package_name_pattern] = other_package_protection_rule.package_name_pattern
end
it_behaves_like 'returning response status', :unprocessable_entity
end
it_behaves_like 'rejecting project packages protection rules request when handling rule ids'
it_behaves_like 'rejecting project packages protection rules request when enough permissions'
end
context 'with invalid token' do
subject(:patch_package_rules) { patch(api(url), headers: headers_with_invalid_token, params: params) }
it_behaves_like 'returning response status', :unauthorized
end
end
@ -175,44 +261,9 @@ RSpec.describe API::ProjectPackagesProtectionRules, :aggregate_failures, feature
end.to raise_error(ActiveRecord::RecordNotFound)
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when the package protection rule does belong to another project' do
let(:url) { "/projects/#{other_project.id}/packages/protection/rules/#{package_protection_rule.id}" }
it_behaves_like 'returning response status', :not_found
end
context 'when the project id is invalid' do
let(:url) { "/projects/invalid/packages/protection/rules/#{package_protection_rule.id}" }
it_behaves_like 'returning response status', :not_found
end
context 'when the project id does not exist' do
let(:url) { "/projects/#{non_existing_record_id}/packages/protection/rules/#{package_protection_rule.id}" }
it_behaves_like 'returning response status', :not_found
end
context 'when the rule id is invalid' do
let(:url) { "/projects/#{project.id}/packages/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}/packages/protection/rules/#{non_existing_record_id}" }
it_behaves_like 'returning response status', :not_found
end
context 'when packages_protected_packages is disabled' do
before do
stub_feature_flags(packages_protected_packages: false)
end
it_behaves_like 'returning response status', :not_found
it_behaves_like 'rejecting project packages protection rules request when handling rule ids'
it_behaves_like 'rejecting project packages protection rules request when enough permissions'
end
context 'with invalid token' do

View File

@ -1,18 +1,22 @@
# frozen_string_literal: true
RSpec.shared_examples_for 'service scheduling async deletes' do
it 'destroys associated todos asynchronously' do
expect(worker_class)
.to receive(:perform_async)
.with(issuable.id, issuable.class.name)
it 'destroys associated todos asynchronously', :sidekiq_inline do
expect(worker_class).to receive(:perform_async).with(issuable.id, issuable.class.base_class.name)
if try(:sync_object).present?
expect(worker_class).to receive(:perform_async).with(sync_object.id, sync_object.class.base_class.name)
end
subject.execute(issuable)
end
it 'works inside a transaction' do
expect(worker_class)
.to receive(:perform_async)
.with(issuable.id, issuable.class.name)
it 'works inside a transaction', :sidekiq_inline do
expect(worker_class).to receive(:perform_async).with(issuable.id, issuable.class.base_class.name)
if try(:sync_object).present?
expect(worker_class).to receive(:perform_async).with(sync_object.id, sync_object.class.base_class.name)
end
ApplicationRecord.transaction do
subject.execute(issuable)