Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-12-06 21:10:14 +00:00
parent a15c9bc9eb
commit 36c5bf80d4
48 changed files with 589 additions and 463 deletions

View File

@ -24,7 +24,7 @@ https://docs.gitlab.com/ee/development/documentation/index.html#move-or-rename-a
specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories.
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/documentation/index.html#redirections-for-pages-with-disqus-comments)
to the new document if there are any Disqus comments on the old document thread.
- [ ] Update the link in `features.yml` (if applicable)
- [ ] Update the link in `features.yml` (if applicable).
- [ ] Assign one of the technical writers for review.
/label ~documentation ~"Technical Writing"

View File

@ -41,7 +41,7 @@ They are frequently updated, and everyone should make sure they are aware of the
## Reviewers
When the content is ready for review, it must be reviewed by Technical Writer and Engineering Manager, but can also be reviewed by
When the content is ready for review, it must be reviewed by a Technical Writer and Engineering Manager, but can also be reviewed by
Product Marketing, Product Design, and the Product Leaders for this area. Please use the
[Reviewers for Merge Requests](https://docs.gitlab.com/ee/user/project/merge_requests/getting_started#reviewer)
feature for all reviews. Reviewers will then `approve` the MR and remove themselves from Reviewers when their review is complete.
@ -61,7 +61,7 @@ as needed, @ message the PM to inform them the first review is complete, and rem
yourself as a reviewer if it's not ready for merge yet.
<details>
<summary>Expand for Details </summary>
<summary>Expand for Details</summary>
- [ ] Title:
- Length limit: 7 words (not including articles or prepositions).

View File

@ -8,7 +8,7 @@
## Author's checklist
- [ ] Consider taking [the GitLab Technical Writing Fundamentals course](https://gitlab.edcast.com/pathways/ECL-02528ee2-c334-4e16-abf3-e9d8b8260de4)
- [ ] Consider taking [the GitLab Technical Writing Fundamentals course](https://gitlab.edcast.com/pathways/ECL-02528ee2-c334-4e16-abf3-e9d8b8260de4).
- [ ] Follow the:
- [Documentation process](https://docs.gitlab.com/ee/development/documentation/workflow.html).
- [Documentation guidelines](https://docs.gitlab.com/ee/development/documentation/).
@ -40,7 +40,7 @@ Documentation-related MRs should be reviewed by a Technical Writer for a non-blo
- [ ] The headings (other than the page title) should be active. Instead of `Configuring GDK`, say something like `Configure GDK`.
- [ ] Any task steps should be written as a numbered list.
- If the content still needs to be edited for topic types, you can create a follow-up issue with the ~"docs-technical-debt" label.
- [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review.
- [ ] Review by assigned maintainer, who can always request/require the reviews above. Maintainer's review can occur before or after a technical writer review.
- [ ] Ensure a release milestone is set.
/label ~documentation ~"type::maintenance"

View File

@ -4,7 +4,7 @@
Please link to the respective test case in the testcases project
-->
### Check-list
### Checklist
- [ ] Confirm the test has a [`testcase:` tag linking to an existing test case](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/best_practices.html#link-a-test-to-its-test-case-issue) in the test case project.
- [ ] Note if the test is intended to run in specific scenarios. If a scenario is new, add a link to the MR that adds the new scenario.
@ -15,7 +15,7 @@ Please link to the respective test case in the testcases project
- [ ] Verify the tags to ensure it runs on the desired test environments.
- [ ] If this MR has a dependency on another MR, such as a GitLab QA MR, specify the order in which the MRs should be merged.
- [ ] (If applicable) Create a follow-up issue to document [the special setup](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/running_tests_that_require_special_setup.html) necessary to run the test: ISSUE_LINK
- [ ] If the test requires an admin's personal access token, ensure that the test passes on your local with and without the `GITLAB_QA_ADMIN_ACCESS_TOKEN` provided.
- [ ] If the test requires an admin's personal access token, ensure that the test passes on your local environment with and without the `GITLAB_QA_ADMIN_ACCESS_TOKEN` provided.
<!-- Base labels. -->
/label ~"Quality" ~"QA" ~test

View File

@ -12,20 +12,20 @@ Please describe the proposal and add a link to the source (for example, http://w
### Check-list
- [ ] Make sure this MR enables a static analysis check rule for new usage but
ignores current offenses
- [ ] Mention this proposal in the relevant Slack channels (e.g. `#development`, `#backend`, `#frontend`)
ignores current offenses.
- [ ] Mention this proposal in the relevant Slack channels (e.g. `#development`, `#backend`, `#frontend`).
- [ ] If there is a choice to make between two potential styles, set up an emoji vote in the MR:
- CHOICE_A: :a:
- CHOICE_B: :b:
- Vote yourself for both choices so that people know these are the choices
- [ ] The MR doesn't have significant objections, and is getting a majority of :+1: vs :-1: (remember that [we don't need to reach a consensus](https://about.gitlab.com/handbook/values/#collaboration-is-not-consensus))
- [ ] (If applicable) One style is getting a majority of vote (compared to the other choice)
- [ ] (If applicable) Update the MR with the chosen style
- Vote for both choices, so they are visible to others.
- [ ] The MR doesn't have significant objections, and is getting a majority of :+1: vs :-1: (remember that [we don't need to reach a consensus](https://about.gitlab.com/handbook/values/#collaboration-is-not-consensus)).
- [ ] (If applicable) One style is getting a majority of vote (compared to the other choice).
- [ ] (If applicable) Update the MR with the chosen style.
- [ ] Create a follow-up issue to fix the current offenses as a separate iteration: ISSUE_LINK
- [ ] Follow the [review process](https://docs.gitlab.com/ee/development/code_review.html) as usual
- [ ] Follow the [review process](https://docs.gitlab.com/ee/development/code_review.html) as usual.
- [ ] Once approved and merged by a maintainer, mention it again:
- [ ] In the relevant Slack channels (e.g. `#development`, `#backend`, `#frontend`)
- [ ] (Optional depending on the impact of the change) In the Engineering Week in Review
- [ ] In the relevant Slack channels (e.g. `#development`, `#backend`, `#frontend`).
- [ ] (Optional depending on the impact of the change) In the Engineering Week in Review.
/label ~"Engineering Productivity" ~"development guidelines" ~"static code analysis"

View File

@ -9,7 +9,7 @@
<!-- Link related issues below. -->
## Check-list
## Checklist
### Pre-merge

View File

@ -20,13 +20,13 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
- [ ] Assign to a reviewer and maintainer, per our [Code Review process].
- [ ] Ensure it's approved according to our [Approval Guidelines].
- [ ] Ensure it's approved by an AppSec engineer.
- Please see the security release [Code reviews and Approvals](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#code-reviews-and-approvals) documentation for details on which AppSec team member to ping for approval.
- Please see the security release [Code reviews and Approvals] documentation for details on which AppSec team member to ping for approval.
- Trigger the [`package-and-qa` build]. The docker image generated will be used by the AppSec engineer to validate the security vulnerability has been remediated.
- [ ] For a backport MR targeting a versioned stable branch (`X-Y-stable-ee`)
- [ ] For a backport MR targeting a versioned stable branch (`X-Y-stable-ee`).
- [ ] Milestone is set to the version this backport applies to. A closed milestone can be assigned via [quick actions].
- [ ] Ensure it's approved by a maintainer.
**Note:** Reviewer/maintainer should not be a Release Manager
**Note:** Reviewer/maintainer should not be a Release Manager.
## Maintainer checklist
@ -39,6 +39,7 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
[quick actions]: https://docs.gitlab.com/ee/user/project/quick_actions.html#quick-actions-for-issues-merge-requests-and-epics
[CHANGELOG entry]: https://docs.gitlab.com/ee/development/changelog.html#overview
[Code Review process]: https://docs.gitlab.com/ee/development/code_review.html
[Code reviews and Approvals]: (https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#code-reviews-and-approvals)
[Approval Guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
[Canonical repository]: https://gitlab.com/gitlab-org/gitlab
[`package-and-qa` build]: https://docs.gitlab.com/ee/development/testing_guide/end_to_end/#using-the-package-and-qa-job

View File

@ -23,7 +23,7 @@ module Integrations::Actions
format.html do
if saved
PropagateIntegrationWorker.perform_async(integration.id)
redirect_to scoped_edit_integration_path(integration), notice: success_message
redirect_to scoped_edit_integration_path(integration, project: integration.project, group: integration.group), notice: success_message
else
render 'shared/integrations/edit'
end

View File

@ -17,31 +17,31 @@ module IntegrationsHelper
"#{event}_events"
end
def scoped_integrations_path
if @project.present?
project_settings_integrations_path(@project)
elsif @group.present?
group_settings_integrations_path(@group)
def scoped_integrations_path(project: nil, group: nil)
if project.present?
project_settings_integrations_path(project)
elsif group.present?
group_settings_integrations_path(group)
else
integrations_admin_application_settings_path
end
end
def scoped_integration_path(integration)
if @project.present?
project_service_path(@project, integration)
elsif @group.present?
group_settings_integration_path(@group, integration)
def scoped_integration_path(integration, project: nil, group: nil)
if project.present?
project_service_path(project, integration)
elsif group.present?
group_settings_integration_path(group, integration)
else
admin_application_settings_integration_path(integration)
end
end
def scoped_edit_integration_path(integration)
if @project.present?
edit_project_service_path(@project, integration)
elsif @group.present?
edit_group_settings_integration_path(@group, integration)
def scoped_edit_integration_path(integration, project: nil, group: nil)
if project.present?
edit_project_service_path(project, integration)
elsif group.present?
edit_group_settings_integration_path(group, integration)
else
edit_admin_application_settings_integration_path(integration)
end
@ -51,11 +51,11 @@ module IntegrationsHelper
overrides_admin_application_settings_integration_path(integration, options)
end
def scoped_test_integration_path(integration)
if @project.present?
test_project_service_path(@project, integration)
elsif @group.present?
test_group_settings_integration_path(@group, integration)
def scoped_test_integration_path(integration, project: nil, group: nil)
if project.present?
test_project_service_path(project, integration)
elsif group.present?
test_group_settings_integration_path(group, integration)
else
test_admin_application_settings_integration_path(integration)
end
@ -71,7 +71,7 @@ module IntegrationsHelper
end
end
def integration_form_data(integration, group: nil)
def integration_form_data(integration, project: nil, group: nil)
form_data = {
id: integration.id,
show_active: integration.show_active_box?.to_s,
@ -87,9 +87,9 @@ module IntegrationsHelper
inherit_from_id: integration.inherit_from_id,
integration_level: integration_level(integration),
editable: integration.editable?.to_s,
cancel_path: scoped_integrations_path,
cancel_path: scoped_integrations_path(project: project, group: group),
can_test: integration.testable?.to_s,
test_path: scoped_test_integration_path(integration),
test_path: scoped_test_integration_path(integration, project: project, group: group),
reset_path: scoped_reset_integration_path(integration, group: group)
}
@ -107,9 +107,9 @@ module IntegrationsHelper
}
end
def integration_list_data(integrations)
def integration_list_data(integrations, group: nil, project: nil)
{
integrations: integrations.map { |i| serialize_integration(i) }.to_json
integrations: integrations.map { |i| serialize_integration(i, group: group, project: project) }.to_json
}
end
@ -215,13 +215,13 @@ module IntegrationsHelper
end
end
def serialize_integration(integration)
def serialize_integration(integration, group: nil, project: nil)
{
active: integration.operating?,
title: integration.title,
description: integration.description,
updated_at: integration.updated_at,
edit_path: scoped_edit_integration_path(integration),
edit_path: scoped_edit_integration_path(integration, group: group, project: project),
name: integration.to_param
}
end

View File

@ -16,7 +16,7 @@ module OperationsHelper
{
'prometheus_activated' => prometheus_integration.manual_configuration?.to_s,
'prometheus_form_path' => scoped_integration_path(prometheus_integration),
'prometheus_form_path' => scoped_integration_path(prometheus_integration, project: prometheus_integration.project, group: prometheus_integration.group),
'prometheus_reset_key_path' => reset_alerting_token_project_settings_operations_path(@project),
'prometheus_authorization_key' => @project.alerting_setting&.token,
'prometheus_api_url' => prometheus_integration.api_url,

View File

@ -1083,6 +1083,16 @@ module Ci
runner&.instance_type?
end
def job_variables_attributes
strong_memoize(:job_variables_attributes) do
job_variables.internal_source.map do |variable|
variable.attributes.except('id', 'job_id', 'encrypted_value', 'encrypted_value_iv').tap do |attrs|
attrs[:value] = variable.value
end
end
end
end
protected
def run_status_commit_hooks!

View File

@ -1614,14 +1614,8 @@ class User < ApplicationRecord
.joins(:runner)
.select('ci_runners.*')
base_and_descendants = if Feature.enabled?(:linear_user_ci_owned_runners, self, default_enabled: :yaml)
owned_groups.self_and_descendant_ids
else
Gitlab::ObjectHierarchy.new(owned_groups).base_and_descendants.select(:id)
end
group_runners = Ci::RunnerNamespace
.where(namespace_id: base_and_descendants)
.where(namespace_id: owned_groups.self_and_descendant_ids)
.joins(:runner)
.select('ci_runners.*')

View File

@ -68,7 +68,13 @@ module Ci
end
def build_attributes(build)
attributes = self.class.clone_accessors.to_h do |attribute|
clone_attributes = if ::Feature.enabled?(:clone_job_variables_at_job_retry, build.project, default_enabled: :yaml)
self.class.clone_accessors + [:job_variables_attributes]
else
self.class.clone_accessors
end
attributes = clone_attributes.to_h do |attribute|
[attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend
end

View File

@ -6,7 +6,7 @@
- if integration.operating?
= sprite_icon('check', css_class: 'gl-text-green-500')
= form_for(integration, as: :service, url: scoped_integration_path(integration), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => test_project_service_path(@project, integration) } }) do |form|
= form_for(integration, as: :service, url: scoped_integration_path(integration, project: @project, group: @group), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => test_project_service_path(@project, integration) } }) do |form|
= render 'shared/service_settings', form: form, integration: integration
%input{ id: 'services_redirect_to', type: 'hidden', name: 'redirect_to', value: request.referer }

View File

@ -2,8 +2,8 @@
.service-settings
- if @default_integration
.js-vue-default-integration-settings{ data: integration_form_data(@default_integration, group: @group) }
.js-vue-integration-settings{ data: integration_form_data(integration, group: @group) }
.js-vue-default-integration-settings{ data: integration_form_data(@default_integration, group: @group, project: @project) }
.js-vue-integration-settings{ data: integration_form_data(integration, group: @group, project: @project) }
.js-integration-help-html.gl-display-none
-# All content below will be repositioned in Vue
- if lookup_context.template_exists?('help', "projects/services/#{integration.to_param}", true)

View File

@ -1,4 +1,4 @@
- integration = local_assigns.fetch(:integration)
= form_for integration, as: :service, url: scoped_integration_path(integration), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => scoped_test_integration_path(integration) } } do |form|
= form_for integration, as: :service, url: scoped_integration_path(integration, group: @group), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => scoped_test_integration_path(integration, group: @group) } } do |form|
= render 'shared/service_settings', form: form, integration: integration

View File

@ -1 +1 @@
.js-integrations-list{ data: integration_list_data(integrations) }
.js-integrations-list{ data: integration_list_data(integrations, group: @group, project: @project) }

View File

@ -2,7 +2,7 @@
.tabs.gl-tabs
%div
= gl_tabs_nav({ class: 'gl-mb-5' }) do
= gl_tab_link_to _('Settings'), scoped_edit_integration_path(integration)
= gl_tab_link_to _('Settings'), scoped_edit_integration_path(integration, project: @project, group: @group)
= gl_tab_link_to s_('Integrations|Projects using custom settings'), scoped_overrides_integration_path(integration)
= yield

View File

@ -1,4 +1,4 @@
- add_to_breadcrumbs _('Integrations'), scoped_integrations_path
- add_to_breadcrumbs _('Integrations'), scoped_integrations_path(project: @project, group: @group)
- breadcrumb_title @integration.title
- page_title @integration.title, _('Integrations')
- @content_class = 'limit-container-width' unless fluid_layout

View File

@ -1,4 +1,4 @@
- add_to_breadcrumbs _('Integrations'), scoped_integrations_path
- add_to_breadcrumbs _('Integrations'), scoped_integrations_path(project: project, group: group)
- breadcrumb_title @integration.title
- page_title @integration.title, _('Integrations')
- @content_class = 'limit-container-width' unless fluid_layout

View File

@ -4,7 +4,7 @@
= page_title
- if @project
- integrations_link_start = '<a href="%{url}">'.html_safe % { url: scoped_integrations_path }
- integrations_link_start = '<a href="%{url}">'.html_safe % { url: scoped_integrations_path(project: @project) }
%p= _("%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook.").html_safe % { webhooks_link_start: webhooks_link_start, webhook_type: hook.pluralized_name, integrations_link_start: integrations_link_start, link_end: '</a>'.html_safe }
- else
%p= _("%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project.").html_safe % { webhooks_link_start: webhooks_link_start, webhook_type: hook.pluralized_name, link_end: '</a>'.html_safe }

View File

@ -1,8 +1,8 @@
---
name: linear_user_ci_owned_runners
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68848
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339435
name: clone_job_variables_at_job_retry
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75720
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347156
milestone: '14.6'
type: development
group: group::access
group: group::pipeline authoring
default_enabled: false

View File

@ -15,8 +15,8 @@ MSG
product_intelligence_paths_to_review = helper.changes_by_category[:product_intelligence]
labels_to_add = product_intelligence.missing_labels
return if product_intelligence_paths_to_review.empty? || labels_to_add.empty?
return if product_intelligence_paths_to_review.empty?
warn format(CHANGED_FILES_MESSAGE, changed_files: helper.markdown_list(product_intelligence_paths_to_review))
warn format(CHANGED_FILES_MESSAGE, changed_files: helper.markdown_list(product_intelligence_paths_to_review)) unless product_intelligence.has_approved_label?
project_helper.labels_to_add.concat(labels_to_add)
project_helper.labels_to_add.concat(labels_to_add) unless labels_to_add.empty?

View File

@ -41,11 +41,12 @@ class Gitlab::Seeder::Users
relation = User.where(admin: false)
Gitlab::Seeder.with_mass_insert(relation.count, Namespace) do
ActiveRecord::Base.connection.execute <<~SQL
INSERT INTO namespaces (name, path, owner_id)
INSERT INTO namespaces (name, path, owner_id, type)
SELECT
username,
username,
id
id,
'User'
FROM users WHERE NOT admin
SQL
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
class ConsumeRemainingUserNamespaceJobs < Gitlab::Database::Migration[1.0]
MIGRATION = 'BackfillUserNamespace'
BATCH_SIZE = 200
DEFAULT_VALUE = 'User'
disable_ddl_transaction!
def up
Gitlab::BackgroundMigration.steal(MIGRATION)
# Do a manual update in case we lost BG jobs. The expected record count should be 0 or very low.
define_batchable_model('namespaces').where(type: nil).each_batch(of: BATCH_SIZE) do |batch|
min, max = batch.pluck('MIN(id), MAX(id)').flatten
Gitlab::BackgroundMigration::BackfillUserNamespace.new.perform(min, max, :namespaces, :id, BATCH_SIZE, 0)
end
change_column_null :namespaces, :type, false
end
def down
change_column_null :namespaces, :type, true
end
end

View File

@ -0,0 +1 @@
a579b14aff1d186d89173e383442f2ffbd69b1baed3f9a4c758fbb001b445139

View File

@ -16419,7 +16419,7 @@ CREATE TABLE namespaces (
owner_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone,
type character varying DEFAULT 'User'::character varying,
type character varying DEFAULT 'User'::character varying NOT NULL,
description character varying DEFAULT ''::character varying NOT NULL,
avatar character varying,
membership_lock boolean DEFAULT false,

View File

@ -601,18 +601,18 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
Approves a pending user for a group and its subgroups and projects.
```plaintext
PUT /groups/:id/members/:user_id/approve
PUT /groups/:id/members/:member_id/approve
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the root group](index.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the member |
| `member_id` | integer | yes | The ID of the member |
Example request:
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id/approve"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:member_id/approve"
```
## Approve all pending members for a group

View File

@ -22,9 +22,11 @@ The final disk space your jobs can use will be less than 25GB. Some disk space a
The `gitlab-shared-runners-manager-X.gitlab.com` fleet of runners are dedicated for GitLab projects as well as community forks of them. They use a slightly larger machine type (n1-standard-2) and have a bigger SSD disk size. They don't run untagged jobs and unlike the general fleet of shared runners, the instances are re-used up to 40 times.
Jobs handled by the shared runners on GitLab.com (`shared-runners-manager-X.gitlab.com`),
Jobs handled by shared runners on GitLab.com (`shared-runners-manager-X.gitlab.com`)
**time out after 3 hours**, regardless of the timeout configured in a
project. Check the issues [#4010](https://gitlab.com/gitlab-com/infrastructure/-/issues/4010) and [#4070](https://gitlab.com/gitlab-com/infrastructure/-/issues/4070) for the reference.
project. Check issue [#4010](https://gitlab.com/gitlab-com/infrastructure/-/issues/4010) and [#4070](https://gitlab.com/gitlab-com/infrastructure/-/issues/4070) for the reference.
Jobs handled by shared runners on Windows and macOS on GitLab.com **time out after 1 hour** while this service is in the Beta stage.
Below are the runners' settings.

View File

@ -52,13 +52,13 @@ The Advanced Search can be useful in various scenarios:
## Use the Advanced Search syntax
Elasticsearch has only data for the default branch. That means that if you go
Elasticsearch has data for the default branch only. That means that if you go
to the repository tree and switch the branch from the default to something else,
then the "Code" tab in the search result page will be served by the basic
then the **Code** tab in the search result page is served by the basic
search even if Elasticsearch is enabled.
The Advanced Search syntax supports fuzzy or exact search queries with prefixes,
boolean operators, and much more. Use the search as before and GitLab will show
boolean operators, and much more. Use the search as before and GitLab shows
you matching code from each project you have access to.
![Advanced Search](img/advanced_search_v13.10.png)
@ -67,7 +67,7 @@ Full details can be found in the [Elasticsearch documentation](https://www.elast
here's a quick guide:
- Searches look for all the words in a query, in any order - for example: searching
issues for [`display bug`](https://gitlab.com/search?snippets=&scope=issues&repository_ref=&search=display+bug&group_id=9970&project_id=278964) and [`bug display`](https://gitlab.com/search?snippets=&scope=issues&repository_ref=&search=bug+Display&group_id=9970&project_id=278964) will return the same results.
issues for [`display bug`](https://gitlab.com/search?snippets=&scope=issues&repository_ref=&search=display+bug&group_id=9970&project_id=278964) and [`bug display`](https://gitlab.com/search?snippets=&scope=issues&repository_ref=&search=bug+Display&group_id=9970&project_id=278964) return the same results.
- To find the exact phrase (stemming still applies), use double quotes: [`"display bug"`](https://gitlab.com/search?snippets=&scope=issues&repository_ref=&search=%22display+bug%22&group_id=9970&project_id=278964)
- To find bugs not mentioning display, use `-`: [`bug -display`](https://gitlab.com/search?snippets=&scope=issues&repository_ref=&search=bug+-display&group_id=9970&project_id=278964)
- To find a bug in display or banner, use `|`: [`bug display | banner`](https://gitlab.com/search?snippets=&scope=issues&repository_ref=&search=bug+display+%7C+banner&group_id=9970&project_id=278964)
@ -85,7 +85,7 @@ Advanced Search also supports the use of filters. The available filters are:
- `blob`: Filters by Git `object ID`. Exact match only.
To use them, add them to your keyword in the format `<filter_name>:<value>` without
any spaces between the colon (`:`) and the value. When no keyword is provided, an asterisk (`*`) will be used as the keyword.
any spaces between the colon (`:`) and the value. When no keyword is provided, an asterisk (`*`) is used as the keyword.
Examples:

View File

@ -16,6 +16,8 @@ module BulkImports
def load(context, data)
url = data['httpUrlToRepo']
return unless url.present?
url = url.sub("://", "://oauth2:#{context.configuration.access_token}@")
project = context.portable

View File

@ -64,45 +64,47 @@ module Gitlab
# be throttled.
#
# @param key [Symbol] Key attribute registered in `.rate_limits`
# @option scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
# @option threshold [Integer] Optional threshold value to override default one registered in `.rate_limits`
# @option users_allowlist [Array<String>] Optional list of usernames to exclude from the limit. This param will only be functional if Scope includes a current user.
# @param scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
# @param threshold [Integer] Optional threshold value to override default one registered in `.rate_limits`
# @param users_allowlist [Array<String>] Optional list of usernames to exclude from the limit. This param will only be functional if Scope includes a current user.
# @param peek [Boolean] Optional. When true the key will not be incremented but the current throttled state will be returned.
#
# @return [Boolean] Whether or not a request should be throttled
def throttled?(key, **options)
def throttled?(key, scope:, threshold: nil, users_allowlist: nil, peek: false)
raise InvalidKeyError unless rate_limits[key]
return if scoped_user_in_allowlist?(options)
return false if scoped_user_in_allowlist?(scope, users_allowlist)
threshold_value = options[:threshold] || threshold(key)
threshold_value > 0 &&
increment(key, options[:scope]) > threshold_value
threshold_value = threshold || threshold(key)
return false if threshold_value == 0
interval_value = interval(key)
# `period_key` is based on the current time and interval so when time passes to the next interval
# the key changes and the rate limit count starts again from 0.
# Based on https://github.com/rack/rack-attack/blob/886ba3a18d13c6484cd511a4dc9b76c0d14e5e96/lib/rack/attack/cache.rb#L63-L68
period_key, time_elapsed_in_period = Time.now.to_i.divmod(interval_value)
cache_key = cache_key(key, scope, period_key)
value = if peek
read(cache_key)
else
increment(cache_key, interval_value, time_elapsed_in_period)
end
value > threshold_value
end
# Increments a cache key that is based on the current time and interval.
# So that when time passes to the next interval, the key changes and the count starts again from 0.
#
# Based on https://github.com/rack/rack-attack/blob/886ba3a18d13c6484cd511a4dc9b76c0d14e5e96/lib/rack/attack/cache.rb#L63-L68
# Returns the current rate limited state without incrementing the count.
#
# @param key [Symbol] Key attribute registered in `.rate_limits`
# @option scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
# @param scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
# @param threshold [Integer] Optional threshold value to override default one registered in `.rate_limits`
# @param users_allowlist [Array<String>] Optional list of usernames to exclude from the limit. This param will only be functional if Scope includes a current user.
#
# @return [Integer] incremented value
def increment(key, scope)
interval_value = interval(key)
period_key, time_elapsed_in_period = Time.now.to_i.divmod(interval_value)
cache_key = "#{action_key(key, scope)}:#{period_key}"
# We add a 1 second buffer to avoid timing issues when we're at the end of a period
expiry = interval_value - time_elapsed_in_period + 1
::Gitlab::Redis::RateLimiting.with do |redis|
redis.pipelined do
redis.incr(cache_key)
redis.expire(cache_key, expiry)
end.first
end
# @return [Boolean] Whether or not a request is currently throttled
def peek(key, scope:, threshold: nil, users_allowlist: nil)
throttled?(key, peek: true, scope: scope, threshold: threshold, users_allowlist: users_allowlist)
end
# Logs request using provided logger
@ -150,7 +152,28 @@ module Gitlab
action[setting] if action
end
def action_key(key, scope)
# Increments the rate limit count and returns the new count value.
def increment(cache_key, interval_value, time_elapsed_in_period)
# We add a 1 second buffer to avoid timing issues when we're at the end of a period
expiry = interval_value - time_elapsed_in_period + 1
::Gitlab::Redis::RateLimiting.with do |redis|
redis.pipelined do
redis.incr(cache_key)
redis.expire(cache_key, expiry)
end.first
end
end
# Returns the rate limit count.
# Will be 0 if there is no data in the cache.
def read(cache_key)
::Gitlab::Redis::RateLimiting.with do |redis|
redis.get(cache_key).to_i
end
end
def cache_key(key, scope, period_key)
composed_key = [key, scope].flatten.compact
serialized = composed_key.map do |obj|
@ -161,20 +184,20 @@ module Gitlab
end
end.join(":")
"application_rate_limiter:#{serialized}"
"application_rate_limiter:#{serialized}:#{period_key}"
end
def application_settings
Gitlab::CurrentSettings.current_application_settings
end
def scoped_user_in_allowlist?(options)
return unless options[:users_allowlist].present?
def scoped_user_in_allowlist?(scope, users_allowlist)
return unless users_allowlist.present?
scoped_user = [options[:scope]].flatten.find { |s| s.is_a?(User) }
scoped_user = [scope].flatten.find { |s| s.is_a?(User) }
return unless scoped_user
scoped_user.username.downcase.in?(options[:users_allowlist])
scoped_user.username.downcase.in?(users_allowlist)
end
end

View File

@ -32,7 +32,7 @@ namespace :gitlab do
tmp_namespace_path = "tmp-project-import-#{Time.now.to_i}"
puts "Creating temporary namespace #{tmp_namespace_path}"
tmp_namespace = Namespace.create!(owner: admin, name: tmp_namespace_path, path: tmp_namespace_path)
tmp_namespace = Namespace.create!(owner: admin, name: tmp_namespace_path, path: tmp_namespace_path, type: Namespaces::UserNamespace.sti_name)
templates = if template_names.empty?
Gitlab::ProjectTemplate.all

View File

@ -23640,6 +23640,9 @@ msgstr ""
msgid "No matching results..."
msgstr ""
msgid "No member provided"
msgstr ""
msgid "No members found"
msgstr ""

View File

@ -158,7 +158,7 @@ postgresql:
memory: 1500M
master:
nodeSelector:
preemptible: "true"
preemptible: "false"
prometheus:
install: false
redis:

View File

@ -47,6 +47,17 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryPipeline do
end
end
context 'project has no repository' do
let(:project_data) { { 'httpUrlToRepo' => '' } }
it 'skips repository import' do
expect(context.portable).not_to receive(:ensure_repository)
expect(context.portable.repository).not_to receive(:fetch_as_mirror)
pipeline.run
end
end
context 'blocked local networks' do
let(:project_data) { { 'httpUrlToRepo' => 'http://localhost/foo.git' } }

View File

@ -2,37 +2,37 @@
require 'spec_helper'
RSpec.describe Gitlab::ApplicationRateLimiter do
RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:rate_limits) do
{
test_action: {
threshold: 1,
interval: 2.minutes
},
another_action: {
threshold: 2,
interval: 3.minutes
}
}
end
subject { described_class }
describe '.throttled?', :clean_gitlab_redis_rate_limiting do
let(:rate_limits) do
{
test_action: {
threshold: 1,
interval: 2.minutes
},
another_action: {
threshold: 2,
interval: 3.minutes
}
}
end
before do
allow(described_class).to receive(:rate_limits).and_return(rate_limits)
end
before do
allow(described_class).to receive(:rate_limits).and_return(rate_limits)
end
describe '.throttled?' do
context 'when the key is invalid' do
context 'is provided as a Symbol' do
context 'but is not defined in the rate_limits Hash' do
it 'raises an InvalidKeyError exception' do
key = :key_not_in_rate_limits_hash
expect { subject.throttled?(key) }.to raise_error(Gitlab::ApplicationRateLimiter::InvalidKeyError)
expect { subject.throttled?(key, scope: [user]) }.to raise_error(Gitlab::ApplicationRateLimiter::InvalidKeyError)
end
end
end
@ -42,7 +42,7 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
it 'raises an InvalidKeyError exception' do
key = rate_limits.keys[0].to_s
expect { subject.throttled?(key) }.to raise_error(Gitlab::ApplicationRateLimiter::InvalidKeyError)
expect { subject.throttled?(key, scope: [user]) }.to raise_error(Gitlab::ApplicationRateLimiter::InvalidKeyError)
end
end
@ -50,7 +50,7 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
it 'raises an InvalidKeyError exception' do
key = 'key_not_in_rate_limits_hash'
expect { subject.throttled?(key) }.to raise_error(Gitlab::ApplicationRateLimiter::InvalidKeyError)
expect { subject.throttled?(key, scope: [user]) }.to raise_error(Gitlab::ApplicationRateLimiter::InvalidKeyError)
end
end
end
@ -89,6 +89,17 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
expect(subject.throttled?(:another_action, scope: scope)).to eq(true)
end
end
it 'allows peeking at the current state without changing its value' do
travel_to(start_time) do
expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
2.times do
expect(subject.throttled?(:test_action, scope: scope, peek: true)).to eq(false)
end
expect(subject.throttled?(:test_action, scope: scope)).to eq(true)
expect(subject.throttled?(:test_action, scope: scope, peek: true)).to eq(true)
end
end
end
context 'when using ActiveRecord models as scope' do
@ -104,6 +115,20 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
end
end
describe '.peek' do
it 'peeks at the current state without changing its value' do
freeze_time do
expect(subject.peek(:test_action, scope: [user])).to eq(false)
expect(subject.throttled?(:test_action, scope: [user])).to eq(false)
2.times do
expect(subject.peek(:test_action, scope: [user])).to eq(false)
end
expect(subject.throttled?(:test_action, scope: [user])).to eq(true)
expect(subject.peek(:test_action, scope: [user])).to eq(true)
end
end
end
describe '.log_request' do
let(:file_path) { 'master/README.md' }
let(:type) { :raw_blob_request_limit }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::DropInvalidSecurityFindings, schema: 20211108211434 do
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user', type: Namespaces::UserNamespace.sti_name) }
let(:project) { table(:projects).create!(namespace_id: namespace.id) }
let(:pipelines) { table(:ci_pipelines) }

View File

@ -6,7 +6,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveVulnerabilityFindingLinks, :mi
let(:vulnerability_findings) { table(:vulnerability_occurrences) }
let(:finding_links) { table(:vulnerability_finding_links) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user', type: Namespaces::UserNamespace.sti_name) }
let(:project) { table(:projects).create!(namespace_id: namespace.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'scanner', name: 'scanner') }
let(:vulnerability_identifier) do

View File

@ -2431,7 +2431,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
let(:issues) { table(:issues) }
def setup
namespace = namespaces.create!(name: 'foo', path: 'foo')
namespace = namespaces.create!(name: 'foo', path: 'foo', type: Namespaces::UserNamespace.sti_name)
projects.create!(namespace_id: namespace.id)
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe ConsumeRemainingUserNamespaceJobs do
let(:namespaces) { table(:namespaces) }
let!(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org', type: nil) }
context 'when Namespaces with nil `type` still exist' do
it 'steals sidekiq jobs from BackfillUserNamespace background migration' do
expect(Gitlab::BackgroundMigration).to receive(:steal).with('BackfillUserNamespace')
migrate!
end
it 'migrates namespaces without type' do
expect { migrate! }.to change { namespaces.where(type: 'User').count }.from(0).to(1)
end
end
end

View File

@ -6,7 +6,7 @@ require_migration!
RSpec.describe ScheduleDropInvalidSecurityFindings, :migration, schema: 20211108211434 do
let_it_be(:background_migration_jobs) { table(:background_migration_jobs) }
let_it_be(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let_it_be(:namespace) { table(:namespaces).create!(name: 'user', path: 'user', type: Namespaces::UserNamespace.sti_name) }
let_it_be(:project) { table(:projects).create!(namespace_id: namespace.id) }
let_it_be(:pipelines) { table(:ci_pipelines) }

View File

@ -1,22 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe ChangeNamespaceTypeDefaultToUser do
let(:namespaces) { table(:namespaces) }
it 'defaults type to User' do
some_namespace1 = namespaces.create!(name: 'magic namespace1', path: 'magicnamespace1', type: nil)
expect(some_namespace1.reload.type).to be_nil
migrate!
some_namespace2 = namespaces.create!(name: 'magic namespace2', path: 'magicnamespace2', type: nil)
expect(some_namespace1.reload.type).to be_nil
expect(some_namespace2.reload.type).to eq 'User'
end
end
# With https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73495, we no longer allow
# a Namespace type to be nil. There is nothing left to test for this migration,
# but we'll keep this file here as a tombstone.

View File

@ -13,8 +13,8 @@ RSpec.describe CaseSensitivity do
end
end
let_it_be(:model_1) { model.create!(path: 'mOdEl-1', name: 'mOdEl 1') }
let_it_be(:model_2) { model.create!(path: 'mOdEl-2', name: 'mOdEl 2') }
let_it_be(:model_1) { model.create!(path: 'mOdEl-1', name: 'mOdEl 1', type: Namespaces::UserNamespace.sti_name) }
let_it_be(:model_2) { model.create!(path: 'mOdEl-2', name: 'mOdEl 2', type: Group.sti_name) }
it 'finds a single instance by a single attribute regardless of case' do
expect(model.iwhere(path: 'MODEL-1')).to contain_exactly(model_1)

View File

@ -259,13 +259,12 @@ RSpec.describe Namespace do
end
end
context 'creating a Namespace with nil type' do
context 'unable to create a Namespace with nil type' do
let(:namespace) { nil }
let(:namespace_type) { nil }
it 'is the correct type of namespace' do
expect(Namespace.find(namespace.id)).to be_a(Namespace)
expect(namespace.kind).to eq('user')
expect(namespace.user_namespace?).to be_truthy
it 'raises ActiveRecord::NotNullViolation' do
expect { create(:namespace, type: namespace_type, parent: parent) }.to raise_error(ActiveRecord::NotNullViolation)
end
end

View File

@ -3723,321 +3723,309 @@ RSpec.describe User do
end
describe '#ci_owned_runners' do
shared_examples 'ci_owned_runners examples' do
let(:user) { create(:user) }
let(:user) { create(:user) }
shared_examples :nested_groups_owner do
context 'when the user is the owner of a multi-level group' do
before do
set_permissions_for_users
end
shared_examples :nested_groups_owner do
context 'when the user is the owner of a multi-level group' do
before do
set_permissions_for_users
end
it 'loads all the runners in the tree of groups' do
expect(user.ci_owned_runners).to contain_exactly(runner, group_runner)
end
it 'loads all the runners in the tree of groups' do
expect(user.ci_owned_runners).to contain_exactly(runner, group_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(true)
expect(user.owns_runner?(group_runner)).to eq(true)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(true)
expect(user.owns_runner?(group_runner)).to eq(true)
end
end
end
shared_examples :group_owner do
context 'when the user is the owner of a one level group' do
before do
group.add_owner(user)
end
it 'loads the runners in the group' do
expect(user.ci_owned_runners).to contain_exactly(group_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(group_runner)).to eq(true)
end
end
end
shared_examples :project_owner do
context 'when the user is the owner of a project' do
it 'loads the runner belonging to the project' do
expect(user.ci_owned_runners).to contain_exactly(runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(true)
end
end
end
shared_examples :project_member do
context 'when the user is a maintainer' do
before do
add_user(:maintainer)
end
it 'loads the runners of the project' do
expect(user.ci_owned_runners).to contain_exactly(project_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(true)
end
end
shared_examples :group_owner do
context 'when the user is the owner of a one level group' do
before do
group.add_owner(user)
end
it 'loads the runners in the group' do
expect(user.ci_owned_runners).to contain_exactly(group_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(group_runner)).to eq(true)
end
end
end
shared_examples :project_owner do
context 'when the user is the owner of a project' do
it 'loads the runner belonging to the project' do
expect(user.ci_owned_runners).to contain_exactly(runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(true)
end
end
end
shared_examples :project_member do
context 'when the user is a maintainer' do
before do
add_user(:maintainer)
end
it 'loads the runners of the project' do
expect(user.ci_owned_runners).to contain_exactly(project_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(true)
end
context 'when the user is a developer' do
before do
add_user(:developer)
end
context 'when the user is a developer' do
before do
add_user(:developer)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
context 'when the user is a reporter' do
before do
add_user(:reporter)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
context 'when the user is a guest' do
before do
add_user(:guest)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
end
shared_examples :group_member do
context 'when the user is a maintainer' do
before do
add_user(:maintainer)
end
it 'does not load the runners of the group' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a developer' do
before do
add_user(:developer)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a reporter' do
before do
add_user(:reporter)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a guest' do
before do
add_user(:guest)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
end
context 'without any projects nor groups' do
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(create(:ci_runner))).to eq(false)
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
context 'with runner in a personal project' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:project) { create(:project, namespace: namespace) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it_behaves_like :project_owner
end
context 'with group runner in a non owned group' do
let!(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
def add_user(access)
group.add_user(user, access)
context 'when the user is a reporter' do
before do
add_user(:reporter)
end
it_behaves_like :group_member
end
context 'with group runner in an owned group' do
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
it_behaves_like :group_owner
end
context 'with group runner in an owned group and group runner in a different owner subgroup' do
let!(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:another_user) { create(:user) }
def set_permissions_for_users
group.add_owner(user)
subgroup.add_owner(another_user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an an owned group and a group runner in that same group' do
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned group and a group runner in a subgroup' do
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned group in an owned namespace and a group runner in that group' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project) { create(:project, namespace: namespace, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned namespace, an owned group, a subgroup and a group runner in that subgroup' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:project) { create(:project, namespace: namespace, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with a project runner that belong to projects that belong to a not owned group' do
let!(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
let!(:project_runner) { create(:ci_runner, :project, projects: [project]) }
def add_user(access)
project.add_user(user, access)
end
it_behaves_like :project_member
end
context 'with project runners that belong to projects that do not belong to any group' do
let!(:project) { create(:project) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
context 'with a group runner that belongs to a subgroup of a group owned by another user' do
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:another_user) { create(:user) }
def add_user(access)
subgroup.add_user(user, access)
group.add_user(another_user, :owner)
context 'when the user is a guest' do
before do
add_user(:guest)
end
it_behaves_like :group_member
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
end
it_behaves_like 'ci_owned_runners examples'
shared_examples :group_member do
context 'when the user is a maintainer' do
before do
add_user(:maintainer)
end
context 'when feature flag :linear_user_ci_owned_runners is disabled' do
before do
stub_feature_flags(linear_user_ci_owned_runners: false)
it 'does not load the runners of the group' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
it_behaves_like 'ci_owned_runners examples'
context 'when the user is a developer' do
before do
add_user(:developer)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a reporter' do
before do
add_user(:reporter)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a guest' do
before do
add_user(:guest)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
end
context 'without any projects nor groups' do
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(create(:ci_runner))).to eq(false)
end
end
context 'with runner in a personal project' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:project) { create(:project, namespace: namespace) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it_behaves_like :project_owner
end
context 'with group runner in a non owned group' do
let!(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
def add_user(access)
group.add_user(user, access)
end
it_behaves_like :group_member
end
context 'with group runner in an owned group' do
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
it_behaves_like :group_owner
end
context 'with group runner in an owned group and group runner in a different owner subgroup' do
let!(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:another_user) { create(:user) }
def set_permissions_for_users
group.add_owner(user)
subgroup.add_owner(another_user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an an owned group and a group runner in that same group' do
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned group and a group runner in a subgroup' do
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned group in an owned namespace and a group runner in that group' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project) { create(:project, namespace: namespace, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned namespace, an owned group, a subgroup and a group runner in that subgroup' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:project) { create(:project, namespace: namespace, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with a project runner that belong to projects that belong to a not owned group' do
let!(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
let!(:project_runner) { create(:ci_runner, :project, projects: [project]) }
def add_user(access)
project.add_user(user, access)
end
it_behaves_like :project_member
end
context 'with project runners that belong to projects that do not belong to any group' do
let!(:project) { create(:project) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
end
context 'with a group runner that belongs to a subgroup of a group owned by another user' do
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:another_user) { create(:user) }
def add_user(access)
subgroup.add_user(user, access)
group.add_user(another_user, :owner)
end
it_behaves_like :group_member
end
end

View File

@ -73,6 +73,8 @@ RSpec.describe Ci::RetryBuildService do
scheduled_at: 10.seconds.since)
end
let_it_be(:internal_job_variable) { create(:ci_job_variable, job: build) }
before_all do
# Make sure that build has both `stage_id` and `stage` because FactoryBot
# can reset one of the fields when assigning another. We plan to deprecate
@ -86,7 +88,7 @@ RSpec.describe Ci::RetryBuildService do
file_type: file_type, job: build, expire_at: build.artifacts_expire_at)
end
create(:ci_job_variable, job: build)
create(:ci_job_variable, :dotenv_source, job: build)
create(:ci_build_need, build: build)
create(:terraform_state_version, build: build)
end
@ -125,6 +127,27 @@ RSpec.describe Ci::RetryBuildService do
expect(new_build.needs_attributes).to match(build.needs_attributes)
expect(new_build.needs).not_to match(build.needs)
end
context 'when clone_job_variables_at_job_retry is enabled' do
before do
stub_feature_flags(clone_job_variables_at_job_retry: true)
end
it 'clones only internal job variables' do
expect(new_build.job_variables.count).to eq(1)
expect(new_build.job_variables).to contain_exactly(having_attributes(key: internal_job_variable.key, value: internal_job_variable.value))
end
end
context 'when clone_job_variables_at_job_retry is not enabled' do
before do
stub_feature_flags(clone_job_variables_at_job_retry: false)
end
it 'does not clone internal job variables' do
expect(new_build.job_variables.count).to eq(0)
end
end
end
describe 'reject accessors' do

View File

@ -4,9 +4,12 @@
module Tooling
module Danger
module ProductIntelligence
APPROVED_LABEL = 'product intelligence::approved'
REVIEW_LABEL = 'product intelligence::review pending'
WORKFLOW_LABELS = [
'product intelligence::approved',
'product intelligence::review pending'
APPROVED_LABEL,
REVIEW_LABEL
].freeze
def missing_labels
@ -14,11 +17,15 @@ module Tooling
labels = []
labels << 'product intelligence' unless helper.mr_has_labels?('product intelligence')
labels << 'product intelligence::review pending' unless has_workflow_labels?
labels << REVIEW_LABEL unless has_workflow_labels?
labels
end
def has_approved_label?
helper.mr_labels.include?(APPROVED_LABEL)
end
private
def has_workflow_labels?