Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
47bb4281f0
commit
6e881971b0
|
@ -16,3 +16,6 @@ charset = utf-8
|
|||
|
||||
[*.{md,markdown,js.snap}]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.rb]
|
||||
max_line_length = 120
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
Style/OpenStructUse:
|
||||
Exclude:
|
||||
- app/helpers/application_settings_helper.rb
|
||||
- ee/spec/features/projects/new_project_spec.rb
|
||||
- ee/spec/finders/template_finder_spec.rb
|
||||
- ee/spec/helpers/ee/blob_helper_spec.rb
|
||||
|
|
|
@ -2,6 +2,15 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 14.9.3 (2022-04-12)
|
||||
|
||||
### Fixed (4 changes)
|
||||
|
||||
- [Revert Protected Environment group access inheritence](gitlab-org/gitlab@488fd8f3f6770eebae10c815398534ff41d57546) ([merge request](gitlab-org/gitlab!84664))
|
||||
- [Fix URL blocker when object storage enabled but type is disabled](gitlab-org/gitlab@d0da89768774de9cf635af530ed7386e65f92d40) ([merge request](gitlab-org/gitlab!84664))
|
||||
- [Remove pending builds from the queue on conflict](gitlab-org/gitlab@8c88898dfd1619cc635ce5b98e30eebd91da497f) ([merge request](gitlab-org/gitlab!84664))
|
||||
- [Fix null argument handling in background migration Rake task](gitlab-org/gitlab@23e1eb3272828b3546e18efdfaea5a8077cb20f4) ([merge request](gitlab-org/gitlab!84664))
|
||||
|
||||
## 14.9.2 (2022-03-31)
|
||||
|
||||
### Security (20 changes)
|
||||
|
|
|
@ -50,11 +50,17 @@ export const SAST_IAC_CONFIG_HELP_PATH = helpPagePath(
|
|||
|
||||
export const DAST_NAME = __('Dynamic Application Security Testing (DAST)');
|
||||
export const DAST_SHORT_NAME = s__('ciReport|DAST');
|
||||
export const DAST_DESCRIPTION = __('Analyze a review version of your web application.');
|
||||
export const DAST_DESCRIPTION = s__(
|
||||
'ciReport|Analyze a deployed version of your web application for known vulnerabilities by examining it from the outside in. DAST works by simulating external attacks on your application while it is running.',
|
||||
);
|
||||
export const DAST_HELP_PATH = helpPagePath('user/application_security/dast/index');
|
||||
export const DAST_CONFIG_HELP_PATH = helpPagePath('user/application_security/dast/index', {
|
||||
anchor: 'enable-dast',
|
||||
});
|
||||
export const DAST_BADGE_TEXT = __('Available on-demand');
|
||||
export const DAST_BADGE_TOOLTIP = __(
|
||||
'On-demand scans run outside of the DevOps cycle and find vulnerabilities in your projects',
|
||||
);
|
||||
|
||||
export const DAST_PROFILES_NAME = __('DAST profiles');
|
||||
export const DAST_PROFILES_DESCRIPTION = s__(
|
||||
|
@ -171,18 +177,23 @@ export const securityFeatures = [
|
|||
type: REPORT_TYPE_SAST_IAC,
|
||||
},
|
||||
{
|
||||
name: DAST_NAME,
|
||||
shortName: DAST_SHORT_NAME,
|
||||
description: DAST_DESCRIPTION,
|
||||
helpPath: DAST_HELP_PATH,
|
||||
configurationHelpPath: DAST_CONFIG_HELP_PATH,
|
||||
type: REPORT_TYPE_DAST,
|
||||
badge: {
|
||||
text: DAST_BADGE_TEXT,
|
||||
tooltipText: DAST_BADGE_TOOLTIP,
|
||||
variant: 'info',
|
||||
},
|
||||
secondary: {
|
||||
type: REPORT_TYPE_DAST_PROFILES,
|
||||
name: DAST_PROFILES_NAME,
|
||||
description: DAST_PROFILES_DESCRIPTION,
|
||||
configurationText: DAST_PROFILES_CONFIG_TEXT,
|
||||
},
|
||||
name: DAST_NAME,
|
||||
shortName: DAST_SHORT_NAME,
|
||||
description: DAST_DESCRIPTION,
|
||||
helpPath: DAST_HELP_PATH,
|
||||
configurationHelpPath: DAST_CONFIG_HELP_PATH,
|
||||
type: REPORT_TYPE_DAST,
|
||||
},
|
||||
{
|
||||
name: DEPENDENCY_SCANNING_NAME,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script>
|
||||
import { GlButton, GlCard, GlIcon, GlLink } from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
|
||||
import { REPORT_TYPE_SAST_IAC } from '~/vue_shared/security_reports/constants';
|
||||
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
|
||||
import FeatureCardBadge from './feature_card_badge.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -10,6 +11,7 @@ export default {
|
|||
GlCard,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
FeatureCardBadge,
|
||||
ManageViaMr,
|
||||
},
|
||||
props: {
|
||||
|
@ -37,11 +39,14 @@ export default {
|
|||
text: this.$options.i18n.enableFeature,
|
||||
};
|
||||
|
||||
button.category = 'secondary';
|
||||
button.category = this.feature.category || 'secondary';
|
||||
button.text = sprintf(button.text, { feature: this.shortName });
|
||||
|
||||
return button;
|
||||
},
|
||||
manageViaMrButtonCategory() {
|
||||
return this.feature.category || 'secondary';
|
||||
},
|
||||
showManageViaMr() {
|
||||
return ManageViaMr.canRender(this.feature);
|
||||
},
|
||||
|
@ -49,13 +54,17 @@ export default {
|
|||
return { 'gl-bg-gray-10': !this.available };
|
||||
},
|
||||
statusClasses() {
|
||||
const { enabled } = this;
|
||||
const { enabled, hasBadge } = this;
|
||||
|
||||
return {
|
||||
'gl-ml-auto': true,
|
||||
'gl-flex-shrink-0': true,
|
||||
'gl-text-gray-500': !enabled,
|
||||
'gl-text-green-500': enabled,
|
||||
'gl-w-full': hasBadge,
|
||||
'gl-justify-content-space-between': hasBadge,
|
||||
'gl-display-flex': hasBadge,
|
||||
'gl-mb-4': hasBadge,
|
||||
};
|
||||
},
|
||||
hasSecondary() {
|
||||
|
@ -68,6 +77,9 @@ export default {
|
|||
isNotSastIACTemporaryHack() {
|
||||
return this.feature.type !== REPORT_TYPE_SAST_IAC;
|
||||
},
|
||||
hasBadge() {
|
||||
return Boolean(this.available && this.feature.badge?.text);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onError(message) {
|
||||
|
@ -88,7 +100,10 @@ export default {
|
|||
|
||||
<template>
|
||||
<gl-card :class="cardClasses">
|
||||
<div class="gl-display-flex gl-align-items-baseline">
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-baseline"
|
||||
:class="{ 'gl-flex-direction-column-reverse': hasBadge }"
|
||||
>
|
||||
<h3 class="gl-font-lg gl-m-0 gl-mr-3">{{ feature.name }}</h3>
|
||||
|
||||
<div
|
||||
|
@ -97,13 +112,19 @@ export default {
|
|||
data-testid="feature-status"
|
||||
:data-qa-selector="`${feature.type}_status`"
|
||||
>
|
||||
<feature-card-badge
|
||||
v-if="hasBadge"
|
||||
:badge="feature.badge"
|
||||
:badge-href="feature.badge.badgeHref"
|
||||
/>
|
||||
|
||||
<template v-if="enabled">
|
||||
<gl-icon name="check-circle-filled" />
|
||||
<span class="gl-text-green-700">{{ $options.i18n.enabled }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="available">
|
||||
{{ $options.i18n.notEnabled }}
|
||||
<span>{{ $options.i18n.notEnabled }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
|
@ -133,7 +154,7 @@ export default {
|
|||
v-else-if="showManageViaMr"
|
||||
:feature="feature"
|
||||
variant="confirm"
|
||||
category="secondary"
|
||||
:category="manageViaMrButtonCategory"
|
||||
class="gl-mt-5"
|
||||
:data-qa-selector="`${feature.type}_mr_button`"
|
||||
@error="onError"
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<script>
|
||||
import { GlBadge, GlTooltip } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlBadge,
|
||||
GlTooltip,
|
||||
},
|
||||
props: {
|
||||
badge: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
badgeHref: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<gl-tooltip
|
||||
v-if="badge.tooltipText"
|
||||
placement="top"
|
||||
boundary="window"
|
||||
title="Tooltip title"
|
||||
:target="() => $refs.badge"
|
||||
>
|
||||
{{ badge.tooltipText }}
|
||||
</gl-tooltip>
|
||||
<span ref="badge">
|
||||
<gl-badge size="sm" :href="badgeHref" :variant="badge.variant">
|
||||
{{ badge.text }}
|
||||
</gl-badge>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
|
@ -30,6 +30,10 @@ export const augmentFeatures = (securityFeatures, complianceFeatures, features =
|
|||
augmented.secondary = { ...augmented.secondary, ...featuresByType[feature.secondary.type] };
|
||||
}
|
||||
|
||||
if (augmented.badge && augmented.metaInfoPath) {
|
||||
augmented.badge.badgeHref = augmented.metaInfoPath;
|
||||
}
|
||||
|
||||
return augmented;
|
||||
};
|
||||
|
||||
|
|
|
@ -308,8 +308,7 @@ module WikiActions
|
|||
end
|
||||
|
||||
def load_content?
|
||||
return false if %w[history destroy diff].include?(params[:action])
|
||||
return false if params[:action] == 'show' && Feature.enabled?(:wiki_async_load, container, default_enabled: :yaml)
|
||||
return false if %w[history destroy diff show].include?(params[:action])
|
||||
|
||||
true
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module Types
|
|||
graphql_name 'DependencyProxyManifestStatus'
|
||||
|
||||
::DependencyProxy::Manifest.statuses.keys.each do |status|
|
||||
value status.upcase, { description: "Dependency proxy manifest has a status of #{status}.", value: status }
|
||||
value status.upcase, description: "Dependency proxy manifest has a status of #{status}.", value: status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,9 +37,15 @@ module ApplicationSettingsHelper
|
|||
end
|
||||
|
||||
def storage_weights
|
||||
Gitlab.config.repositories.storages.keys.each_with_object(OpenStruct.new) do |storage, weights|
|
||||
weights[storage.to_sym] = @application_setting.repository_storages_weighted[storage] || 0
|
||||
# Instead of using a `Struct` we could wrap this into an object.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/358419
|
||||
weights = Struct.new(*Gitlab.config.repositories.storages.keys.map(&:to_sym))
|
||||
|
||||
values = Gitlab.config.repositories.storages.keys.map do |storage|
|
||||
@application_setting.repository_storages_weighted[storage] || 0
|
||||
end
|
||||
|
||||
weights.new(*values)
|
||||
end
|
||||
|
||||
def all_protocols_enabled?
|
||||
|
@ -223,6 +229,7 @@ module ApplicationSettingsHelper
|
|||
:default_project_visibility,
|
||||
:default_projects_limit,
|
||||
:default_snippet_visibility,
|
||||
:delete_inactive_projects,
|
||||
:disable_feed_token,
|
||||
:disabled_oauth_sign_in_sources,
|
||||
:domain_denylist,
|
||||
|
@ -273,6 +280,9 @@ module ApplicationSettingsHelper
|
|||
:html_emails_enabled,
|
||||
:import_sources,
|
||||
:in_product_marketing_emails_enabled,
|
||||
:inactive_projects_delete_after_months,
|
||||
:inactive_projects_min_size_mb,
|
||||
:inactive_projects_send_warning_email_after_months,
|
||||
:invisible_captcha_enabled,
|
||||
:max_artifacts_size,
|
||||
:max_attachment_size,
|
||||
|
|
|
@ -578,6 +578,15 @@ class ApplicationSetting < ApplicationRecord
|
|||
|
||||
validates :public_runner_releases_url, addressable_url: true, presence: true
|
||||
|
||||
validates :inactive_projects_min_size_mb,
|
||||
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||
|
||||
validates :inactive_projects_delete_after_months,
|
||||
numericality: { only_integer: true, greater_than: 0 }
|
||||
|
||||
validates :inactive_projects_send_warning_email_after_months,
|
||||
numericality: { only_integer: true, greater_than: 0, less_than: :inactive_projects_delete_after_months }
|
||||
|
||||
attr_encrypted :asset_proxy_secret_key,
|
||||
mode: :per_attribute_iv,
|
||||
key: Settings.attr_encrypted_db_key_base_truncated,
|
||||
|
|
|
@ -81,7 +81,8 @@ module Projects
|
|||
configured: scan.configured?,
|
||||
configuration_path: scan.configuration_path,
|
||||
available: scan.available?,
|
||||
can_enable_by_merge_request: scan.can_enable_by_merge_request?
|
||||
can_enable_by_merge_request: scan.can_enable_by_merge_request?,
|
||||
meta_info_path: scan.meta_info_path
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -27,9 +27,6 @@
|
|||
- if can?(current_user, :create_wiki, @wiki.container) && @page.latest? && @valid_encoding
|
||||
= link_to sprite_icon('pencil', css_class: 'gl-icon'), wiki_page_path(@wiki, @page, action: :edit), title: 'Edit', role: "button", class: 'btn gl-button btn-icon btn-default js-wiki-edit', data: { qa_selector: 'edit_page_button', testid: 'wiki_edit_button' }
|
||||
|
||||
- if Feature.enabled?(:wiki_async_load, @wiki.container, default_enabled: :yaml)
|
||||
.js-async-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki_page_content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } }
|
||||
- else
|
||||
= render 'shared/wikis/wiki_content'
|
||||
.js-async-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki_page_content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } }
|
||||
|
||||
= render 'shared/wikis/sidebar'
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: wiki_async_load
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82394
|
||||
rollout_issue_url:
|
||||
milestone: '14.9'
|
||||
type: development
|
||||
group: group::editor
|
||||
default_enabled: false
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: bypass_registration
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72827
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340560
|
||||
milestone: '14.5'
|
||||
type: experiment
|
||||
group: group::adoption
|
||||
default_enabled: false
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddInactiveProjectDeletionToApplicationSettings < Gitlab::Database::Migration[1.0]
|
||||
def change
|
||||
add_column :application_settings, :delete_inactive_projects, :boolean, default: false, null: false
|
||||
add_column :application_settings, :inactive_projects_delete_after_months, :integer, default: 2, null: false
|
||||
add_column :application_settings, :inactive_projects_min_size_mb, :integer, default: 0, null: false
|
||||
add_column :application_settings, :inactive_projects_send_warning_email_after_months, :integer, default: 1,
|
||||
null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
161ba8db7400c12dc0550246af8db86487e811803eaecedcb2761f4a8349920b
|
|
@ -11257,6 +11257,10 @@ CREATE TABLE application_settings (
|
|||
database_grafana_api_url text,
|
||||
database_grafana_tag text,
|
||||
public_runner_releases_url text DEFAULT 'https://gitlab.com/api/v4/projects/gitlab-org%2Fgitlab-runner/releases'::text NOT NULL,
|
||||
delete_inactive_projects boolean DEFAULT false NOT NULL,
|
||||
inactive_projects_delete_after_months integer DEFAULT 2 NOT NULL,
|
||||
inactive_projects_min_size_mb integer DEFAULT 0 NOT NULL,
|
||||
inactive_projects_send_warning_email_after_months integer DEFAULT 1 NOT NULL,
|
||||
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
||||
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
|
||||
|
|
|
@ -524,6 +524,7 @@ repurposing
|
|||
requeue
|
||||
requeued
|
||||
requeues
|
||||
requeuing
|
||||
Restlet
|
||||
resync
|
||||
resynced
|
||||
|
|
|
@ -274,6 +274,7 @@ listed in the descriptions of the relevant settings.
|
|||
| `default_projects_limit` | integer | no | Project limit per user. Default is `100000`. |
|
||||
| `default_snippet_visibility` | string | no | What visibility level new snippets receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
|
||||
| `delayed_project_deletion` **(PREMIUM SELF)** | boolean | no | Enable delayed project deletion by default in new groups. Default is `false`. |
|
||||
| `delete_inactive_projects` | boolean | no | Enable inactive project deletion feature. Default is `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84519) in GitLab 14.10. |
|
||||
| `deletion_adjourned_period` **(PREMIUM SELF)** | integer | no | The number of days to wait before deleting a project or group that is marked for deletion. Value must be between 0 and 90.
|
||||
| `diff_max_patch_bytes` | integer | no | Maximum [diff patch size](../user/admin_area/diff_limits.md), in bytes. |
|
||||
| `diff_max_files` | integer | no | Maximum [files in a diff](../user/admin_area/diff_limits.md). |
|
||||
|
@ -350,6 +351,9 @@ listed in the descriptions of the relevant settings.
|
|||
| `html_emails_enabled` | boolean | no | Enable HTML emails. |
|
||||
| `import_sources` | array of strings | no | Sources to allow project import from, possible values: `github`, `bitbucket`, `bitbucket_server`, `gitlab`, `fogbugz`, `git`, `gitlab_project`, `gitea`, `manifest`, and `phabricator`. |
|
||||
| `in_product_marketing_emails_enabled` | boolean | no | Enable [in-product marketing emails](../user/profile/notifications.md#global-notification-settings). Enabled by default. |
|
||||
| `inactive_projects_delete_after_months` | integer | no | If `delete_inactive_projects` is `true`, the time (in months) to wait before deleting inactive projects. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84519) in GitLab 14.10. |
|
||||
| `inactive_projects_min_size_mb` | integer | no | If `delete_inactive_projects` is `true`, the minimum repository size for projects to be checked for inactivity. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84519) in GitLab 14.10. |
|
||||
| `inactive_projects_send_warning_email_after_months` | integer | no | If `delete_inactive_projects` is `true`, sets the time (in months) to wait before emailing maintainers that the project will be deleted because it is inactive. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84519) in GitLab 14.10. |
|
||||
| `invisible_captcha_enabled` | boolean | no | Enable Invisible CAPTCHA spam detection during sign-up. Disabled by default. |
|
||||
| `issues_create_limit` | integer | no | Max number of issue creation requests per minute per user. Disabled by default.|
|
||||
| `keep_latest_artifact` | boolean | no | Prevent the deletion of the artifacts from the most recent successful jobs, regardless of the expiry time. Enabled by default. |
|
||||
|
|
|
@ -0,0 +1,319 @@
|
|||
---
|
||||
type: reference, dev
|
||||
stage: Enablement
|
||||
group: Database
|
||||
info: "See the Technical Writers assigned to Development Guidelines: https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments-to-development-guidelines"
|
||||
---
|
||||
|
||||
# Batched background migrations
|
||||
|
||||
Batched Background Migrations should be used to perform data migrations whenever a
|
||||
migration exceeds [the time limits](migration_style_guide.md#how-long-a-migration-should-take)
|
||||
in our guidelines. For example, you can use batched background
|
||||
migrations to migrate data that's stored in a single JSON column
|
||||
to a separate table instead.
|
||||
|
||||
## When to use batched background migrations
|
||||
|
||||
Use a batched background migration when you migrate _data_ in tables containing
|
||||
so many rows that the process would exceed
|
||||
[the time limits in our guidelines](migration_style_guide.md#how-long-a-migration-should-take)
|
||||
if performed using a regular Rails migration.
|
||||
|
||||
- Batched background migrations should be used when migrating data in
|
||||
[high-traffic tables](migration_style_guide.md#high-traffic-tables).
|
||||
- Batched background migrations may also be used when executing numerous single-row queries
|
||||
for every item on a large dataset. Typically, for single-record patterns, runtime is
|
||||
largely dependent on the size of the dataset. Split the dataset accordingly,
|
||||
and put it into background migrations.
|
||||
- Don't use batched background migrations to perform schema migrations.
|
||||
|
||||
Background migrations can help when:
|
||||
|
||||
- Migrating events from one table to multiple separate tables.
|
||||
- Populating one column based on JSON stored in another column.
|
||||
- Migrating data that depends on the output of external services. (For example, an API.)
|
||||
|
||||
NOTE:
|
||||
If the batched background migration is part of an important upgrade, it must be announced
|
||||
in the release post. Discuss with your Project Manager if you're unsure if the migration falls
|
||||
into this category.
|
||||
|
||||
## Isolation
|
||||
|
||||
Batched background migrations must be isolated and can not use application code. (For example,
|
||||
models defined in `app/models`.). Because these migrations can take a long time to
|
||||
run, it's possible for new versions to deploy while the migrations are still running.
|
||||
|
||||
## Idempotence
|
||||
|
||||
Batched background migrations are executed in a context of a Sidekiq process.
|
||||
The usual Sidekiq rules apply, especially the rule that jobs should be small
|
||||
and idempotent. Make sure that in case that your migration job is retried, data
|
||||
integrity is guaranteed.
|
||||
|
||||
See [Sidekiq best practices guidelines](https://github.com/mperham/sidekiq/wiki/Best-Practices)
|
||||
for more details.
|
||||
|
||||
## Batched background migrations for EE-only features
|
||||
|
||||
All the background migration classes for EE-only features should be present in GitLab CE.
|
||||
For this purpose, create an empty class for GitLab CE, and extend it for GitLab EE
|
||||
as explained in the guidelines for
|
||||
[implementing Enterprise Edition features](ee_features.md#code-in-libgitlabbackground_migration).
|
||||
|
||||
Batched Background migrations are simple classes that define a `perform` method. A
|
||||
Sidekiq worker then executes such a class, passing any arguments to it. All
|
||||
migration classes must be defined in the namespace
|
||||
`Gitlab::BackgroundMigration`. Place the files in the directory
|
||||
`lib/gitlab/background_migration/`.
|
||||
|
||||
## Queueing
|
||||
|
||||
Queueing a batched background migration should be done in a post-deployment
|
||||
migration. Use this `queue_batched_background_migration` example, queueing the
|
||||
migration to be executed in batches. Replace the class name and arguments with the values
|
||||
from your migration:
|
||||
|
||||
```ruby
|
||||
queue_batched_background_migration(
|
||||
JOB_CLASS_NAME,
|
||||
TABLE_NAME,
|
||||
JOB_ARGUMENTS,
|
||||
JOB_INTERVAL
|
||||
)
|
||||
```
|
||||
|
||||
Make sure the newly-created data is either migrated, or
|
||||
saved in both the old and new version upon creation. Removals in
|
||||
turn can be handled by defining foreign keys with cascading deletes.
|
||||
|
||||
### Requeuing batched background migrations
|
||||
|
||||
If one of the batched background migrations contains a bug that is fixed in a patch
|
||||
release, you must requeue the batched background migration so the migration
|
||||
repeats on systems that already performed the initial migration.
|
||||
|
||||
When you requeue the batched background migration, turn the original
|
||||
queuing into a no-op by clearing up the `#up` and `#down` methods of the
|
||||
migration performing the requeuing. Otherwise, the batched background migration is
|
||||
queued multiple times on systems that are upgrading multiple patch releases at
|
||||
once.
|
||||
|
||||
When you start the second post-deployment migration, delete the
|
||||
previously batched migration with the provided code:
|
||||
|
||||
```ruby
|
||||
Gitlab::Database::BackgroundMigration::BatchedMigration
|
||||
.for_configuration(MIGRATION_NAME, TABLE_NAME, COLUMN, JOB_ARGUMENTS)
|
||||
.delete_all
|
||||
```
|
||||
|
||||
## Cleaning up
|
||||
|
||||
NOTE:
|
||||
Cleaning up any remaining background migrations must be done in either a major
|
||||
or minor release. You must not do this in a patch release.
|
||||
|
||||
Because background migrations can take a long time, you can't immediately clean
|
||||
things up after queueing them. For example, you can't drop a column used in the
|
||||
migration process, as jobs would fail. You must add a separate _post-deployment_
|
||||
migration in a future release that finishes any remaining
|
||||
jobs before cleaning things up. (For example, removing a column.)
|
||||
|
||||
To migrate the data from column `foo` (containing a big JSON blob) to column `bar`
|
||||
(containing a string), you would:
|
||||
|
||||
1. Release A:
|
||||
1. Create a migration class that performs the migration for a row with a given ID.
|
||||
1. Update new rows using one of these techniques:
|
||||
- Create a new trigger for simple copy operations that don't need application logic.
|
||||
- Handle this operation in the model/service as the records are created or updated.
|
||||
- Create a new custom background job that updates the records.
|
||||
1. Queue the batched background migration for all existing rows in a post-deployment migration.
|
||||
1. Release B:
|
||||
1. Add a post-deployment migration that checks if the batched background migration is completed.
|
||||
1. Deploy code so that the application starts using the new column and stops to update new records.
|
||||
1. Remove the old column.
|
||||
|
||||
Bump to the [import/export version](../user/project/settings/import_export.md) may
|
||||
be required, if importing a project from a prior version of GitLab requires the
|
||||
data to be in the new format.
|
||||
|
||||
## Example
|
||||
|
||||
The table `integrations` has a field called `properties`, stored in JSON. For all rows,
|
||||
extract the `url` key from this JSON object and store it in the `integrations.url`
|
||||
column. Millions of integrations exist, and parsing JSON is slow, so you can't
|
||||
do this work in a regular migration.
|
||||
|
||||
1. Start by defining our migration class:
|
||||
|
||||
```ruby
|
||||
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
|
||||
class Integration < ActiveRecord::Base
|
||||
self.table_name = 'integrations'
|
||||
end
|
||||
|
||||
def perform(start_id, end_id)
|
||||
Integration.where(id: start_id..end_id).each do |integration|
|
||||
json = JSON.load(integration.properties)
|
||||
|
||||
integration.update(url: json['url']) if json['url']
|
||||
rescue JSON::ParserError
|
||||
# If the JSON is invalid we don't want to keep the job around forever,
|
||||
# instead we'll just leave the "url" field to whatever the default value
|
||||
# is.
|
||||
next
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
NOTE:
|
||||
To get a `connection` in the batched background migration,use an inheritance
|
||||
relation using the following base class `Gitlab::BackgroundMigration::BaseJob`.
|
||||
For example: `class Gitlab::BackgroundMigration::ExtractIntegrationsUrl < Gitlab::BackgroundMigration::BaseJob`
|
||||
|
||||
1. Add a new trigger to the database to update newly created and updated integrations,
|
||||
similar to this example:
|
||||
|
||||
```ruby
|
||||
execute(<<~SQL)
|
||||
CREATE OR REPLACE FUNCTION example() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW."url" := NEW.properties -> "url"
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
SQL
|
||||
```
|
||||
|
||||
1. Create a post-deployment migration that queues the migration for existing data:
|
||||
|
||||
```ruby
|
||||
class QueueExtractIntegrationsUrl < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
MIGRATION = 'ExtractIntegrationsUrl'
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:migrations,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
Gitlab::Database::BackgroundMigration::BatchedMigration
|
||||
.for_configuration(MIGRATION, :migrations, :id, []).delete_all
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
After deployment, our application:
|
||||
- Continues using the data as before.
|
||||
- Ensures that both existing and new data are migrated.
|
||||
|
||||
1. In the next release, remove the trigger. We must also add a new post-deployment migration
|
||||
that checks that the batched background migration is completed. For example:
|
||||
|
||||
```ruby
|
||||
class FinalizeExtractIntegrationsUrlJobs < Gitlab::Database::Migration[1.0]
|
||||
MIGRATION = 'ExtractIntegrationsUrl'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: MIGRATION,
|
||||
table_name: :integrations,
|
||||
column_name: :id,
|
||||
job_arguments: []
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
If the application does not depend on the data being 100% migrated (for
|
||||
instance, the data is advisory, and not mission-critical), then you can skip this
|
||||
final step. This step confirms that the migration is completed, and all of the rows were migrated.
|
||||
|
||||
After the batched migration is completed, you can safely remove the `integrations.properties` column.
|
||||
|
||||
## Testing
|
||||
|
||||
Writing tests is required for:
|
||||
|
||||
- The batched background migrations' queueing migration.
|
||||
- The batched background migration itself.
|
||||
- A cleanup migration.
|
||||
|
||||
The `:migration` and `schema: :latest` RSpec tags are automatically set for
|
||||
background migration specs. Refer to the
|
||||
[Testing Rails migrations](testing_guide/testing_migrations_guide.md#testing-a-non-activerecordmigration-class)
|
||||
style guide.
|
||||
|
||||
Remember that `before` and `after` RSpec hooks
|
||||
migrate your database down and up. These hooks can result in other batched background
|
||||
migrations being called. Using `spy` test doubles with
|
||||
`have_received` is encouraged, instead of using regular test doubles, because
|
||||
your expectations defined in a `it` block can conflict with what is
|
||||
called in RSpec hooks. Refer to [issue #35351](https://gitlab.com/gitlab-org/gitlab/-/issues/18839)
|
||||
for more details.
|
||||
|
||||
## Best practices
|
||||
|
||||
1. Know how much data you're dealing with.
|
||||
1. Make sure the batched background migration jobs are idempotent.
|
||||
1. Confirm the tests you write are not false positives.
|
||||
1. If the data being migrated is critical and cannot be lost, the
|
||||
clean-up migration must also check the final state of the data before completing.
|
||||
1. Discuss the numbers with a database specialist. The migration may add
|
||||
more pressure on DB than you expect. Measure on staging,
|
||||
or ask someone to measure on production.
|
||||
1. Know how much time is required to run the batched background migration.
|
||||
|
||||
## Additional tips and strategies
|
||||
|
||||
### Viewing failure error logs
|
||||
|
||||
You can view failures in two ways:
|
||||
|
||||
- Via GitLab logs:
|
||||
1. After running a batched background migration, if any jobs fail,
|
||||
view the logs in [Kibana](https://log.gprd.gitlab.net/goto/5f06a57f768c6025e1c65aefb4075694).
|
||||
View the production Sidekiq log and filter for:
|
||||
|
||||
- `json.new_state: failed`
|
||||
- `json.job_class_name: <Batched Background Migration job class name>`
|
||||
- `json.job_arguments: <Batched Background Migration job class arguments>`
|
||||
|
||||
1. Review the `json.exception_class` and `json.exception_message` values to help
|
||||
understand why the jobs failed.
|
||||
|
||||
1. Remember the retry mechanism. Having a failure does not mean the job failed.
|
||||
Always check the last status of the job.
|
||||
|
||||
- Via database:
|
||||
|
||||
1. Get the batched background migration `CLASS_NAME`.
|
||||
1. Execute the following query in the PostgreSQL console:
|
||||
|
||||
```sql
|
||||
SELECT migration.id, migration.job_class_name, transition_logs.exception_class, transition_logs.exception_message
|
||||
FROM batched_background_migrations as migration
|
||||
INNER JOIN batched_background_migration_jobs as jobs
|
||||
ON jobs.batched_background_migration_id = migration.id
|
||||
INNER JOIN batched_background_migration_job_transition_logs as transition_logs
|
||||
ON transition_logs.batched_background_migration_job_id = jobs.id
|
||||
WHERE transition_logs.next_status = '2' AND migration.job_class_name = "CLASS_NAME";
|
||||
```
|
|
@ -982,7 +982,7 @@ There is also an easy way to check it automatically with `sudo gitlab-rake gitla
|
|||
|
||||
This exception is seen when your Elasticsearch cluster is configured to reject requests above a certain size (10MiB in this case). This corresponds to the `http.max_content_length` setting in `elasticsearch.yml`. Increase it to a larger size and restart your Elasticsearch cluster.
|
||||
|
||||
AWS has [fixed limits](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/aes-limits.html) for this setting ("Maximum Size of HTTP Request Payloads"), based on the size of the underlying instance.
|
||||
AWS has [fixed limits](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/limits.html#network-limits) for this setting ("Maximum size of HTTP request payloads"), based on the size of the underlying instance.
|
||||
|
||||
### My single node Elasticsearch cluster status never goes from `yellow` to `green` even though everything seems to be running properly
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ flowchart RL
|
|||
[Feature flag `invite_members_group_modal`](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) removed.
|
||||
|
||||
Add users to a project so they become members and have permission
|
||||
to perform actions.
|
||||
to perform actions. The Owner [role](../../permissions.md#project-members-permissions) can only be added at the group level.
|
||||
|
||||
Prerequisite:
|
||||
|
||||
|
|
|
@ -63,7 +63,13 @@ module Gitlab
|
|||
|
||||
job.split_and_retry! if job.can_split?(exception)
|
||||
rescue SplitAndRetryError => error
|
||||
Gitlab::AppLogger.error(message: error.message, batched_job_id: job.id)
|
||||
Gitlab::AppLogger.error(
|
||||
message: error.message,
|
||||
batched_job_id: job.id,
|
||||
batched_migration_id: job.batched_migration.id,
|
||||
job_class_name: job.migration_job_class_name,
|
||||
job_arguments: job.migration_job_arguments
|
||||
)
|
||||
end
|
||||
|
||||
after_transition do |job, transition|
|
||||
|
@ -73,13 +79,23 @@ module Gitlab
|
|||
|
||||
job.batched_job_transition_logs.create(previous_status: transition.from, next_status: transition.to, exception_class: exception&.class, exception_message: exception&.message)
|
||||
|
||||
Gitlab::ErrorTracking.track_exception(exception, batched_job_id: job.id) if exception
|
||||
Gitlab::ErrorTracking.track_exception(exception, batched_job_id: job.id, job_class_name: job.migration_job_class_name, job_arguments: job.migration_job_arguments) if exception
|
||||
|
||||
Gitlab::AppLogger.info(message: 'BatchedJob transition', batched_job_id: job.id, previous_state: transition.from_name, new_state: transition.to_name)
|
||||
Gitlab::AppLogger.info(
|
||||
message: 'BatchedJob transition',
|
||||
batched_job_id: job.id,
|
||||
previous_state: transition.from_name,
|
||||
new_state: transition.to_name,
|
||||
batched_migration_id: job.batched_migration.id,
|
||||
job_class_name: job.migration_job_class_name,
|
||||
job_arguments: job.migration_job_arguments,
|
||||
exception_class: exception&.class,
|
||||
exception_message: exception&.message
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
delegate :job_class, :table_name, :column_name, :job_arguments,
|
||||
delegate :job_class, :table_name, :column_name, :job_arguments, :job_class_name,
|
||||
to: :batched_migration, prefix: :migration
|
||||
|
||||
attribute :pause_ms, :integer, default: 100
|
||||
|
|
|
@ -31,6 +31,8 @@ module Gitlab
|
|||
|
||||
def configuration_path; end
|
||||
|
||||
def meta_info_path; end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :configured
|
||||
|
|
|
@ -4220,9 +4220,6 @@ msgstr ""
|
|||
msgid "Analytics"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analyze a review version of your web application."
|
||||
msgstr ""
|
||||
|
||||
msgid "Analyze your dependencies for known vulnerabilities."
|
||||
msgstr ""
|
||||
|
||||
|
@ -5455,6 +5452,9 @@ msgstr ""
|
|||
msgid "Available group runners: %{runners}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Available on-demand"
|
||||
msgstr ""
|
||||
|
||||
msgid "Available runners: %{runners}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21295,9 +21295,6 @@ msgstr ""
|
|||
msgid "Iteration|cannot be more than 500 years in the future"
|
||||
msgstr ""
|
||||
|
||||
msgid "I’m joining my team who’s already on GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jaeger URL"
|
||||
msgstr ""
|
||||
|
||||
|
@ -25992,6 +25989,9 @@ msgstr ""
|
|||
msgid "On-call schedules"
|
||||
msgstr ""
|
||||
|
||||
msgid "On-demand scans run outside of the DevOps cycle and find vulnerabilities in your projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallScheduless|Any escalation rules that are using this schedule will also be deleted."
|
||||
msgstr ""
|
||||
|
||||
|
@ -44123,6 +44123,9 @@ msgstr ""
|
|||
msgid "ciReport|All tools"
|
||||
msgstr ""
|
||||
|
||||
msgid "ciReport|Analyze a deployed version of your web application for known vulnerabilities by examining it from the outside in. DAST works by simulating external attacks on your application while it is running."
|
||||
msgstr ""
|
||||
|
||||
msgid "ciReport|Automatically apply the patch in a new branch"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -8,26 +8,14 @@ RSpec.describe 'Project wikis', :js do
|
|||
let(:wiki) { create(:project_wiki, user: user, project: project) }
|
||||
let(:project) { create(:project, namespace: user.namespace, creator: user) }
|
||||
|
||||
shared_examples 'wiki feature tests' do
|
||||
it_behaves_like 'User creates wiki page'
|
||||
it_behaves_like 'User deletes wiki page'
|
||||
it_behaves_like 'User previews wiki changes'
|
||||
it_behaves_like 'User updates wiki page'
|
||||
it_behaves_like 'User uses wiki shortcuts'
|
||||
it_behaves_like 'User views AsciiDoc page with includes'
|
||||
it_behaves_like 'User views a wiki page'
|
||||
it_behaves_like 'User views wiki pages'
|
||||
it_behaves_like 'User views wiki sidebar'
|
||||
it_behaves_like 'User views Git access wiki page'
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki feature tests'
|
||||
|
||||
context 'when feature flag :wiki_async_load is disabled' do
|
||||
before do
|
||||
stub_feature_flags(wiki_async_load: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki feature tests'
|
||||
end
|
||||
it_behaves_like 'User creates wiki page'
|
||||
it_behaves_like 'User deletes wiki page'
|
||||
it_behaves_like 'User previews wiki changes'
|
||||
it_behaves_like 'User updates wiki page'
|
||||
it_behaves_like 'User uses wiki shortcuts'
|
||||
it_behaves_like 'User views AsciiDoc page with includes'
|
||||
it_behaves_like 'User views a wiki page'
|
||||
it_behaves_like 'User views wiki pages'
|
||||
it_behaves_like 'User views wiki sidebar'
|
||||
it_behaves_like 'User views Git access wiki page'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlBadge, GlTooltip } from '@gitlab/ui';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import FeatureCardBadge from '~/security_configuration/components/feature_card_badge.vue';
|
||||
|
||||
describe('Feature card badge component', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (propsData) => {
|
||||
wrapper = extendedWrapper(
|
||||
mount(FeatureCardBadge, {
|
||||
propsData,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const findTooltip = () => wrapper.findComponent(GlTooltip);
|
||||
const findBadge = () => wrapper.findComponent(GlBadge);
|
||||
|
||||
describe('tooltip render', () => {
|
||||
describe.each`
|
||||
context | badge | badgeHref
|
||||
${'href on a badge object'} | ${{ tooltipText: 'test', badgeHref: 'href' }} | ${undefined}
|
||||
${'href as property '} | ${{ tooltipText: null, badgeHref: '' }} | ${'link'}
|
||||
${'default href no property on badge or component'} | ${{ tooltipText: null, badgeHref: '' }} | ${undefined}
|
||||
`('given $context', ({ badge, badgeHref }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({ badge, badgeHref });
|
||||
});
|
||||
|
||||
it('should show badge when badge given in configuration and available', () => {
|
||||
expect(findTooltip().exists()).toBe(Boolean(badge && badge.tooltipText));
|
||||
});
|
||||
|
||||
it('should render correct link if link is provided', () => {
|
||||
expect(findBadge().attributes().href).toEqual(badgeHref);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,6 +2,7 @@ import { GlIcon } from '@gitlab/ui';
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import FeatureCard from '~/security_configuration/components/feature_card.vue';
|
||||
import FeatureCardBadge from '~/security_configuration/components/feature_card_badge.vue';
|
||||
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
|
||||
import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants';
|
||||
import { makeFeature } from './utils';
|
||||
|
@ -16,6 +17,7 @@ describe('FeatureCard component', () => {
|
|||
propsData,
|
||||
stubs: {
|
||||
ManageViaMr: true,
|
||||
FeatureCardBadge: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
@ -24,6 +26,8 @@ describe('FeatureCard component', () => {
|
|||
const findLinks = ({ text, href }) =>
|
||||
wrapper.findAll(`a[href="${href}"]`).filter((link) => link.text() === text);
|
||||
|
||||
const findBadge = () => wrapper.findComponent(FeatureCardBadge);
|
||||
|
||||
const findEnableLinks = () =>
|
||||
findLinks({
|
||||
text: `Enable ${feature.shortName ?? feature.name}`,
|
||||
|
@ -262,5 +266,28 @@ describe('FeatureCard component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('information badge', () => {
|
||||
describe.each`
|
||||
context | available | badge
|
||||
${'available feature with badge'} | ${true} | ${{ text: 'test' }}
|
||||
${'unavailable feature without badge'} | ${false} | ${null}
|
||||
${'available feature without badge'} | ${true} | ${null}
|
||||
${'unavailable feature with badge'} | ${false} | ${{ text: 'test' }}
|
||||
${'available feature with empty badge'} | ${false} | ${{}}
|
||||
`('given $context', ({ available, badge }) => {
|
||||
beforeEach(() => {
|
||||
feature = makeFeature({
|
||||
available,
|
||||
badge,
|
||||
});
|
||||
createComponent({ feature });
|
||||
});
|
||||
|
||||
it('should show badge when badge given in configuration and available', () => {
|
||||
expect(findBadge().exists()).toBe(Boolean(available && badge && badge.text));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,7 +21,19 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d
|
|||
|
||||
context 'when a job is running' do
|
||||
it 'logs the transition' do
|
||||
expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :running, previous_state: :failed } )
|
||||
expect(Gitlab::AppLogger).to receive(:info).with(
|
||||
{
|
||||
batched_job_id: job.id,
|
||||
batched_migration_id: job.batched_background_migration_id,
|
||||
exception_class: nil,
|
||||
exception_message: nil,
|
||||
job_arguments: job.batched_migration.job_arguments,
|
||||
job_class_name: job.batched_migration.job_class_name,
|
||||
message: 'BatchedJob transition',
|
||||
new_state: :running,
|
||||
previous_state: :failed
|
||||
}
|
||||
)
|
||||
|
||||
expect { job.run! }.to change(job, :started_at)
|
||||
end
|
||||
|
@ -31,7 +43,19 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d
|
|||
let(:job) { create(:batched_background_migration_job, :running) }
|
||||
|
||||
it 'logs the transition' do
|
||||
expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :succeeded, previous_state: :running } )
|
||||
expect(Gitlab::AppLogger).to receive(:info).with(
|
||||
{
|
||||
batched_job_id: job.id,
|
||||
batched_migration_id: job.batched_background_migration_id,
|
||||
exception_class: nil,
|
||||
exception_message: nil,
|
||||
job_arguments: job.batched_migration.job_arguments,
|
||||
job_class_name: job.batched_migration.job_class_name,
|
||||
message: 'BatchedJob transition',
|
||||
new_state: :succeeded,
|
||||
previous_state: :running
|
||||
}
|
||||
)
|
||||
|
||||
job.succeed!
|
||||
end
|
||||
|
@ -89,7 +113,15 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d
|
|||
end
|
||||
|
||||
it 'logs the error' do
|
||||
expect(Gitlab::AppLogger).to receive(:error).with( { message: error_message, batched_job_id: job.id } )
|
||||
expect(Gitlab::AppLogger).to receive(:error).with(
|
||||
{
|
||||
batched_job_id: job.id,
|
||||
batched_migration_id: job.batched_background_migration_id,
|
||||
job_arguments: job.batched_migration.job_arguments,
|
||||
job_class_name: job.batched_migration.job_class_name,
|
||||
message: error_message
|
||||
}
|
||||
)
|
||||
|
||||
job.failure!(error: exception)
|
||||
end
|
||||
|
@ -100,13 +132,32 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d
|
|||
let(:job) { create(:batched_background_migration_job, :running) }
|
||||
|
||||
it 'logs the transition' do
|
||||
expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :failed, previous_state: :running } )
|
||||
expect(Gitlab::AppLogger).to receive(:info).with(
|
||||
{
|
||||
batched_job_id: job.id,
|
||||
batched_migration_id: job.batched_background_migration_id,
|
||||
exception_class: RuntimeError,
|
||||
exception_message: 'error',
|
||||
job_arguments: job.batched_migration.job_arguments,
|
||||
job_class_name: job.batched_migration.job_class_name,
|
||||
message: 'BatchedJob transition',
|
||||
new_state: :failed,
|
||||
previous_state: :running
|
||||
}
|
||||
)
|
||||
|
||||
job.failure!
|
||||
job.failure!(error: RuntimeError.new('error'))
|
||||
end
|
||||
|
||||
it 'tracks the exception' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(RuntimeError, { batched_job_id: job.id } )
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
|
||||
RuntimeError,
|
||||
{
|
||||
batched_job_id: job.id,
|
||||
job_arguments: job.batched_migration.job_arguments,
|
||||
job_class_name: job.batched_migration.job_class_name
|
||||
}
|
||||
)
|
||||
|
||||
job.failure!(error: RuntimeError.new)
|
||||
end
|
||||
|
@ -198,6 +249,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d
|
|||
expect(batched_job.migration_job_arguments).to eq(batched_migration.job_arguments)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#migration_job_class_name' do
|
||||
it 'returns the migration job_class_name' do
|
||||
expect(batched_job.migration_job_class_name).to eq(batched_migration.job_class_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_split?' do
|
||||
|
|
|
@ -47,6 +47,16 @@ RSpec.describe ::Gitlab::Security::ScanConfiguration do
|
|||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
describe '#meta_info_path' do
|
||||
subject { scan.meta_info_path }
|
||||
|
||||
let(:configured) { true }
|
||||
let(:available) { true }
|
||||
let(:type) { :dast }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
describe '#can_enable_by_merge_request?' do
|
||||
subject { scan.can_enable_by_merge_request? }
|
||||
|
||||
|
|
|
@ -1322,4 +1322,19 @@ RSpec.describe ApplicationSetting do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "inactive project deletion" do
|
||||
it "validates that inactive_projects_send_warning_email_after_months is less than inactive_projects_delete_after_months" do
|
||||
subject[:inactive_projects_delete_after_months] = 3
|
||||
subject[:inactive_projects_send_warning_email_after_months] = 6
|
||||
|
||||
expect(subject).to be_invalid
|
||||
end
|
||||
|
||||
it { is_expected.to validate_numericality_of(:inactive_projects_send_warning_email_after_months).is_greater_than(0) }
|
||||
|
||||
it { is_expected.to validate_numericality_of(:inactive_projects_delete_after_months).is_greater_than(0) }
|
||||
|
||||
it { is_expected.to validate_numericality_of(:inactive_projects_min_size_mb).is_greater_than_or_equal_to(0) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -87,6 +87,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
|
|||
expect(feature['configuration_path']).to be_nil
|
||||
expect(feature['available']).to eq(true)
|
||||
expect(feature['can_enable_by_merge_request']).to eq(true)
|
||||
expect(feature['meta_info_path']).to be_nil
|
||||
end
|
||||
|
||||
context 'when checking features configured status' do
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/gddo/httputil"
|
||||
grpccodes "google.golang.org/grpc/codes"
|
||||
|
@ -64,21 +65,43 @@ func handleGetInfoRefsWithGitaly(ctx context.Context, responseWriter *HttpRespon
|
|||
return err
|
||||
}
|
||||
|
||||
var w io.Writer
|
||||
|
||||
var w io.WriteCloser = nopCloser{responseWriter}
|
||||
if encoding == "gzip" {
|
||||
gzWriter := gzip.NewWriter(responseWriter)
|
||||
w = gzWriter
|
||||
defer gzWriter.Close()
|
||||
gzWriter := getGzWriter(responseWriter)
|
||||
defer putGzWriter(gzWriter)
|
||||
|
||||
w = gzWriter
|
||||
responseWriter.Header().Set("Content-Encoding", "gzip")
|
||||
} else {
|
||||
w = responseWriter
|
||||
}
|
||||
|
||||
if _, err = io.Copy(w, infoRefsResponseReader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var gzipPool = &sync.Pool{New: func() interface{} {
|
||||
// Invariant: the inner writer is io.Discard. We do not want to retain
|
||||
// response writers of past requests in the pool.
|
||||
return gzip.NewWriter(io.Discard)
|
||||
}}
|
||||
|
||||
func getGzWriter(w io.Writer) *gzip.Writer {
|
||||
gzWriter := gzipPool.Get().(*gzip.Writer)
|
||||
gzWriter.Reset(w)
|
||||
return gzWriter
|
||||
}
|
||||
|
||||
func putGzWriter(w *gzip.Writer) {
|
||||
w.Reset(io.Discard) // Maintain pool invariant
|
||||
gzipPool.Put(w)
|
||||
}
|
||||
|
||||
type nopCloser struct{ io.Writer }
|
||||
|
||||
func (nc nopCloser) Close() error { return nil }
|
||||
|
|
Loading…
Reference in New Issue