Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5699348c82
commit
6031d32432
|
|
@ -96,7 +96,7 @@ class Ability
|
||||||
|
|
||||||
# Hook call right before ability check.
|
# Hook call right before ability check.
|
||||||
def before_check(policy, ability, user, subject, opts)
|
def before_check(policy, ability, user, subject, opts)
|
||||||
# See Support::AbilityCheck.
|
# See Support::AbilityCheck and Support::PermissionsCheck.
|
||||||
end
|
end
|
||||||
|
|
||||||
def policy_for(user, subject = :global)
|
def policy_for(user, subject = :global)
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,9 @@ class MergeRequest < ApplicationRecord
|
||||||
# when creating new merge request
|
# when creating new merge request
|
||||||
attr_accessor :can_be_created, :compare_commits, :diff_options, :compare
|
attr_accessor :can_be_created, :compare_commits, :diff_options, :compare
|
||||||
|
|
||||||
|
# Flag to skip triggering mergeRequestMergeStatusUpdated GraphQL subscription.
|
||||||
|
attr_accessor :skip_merge_status_trigger
|
||||||
|
|
||||||
participant :reviewers
|
participant :reviewers
|
||||||
|
|
||||||
# Keep states definition to be evaluated before the state_machine block to avoid spec failures.
|
# Keep states definition to be evaluated before the state_machine block to avoid spec failures.
|
||||||
|
|
@ -252,6 +255,8 @@ class MergeRequest < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
after_transition any => [:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking, :can_be_merged, :cannot_be_merged] do |merge_request, transition|
|
after_transition any => [:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking, :can_be_merged, :cannot_be_merged] do |merge_request, transition|
|
||||||
|
next if merge_request.skip_merge_status_trigger
|
||||||
|
|
||||||
merge_request.run_after_commit do
|
merge_request.run_after_commit do
|
||||||
GraphqlTriggers.merge_request_merge_status_updated(merge_request)
|
GraphqlTriggers.merge_request_merge_status_updated(merge_request)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -127,16 +127,23 @@ module MergeRequests
|
||||||
|
|
||||||
merge_requests_array = merge_requests.to_a + merge_requests_from_forks.to_a
|
merge_requests_array = merge_requests.to_a + merge_requests_from_forks.to_a
|
||||||
filter_merge_requests(merge_requests_array).each do |merge_request|
|
filter_merge_requests(merge_requests_array).each do |merge_request|
|
||||||
|
skip_merge_status_trigger = true
|
||||||
|
|
||||||
if branch_and_project_match?(merge_request) || @push.force_push?
|
if branch_and_project_match?(merge_request) || @push.force_push?
|
||||||
merge_request.reload_diff(current_user)
|
merge_request.reload_diff(current_user)
|
||||||
# Clear existing merge error if the push were directed at the
|
# Clear existing merge error if the push were directed at the
|
||||||
# source branch. Clearing the error when the target branch
|
# source branch. Clearing the error when the target branch
|
||||||
# changes will hide the error from the user.
|
# changes will hide the error from the user.
|
||||||
merge_request.merge_error = nil
|
merge_request.merge_error = nil
|
||||||
|
|
||||||
|
# Don't skip trigger since we to update the MR's merge status in real-time
|
||||||
|
# when the push if for the MR's source branch and project.
|
||||||
|
skip_merge_status_trigger = false
|
||||||
elsif merge_request.merge_request_diff.includes_any_commits?(push_commit_ids)
|
elsif merge_request.merge_request_diff.includes_any_commits?(push_commit_ids)
|
||||||
merge_request.reload_diff(current_user)
|
merge_request.reload_diff(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
merge_request.skip_merge_status_trigger = skip_merge_status_trigger
|
||||||
merge_request.mark_as_unchecked
|
merge_request.mark_as_unchecked
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
- group = local_assigns.fetch(:group)
|
- group = local_assigns.fetch(:group)
|
||||||
|
|
||||||
%li.group-row.gl-py-3.gl-align-items-center{ class: 'gl-display-flex!', data: { qa_selector: 'group_row_content' } }
|
%li.group-row.gl-py-3.gl-align-items-center{ class: 'gl-display-flex!', data: { qa_selector: 'group_row_content' } }
|
||||||
.avatar-container.rect-avatar.s40.gl-flex-shrink-0
|
= render Pajamas::AvatarComponent.new(group, size: 32, alt: '')
|
||||||
= group_icon(group, class: "avatar s40")
|
|
||||||
|
|
||||||
.gl-min-w-0.gl-flex-grow-1
|
.gl-min-w-0.gl-flex-grow-1.gl-ml-3
|
||||||
.title
|
.title
|
||||||
= link_to [:admin, group], class: 'group-name', data: { qa_selector: 'group_name_link' } do
|
= link_to [:admin, group], class: 'group-name', data: { qa_selector: 'group_name_link' } do
|
||||||
= group.full_name
|
= group.full_name
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@
|
||||||
- c.body do
|
- c.body do
|
||||||
%ul.content-list.content-list-items-padding
|
%ul.content-list.content-list-items-padding
|
||||||
%li
|
%li
|
||||||
.avatar-container.rect-avatar.s60
|
= render Pajamas::AvatarComponent.new(@group, size: 64, alt: '')
|
||||||
= group_icon(@group, class: "avatar s60")
|
|
||||||
%li
|
%li
|
||||||
%span.light= _('Name:')
|
%span.light= _('Name:')
|
||||||
%strong
|
%strong
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
%ul.content-list
|
%ul.content-list
|
||||||
- @projects.each do |project|
|
- @projects.each do |project|
|
||||||
%li.project-row.gl-align-items-center{ class: 'gl-display-flex!' }
|
%li.project-row.gl-align-items-center{ class: 'gl-display-flex!' }
|
||||||
.avatar-container.rect-avatar.s40.gl-flex-shrink-0
|
= render Pajamas::AvatarComponent.new(project, size: 32, alt: '')
|
||||||
= project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40)
|
.gl-min-w-0.gl-flex-grow-1.gl-ml-3
|
||||||
.gl-min-w-0.gl-flex-grow-1
|
|
||||||
.title
|
.title
|
||||||
= link_to(admin_project_path(project)) do
|
= link_to(admin_project_path(project)) do
|
||||||
%span.project-full-name
|
%span.project-full-name
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@
|
||||||
- title = topic.title || topic.name
|
- title = topic.title || topic.name
|
||||||
|
|
||||||
%li.topic-row.gl-py-3.gl-align-items-center{ class: 'gl-display-flex!', data: { qa_selector: 'topic_row_content' } }
|
%li.topic-row.gl-py-3.gl-align-items-center{ class: 'gl-display-flex!', data: { qa_selector: 'topic_row_content' } }
|
||||||
.avatar-container.rect-avatar.s40.gl-flex-shrink-0
|
= render Pajamas::AvatarComponent.new(topic, size: 32, alt: '')
|
||||||
= topic_icon(topic, class: "avatar s40")
|
|
||||||
|
|
||||||
.gl-min-w-0.gl-flex-grow-1
|
.gl-min-w-0.gl-flex-grow-1.gl-ml-3
|
||||||
.title
|
.title
|
||||||
= link_to title, topic_explore_projects_path(topic_name: topic.name)
|
= link_to title, topic_explore_projects_path(topic_name: topic.name)
|
||||||
%div
|
%div
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
- if current_action?(:new) || current_action?(:create)
|
- if current_action?(:new) || current_action?(:create)
|
||||||
%span.float-left.gl-mr-3
|
%span.float-left.gl-mr-3
|
||||||
\/
|
\/
|
||||||
= text_field_tag 'file_name', params[:file_name], placeholder: "File name", data: { qa_selector: 'file_name_field' },
|
= text_field_tag 'file_name', params[:file_name], placeholder: "Filename", data: { qa_selector: 'file_name_field' },
|
||||||
required: true, class: 'form-control gl-form-input new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '')
|
required: true, class: 'form-control gl-form-input new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '')
|
||||||
= render 'template_selectors'
|
= render 'template_selectors'
|
||||||
- if should_suggest_gitlab_ci_yml?
|
- if should_suggest_gitlab_ci_yml?
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,13 @@ module Gitlab
|
||||||
# client - An instance of Gitlab::GithubImport::Client.
|
# client - An instance of Gitlab::GithubImport::Client.
|
||||||
# project - An instance of Project.
|
# project - An instance of Project.
|
||||||
def import(client, project)
|
def import(client, project)
|
||||||
info(project.id, message: "starting importer", importer: 'Importer::CollaboratorsImporter')
|
info(project.id, message: 'starting importer', importer: 'Importer::CollaboratorsImporter')
|
||||||
waiter = Importer::CollaboratorsImporter
|
return skip_to_next_stage(project) unless has_push_access?(client, project.import_source)
|
||||||
.new(project, client)
|
|
||||||
.execute
|
|
||||||
|
|
||||||
|
waiter = Importer::CollaboratorsImporter.new(project, client).execute
|
||||||
project.import_state.refresh_jid_expiration
|
project.import_state.refresh_jid_expiration
|
||||||
|
|
||||||
AdvanceStageWorker.perform_async(
|
move_to_next_stage(project, { waiter.key => waiter.jobs_remaining })
|
||||||
project.id,
|
|
||||||
{ waiter.key => waiter.jobs_remaining },
|
|
||||||
:pull_requests_merged_by
|
|
||||||
)
|
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Gitlab::Import::ImportFailureService.track(
|
Gitlab::Import::ImportFailureService.track(
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
|
|
@ -41,6 +36,27 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def has_push_access?(client, repo)
|
||||||
|
client.repository(repo).dig(:permissions, :push)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip_to_next_stage(project)
|
||||||
|
Gitlab::GithubImport::Logger.warn(
|
||||||
|
log_attributes(
|
||||||
|
project.id,
|
||||||
|
message: 'no push access rights to fetch collaborators',
|
||||||
|
importer: 'Importer::CollaboratorsImporter'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
move_to_next_stage(project, {})
|
||||||
|
end
|
||||||
|
|
||||||
|
def move_to_next_stage(project, waiters = {})
|
||||||
|
AdvanceStageWorker.perform_async(
|
||||||
|
project.id, waiters, :pull_requests_merged_by
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def abort_on_failure
|
def abort_on_failure
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
---
|
||||||
|
stage: Create
|
||||||
|
group: Source Code
|
||||||
|
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||||
|
---
|
||||||
|
|
||||||
|
# Code Owners development guidelines
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219916) in GitLab 15.10.
|
||||||
|
|
||||||
|
This document was created to help contributors understand the code design of
|
||||||
|
[Code Owners](../../user/project/code_owners.md). You should read this
|
||||||
|
document before making changes to the code for this feature.
|
||||||
|
|
||||||
|
This document is intentionally limited to an overview of how the code is
|
||||||
|
designed, as code can change often. To understand how a specific part of the
|
||||||
|
feature works, view the code and the specs. The details here explain how the
|
||||||
|
major components of the Code Owners feature work.
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
This document should be updated when parts of the codebase referenced in this
|
||||||
|
document are updated, removed, or new parts are added.
|
||||||
|
|
||||||
|
## Business logic
|
||||||
|
|
||||||
|
All of the business logic for code owners is located in the `Gitlab::CodeOwners`
|
||||||
|
namespace. Code Owners is an EE-only feature, so the files only exist in the `./ee` directory.
|
||||||
|
|
||||||
|
- `Gitlab::CodeOwners`: the main module used to interact with the code owner rules.
|
||||||
|
- Defined in `./ee/lib/gitlab/code_owners.rb`.
|
||||||
|
- `Gitlab::CodeOwners::Loader`: finds the correct `CODEOWNER` file and loads the
|
||||||
|
content into a `Gitlab::CodeOwners::File` instance.
|
||||||
|
- Defined in `./ee/lib/gitlab/code_owners/loader.rb`.
|
||||||
|
- `Gitlab::CodeOwners::ReferenceExtractor`: extracts `CODEOWNER` user, group,
|
||||||
|
and email references from texts.
|
||||||
|
- Defined in `./ee/lib/gitlab/code_owners/reference_extractor.rb`.
|
||||||
|
- `Gitlab::CodeOwners::UsersLoader`: the correct `CODEOWNER` file and loads the
|
||||||
|
content into a `Gitlab::CodeOwners::File` instance.
|
||||||
|
- Defined in `./ee/lib/gitlab/code_owners/users_loader.rb`.
|
||||||
|
- `Gitlab::CodeOwners::GroupsLoader`: finds the correct `CODEOWNER` file and loads
|
||||||
|
the content into a `Gitlab::CodeOwners::File` instance.
|
||||||
|
- Defined in `./ee/lib/gitlab/code_owners/groups_loader.rb`.
|
||||||
|
- `Gitlab::CodeOwners::File`: wraps a `CODEOWNERS` file and exposes the data through
|
||||||
|
the class' public methods.
|
||||||
|
- Defined in `./ee/lib/gitlab/code_owners/file.rb`.
|
||||||
|
- `Gitlab::CodeOwners::Entry`: wraps an entry (a pattern and owners line) in a
|
||||||
|
`CODEOWNERS` file and exposes the data through the class' public methods.
|
||||||
|
- Defined in `./ee/lib/gitlab/code_owners/entry.rb`.
|
||||||
|
- `Gitlab::CodeOwners::Validator`: validates no files in the `CODEOWNERS` entries
|
||||||
|
have been changed when a user pushes to a protected branch with `require_code_owner_approval` enabled.
|
||||||
|
- Defined in `./ee/lib/gitlab/code_owners/validator.rb`.
|
||||||
|
|
||||||
|
## Related models
|
||||||
|
|
||||||
|
### `ProtectedBranch`
|
||||||
|
|
||||||
|
The `ProtectedBranch` model is defined in `app/models/protected_branch.rb` and
|
||||||
|
extended in `ee/app/ee/models/protected_branch.rb`. The EE version includes a column
|
||||||
|
named `require_code_owner_approval` which prevents changes from being pushed directly
|
||||||
|
to the branch being protected if the file is listed in `CODEOWNERS`.
|
||||||
|
|
||||||
|
### `ApprovalMergeRequestRule`
|
||||||
|
|
||||||
|
The `ApprovalMergeRequestRule` model is defined in `ee/app/models/approval_merge_request_rule.rb`.
|
||||||
|
The model stores approval rules for a merge request. We use multiple rule types,
|
||||||
|
including a `code_owner` type rule.
|
||||||
|
|
||||||
|
## Controllers and Services
|
||||||
|
|
||||||
|
The following controllers and services below are being used for the approval
|
||||||
|
rules feature to work:
|
||||||
|
|
||||||
|
### `Api::Internal::Base`
|
||||||
|
|
||||||
|
This `/internal/allowed` endpoint is called when pushing to GitLab to ensure the
|
||||||
|
user is allowed to push. The `/internal/allowed` endpoint performs a `Gitlab::Checks::DiffCheck`.
|
||||||
|
In EE, this includes code owner checks.
|
||||||
|
|
||||||
|
Defined in `lib/api/internal/base.rb`.
|
||||||
|
|
||||||
|
### `Repositories::GitHttpController`
|
||||||
|
|
||||||
|
When changes are pushed to GitLab over HTTP, the controller performs an access check
|
||||||
|
to ensure the user is allowed to push. The checks perform a `Gitlab::Checks::DiffCheck`.
|
||||||
|
In EE, this includes Code Owner checks.
|
||||||
|
|
||||||
|
Defined in `app/controllers/repositories/git_http_controller.rb`.
|
||||||
|
|
||||||
|
### `EE::Gitlab::Checks::DiffCheck`
|
||||||
|
|
||||||
|
This module extends the CE `Gitlab::Checks::DiffChecks` class and adds code owner
|
||||||
|
validation. It uses the `Gitlab::CodeOwner::Validator` class to verify users are
|
||||||
|
not pushing files listed in `CODEOWNER` directly to a protected branch while the
|
||||||
|
branch requires code owner approval.
|
||||||
|
|
||||||
|
### `MergeRequests::SyncCodeOwnerApprovalRules`
|
||||||
|
|
||||||
|
This service is defined in `services/merge_requests/sync_code_owner_approval_rules.rb` and used for:
|
||||||
|
|
||||||
|
- Deleting outdated code owner approval rules when new changes are pushed to a merge request.
|
||||||
|
- Creating code owner approval rules for each changed file in a merge request that is also listed in the `CODEOWNER` file.
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
|
||||||
|
These flowcharts should help explain the flow from the controllers down to the
|
||||||
|
models for different features.
|
||||||
|
|
||||||
|
### Push changes to a protected branch with `require_code_owner_approval` enabled
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Api::Internal::Base --> Gitlab::GitAccess
|
||||||
|
Gitlab::GitAccess --> Gitlab::Checks::DiffCheck
|
||||||
|
Gitlab::Checks::DiffCheck --> Gitlab::CodeOwners::Validator
|
||||||
|
Gitlab::CodeOwners::Validator --> ProtectedBranch
|
||||||
|
Gitlab::CodeOwners::Validator --> Gitlab::CodeOwners::Loader
|
||||||
|
Gitlab::CodeOwners::Loader --> Gitlab::CodeOwners::Entry
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sync code owner rules to merge request approval rules
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
EE::ProtectedBranches::CreateService --> MergeRequest::SyncCodeOwnerApprovalRules
|
||||||
|
EE::MergeRequestRefreshService --> MergeRequest::SyncCodeOwnerApprovalRules
|
||||||
|
EE::MergeRequests::ReloadMergeHeadDiffService --> MergeRequest::SyncCodeOwnerApprovalRules
|
||||||
|
EE::MergeRequests::CreateService --> MergeRequests::SyncCodeOwnerApprovalRulesWorker
|
||||||
|
EE::MergeRequests::UpdateService --> MergeRequests::SyncCodeOwnerApprovalRulesWorker
|
||||||
|
MergeRequests::SyncCodeOwnerApprovalRulesWorker --> MergeRequest::SyncCodeOwnerApprovalRules
|
||||||
|
MergeRequest::SyncCodeOwnerApprovalRules --> id1{delete outdated code owner rules}
|
||||||
|
MergeRequest::SyncCodeOwnerApprovalRules --> id2{create rule for each code owner entry}
|
||||||
|
```
|
||||||
|
|
@ -969,6 +969,12 @@ Use lowercase for **personal access token**.
|
||||||
|
|
||||||
Do not use **please**. For details, see the [Microsoft style guide](https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/p/please).
|
Do not use **please**. For details, see the [Microsoft style guide](https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/p/please).
|
||||||
|
|
||||||
|
## prerequisites
|
||||||
|
|
||||||
|
Use **prerequisites** when documenting the steps before a task. Do not use **requirements**.
|
||||||
|
|
||||||
|
For more information, see [the task topic type](../topic_types/task.md).
|
||||||
|
|
||||||
## press
|
## press
|
||||||
|
|
||||||
Use **press** when talking about keyboard keys. For example:
|
Use **press** when talking about keyboard keys. For example:
|
||||||
|
|
@ -1031,6 +1037,12 @@ Do not use **Reporter permissions**. A user who is assigned the Reporter role ha
|
||||||
|
|
||||||
Use title case for **Repository Mirroring**.
|
Use title case for **Repository Mirroring**.
|
||||||
|
|
||||||
|
## requirements
|
||||||
|
|
||||||
|
Use **prerequisites** when documenting the steps before a task. Do not use **requirements**.
|
||||||
|
|
||||||
|
For more information, see [the task topic type](../topic_types/task.md).
|
||||||
|
|
||||||
## respectively
|
## respectively
|
||||||
|
|
||||||
Avoid **respectively** and be more precise instead.
|
Avoid **respectively** and be more precise instead.
|
||||||
|
|
|
||||||
|
|
@ -198,3 +198,124 @@ Say, for example, we extract the whole endpoint into a service. The `can?` check
|
||||||
- If the finder doesn't accept `current_user`, and therefore doesn't check permissions, then probably no.
|
- If the finder doesn't accept `current_user`, and therefore doesn't check permissions, then probably no.
|
||||||
- If the finder accepts `current_user`, and doesn't check permissions, then it would be a good idea to double check other usages of the finder, and we might consider adding authorization.
|
- If the finder accepts `current_user`, and doesn't check permissions, then it would be a good idea to double check other usages of the finder, and we might consider adding authorization.
|
||||||
- If the finder accepts `current_user`, and already checks permissions, then either we need to add our case, or the existing checks are appropriate.
|
- If the finder accepts `current_user`, and already checks permissions, then either we need to add our case, or the existing checks are appropriate.
|
||||||
|
|
||||||
|
### Refactoring permissions
|
||||||
|
|
||||||
|
#### Finding existing permissions checks
|
||||||
|
|
||||||
|
As mentioned [above](#where-should-permissions-be-checked), permissions are
|
||||||
|
often checked in multiple locations for a single endpoint or web request. As a
|
||||||
|
result, finding the list of authorization checks that are run for a given endpoint
|
||||||
|
can be challenging.
|
||||||
|
|
||||||
|
To assist with this, you can locally set `GITLAB_DEBUG_POLICIES=true`.
|
||||||
|
|
||||||
|
This outputs information about which abilities are checked in the requests
|
||||||
|
made in any specs that you run. The output also includes the line of code where the
|
||||||
|
authorization check was made. Caller information is especially helpful in cases
|
||||||
|
where there is metaprogramming used because those cases are difficult to find by
|
||||||
|
grepping for ability name strings.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# example spec run
|
||||||
|
|
||||||
|
GITLAB_DEBUG_POLICIES=true bundle exec rspec spec/controllers/groups_controller_spec.rb:162
|
||||||
|
|
||||||
|
# permissions debug output when spec is run; if multiple policy checks are run they will all be in the debug output.
|
||||||
|
|
||||||
|
POLICY CHECK DEBUG -> policy: GlobalPolicy, ability: create_group, called_from: ["/gitlab/app/controllers/application_controller.rb:245:in `can?'", "/gitlab/app/controllers/groups_controller.rb:255:in `authorize_create_group!'"]
|
||||||
|
```
|
||||||
|
|
||||||
|
This flag is meant to help learn more about authorization checks while
|
||||||
|
refactoring and should not remain enabled for any specs on the default branch.
|
||||||
|
|
||||||
|
#### Understanding logic for individual abilities
|
||||||
|
|
||||||
|
References to an ability may appear in a `DeclarativePolicy` class many times
|
||||||
|
and depend on conditions and rules which reference other abilities. As a result,
|
||||||
|
it can be challenging to know exactly which conditions apply to a particular
|
||||||
|
ability.
|
||||||
|
|
||||||
|
`DeclarativePolicy` provides a `ability_map` for each Policy class, which
|
||||||
|
pulls all Rules for an ability into an array.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
> GroupPolicy.ability_map.map.select { |k,v| k == :read_group_member }
|
||||||
|
=> {:read_group_member=>[[:enable, #<Rule can?(:read_group)>], [:prevent, #<Rule ~can_read_group_member>]]}
|
||||||
|
|
||||||
|
> GroupPolicy.ability_map.map.select { |k,v| k == :read_group }
|
||||||
|
=> {:read_group=>
|
||||||
|
[[:enable, #<Rule public_group>],
|
||||||
|
[:enable, #<Rule logged_in_viewable>],
|
||||||
|
[:enable, #<Rule guest>],
|
||||||
|
[:enable, #<Rule admin>],
|
||||||
|
[:enable, #<Rule has_projects>],
|
||||||
|
[:enable, #<Rule read_package_registry_deploy_token>],
|
||||||
|
[:enable, #<Rule write_package_registry_deploy_token>],
|
||||||
|
[:prevent, #<Rule all?(~public_group, ~admin, user_banned_from_group)>],
|
||||||
|
[:enable, #<Rule auditor>],
|
||||||
|
[:prevent, #<Rule needs_new_sso_session>],
|
||||||
|
[:prevent, #<Rule all?(ip_enforcement_prevents_access, ~owner, ~auditor)>]]}
|
||||||
|
```
|
||||||
|
|
||||||
|
`DeclarativePolicy` also provides a `debug` method that can be used to
|
||||||
|
understand the logic tree for a specific object and actor. The output is similar
|
||||||
|
to the list of rules from `ability_map`. But, `DeclarativePolicy` stops
|
||||||
|
evaluating rules once one `prevent`s an ability, so it is possible that
|
||||||
|
not all conditions are called.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
policy = GroupPolicy.new(User.last, Group.last)
|
||||||
|
policy.debug(:read_group)
|
||||||
|
|
||||||
|
- [0] enable when public_group ((@custom_guest_user1 : Group/139))
|
||||||
|
- [0] enable when logged_in_viewable ((@custom_guest_user1 : Group/139))
|
||||||
|
- [0] enable when admin ((@custom_guest_user1 : Group/139))
|
||||||
|
- [0] enable when auditor ((@custom_guest_user1 : Group/139))
|
||||||
|
- [14] prevent when all?(~public_group, ~admin, user_banned_from_group) ((@custom_guest_user1 : Group/139))
|
||||||
|
- [14] prevent when needs_new_sso_session ((@custom_guest_user1 : Group/139))
|
||||||
|
- [16] enable when guest ((@custom_guest_user1 : Group/139))
|
||||||
|
- [16] enable when has_projects ((@custom_guest_user1 : Group/139))
|
||||||
|
- [16] enable when read_package_registry_deploy_token ((@custom_guest_user1 : Group/139))
|
||||||
|
- [16] enable when write_package_registry_deploy_token ((@custom_guest_user1 : Group/139))
|
||||||
|
[21] prevent when all?(ip_enforcement_prevents_access, ~owner, ~auditor) ((@custom_guest_user1 : Group/139))
|
||||||
|
|
||||||
|
=> #<DeclarativePolicy::Runner::State:0x000000015c665050
|
||||||
|
@called_conditions=
|
||||||
|
#<Set: {
|
||||||
|
"/dp/condition/GroupPolicy/public_group/Group:139",
|
||||||
|
"/dp/condition/GroupPolicy/logged_in_viewable/User:83,Group:139",
|
||||||
|
"/dp/condition/BasePolicy/admin/User:83",
|
||||||
|
"/dp/condition/BasePolicy/auditor/User:83",
|
||||||
|
"/dp/condition/GroupPolicy/user_banned_from_group/User:83,Group:139",
|
||||||
|
"/dp/condition/GroupPolicy/needs_new_sso_session/User:83,Group:139",
|
||||||
|
"/dp/condition/GroupPolicy/guest/User:83,Group:139",
|
||||||
|
"/dp/condition/GroupPolicy/has_projects/User:83,Group:139",
|
||||||
|
"/dp/condition/GroupPolicy/read_package_registry_deploy_token/User:83,Group:139",
|
||||||
|
"/dp/condition/GroupPolicy/write_package_registry_deploy_token/User:83,Group:139"}>,
|
||||||
|
@enabled=false,
|
||||||
|
@prevented=true>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Testing that individual policies are equivalent
|
||||||
|
|
||||||
|
You can use the `'equivalent project policy abilities'` shared example to ensure
|
||||||
|
that 2 project policy abilities are equivalent for all project visibility levels
|
||||||
|
and access levels.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
context 'when refactoring read_pipeline_schedule and read_pipeline' do
|
||||||
|
let(:old_policy) { :read_pipeline_schedule }
|
||||||
|
let(:new_policy) { :read_pipeline }
|
||||||
|
|
||||||
|
it_behaves_like 'equivalent policies'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -149,19 +149,19 @@ Depending on your version of GitLab, the Chain of Custody report is either sent
|
||||||
|
|
||||||
#### Generate commit-specific Chain of Custody report
|
#### Generate commit-specific Chain of Custody report
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267629) in GitLab 13.6.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267629) in GitLab 13.6.
|
||||||
|
> - Support for including all commits instead of only merge commits [added](https://gitlab.com/gitlab-org/gitlab/-/issues/393446) in GitLab 15.10.
|
||||||
|
|
||||||
You can generate a commit-specific Chain of Custody report for a given merge commit SHA. This report provides only the
|
You can generate a commit-specific Chain of Custody report for a given commit SHA. This report provides only the
|
||||||
details for the provided merge commit SHA. Issue [393446](https://gitlab.com/gitlab-org/gitlab/-/issues/393446) proposes
|
details for the provided commit SHA.
|
||||||
to extend the commit SHA filtering to work with all commits instead of only merge commits.
|
|
||||||
|
|
||||||
To generate a commit-specific Chain of Custody report:
|
To generate a commit-specific Chain of Custody report:
|
||||||
|
|
||||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||||
1. On the left sidebar, select **Security and Compliance > Compliance report**.
|
1. On the left sidebar, select **Security and Compliance > Compliance report**.
|
||||||
1. At the top of the compliance report, to the right of **List of all merge commits**, select the down arrow
|
1. At the top of the compliance report, to the right of **List of all commits**, select the down arrow
|
||||||
(**{chevron-lg-down}**).
|
(**{chevron-lg-down}**).
|
||||||
1. Enter the merge commit SHA, and then select **Export commit custody report**.
|
1. Enter the commit SHA, and then select **Export commit custody report**.
|
||||||
|
|
||||||
Depending on your version of GitLab, the Chain of Custody report is either sent through email or available for download.
|
Depending on your version of GitLab, the Chain of Custody report is either sent through email or available for download.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,8 @@ GitHub Enterprise Cloud has
|
||||||
[custom repository roles](https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/about-custom-repository-roles).
|
[custom repository roles](https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/about-custom-repository-roles).
|
||||||
These roles aren't supported and cause partial imports.
|
These roles aren't supported and cause partial imports.
|
||||||
|
|
||||||
|
To import GitHub collaborators, you must have at least the Write role on the GitHub project. Otherwise collaborators import is skipped.
|
||||||
|
|
||||||
## Import from GitHub Enterprise on an internal network
|
## Import from GitHub Enterprise on an internal network
|
||||||
|
|
||||||
If your GitHub Enterprise instance is on a internal network that is inaccessible to the internet, you can use a reverse proxy
|
If your GitHub Enterprise instance is on a internal network that is inaccessible to the internet, you can use a reverse proxy
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,32 @@ for any change you commit through the Web Editor.
|
||||||
To create a text file in the Web Editor:
|
To create a text file in the Web Editor:
|
||||||
|
|
||||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||||
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
|
1. From the project dashboard or repository, next to the branch name,
|
||||||
|
select the plus icon (**{plus}**).
|
||||||
1. From the dropdown list, select **New file**.
|
1. From the dropdown list, select **New file**.
|
||||||
1. Complete the fields. To create a merge request with the new file, ensure the **Start a new merge request with these changes** checkbox is selected.
|
1. Complete the fields.
|
||||||
|
1. To create a merge request with the new file, ensure the **Start a new merge request with these changes** checkbox is selected, if you had chosen a **Target branch** other than the [default branch (such as `main`)](../../../user/project/repository/branches/default.md).
|
||||||
1. Select **Commit changes**.
|
1. Select **Commit changes**.
|
||||||
|
|
||||||
|
### Create a file from a template
|
||||||
|
|
||||||
|
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||||
|
1. On the left sidebar, select **Repository > Files**.
|
||||||
|
1. Next to the project name, select the plus icon (**{plus}**) to display a
|
||||||
|
dropdown list, then select **New file** from the list.
|
||||||
|
1. For **Filename**, provide one of the filenames that GitLab provides a template for:
|
||||||
|
- `.gitignore`
|
||||||
|
- `.gitlab-ci.yml`
|
||||||
|
- `LICENSE`
|
||||||
|
- `Dockerfile`
|
||||||
|
1. Select **Apply a template**, then select the template you want to apply.
|
||||||
|
1. Make your changes to the file.
|
||||||
|
1. Provide a **Commit message**.
|
||||||
|
1. Enter a **Target branch** to merge into. To create a new merge request with
|
||||||
|
your changes, enter a branch name that is not your repository's
|
||||||
|
[default branch](../../../user/project/repository/branches/default.md),
|
||||||
|
1. Select **Commit changes** to add the commit to your branch.
|
||||||
|
|
||||||
## Edit a file
|
## Edit a file
|
||||||
|
|
||||||
To edit a text file in the Web Editor:
|
To edit a text file in the Web Editor:
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ module Gitlab
|
||||||
def load(string)
|
def load(string)
|
||||||
return unless string
|
return unless string
|
||||||
|
|
||||||
object = YAML.safe_load(string, [Symbol])
|
object = YAML.safe_load(string, permitted_classes: [Symbol])
|
||||||
|
|
||||||
object.map do |variable|
|
object.map do |variable|
|
||||||
variable.symbolize_keys.tap do |variable|
|
variable.symbolize_keys.tap do |variable|
|
||||||
|
|
|
||||||
|
|
@ -26847,9 +26847,6 @@ msgstr ""
|
||||||
msgid "Merge blocked: pipeline must succeed. It's waiting for a manual job to continue."
|
msgid "Merge blocked: pipeline must succeed. It's waiting for a manual job to continue."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Merge commit SHA"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Merge commit message"
|
msgid "Merge commit message"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,8 @@ RSpec.describe Gitlab::Ci::Lint, feature_category: :pipeline_composition do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets merged_config' do
|
it 'sets merged_config' do
|
||||||
root_config = YAML.safe_load(content, [Symbol])
|
root_config = YAML.safe_load(content, permitted_classes: [Symbol])
|
||||||
included_config = YAML.safe_load(included_content, [Symbol])
|
included_config = YAML.safe_load(included_content, permitted_classes: [Symbol])
|
||||||
expected_config = included_config.merge(root_config).except(:include).deep_stringify_keys
|
expected_config = included_config.merge(root_config).except(:include).deep_stringify_keys
|
||||||
|
|
||||||
expect(subject.merged_yaml).to eq(expected_config.to_yaml)
|
expect(subject.merged_yaml).to eq(expected_config.to_yaml)
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns expanded yaml config' do
|
it 'returns expanded yaml config' do
|
||||||
expanded_config = YAML.safe_load(config_metadata[:merged_yaml], [Symbol])
|
expanded_config = YAML.safe_load(config_metadata[:merged_yaml], permitted_classes: [Symbol])
|
||||||
included_config = YAML.safe_load(included_yml, [Symbol])
|
included_config = YAML.safe_load(included_yml, permitted_classes: [Symbol])
|
||||||
|
|
||||||
expect(expanded_config).to include(*included_config.keys)
|
expect(expanded_config).to include(*included_config.keys)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ RSpec.describe Gitlab::ImportExport::Config, feature_category: :importers do
|
||||||
EOF
|
EOF
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:config_hash) { YAML.safe_load(config, [Symbol]) }
|
let(:config_hash) { YAML.safe_load(config, permitted_classes: [Symbol]) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow_any_instance_of(described_class).to receive(:parse_yaml) do
|
allow_any_instance_of(described_class).to receive(:parse_yaml) do
|
||||||
|
|
|
||||||
|
|
@ -4321,6 +4321,14 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
|
||||||
transition!
|
transition!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when skip_merge_status_trigger is set to true' do
|
||||||
|
before do
|
||||||
|
subject.skip_merge_status_trigger = true
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'transition not triggering mergeRequestMergeStatusUpdated GraphQL subscription'
|
||||||
|
end
|
||||||
|
|
||||||
context 'when transaction is not committed' do
|
context 'when transaction is not committed' do
|
||||||
it_behaves_like 'transition not triggering mergeRequestMergeStatusUpdated GraphQL subscription' do
|
it_behaves_like 'transition not triggering mergeRequestMergeStatusUpdated GraphQL subscription' do
|
||||||
def transition!
|
def transition!
|
||||||
|
|
|
||||||
|
|
@ -245,8 +245,8 @@ RSpec.describe API::Lint, feature_category: :pipeline_composition do
|
||||||
it 'passes validation' do
|
it 'passes validation' do
|
||||||
ci_lint
|
ci_lint
|
||||||
|
|
||||||
included_config = YAML.safe_load(included_content, [Symbol])
|
included_config = YAML.safe_load(included_content, permitted_classes: [Symbol])
|
||||||
root_config = YAML.safe_load(yaml_content, [Symbol])
|
root_config = YAML.safe_load(yaml_content, permitted_classes: [Symbol])
|
||||||
expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml
|
expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
|
@ -535,8 +535,8 @@ RSpec.describe API::Lint, feature_category: :pipeline_composition do
|
||||||
it 'passes validation' do
|
it 'passes validation' do
|
||||||
ci_lint
|
ci_lint
|
||||||
|
|
||||||
included_config = YAML.safe_load(included_content, [Symbol])
|
included_config = YAML.safe_load(included_content, permitted_classes: [Symbol])
|
||||||
root_config = YAML.safe_load(yaml_content, [Symbol])
|
root_config = YAML.safe_load(yaml_content, permitted_classes: [Symbol])
|
||||||
expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml
|
expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,14 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor
|
||||||
expect(@fork_build_failed_todo).to be_done
|
expect(@fork_build_failed_todo).to be_done
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'triggers mergeRequestMergeStatusUpdated GraphQL subscription conditionally' do
|
||||||
|
expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@merge_request)
|
||||||
|
expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@another_merge_request)
|
||||||
|
expect(GraphqlTriggers).not_to receive(:merge_request_merge_status_updated).with(@fork_merge_request)
|
||||||
|
|
||||||
|
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
|
||||||
|
end
|
||||||
|
|
||||||
context 'when a merge error exists' do
|
context 'when a merge error exists' do
|
||||||
let(:error_message) { 'This is a merge error' }
|
let(:error_message) { 'This is a merge error' }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,7 @@ end
|
||||||
# Disabled because it's causing N+1 queries.
|
# Disabled because it's causing N+1 queries.
|
||||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/396352.
|
# See https://gitlab.com/gitlab-org/gitlab/-/issues/396352.
|
||||||
# Support::AbilityCheck.inject(Ability.singleton_class)
|
# Support::AbilityCheck.inject(Ability.singleton_class)
|
||||||
|
Support::PermissionsCheck.inject(Ability.singleton_class)
|
||||||
|
|
||||||
ActiveRecord::Migration.maintain_test_schema!
|
ActiveRecord::Migration.maintain_test_schema!
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Support
|
||||||
|
module PermissionsCheck
|
||||||
|
def self.inject(mod)
|
||||||
|
mod.prepend PermissionsExtension if Gitlab::Utils.to_boolean(ENV['GITLAB_DEBUG_POLICIES'])
|
||||||
|
end
|
||||||
|
|
||||||
|
module PermissionsExtension
|
||||||
|
def before_check(policy, ability, _user, _subject, _opts)
|
||||||
|
puts(
|
||||||
|
"POLICY CHECK DEBUG -> " \
|
||||||
|
"policy: #{policy.class.name}, ability: #{ability}, called_from: #{caller_locations(2, 5)}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -401,3 +401,24 @@ RSpec.shared_examples 'package access with repository disabled' do
|
||||||
|
|
||||||
it { is_expected.to be_allowed(:read_package) }
|
it { is_expected.to be_allowed(:read_package) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'equivalent project policy abilities' do
|
||||||
|
where(:project_visibility, :user_role_on_project) do
|
||||||
|
project_visibilities = [:public, :internal, :private]
|
||||||
|
user_role_on_project = [:anonymous, :non_member, :guest, :reporter, :developer, :maintainer, :owner, :admin]
|
||||||
|
project_visibilities.product(user_role_on_project)
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
it 'evaluates the same' do
|
||||||
|
project = public_send("#{project_visibility}_project")
|
||||||
|
current_user = public_send(user_role_on_project)
|
||||||
|
enable_admin_mode!(current_user) if user_role_on_project == :admin
|
||||||
|
policy = ProjectPolicy.new(current_user, project)
|
||||||
|
old_permissions = policy.allowed?(old_policy)
|
||||||
|
new_permissions = policy.allowed?(new_policy)
|
||||||
|
|
||||||
|
expect(old_permissions).to eq new_permissions
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -11,40 +11,63 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportCollaboratorsWorker, feature_c
|
||||||
let(:client) { instance_double(Gitlab::GithubImport::Client) }
|
let(:client) { instance_double(Gitlab::GithubImport::Client) }
|
||||||
|
|
||||||
describe '#import' do
|
describe '#import' do
|
||||||
it 'imports all the pull requests' do
|
let(:push_rights_granted) { true }
|
||||||
waiter = Gitlab::JobWaiter.new(2, '123')
|
|
||||||
|
|
||||||
expect(Gitlab::GithubImport::Importer::CollaboratorsImporter)
|
before do
|
||||||
.to receive(:new)
|
allow(client).to receive(:repository).with(project.import_source)
|
||||||
.with(project, client)
|
.and_return({ permissions: { push: push_rights_granted } })
|
||||||
.and_return(importer)
|
|
||||||
expect(importer).to receive(:execute).and_return(waiter)
|
|
||||||
|
|
||||||
expect(import_state).to receive(:refresh_jid_expiration)
|
|
||||||
|
|
||||||
expect(Gitlab::GithubImport::AdvanceStageWorker)
|
|
||||||
.to receive(:perform_async)
|
|
||||||
.with(project.id, { '123' => 2 }, :pull_requests_merged_by)
|
|
||||||
|
|
||||||
worker.import(client, project)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
it 'raises an error' do
|
context 'when user has push access for this repo' do
|
||||||
exception = StandardError.new('_some_error_')
|
it 'imports all the pull requests' do
|
||||||
|
waiter = Gitlab::JobWaiter.new(2, '123')
|
||||||
|
|
||||||
expect_next_instance_of(Gitlab::GithubImport::Importer::CollaboratorsImporter) do |importer|
|
expect(Gitlab::GithubImport::Importer::CollaboratorsImporter)
|
||||||
expect(importer).to receive(:execute).and_raise(exception)
|
.to receive(:new)
|
||||||
|
.with(project, client)
|
||||||
|
.and_return(importer)
|
||||||
|
expect(importer).to receive(:execute).and_return(waiter)
|
||||||
|
|
||||||
|
expect(import_state).to receive(:refresh_jid_expiration)
|
||||||
|
|
||||||
|
expect(Gitlab::GithubImport::AdvanceStageWorker)
|
||||||
|
.to receive(:perform_async)
|
||||||
|
.with(project.id, { '123' => 2 }, :pull_requests_merged_by)
|
||||||
|
|
||||||
|
worker.import(client, project)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
expect(Gitlab::Import::ImportFailureService).to receive(:track)
|
|
||||||
.with(
|
|
||||||
project_id: project.id,
|
|
||||||
exception: exception,
|
|
||||||
error_source: described_class.name,
|
|
||||||
fail_import: true,
|
|
||||||
metrics: true
|
|
||||||
).and_call_original
|
|
||||||
|
|
||||||
expect { worker.import(client, project) }.to raise_error(StandardError)
|
context 'when user do not have push access for this repo' do
|
||||||
|
let(:push_rights_granted) { false }
|
||||||
|
|
||||||
|
it 'skips stage' do
|
||||||
|
expect(Gitlab::GithubImport::Importer::CollaboratorsImporter).not_to receive(:new)
|
||||||
|
|
||||||
|
expect(Gitlab::GithubImport::AdvanceStageWorker)
|
||||||
|
.to receive(:perform_async)
|
||||||
|
.with(project.id, {}, :pull_requests_merged_by)
|
||||||
|
|
||||||
|
worker.import(client, project)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an error' do
|
||||||
|
exception = StandardError.new('_some_error_')
|
||||||
|
|
||||||
|
expect_next_instance_of(Gitlab::GithubImport::Importer::CollaboratorsImporter) do |importer|
|
||||||
|
expect(importer).to receive(:execute).and_raise(exception)
|
||||||
|
end
|
||||||
|
expect(Gitlab::Import::ImportFailureService).to receive(:track)
|
||||||
|
.with(
|
||||||
|
project_id: project.id,
|
||||||
|
exception: exception,
|
||||||
|
error_source: described_class.name,
|
||||||
|
fail_import: true,
|
||||||
|
metrics: true
|
||||||
|
).and_call_original
|
||||||
|
|
||||||
|
expect { worker.import(client, project) }.to raise_error(StandardError)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ module Tooling
|
||||||
def parse(yaml_files)
|
def parse(yaml_files)
|
||||||
Array(yaml_files).each do |yaml_file|
|
Array(yaml_files).each do |yaml_file|
|
||||||
data = File.read(yaml_file)
|
data = File.read(yaml_file)
|
||||||
metadata, example_groups = data.split("---\n").reject(&:empty?).map { |yml| YAML.safe_load(yml, [Symbol]) }
|
metadata, example_groups = data.split("---\n").reject(&:empty?).map do |yml|
|
||||||
|
YAML.safe_load(yml, permitted_classes: [Symbol])
|
||||||
|
end
|
||||||
|
|
||||||
if example_groups.nil?
|
if example_groups.nil?
|
||||||
puts "No examples in #{yaml_file}! Metadata: #{metadata}"
|
puts "No examples in #{yaml_file}! Metadata: #{metadata}"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue