Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-12 18:08:38 +00:00
parent 0024c2f444
commit aed0a60015
47 changed files with 509 additions and 350 deletions

View File

@ -303,12 +303,6 @@ That's all of the required database changes.
git_access_class.error_message(:no_repo)
end
# The feature flag follows the format `geo_#{replicable_name}_replication`,
# so here it would be `geo_cool_widget_replication`
def self.replication_enabled_by_default?
false
end
override :verification_feature_flag_enabled?
def self.verification_feature_flag_enabled?
# We are adding verification at the same time as replication, so we
@ -715,27 +709,6 @@ As illustrated by the above two examples, batch destroy logic cannot be handled
- [ ] Add a step to `Test replication and verification of Cool Widgets on a non-GDK-deployment. For example, using GitLab Environment Toolkit`.
- [ ] Add a step to `Ping the Geo PM and EM to coordinate testing`. For example, you might add steps to generate Cool Widgets, and then a Geo engineer may take it from there.
- [ ] In `ee/config/feature_flags/development/geo_cool_widget_replication.yml`, set `default_enabled: true`
- [ ] In `ee/app/replicators/geo/cool_widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
```ruby
module Geo
class CoolWidgetReplicator < Gitlab::Geo::Replicator
...
# REMOVE THIS LINE IF IT IS NO LONGER NEEDED
extend ::Gitlab::Utils::Override
# REMOVE THIS METHOD
def self.replication_enabled_by_default?
false
end
# REMOVE THIS METHOD
...
end
end
```
- [ ] In `ee/app/graphql/types/geo/geo_node_type.rb`, remove the `feature_flag` option for the released type:
```ruby

View File

@ -291,12 +291,6 @@ That's all of the required database changes.
model_record.file
end
# The feature flag follows the format `geo_#{replicable_name}_replication`,
# so here it would be `geo_cool_widget_replication`
def self.replication_enabled_by_default?
false
end
override :verification_feature_flag_enabled?
def self.verification_feature_flag_enabled?
# We are adding verification at the same time as replication, so we
@ -680,28 +674,6 @@ As illustrated by the above two examples, batch destroy logic cannot be handled
- [ ] Add a step to `Test replication and verification of Cool Widgets on a non-GDK-deployment. For example, using GitLab Environment Toolkit`.
- [ ] Add a step to `Ping the Geo PM and EM to coordinate testing`. For example, you might add steps to generate Cool Widgets, and then a Geo engineer may take it from there.
- [ ] In `ee/config/feature_flags/development/geo_cool_widget_replication.yml`, set `default_enabled: true`
- [ ] In `ee/app/replicators/geo/cool_widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
```ruby
module Geo
class CoolWidgetReplicator < Gitlab::Geo::Replicator
...
# REMOVE THIS LINE IF IT IS NO LONGER NEEDED
extend ::Gitlab::Utils::Override
...
# REMOVE THIS METHOD
def self.replication_enabled_by_default?
false
end
# REMOVE THIS METHOD
...
end
end
```
- [ ] In `ee/app/graphql/types/geo/geo_node_type.rb`, remove the `feature_flag` option for the released type:
```ruby

View File

@ -8,7 +8,7 @@
## Author's checklist
- [ ] Optional. Consider taking [the GitLab Technical Writing Fundamentals course](https://gitlab.edcast.com/pathways/ECL-02528ee2-c334-4e16-abf3-e9d8b8260de4).
- [ ] Optional. Consider taking [the GitLab Technical Writing Fundamentals course](https://about.gitlab.com/handbook/engineering/ux/technical-writing/fundamentals/).
- [ ] Follow the:
- [Documentation process](https://docs.gitlab.com/ee/development/documentation/workflow.html).
- [Documentation guidelines](https://docs.gitlab.com/ee/development/documentation/).

View File

@ -39,7 +39,7 @@ export default {
configHelpLink: helpPagePath('user/clusters/agent/install/index', {
anchor: 'create-an-agent-configuration-file',
}),
inject: ['gitlabVersion'],
inject: ['gitlabVersion', 'kasVersion'],
props: {
agents: {
required: true,
@ -102,6 +102,9 @@ export default {
return { ...agent, versions };
});
},
serverVersion() {
return this.kasVersion || this.gitlabVersion;
},
},
methods: {
getStatusCellId(item) {
@ -135,12 +138,12 @@ export default {
if (!agent.versions.length) return false;
const [agentMajorVersion, agentMinorVersion] = this.getAgentVersionString(agent).split('.');
const [gitlabMajorVersion, gitlabMinorVersion] = this.gitlabVersion.split('.');
const [serverMajorVersion, serverMinorVersion] = this.serverVersion.split('.');
const majorVersionMismatch = agentMajorVersion !== gitlabMajorVersion;
const majorVersionMismatch = agentMajorVersion !== serverMajorVersion;
// We should warn user if their current GitLab and agent versions are more than 1 minor version apart:
const minorVersionMismatch = Math.abs(agentMinorVersion - gitlabMinorVersion) > 1;
const minorVersionMismatch = Math.abs(agentMinorVersion - serverMinorVersion) > 1;
return majorVersionMismatch || minorVersionMismatch;
},
@ -240,7 +243,7 @@ export default {
<p class="gl-mb-0">
<gl-sprintf :message="$options.i18n.versionOutdatedText">
<template #version>{{ gitlabVersion }}</template>
<template #version>{{ serverVersion }}</template>
</gl-sprintf>
<gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
{{ $options.i18n.viewDocsText }}</gl-link
@ -253,7 +256,7 @@ export default {
<p v-else-if="isVersionOutdated(item)" class="gl-mb-0">
<gl-sprintf :message="$options.i18n.versionOutdatedText">
<template #version>{{ gitlabVersion }}</template>
<template #version>{{ serverVersion }}</template>
</gl-sprintf>
<gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
{{ $options.i18n.viewDocsText }}</gl-link

View File

@ -30,6 +30,7 @@ export default () => {
canAddCluster,
canAdminCluster,
gitlabVersion,
kasVersion,
displayClusterAgents,
certificateBasedClustersEnabled,
} = el.dataset;
@ -48,6 +49,7 @@ export default () => {
canAddCluster: parseBoolean(canAddCluster),
canAdminCluster: parseBoolean(canAdminCluster),
gitlabVersion,
kasVersion,
displayClusterAgents: parseBoolean(displayClusterAgents),
certificateBasedClustersEnabled: parseBoolean(certificateBasedClustersEnabled),
},

View File

@ -1,6 +1,6 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { s__ } from '~/locale';
import { s__, __ } from '~/locale';
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -179,7 +179,10 @@ export default {
if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
const confirmed = await confirmAction(msg);
const confirmed = await confirmAction(msg, {
primaryBtnText: __('Discard changes'),
cancelBtnText: __('Continue editing'),
});
if (!confirmed) {
return;

View File

@ -3,7 +3,6 @@ import { GlModal, GlSafeHtmlDirective } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
cancelAction: { text: __('Cancel') },
directives: {
SafeHtml: GlSafeHtmlDirective,
},
@ -36,6 +35,16 @@ export default {
required: false,
default: 'confirm',
},
cancelText: {
type: String,
required: false,
default: __('Cancel'),
},
cancelVariant: {
type: String,
required: false,
default: 'default',
},
modalHtmlMessage: {
type: String,
required: false,
@ -71,7 +80,14 @@ export default {
};
},
cancelAction() {
return this.hideCancel ? null : this.$options.cancelAction;
return this.hideCancel
? null
: {
text: this.cancelText,
attributes: {
variant: this.cancelVariant,
},
};
},
shouldShowHeader() {
return Boolean(this.title?.length);

View File

@ -7,6 +7,8 @@ export function confirmAction(
primaryBtnText,
secondaryBtnVariant,
secondaryBtnText,
cancelBtnVariant,
cancelBtnText,
modalHtmlMessage,
title,
hideCancel,
@ -28,6 +30,8 @@ export function confirmAction(
secondaryVariant: secondaryBtnVariant,
primaryVariant: primaryBtnVariant,
primaryText: primaryBtnText,
cancelVariant: cancelBtnVariant,
cancelText: cancelBtnText,
title,
modalHtmlMessage,
hideCancel,

View File

@ -176,7 +176,10 @@ export default {
if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
const confirmed = await confirmAction(msg);
const confirmed = await confirmAction(msg, {
primaryBtnText: __('Discard changes'),
cancelBtnText: __('Continue editing'),
});
if (!confirmed) {
return;

View File

@ -15,5 +15,8 @@ export const VIEW_TYPE_KEY = 'pipeline_graph_view_type';
export const SINGLE_JOB = 'single_job';
export const JOB_DROPDOWN = 'job_dropdown';
export const BUILD_KIND = 'BUILD';
export const BRIDGE_KIND = 'BRIDGE';
export const ACTION_FAILURE = 'action_failure';
export const IID_FAILURE = 'missing_iid';

View File

@ -1,5 +1,5 @@
<script>
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import { GlBadge, GlLink, GlTooltipDirective } from '@gitlab/ui';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { sprintf, __ } from '~/locale';
@ -7,7 +7,7 @@ import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { reportToSentry } from '../../utils';
import ActionComponent from '../jobs_shared/action_component.vue';
import JobNameComponent from '../jobs_shared/job_name_component.vue';
import { SINGLE_JOB } from './constants';
import { BRIDGE_KIND, SINGLE_JOB } from './constants';
/**
* Renders the badge for the pipeline graph and the job's dropdown.
@ -35,11 +35,16 @@ import { SINGLE_JOB } from './constants';
*/
export default {
i18n: {
bridgeBadgeText: __('Trigger job'),
unauthorizedTooltip: __('You are not authorized to run this manual job'),
},
hoverClass: 'gl-shadow-x0-y0-b3-s1-blue-500',
components: {
ActionComponent,
CiIcon,
JobNameComponent,
GlBadge,
GlLink,
},
directives: {
@ -113,6 +118,12 @@ export default {
isSingleItem() {
return this.type === SINGLE_JOB;
},
isBridge() {
return this.kind === BRIDGE_KIND;
},
kind() {
return this.job?.kind || '';
},
nameComponent() {
return this.hasDetails ? 'gl-link' : 'div';
},
@ -187,6 +198,7 @@ export default {
[this.$options.hoverClass]:
this.relatedDownstreamHovered || this.relatedDownstreamExpanded,
},
{ 'gl-rounded-lg': this.isBridge },
this.cssClassJobName,
];
},
@ -213,9 +225,6 @@ export default {
this.$emit('pipelineActionRequestComplete');
},
},
i18n: {
unauthorizedTooltip: __('You are not authorized to run this manual job'),
},
};
</script>
<template>
@ -253,6 +262,9 @@ export default {
</div>
</div>
</div>
<gl-badge v-if="isBridge" class="gl-mt-3" variant="info" size="sm">
{{ $options.i18n.bridgeBadgeText }}
</gl-badge>
</component>
<action-component

View File

@ -111,7 +111,7 @@ class Projects::BranchesController < Projects::ApplicationController
flash_type = result.error? ? :alert : :notice
flash[flash_type] = result.message
redirect_to project_branches_path(@project), status: :see_other
redirect_back_or_default(default: project_branches_path(@project), options: { status: :see_other })
end
format.js { head result.http_status }

View File

@ -102,6 +102,7 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) {
__typename
id
name
kind
scheduledAt
needs {
__typename

View File

@ -26,7 +26,8 @@ module ClustersHelper
default_branch_name: default_branch_name(clusterable),
project_path: clusterable_project_path(clusterable),
kas_address: Gitlab::Kas.external_url,
gitlab_version: Gitlab.version_info
gitlab_version: Gitlab.version_info,
kas_version: Gitlab::Kas.version_info
}
end

View File

@ -5,25 +5,50 @@ module Preloaders
# stores the values in requests store via the ProjectTeam class.
class UserMaxAccessLevelInProjectsPreloader
def initialize(projects, user)
@projects = projects
@projects = if projects.is_a?(Array)
Project.where(id: projects)
else
# Push projects base query in to a sub-select to avoid
# table name clashes. Performs better than aliasing.
Project.where(id: projects.reselect(:id))
end
@user = user
end
def execute
# Use reselect to override the existing select to prevent
# the error `subquery has too many columns`
# NotificationsController passes in an Array so we need to check the type
project_ids = @projects.is_a?(ActiveRecord::Relation) ? @projects.reselect(:id) : @projects
access_levels = @user
.project_authorizations
.where(project_id: project_ids)
.group(:project_id)
.maximum(:access_level)
project_authorizations = ProjectAuthorization.arel_table
@projects.each do |project|
access_level = access_levels[project.id] || Gitlab::Access::NO_ACCESS
auths = @projects
.select(
Project.default_select_columns,
project_authorizations[:user_id],
project_authorizations[:access_level]
)
.joins(project_auth_join)
auths.each do |project|
access_level = project.access_level || Gitlab::Access::NO_ACCESS
ProjectTeam.new(project).write_member_access_for_user_id(@user.id, access_level)
end
end
private
def project_auth_join
project_authorizations = ProjectAuthorization.arel_table
projects = Project.arel_table
projects
.join(
project_authorizations.as(project_authorizations.name),
Arel::Nodes::OuterJoin
)
.on(
project_authorizations[:project_id].eq(projects[:id])
.and(project_authorizations[:user_id].eq(@user.id))
)
.join_sources
end
end
end

View File

@ -239,6 +239,8 @@ class User < ApplicationRecord
has_many :timelogs
has_many :resource_label_events, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent
#
# Validations
#

View File

@ -63,10 +63,7 @@ module Users
# destroying: https://github.com/rails/rails/issues/22510
# This ensures we delete records in batches.
user.destroy_dependent_associations_in_batches(exclude: [:snippets])
if Feature.enabled?(:nullify_in_batches_on_user_deletion)
user.nullify_dependent_associations_in_batches
end
user.nullify_dependent_associations_in_batches
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
user_data = user.destroy

View File

@ -3,17 +3,17 @@
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= render "devise/shared/error_messages", resource: resource
.form-group
.form-group.gl-px-5.gl-pt-5
= f.label :email
= f.email_field :email, class: "form-control gl-form-input", required: true, autocomplete: 'off', value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.')
.form-text.text-muted
= _('Requires your primary GitLab email address.')
%div
- if recaptcha_enabled?
= recaptcha_tags nonce: content_security_policy_nonce
.gl-px-5
= recaptcha_tags nonce: content_security_policy_nonce
.gl-mt-5
.gl-p-5
= f.submit _("Reset password"), class: "gl-button btn-confirm btn"
.clearfix.prepend-top-20

View File

@ -1,6 +1,6 @@
= gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors js-sign-in-form', aria: { live: 'assertive' }, data: { testid: 'sign-in-form' }}) do |f|
.form-group.gl-px-5.gl-pt-5
= f.label _('Username or email'), for: 'user_login', class: 'label-bold'
= render_if_exists 'devise/sessions/new_base_user_login_label', form: f
= f.text_field :login, value: @invite_email, class: 'form-control gl-form-input top js-username-field', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', required: true, title: _('This field is required.'), data: { qa_selector: 'login_field', testid: 'username-field' }
.form-group.gl-px-5
= f.label :password, class: 'label-bold'

View File

@ -0,0 +1 @@
= local_assigns[:form].label _('Username or email'), for: 'user_login', class: 'label-bold'

View File

@ -1,8 +0,0 @@
---
name: 'nullify_in_batches_on_user_deletion'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84709
rollout_issue_url:
milestone: '14.10'
type: development
group: group::optimize
default_enabled: true

View File

@ -0,0 +1,16 @@
- name: "Elasticsearch 6.8.x in GitLab 15.0"
announcement_milestone: "14.8"
announcement_date: "2022-02-22"
removal_milestone: "15.0"
removal_date: "2022-05-22"
breaking_change: true
reporter: JohnMcGuire
body: | # Do not modify this line, instead modify the lines below.
Elasticsearch 6.8 support has been removed in GitLab 15.0. Elasticsearch 6.8 has reached [end of life](https://www.elastic.co/support/eol).
If you use Elasticsearch 6.8, **you must upgrade your Elasticsearch version to 7.x** prior to upgrading to GitLab 15.0.
You should not upgrade to Elasticsearch 8 until you have completed the GitLab 15.0 upgrade.
View the [version requirements](https://docs.gitlab.com/ee/integration/elasticsearch.html#version-requirements) for details.
# The following items are not published on the docs page, but may be used in the future.
stage: "Enablement"
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350275

View File

@ -144,7 +144,7 @@ You can optionally track progress and verify that all packages migrated successf
Verify `objectstg` below (where `file_store=2`) has count of all states:
```shell
gitlabhq_production=# SELECT count(*) AS total, sum(case when file_store = '1' then 1 else 0 end) AS filesystem, sum(case when file_store = '2' then 1 else 0 end) AS objectstg FROM terraform_states;
gitlabhq_production=# SELECT count(*) AS total, sum(case when file_store = '1' then 1 else 0 end) AS filesystem, sum(case when file_store = '2' then 1 else 0 end) AS objectstg FROM terraform_state_versions;
total | filesystem | objectstg
------+------------+-----------
@ -154,7 +154,7 @@ total | filesystem | objectstg
Verify that there are no files on disk in the `terraform_state` folder:
```shell
sudo find /var/opt/gitlab/gitlab-rails/shared/terraform_state -type f | wc -l
sudo find /var/opt/gitlab/gitlab-rails/shared/terraform_state -type f | grep -v tmp | wc -l
```
### S3-compatible connection settings

View File

@ -93,12 +93,6 @@ module Geo
def self.model
::Packages::PackageFile
end
# The feature flag follows the format `geo_#{replicable_name}_replication`,
# so here it would be `geo_package_file_replication`
def self.replication_enabled_by_default?
false
end
end
end
```

View File

@ -247,12 +247,18 @@ The `* as-if-jh` jobs are run in addition to the regular EE-context jobs. The `j
The intent is to ensure that a change doesn't introduce a failure after `gitlab-org/gitlab` is synced to [GitLab JH](https://jihulab.com/gitlab-cn/gitlab).
### When to consider applying `pipeline:run-as-if-jh` label
If a Ruby file is renamed and there's a corresponding [`prepend_mod` line](jh_features_review.md#jh-features-based-on-ce-or-ee-features),
it's likely that GitLab JH is relying on it and requires a corresponding
change to rename the module or class it's prepending.
### Corresponding JH branch
You can create a corresponding JH branch on [GitLab JH](https://jihulab.com/gitlab-cn/gitlab) by
appending `-jh` to the branch name. If a corresponding JH branch is found,
`* as-if-jh` jobs grab the `jh` folder from the respective branch,
rather than from the default branch.
rather than from the default branch `main-jh`.
NOTE:
For now, CI will try to fetch the branch on the [GitLab JH mirror](https://gitlab.com/gitlab-org/gitlab-jh-mirrors/gitlab), so it might take some time for the new JH branch to propagate to the mirror.

View File

@ -116,6 +116,20 @@ The following `geo:db:*` tasks have been removed from GitLab 15.0 and have been
- `geo:db:test:load` -> `db:test:load:geo`
- `geo:db:test:purge` -> `db:test:purge:geo`
### Elasticsearch 6.8.x in GitLab 15.0
WARNING:
This feature was changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
Elasticsearch 6.8 support has been removed in GitLab 15.0. Elasticsearch 6.8 has reached [end of life](https://www.elastic.co/support/eol).
If you use Elasticsearch 6.8, **you must upgrade your Elasticsearch version to 7.x** prior to upgrading to GitLab 15.0.
You should not upgrade to Elasticsearch 8 until you have completed the GitLab 15.0 upgrade.
View the [version requirements](https://docs.gitlab.com/ee/integration/elasticsearch.html#version-requirements) for details.
### GitLab Serverless
WARNING:

View File

@ -292,14 +292,12 @@ scan images from within your Kubernetes cluster and record the vulnerabilities i
### Prerequisites
- [Starboard Operator](https://aquasecurity.github.io/starboard/v0.10.3/operator/installation/kubectl/)
installed and configured in your cluster.
- [GitLab agent](../../clusters/agent/install/index.md)
set up in GitLab, installed in your cluster, and configured using a configuration repository.
### Configuration
The agent runs the cluster image scanning once the `cluster_image_scanning`
The agent runs the cluster image scanning once the `starboard`
directive is added to your [agent's configuration repository](../../clusters/agent/vulnerabilities.md).
## Security Dashboard

View File

@ -244,7 +244,7 @@ table.supported-languages ul {
</tr>
<tr>
<td rowspan="3">Python</td>
<td rowspan="3">3.6</td>
<td rowspan="3">3.9</td>
<td><a href="https://setuptools.readthedocs.io/en/latest/">setuptools</a></td>
<td><code>setup.py</code></td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
@ -915,9 +915,9 @@ import the following default dependency scanning analyzer images from `registry.
your [local Docker container registry](../../packages/container_registry/index.md):
```plaintext
registry.gitlab.com/security-products/gemnasium:2
registry.gitlab.com/security-products/gemnasium-maven:2
registry.gitlab.com/security-products/gemnasium-python:2
registry.gitlab.com/security-products/gemnasium:3
registry.gitlab.com/security-products/gemnasium-maven:3
registry.gitlab.com/security-products/gemnasium-python:3
```
The process for importing Docker images into a local offline Docker registry depends on
@ -1219,5 +1219,4 @@ To work around this error, downgrade the analyzer's version of `setuptools` (e.g
gemnasium-python-dependency_scanning:
before_script:
- pip install setuptools==57.5.0
image: registry.gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python:2-python-3.9
```

View File

@ -87,6 +87,13 @@ This rule enforces the defined actions and schedules a scan on the provided date
| `cadence` | `string` | CRON expression (for example, `0 0 * * *`) | A whitespace-separated string containing five fields that represents the scheduled time. |
| `clusters` | `object` | | The cluster where the given policy enforces running selected scans (only for `container_scanning`/`cluster_image_scanning` scans). The key of the object is the name of the Kubernetes cluster configured for your project in GitLab. In the optionally provided value of the object, you can precisely select Kubernetes resources that are scanned. |
GitLab supports the following types of CRON syntax for the `cadence` field:
- A daily cadence of once per hour at a specified hour, for example: `0 18 * * *`
- A weekly cadence of once per week on a specified day and at a specified hour, for example: `0 13 * * 0`
It is possible that other elements of the CRON syntax will work in the cadence field, however, GitLab does not officially test or support them.
### `cluster` schema
Use this schema to define `clusters` objects in the [`schedule` rule type](#schedule-rule-type).

View File

@ -11,7 +11,7 @@ When you are using the GitLab agent for Kubernetes, you might experience issues
You can start by viewing the service logs:
```shell
kubectl logs -f -l=app=gitlab-agent -n gitlab-kubernetes-agent
kubectl logs -f -l=app=gitlab-agent -n gitlab-agent
```
If you are a GitLab administrator, you can also view the [GitLab agent server logs](../../../administration/clusters/kas.md#troubleshooting).
@ -113,14 +113,14 @@ will be picked up automatically.
For example, if your internal CA certificate is `myCA.pem`:
```plaintext
kubectl -n gitlab-kubernetes-agent create configmap ca-pemstore --from-file=myCA.pem
kubectl -n gitlab-agent create configmap ca-pemstore --from-file=myCA.pem
```
Then in `resources.yml`:
```yaml
spec:
serviceAccountName: gitlab-kubernetes-agent
serviceAccountName: gitlab-agent
containers:
- name: agent
image: "registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/agentk:<version>"
@ -140,7 +140,7 @@ Then in `resources.yml`:
volumes:
- name: token-volume
secret:
secretName: gitlab-kubernetes-agent-token
secretName: gitlab-agent-token
- name: ca-pemstore-volume
configMap:
name: ca-pemstore

View File

@ -34,80 +34,34 @@ You can use [cluster image scanning](../../application_security/cluster_image_sc
to scan container images in your cluster for security vulnerabilities.
To begin scanning all resources in your cluster, add a `starboard`
configuration block to your agent configuration file with no `filters`:
configuration block to your agent configuration with a `cadence` field
containing a CRON expression for when the scans will be run.
```yaml
starboard:
vulnerability_report:
filters: []
cadence: '0 0 * * *' # Daily at 00:00 (Kubernetes cluster time)
```
The namespaces that are able to be scanned depend on the [Starboard Operator install mode](https://aquasecurity.github.io/starboard/latest/operator/configuration/#install-modes).
By default, the Starboard Operator only scans resources in the `default` namespace. To change this
behavior, edit the `STARBOARD_OPERATOR` environment variable in the `starboard-operator` deployment
definition.
The `cadence` field is required. GitLab supports the following types of CRON syntax for the cadence field:
By adding filters, you can limit scans by:
- A daily cadence of once per hour at a specified hour, for example: `0 18 * * *`
- A weekly cadence of once per week on a specified day and at a specified hour, for example: `0 13 * * 0`
- Resource name
- Kind
- Container name
- Namespace
```yaml
starboard:
vulnerability_report:
filters:
- namespaces:
- staging
- production
kinds:
- Deployment
- DaemonSet
containers:
- ruby
- postgres
- nginx
resources:
- my-app-name
- postgres
- ingress-nginx
```
A resource is scanned if the resource matches any of the given names and all of the given filter
types (`namespaces`, `kinds`, `containers`, `resources`). If a filter type is omitted, then all
names are scanned. In this example, a resource isn't scanned unless it has a container named `ruby`,
`postgres`, or `nginx`, and it's a `Deployment`:
```yaml
starboard:
vulnerability_report:
filters:
- kinds:
- Deployment
containers:
- ruby
- postgres
- nginx
```
There is also a global `namespaces` field that applies to all filters:
It is possible that other elements of the CRON syntax will work in the cadence field, however, GitLab does not officially test or support them.
By default, cluster image scanning will attempt to scan the workloads in all
namespaces for vulnerabilities. The `vulnerability_report` block has a `namespaces`
field which can be used to restrict which namespaces are scanned. For example,
if you would like to scan only the `development`, `staging`, and `production`
namespaces, you can use this configuration:
```yaml
starboard:
vulnerability_report:
cadence: '0 0 * * *'
namespaces:
- production
filters:
- kinds:
- Deployment
- kinds:
- DaemonSet
resources:
- log-collector
- development
- staging
- production
```
In this example, the following resources are scanned:
- All deployments (`Deployment`) in the `production` namespace.
- All daemon sets (`DaemonSet`) named `log-collector` in the `production` namespace.

View File

@ -31,7 +31,7 @@ pre-push:
rubocop:
tags: backend style
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
glob: '*.rb'
glob: '*.{rb,rake}'
run: REVEAL_RUBOCOP_TODO=0 bundle exec rubocop --parallel --force-exclusion {files}
graphql_docs:
tags: documentation

View File

@ -14,7 +14,7 @@ variables:
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
DS_EXCLUDED_ANALYZERS: ""
DS_EXCLUDED_PATHS: "spec, test, tests, tmp"
DS_MAJOR_VERSION: 2
DS_MAJOR_VERSION: 3
dependency_scanning:
stage: test
@ -82,9 +82,6 @@ gemnasium-maven-dependency_scanning:
- .cyclone-dx-reports
variables:
DS_ANALYZER_NAME: "gemnasium-maven"
# Stop reporting Gradle as "maven".
# See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
DS_REPORT_PACKAGE_MANAGER_MAVEN_WHEN_JAVA: "false"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED
when: never
@ -104,9 +101,6 @@ gemnasium-python-dependency_scanning:
- .cyclone-dx-reports
variables:
DS_ANALYZER_NAME: "gemnasium-python"
# Stop reporting Pipenv and Setuptools as "pip".
# See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
DS_REPORT_PACKAGE_MANAGER_PIP_WHEN_PYTHON: "false"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED
when: never

View File

@ -33,6 +33,10 @@ module Gitlab
@_version ||= Rails.root.join(VERSION_FILE).read.chomp
end
def version_info
Gitlab::VersionInfo.parse(version)
end
# Return GitLab KAS external_url
#
# @return [String] external_url

View File

@ -3258,6 +3258,9 @@ msgstr ""
msgid "Advanced export options"
msgstr ""
msgid "AdvancedSearch|Elasticsearch version not compatible"
msgstr ""
msgid "AdvancedSearch|Reindex required"
msgstr ""
@ -27187,6 +27190,9 @@ msgstr ""
msgid "Pause"
msgstr ""
msgid "Pause indexing and upgrade Elasticsearch to a supported version."
msgstr ""
msgid "Pause time (ms)"
msgstr ""
@ -39757,6 +39763,9 @@ msgstr ""
msgid "Trigger cluster reindexing. Only use this with an index that was created in GitLab 13.0 or later."
msgstr ""
msgid "Trigger job"
msgstr ""
msgid "Trigger manual job"
msgstr ""

View File

@ -307,17 +307,36 @@ RSpec.describe Projects::BranchesController do
sign_in(developer)
end
it 'returns 303' do
post :destroy,
format: :html,
params: {
id: 'foo/bar/baz',
namespace_id: project.namespace,
project_id: project
}
subject(:post_request) do
post :destroy, format: :html, params: {
id: 'foo/bar/baz',
namespace_id: project.namespace,
project_id: project
}
end
it "returns response code 303" do
post_request
expect(response).to have_gitlab_http_status(:see_other)
end
context 'with http referer' do
before do
request.env['HTTP_REFERER'] = '/'
end
it "redirects to the referer path" do
post_request
expect(response).to redirect_to('/')
end
end
context 'without http referer' do
it "redirects to the project branches path" do
post_request
expect(response).to redirect_to(project_branches_path(project))
end
end
end
describe "POST destroy" do

View File

@ -445,7 +445,7 @@ RSpec.describe 'GFM autocomplete', :js do
click_button('Cancel')
page.within('.modal') do
click_button('OK', match: :first)
click_button('Discard changes', match: :first)
end
wait_for_requests

View File

@ -13,6 +13,7 @@ const defaultConfigHelpUrl =
const provideData = {
gitlabVersion: '14.8',
kasVersion: '14.8',
};
const propsData = {
agents: clusterAgents,
@ -26,7 +27,7 @@ const outdatedTitle = I18N_AGENT_TABLE.versionOutdatedTitle;
const mismatchTitle = I18N_AGENT_TABLE.versionMismatchTitle;
const mismatchOutdatedTitle = I18N_AGENT_TABLE.versionMismatchOutdatedTitle;
const outdatedText = sprintf(I18N_AGENT_TABLE.versionOutdatedText, {
version: provideData.gitlabVersion,
version: provideData.kasVersion,
});
const mismatchText = I18N_AGENT_TABLE.versionMismatchText;

View File

@ -11,6 +11,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "6",
"kind": "BUILD",
"name": "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@ -53,6 +54,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "11",
"kind": "BUILD",
"name": "build_b",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@ -95,6 +97,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "16",
"kind": "BUILD",
"name": "build_c",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@ -137,6 +140,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "21",
"kind": "BUILD",
"name": "build_d 1/3",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@ -163,6 +167,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "24",
"kind": "BUILD",
"name": "build_d 2/3",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@ -189,6 +194,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "27",
"kind": "BUILD",
"name": "build_d 3/3",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@ -231,6 +237,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "59",
"kind": "BUILD",
"name": "test_c",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@ -275,6 +282,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "34",
"kind": "BUILD",
"name": "test_a",
"needs": Array [
"build_c",
@ -325,6 +333,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "42",
"kind": "BUILD",
"name": "test_b 1/2",
"needs": Array [
"build_d 3/3",
@ -363,6 +372,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "67",
"kind": "BUILD",
"name": "test_b 2/2",
"needs": Array [
"build_d 3/3",
@ -417,6 +427,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "53",
"kind": "BUILD",
"name": "test_d",
"needs": Array [
"build_b",

View File

@ -1,89 +1,34 @@
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { GlBadge } from '@gitlab/ui';
import JobItem from '~/pipelines/components/graph/job_item.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
delayedJob,
mockJob,
mockJobWithoutDetails,
mockJobWithUnauthorizedAction,
triggerJob,
} from './mock_data';
describe('pipeline graph job item', () => {
let wrapper;
const findJobWithoutLink = () => wrapper.find('[data-testid="job-without-link"]');
const findJobWithLink = () => wrapper.find('[data-testid="job-with-link"]');
const findActionComponent = () => wrapper.find('[data-testid="ci-action-component"]');
const findJobWithoutLink = () => wrapper.findByTestId('job-without-link');
const findJobWithLink = () => wrapper.findByTestId('job-with-link');
const findActionComponent = () => wrapper.findByTestId('ci-action-component');
const findBadge = () => wrapper.findComponent(GlBadge);
const createWrapper = (propsData) => {
wrapper = mount(JobItem, {
propsData,
});
wrapper = extendedWrapper(
mount(JobItem, {
propsData,
}),
);
};
const triggerActiveClass = 'gl-shadow-x0-y0-b3-s1-blue-500';
const delayedJob = {
__typename: 'CiJob',
name: 'delayed job',
scheduledAt: '2015-07-03T10:01:00.000Z',
needs: [],
status: {
__typename: 'DetailedStatus',
icon: 'status_scheduled',
tooltip: 'delayed manual action (%{remainingTime})',
hasDetails: true,
detailsPath: '/root/kinder-pipe/-/jobs/5339',
group: 'scheduled',
action: {
__typename: 'StatusAction',
icon: 'time-out',
title: 'Unschedule',
path: '/frontend-fixtures/builds-project/-/jobs/142/unschedule',
buttonTitle: 'Unschedule job',
},
},
};
const mockJob = {
id: 4256,
name: 'test',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
tooltip: 'passed',
group: 'success',
detailsPath: '/root/ci-mock/builds/4256',
hasDetails: true,
action: {
icon: 'retry',
title: 'Retry',
path: '/root/ci-mock/builds/4256/retry',
method: 'post',
},
},
};
const mockJobWithoutDetails = {
id: 4257,
name: 'job_without_details',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
detailsPath: '/root/ci-mock/builds/4257',
hasDetails: false,
},
};
const mockJobWithUnauthorizedAction = {
id: 4258,
name: 'stop-environment',
status: {
icon: 'status_manual',
label: 'manual stop action (not allowed)',
tooltip: 'manual action',
group: 'manual',
detailsPath: '/root/ci-mock/builds/4258',
hasDetails: true,
action: null,
},
};
afterEach(() => {
wrapper.destroy();
});
@ -148,13 +93,25 @@ describe('pipeline graph job item', () => {
});
});
it('should render provided class name', () => {
createWrapper({
job: mockJob,
cssClassJobName: 'css-class-job-name',
describe('job style', () => {
beforeEach(() => {
createWrapper({
job: mockJob,
cssClassJobName: 'css-class-job-name',
});
});
expect(wrapper.find('a').classes()).toContain('css-class-job-name');
it('should render provided class name', () => {
expect(wrapper.find('a').classes()).toContain('css-class-job-name');
});
it('does not show a badge on the job item', () => {
expect(findBadge().exists()).toBe(false);
});
it('does not apply the trigger job class', () => {
expect(findJobWithLink().classes()).not.toContain('gl-rounded-lg');
});
});
describe('status label', () => {
@ -201,34 +158,51 @@ describe('pipeline graph job item', () => {
});
});
describe('trigger job highlighting', () => {
it.each`
job | jobName | expanded | link
${mockJob} | ${mockJob.name} | ${true} | ${true}
${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${true} | ${false}
`(
`trigger job should stay highlighted when downstream is expanded`,
({ job, jobName, expanded, link }) => {
createWrapper({ job, pipelineExpanded: { jobName, expanded } });
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
describe('trigger job', () => {
describe('card', () => {
beforeEach(() => {
createWrapper({ job: triggerJob });
});
expect(findJobEl().classes()).toContain(triggerActiveClass);
},
);
it('shows a badge on the job item', () => {
expect(findBadge().exists()).toBe(true);
expect(findBadge().text()).toBe('Trigger job');
});
it.each`
job | jobName | expanded | link
${mockJob} | ${mockJob.name} | ${false} | ${true}
${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${false} | ${false}
`(
`trigger job should not be highlighted when downstream is not expanded`,
({ job, jobName, expanded, link }) => {
createWrapper({ job, pipelineExpanded: { jobName, expanded } });
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
it('applies a rounded corner style instead of the usual pill shape', () => {
expect(findJobWithoutLink().classes()).toContain('gl-rounded-lg');
});
});
expect(findJobEl().classes()).not.toContain(triggerActiveClass);
},
);
describe('highlighting', () => {
it.each`
job | jobName | expanded | link
${mockJob} | ${mockJob.name} | ${true} | ${true}
${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${true} | ${false}
`(
`trigger job should stay highlighted when downstream is expanded`,
({ job, jobName, expanded, link }) => {
createWrapper({ job, pipelineExpanded: { jobName, expanded } });
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
expect(findJobEl().classes()).toContain(triggerActiveClass);
},
);
it.each`
job | jobName | expanded | link
${mockJob} | ${mockJob.name} | ${false} | ${true}
${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${false} | ${false}
`(
`trigger job should not be highlighted when downstream is not expanded`,
({ job, jobName, expanded, link }) => {
createWrapper({ job, pipelineExpanded: { jobName, expanded } });
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
expect(findJobEl().classes()).not.toContain(triggerActiveClass);
},
);
});
});
describe('job classes', () => {

View File

@ -1,4 +1,5 @@
import { unwrapPipelineData } from '~/pipelines/components/graph/utils';
import { BUILD_KIND, BRIDGE_KIND } from '~/pipelines/components/graph/constants';
export const mockPipelineResponse = {
data: {
@ -50,6 +51,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '6',
kind: BUILD_KIND,
name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
scheduledAt: null,
status: {
@ -101,6 +103,7 @@ export const mockPipelineResponse = {
__typename: 'CiJob',
id: '11',
name: 'build_b',
kind: BUILD_KIND,
scheduledAt: null,
status: {
__typename: 'DetailedStatus',
@ -151,6 +154,7 @@ export const mockPipelineResponse = {
__typename: 'CiJob',
id: '16',
name: 'build_c',
kind: BUILD_KIND,
scheduledAt: null,
status: {
__typename: 'DetailedStatus',
@ -200,6 +204,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '21',
kind: BUILD_KIND,
name: 'build_d 1/3',
scheduledAt: null,
status: {
@ -232,6 +237,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '24',
kind: BUILD_KIND,
name: 'build_d 2/3',
scheduledAt: null,
status: {
@ -264,6 +270,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '27',
kind: BUILD_KIND,
name: 'build_d 3/3',
scheduledAt: null,
status: {
@ -329,6 +336,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '34',
kind: BUILD_KIND,
name: 'test_a',
scheduledAt: null,
status: {
@ -413,6 +421,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '42',
kind: BUILD_KIND,
name: 'test_b 1/2',
scheduledAt: null,
status: {
@ -499,6 +508,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '67',
kind: BUILD_KIND,
name: 'test_b 2/2',
scheduledAt: null,
status: {
@ -603,6 +613,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '59',
kind: BUILD_KIND,
name: 'test_c',
scheduledAt: null,
status: {
@ -646,6 +657,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '53',
kind: BUILD_KIND,
name: 'test_d',
scheduledAt: null,
status: {
@ -871,6 +883,7 @@ export const wrappedPipelineReturn = {
{
__typename: 'CiJob',
id: '83',
kind: BUILD_KIND,
name: 'build_n',
scheduledAt: null,
needs: {
@ -941,3 +954,87 @@ export const mockCalloutsResponse = (mappedCallouts) => ({
},
},
});
export const delayedJob = {
__typename: 'CiJob',
kind: BUILD_KIND,
name: 'delayed job',
scheduledAt: '2015-07-03T10:01:00.000Z',
needs: [],
status: {
__typename: 'DetailedStatus',
icon: 'status_scheduled',
tooltip: 'delayed manual action (%{remainingTime})',
hasDetails: true,
detailsPath: '/root/kinder-pipe/-/jobs/5339',
group: 'scheduled',
action: {
__typename: 'StatusAction',
icon: 'time-out',
title: 'Unschedule',
path: '/frontend-fixtures/builds-project/-/jobs/142/unschedule',
buttonTitle: 'Unschedule job',
},
},
};
export const mockJob = {
id: 4256,
name: 'test',
kind: BUILD_KIND,
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
tooltip: 'passed',
group: 'success',
detailsPath: '/root/ci-mock/builds/4256',
hasDetails: true,
action: {
icon: 'retry',
title: 'Retry',
path: '/root/ci-mock/builds/4256/retry',
method: 'post',
},
},
};
export const mockJobWithoutDetails = {
id: 4257,
name: 'job_without_details',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
detailsPath: '/root/ci-mock/builds/4257',
hasDetails: false,
},
};
export const mockJobWithUnauthorizedAction = {
id: 4258,
name: 'stop-environment',
status: {
icon: 'status_manual',
label: 'manual stop action (not allowed)',
tooltip: 'manual action',
group: 'manual',
detailsPath: '/root/ci-mock/builds/4258',
hasDetails: true,
action: null,
},
};
export const triggerJob = {
id: 4259,
name: 'trigger',
kind: BRIDGE_KIND,
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
action: null,
},
};

View File

@ -90,6 +90,10 @@ RSpec.describe ClustersHelper do
expect(subject[:gitlab_version]).to eq(Gitlab.version_info)
end
it 'displays KAS version' do
expect(subject[:kas_version]).to eq(Gitlab::Kas.version_info)
end
context 'user has no permissions to create a cluster' do
it 'displays that user can\'t add cluster' do
expect(subject[:can_add_cluster]).to eq("false")

View File

@ -9,29 +9,47 @@ RSpec.describe Preloaders::UserMaxAccessLevelInProjectsPreloader do
let_it_be(:project_3) { create(:project) }
let(:projects) { [project_1, project_2, project_3] }
let(:query) { projects.each { |project| user.can?(:read_project, project) } }
before do
project_1.add_developer(user)
project_2.add_developer(user)
end
context 'preload maximum access level to avoid querying project_authorizations', :request_store do
it 'avoids N+1 queries', :request_store do
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, user).execute
context 'without preloader' do
it 'runs N queries' do
expect { query }.to make_queries(projects.size)
end
end
query_count = ActiveRecord::QueryRecorder.new do
projects.each { |project| user.can?(:read_project, project) }
end.count
describe '#execute', :request_store do
let(:projects_arg) { projects }
expect(query_count).to eq(0)
before do
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_arg, user).execute
end
it 'runs N queries without preloading' do
query_count = ActiveRecord::QueryRecorder.new do
projects.each { |project| user.can?(:read_project, project) }
end.count
it 'avoids N+1 queries' do
expect { query }.not_to make_queries
end
expect(query_count).to eq(projects.size)
context 'when projects is an array of IDs' do
let(:projects_arg) { [project_1.id, project_2.id, project_3.id] }
it 'avoids N+1 queries' do
expect { query }.not_to make_queries
end
end
# Test for handling of SQL table name clashes.
context 'when projects is a relation including project_authorizations' do
let(:projects_arg) do
Project.where(id: ProjectAuthorization.where(project_id: projects).select(:project_id))
end
it 'avoids N+1 queries' do
expect { query }.not_to make_queries
end
end
end
end

View File

@ -336,35 +336,24 @@ RSpec.describe Users::DestroyService do
context 'batched nullify' do
let(:other_user) { create(:user) }
context 'when :nullify_in_batches_on_user_deletion feature flag is enabled' do
it 'nullifies related associations in batches' do
expect(other_user).to receive(:nullify_dependent_associations_in_batches).and_call_original
it 'nullifies related associations in batches' do
expect(other_user).to receive(:nullify_dependent_associations_in_batches).and_call_original
described_class.new(user).execute(other_user, skip_authorization: true)
end
it 'nullifies last_updated_issues and closed_issues' do
issue = create(:issue, closed_by: other_user, updated_by: other_user)
described_class.new(user).execute(other_user, skip_authorization: true)
issue.reload
expect(issue.closed_by).to be_nil
expect(issue.updated_by).to be_nil
end
described_class.new(user).execute(other_user, skip_authorization: true)
end
context 'when :nullify_in_batches_on_user_deletion feature flag is disabled' do
before do
stub_feature_flags(nullify_in_batches_on_user_deletion: false)
end
it 'nullifies last_updated_issues, closed_issues, resource_label_events' do
issue = create(:issue, closed_by: other_user, updated_by: other_user)
resource_label_event = create(:resource_label_event, user: other_user)
it 'does not use batching' do
expect(other_user).not_to receive(:nullify_dependent_associations_in_batches)
described_class.new(user).execute(other_user, skip_authorization: true)
described_class.new(user).execute(other_user, skip_authorization: true)
end
issue.reload
resource_label_event.reload
expect(issue.closed_by).to be_nil
expect(issue.updated_by).to be_nil
expect(resource_label_event.user).to be_nil
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
RSpec::Matchers.define :make_queries do |expected_count = nil|
supports_block_expectations
match do |block|
@recorder = ActiveRecord::QueryRecorder.new(&block)
@counter = @recorder.count
if expected_count
@counter == expected_count
else
@counter > 0
end
end
failure_message do |_|
if expected_count
"expected to make #{expected_count} queries but made #{@counter} queries"
else
"expected to make queries but did not make any"
end
end
failure_message_when_negated do |_|
if expected_count
"expected not to make #{expected_count} queries but received #{@counter} queries"
else
"expected not to make queries but received #{@counter} queries"
end
end
end

View File

@ -28,6 +28,20 @@ RSpec.describe 'devise/shared/_signin_box' do
end
end
describe 'Base form' do
before do
stub_devise
allow(view).to receive(:captcha_enabled?).and_return(false)
allow(view).to receive(:captcha_on_login_required?).and_return(false)
end
it 'renders user_login label' do
render
expect(rendered).to have_content(_('Username or email'))
end
end
def stub_devise
allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:user])
allow(view).to receive(:resource).and_return(spy)

View File

@ -35,7 +35,6 @@ var zipSubcommandsErrorsCounter = promauto.NewCounterVec(
}, []string{"error"})
type artifactsUploadProcessor struct {
opts *destination.UploadOpts
format string
SavedFileTracker
@ -52,7 +51,7 @@ func Artifacts(myAPI *api.API, h http.Handler, p Preparer) http.Handler {
format := r.URL.Query().Get(ArtifactFormatKey)
mg := &artifactsUploadProcessor{opts: opts, format: format, SavedFileTracker: SavedFileTracker{Request: r}}
mg := &artifactsUploadProcessor{format: format, SavedFileTracker: SavedFileTracker{Request: r}}
interceptMultipartFiles(w, r, h, a, mg, opts)
}, "/authorize")
}
@ -62,12 +61,9 @@ func (a *artifactsUploadProcessor) generateMetadataFromZip(ctx context.Context,
defer metaWriter.Close()
metaOpts := &destination.UploadOpts{
LocalTempPath: a.opts.LocalTempPath,
LocalTempPath: os.TempDir(),
TempFilePrefix: "metadata.gz",
}
if metaOpts.LocalTempPath == "" {
metaOpts.LocalTempPath = os.TempDir()
}
fileName := file.LocalPath
if fileName == "" {