Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a15c9bc9eb
commit
36c5bf80d4
|
@ -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"
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<!-- Link related issues below. -->
|
||||
|
||||
## Check-list
|
||||
## Checklist
|
||||
|
||||
### Pre-merge
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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.*')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
.js-integrations-list{ data: integration_list_data(integrations) }
|
||||
.js-integrations-list{ data: integration_list_data(integrations, group: @group, project: @project) }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
a579b14aff1d186d89173e383442f2ffbd69b1baed3f9a4c758fbb001b445139
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -23640,6 +23640,9 @@ msgstr ""
|
|||
msgid "No matching results..."
|
||||
msgstr ""
|
||||
|
||||
msgid "No member provided"
|
||||
msgstr ""
|
||||
|
||||
msgid "No members found"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ postgresql:
|
|||
memory: 1500M
|
||||
master:
|
||||
nodeSelector:
|
||||
preemptible: "true"
|
||||
preemptible: "false"
|
||||
prometheus:
|
||||
install: false
|
||||
redis:
|
||||
|
|
|
@ -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' } }
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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) }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
Loading…
Reference in New Issue