Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-10 21:09:14 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-10 21:09:14 +0300
commitd18b7dc5eea84db5008986c6879a24ad7f6462a6 (patch)
tree98d6e8635ac32f210f15fcfb3dc583a6295e0b9a
parent6ebe886c82111e1ab9e71d4c02a888d2312898bc (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock14
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/settings_form.vue17
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/constants.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/graphql/utils/cache_update.js1
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue7
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/index/index.js5
-rw-r--r--app/assets/javascripts/search/topbar/components/app.vue55
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js11
-rw-r--r--app/assets/javascripts/security_configuration/components/training_provider_list.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue42
-rw-r--r--app/models/container_repository.rb4
-rw-r--r--app/models/packages/package_file.rb1
-rw-r--r--app/services/ci/job_artifacts/destroy_associations_service.rb2
-rw-r--r--app/services/ci/job_artifacts/destroy_batch_service.rb56
-rw-r--r--app/views/projects/empty.html.haml9
-rw-r--r--app/views/projects/learn_gitlab/index.html.haml6
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml2
-rw-r--r--config/feature_flags/development/ci_detect_wrongly_expired_artifacts.yml8
-rw-r--r--config/feature_flags/development/enable_new_sentry_integration.yml8
-rw-r--r--config/feature_flags/development/enable_old_sentry_integration.yml8
-rw-r--r--config/feature_flags/development/fips_mode.yml8
-rw-r--r--config/feature_flags/development/gl_avatar_for_all_user_avatars.yml (renamed from config/feature_flags/experiment/confetti_post_signup.yml)12
-rw-r--r--doc/ci/pipelines/settings.md56
-rw-r--r--doc/ci/unit_test_reports.md2
-rw-r--r--doc/ci/yaml/index.md2
-rw-r--r--doc/development/pipelines.md17
-rw-r--r--doc/user/infrastructure/clusters/connect/new_gke_cluster.md131
-rw-r--r--doc/user/infrastructure/iac/index.md9
-rw-r--r--doc/user/project/merge_requests/test_coverage_visualization.md7
-rw-r--r--lib/api/package_files.rb2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml4
-rw-r--r--lib/gitlab/error_tracking.rb61
-rw-r--r--lib/gitlab/error_tracking/processor/grpc_error_processor.rb20
-rw-r--r--lib/gitlab/fips.rb8
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--locale/gitlab.pot12
-rw-r--r--spec/features/boards/boards_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb1
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb8
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb4
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb2
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap2
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js26
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js25
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js12
-rw-r--r--spec/frontend/search/topbar/components/app_spec.js33
-rw-r--r--spec/frontend/security_configuration/components/training_provider_list_spec.js7
-rw-r--r--spec/frontend/security_configuration/mock_data.js12
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js172
-rw-r--r--spec/lib/api/entities/ci/job_artifact_file_spec.rb18
-rw-r--r--spec/lib/api/entities/ci/job_request/dependency_spec.rb27
-rw-r--r--spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb142
-rw-r--r--spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb126
-rw-r--r--spec/lib/gitlab/error_tracking_spec.rb131
-rw-r--r--spec/lib/gitlab/fips_spec.rb42
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/services/ci/job_artifacts/destroy_batch_service_spec.rb76
-rw-r--r--spec/support/helpers/stub_configuration.rb16
-rw-r--r--spec/support/sentry.rb13
-rw-r--r--spec/support/shared_contexts/container_repositories_shared_context.rb9
-rw-r--r--spec/views/projects/empty.html.haml_spec.rb15
66 files changed, 1133 insertions, 431 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index cdf11c7f4d7..0993e77fdf7 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-f6ca4c932139d0e6b4407f2ec6251858479382f0
+0206458c621892e0bc247098eb6f3a21d2424477
diff --git a/Gemfile b/Gemfile
index 327e0b5b701..64e5ea8bcce 100644
--- a/Gemfile
+++ b/Gemfile
@@ -302,6 +302,9 @@ gem 'rack-attack', '~> 6.3.0'
# Sentry integration
gem 'sentry-raven', '~> 3.1'
+gem 'sentry-ruby', '~> 5.1.1'
+gem 'sentry-rails', '~> 5.1.1'
+gem 'sentry-sidekiq', '~> 5.1.1'
# PostgreSQL query parsing
#
diff --git a/Gemfile.lock b/Gemfile.lock
index 49ec246a727..6fe4cdc9726 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1174,8 +1174,19 @@ GEM
selenium-webdriver (3.142.7)
childprocess (>= 0.5, < 4.0)
rubyzip (>= 1.2.2)
+ sentry-rails (5.1.1)
+ railties (>= 5.0)
+ sentry-ruby-core (~> 5.1.1)
sentry-raven (3.1.2)
faraday (>= 1.0)
+ sentry-ruby (5.1.1)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ sentry-ruby-core (= 5.1.1)
+ sentry-ruby-core (5.1.1)
+ concurrent-ruby
+ sentry-sidekiq (5.1.1)
+ sentry-ruby-core (~> 5.1.1)
+ sidekiq (>= 3.0)
set (1.0.1)
settingslogic (2.0.9)
sexp_processor (4.15.1)
@@ -1627,7 +1638,10 @@ DEPENDENCIES
sd_notify (~> 0.1.0)
seed-fu (~> 2.3.7)
selenium-webdriver (~> 3.142)
+ sentry-rails (~> 5.1.1)
sentry-raven (~> 3.1)
+ sentry-ruby (~> 5.1.1)
+ sentry-sidekiq (~> 5.1.1)
settingslogic (~> 2.0.9)
shoulda-matchers (~> 4.0.1)
sidekiq (~> 6.4)
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/settings_form.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/settings_form.vue
index 6030af9d2c3..ae2d5f4fbc5 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/settings_form.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/settings_form.vue
@@ -13,7 +13,6 @@ import {
REMOVE_INFO_TEXT,
EXPIRATION_SCHEDULE_LABEL,
NAME_REGEX_LABEL,
- NAME_REGEX_PLACEHOLDER,
NAME_REGEX_DESCRIPTION,
CADENCE_LABEL,
EXPIRATION_POLICY_FOOTER_NOTE,
@@ -68,7 +67,6 @@ export default {
REMOVE_INFO_TEXT,
EXPIRATION_SCHEDULE_LABEL,
NAME_REGEX_LABEL,
- NAME_REGEX_PLACEHOLDER,
NAME_REGEX_DESCRIPTION,
CADENCE_LABEL,
EXPIRATION_POLICY_FOOTER_NOTE,
@@ -141,6 +139,17 @@ export default {
[model]: state,
};
},
+ encapsulateError(path, message) {
+ return {
+ graphQLErrors: [
+ {
+ extensions: {
+ problems: [{ path: [path], message }],
+ },
+ },
+ ],
+ };
+ },
submit() {
this.track('submit_form');
this.apiErrors = {};
@@ -156,7 +165,8 @@ export default {
.then(({ data }) => {
const errorMessage = data?.updateContainerExpirationPolicy?.errors[0];
if (errorMessage) {
- this.$toast.show(errorMessage);
+ const customError = this.encapsulateError('nameRegex', errorMessage);
+ throw customError;
} else {
this.$toast.show(UPDATE_SETTINGS_SUCCESS_MESSAGE);
}
@@ -273,7 +283,6 @@ export default {
:error="apiErrors.nameRegex"
:disabled="isFieldDisabled"
:label="$options.i18n.NAME_REGEX_LABEL"
- :placeholder="$options.i18n.NAME_REGEX_PLACEHOLDER"
:description="$options.i18n.NAME_REGEX_DESCRIPTION"
name="remove-regex"
data-testid="remove-regex-input"
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/constants.js b/app/assets/javascripts/packages_and_registries/settings/project/constants.js
index 4d477fbd05d..841585c5646 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/constants.js
+++ b/app/assets/javascripts/packages_and_registries/settings/project/constants.js
@@ -32,7 +32,6 @@ export const REMOVE_INFO_TEXT = s__(
);
export const EXPIRATION_SCHEDULE_LABEL = s__('ContainerRegistry|Remove tags older than:');
export const NAME_REGEX_LABEL = s__('ContainerRegistry|Remove tags matching:');
-export const NAME_REGEX_PLACEHOLDER = '.*';
export const NAME_REGEX_DESCRIPTION = s__(
'ContainerRegistry|Tags with names that match this regex pattern are removed. %{linkStart}View regex examples.%{linkEnd}',
);
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/graphql/utils/cache_update.js b/app/assets/javascripts/packages_and_registries/settings/project/graphql/utils/cache_update.js
index c4b2af13862..5e0be3834cb 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/graphql/utils/cache_update.js
+++ b/app/assets/javascripts/packages_and_registries/settings/project/graphql/utils/cache_update.js
@@ -10,6 +10,7 @@ export const updateContainerExpirationPolicy = (projectPath) => (client, { data:
const data = produce(sourceData, (draftState) => {
draftState.project.containerExpirationPolicy = {
+ ...draftState.project.containerExpirationPolicy,
...updatedData.updateContainerExpirationPolicy.containerExpirationPolicy,
};
});
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
index adae97c6b6f..67962d69fa5 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
@@ -27,11 +27,6 @@ export default {
required: true,
type: Object,
},
- inviteMembers: {
- type: Boolean,
- required: false,
- default: false,
- },
project: {
required: true,
type: Object,
@@ -54,7 +49,7 @@ export default {
},
},
mounted() {
- if (this.inviteMembers && this.getCookieForInviteMembers()) {
+ if (this.getCookieForInviteMembers()) {
this.openInviteMembersModal('celebrate');
}
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js
index c62cab1a425..63357ea9c72 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
-import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import LearnGitlab from '../components/learn_gitlab.vue';
function initLearnGitlab() {
@@ -13,13 +13,12 @@ function initLearnGitlab() {
const actions = convertObjectPropsToCamelCase(JSON.parse(el.dataset.actions));
const sections = convertObjectPropsToCamelCase(JSON.parse(el.dataset.sections));
const project = convertObjectPropsToCamelCase(JSON.parse(el.dataset.project));
- const { inviteMembers } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(LearnGitlab, {
- props: { actions, sections, project, inviteMembers: parseBoolean(inviteMembers) },
+ props: { actions, sections, project },
});
},
});
diff --git a/app/assets/javascripts/search/topbar/components/app.vue b/app/assets/javascripts/search/topbar/components/app.vue
index 65114ee066e..f27dae8249d 100644
--- a/app/assets/javascripts/search/topbar/components/app.vue
+++ b/app/assets/javascripts/search/topbar/components/app.vue
@@ -1,17 +1,20 @@
<script>
-import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui';
+import { GlSearchBoxByClick } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
+import { s__ } from '~/locale';
import GroupFilter from './group_filter.vue';
import ProjectFilter from './project_filter.vue';
export default {
name: 'GlobalSearchTopbar',
+ i18n: {
+ searchPlaceholder: s__(`GlobalSearch|Search for projects, issues, etc.`),
+ searchLabel: s__(`GlobalSearch|What are you searching for?`),
+ },
components: {
- GlForm,
- GlSearchBoxByType,
+ GlSearchBoxByClick,
GroupFilter,
ProjectFilter,
- GlButton,
},
props: {
groupInitialData: {
@@ -49,28 +52,24 @@ export default {
</script>
<template>
- <gl-form class="search-page-form" @submit.prevent="applyQuery">
- <section class="gl-lg-display-flex gl-align-items-flex-end">
- <div class="gl-flex-grow-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2">
- <label>{{ __('What are you searching for?') }}</label>
- <gl-search-box-by-type
- id="dashboard_search"
- v-model="search"
- name="search"
- :placeholder="__(`Search for projects, issues, etc.`)"
- />
- </div>
- <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
- <label class="gl-display-block">{{ __('Group') }}</label>
- <group-filter :initial-data="groupInitialData" />
- </div>
- <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
- <label class="gl-display-block">{{ __('Project') }}</label>
- <project-filter :initial-data="projectInitialData" />
- </div>
- <gl-button class="btn-search gl-lg-ml-2" category="primary" variant="confirm" type="submit"
- >{{ __('Search') }}
- </gl-button>
- </section>
- </gl-form>
+ <section class="search-page-form gl-lg-display-flex gl-align-items-flex-end">
+ <div class="gl-flex-grow-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2">
+ <label>{{ $options.i18n.searchLabel }}</label>
+ <gl-search-box-by-click
+ id="dashboard_search"
+ v-model="search"
+ name="search"
+ :placeholder="$options.i18n.searchPlaceholder"
+ @submit="applyQuery"
+ />
+ </div>
+ <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
+ <label class="gl-display-block">{{ __('Group') }}</label>
+ <group-filter :initial-data="groupInitialData" />
+ </div>
+ <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
+ <label class="gl-display-block">{{ __('Project') }}</label>
+ <project-filter :initial-data="projectInitialData" />
+ </div>
+ </section>
</template>
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index 81d222438e3..11e092d8eb4 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -281,3 +281,14 @@ export const featureToMutationMap = {
export const AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY =
'security_configuration_auto_devops_enabled_dismissed_projects';
+
+// Fetch the svg path from the GraphQL query once this issue is resolved
+// https://gitlab.com/gitlab-org/gitlab/-/issues/346899
+export const TEMP_PROVIDER_LOGOS = {
+ Kontra: {
+ svg: '/assets/illustrations/vulnerability/kontra-logo.svg',
+ },
+ [__('Secure Code Warrior')]: {
+ svg: '/assets/illustrations/vulnerability/scw-logo.svg',
+ },
+};
diff --git a/app/assets/javascripts/security_configuration/components/training_provider_list.vue b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
index bfdabc10227..ea3eedc6e90 100644
--- a/app/assets/javascripts/security_configuration/components/training_provider_list.vue
+++ b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
@@ -24,6 +24,7 @@ import {
updateSecurityTrainingCache,
updateSecurityTrainingOptimisticResponse,
} from '~/security_configuration/graphql/cache_utils';
+import { TEMP_PROVIDER_LOGOS } from './constants';
const i18n = {
providerQueryErrorMessage: __(
@@ -38,17 +39,6 @@ const i18n = {
),
};
-// Fetch the svg path from the GraphQL query once this issue is resolved
-// https://gitlab.com/gitlab-org/gitlab/-/issues/346899
-const TEMP_PROVIDER_LOGOS = {
- 'gid://gitlab/Security::TrainingProvider/1': {
- svg: '/assets/illustrations/vulnerability/kontra-logo.svg',
- },
- 'gid://gitlab/Security::TrainingProvider/2': {
- svg: '/assets/illustrations/vulnerability/scw-logo.svg',
- },
-};
-
export default {
components: {
GlAlert,
@@ -242,8 +232,12 @@ export default {
label-position="hidden"
@change="toggleProvider(provider)"
/>
- <div v-if="$options.TEMP_PROVIDER_LOGOS[provider.id]" class="gl-ml-4">
- <img :src="$options.TEMP_PROVIDER_LOGOS[provider.id].svg" width="18" />
+ <div v-if="$options.TEMP_PROVIDER_LOGOS[provider.name]" class="gl-ml-4">
+ <img
+ :src="$options.TEMP_PROVIDER_LOGOS[provider.name].svg"
+ width="18"
+ role="presentation"
+ />
</div>
<div class="gl-ml-3">
<h3 class="gl-font-lg gl-m-0 gl-mb-2">{{ provider.name }}</h3>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
index efb99eb0d94..56fae425d1a 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -1,30 +1,33 @@
<script>
/* This is a re-usable vue component for rendering a user avatar that
- does not need to link to the user's profile. The image and an optional
- tooltip can be configured by props passed to this component.
+ does not need to link to the user's profile. The image and an optional
+ tooltip can be configured by props passed to this component.
- Sample configuration:
+ Sample configuration:
- <user-avatar-image
- :lazy="true"
- :img-src="userAvatarSrc"
- :img-alt="tooltipText"
- :tooltip-text="tooltipText"
- tooltip-placement="top"
- />
+ <user-avatar-image
+ lazy
+ :img-src="userAvatarSrc"
+ :img-alt="tooltipText"
+ :tooltip-text="tooltipText"
+ tooltip-placement="top"
+ />
-*/
+ */
-import { GlTooltip } from '@gitlab/ui';
+import { GlTooltip, GlAvatar } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { __ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { placeholderImage } from '../../../lazy_loader';
export default {
name: 'UserAvatarImage',
components: {
GlTooltip,
+ GlAvatar,
},
+ mixins: [glFeatureFlagMixin()],
props: {
lazy: {
type: Boolean,
@@ -85,7 +88,20 @@ export default {
<template>
<span>
+ <gl-avatar
+ v-if="glFeatures.glAvatarForAllUserAvatars"
+ ref="userAvatarImage"
+ :class="{
+ lazy: lazy,
+ [cssClasses]: true,
+ }"
+ :src="resultantSrcAttribute"
+ :data-src="sanitizedSource"
+ :size="size"
+ :alt="imgAlt"
+ />
<img
+ v-else
ref="userAvatarImage"
:class="{
lazy: lazy,
@@ -100,11 +116,9 @@ export default {
class="avatar"
/>
<gl-tooltip
- v-if="tooltipText || $slots.default"
:target="() => $refs.userAvatarImage"
:placement="tooltipPlacement"
boundary="window"
- class="js-user-avatar-image-tooltip"
>
<slot> {{ tooltipText }} </slot>
</gl-tooltip>
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 56c61c1bea2..fa03d73646d 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -66,7 +66,7 @@ class ContainerRepository < ApplicationRecord
# feature flag since it is only accessed in this query.
# https://gitlab.com/gitlab-org/gitlab/-/issues/350543 tracks the rollout and
# removal of this feature flag.
- joins(:project).where(
+ joins(project: [:namespace]).where(
migration_state: [:default],
created_at: ...ContainerRegistry::Migration.created_before
).with_target_import_tier
@@ -76,7 +76,7 @@ class ContainerRepository < ApplicationRecord
FROM feature_gates
WHERE feature_gates.feature_key = 'container_registry_phase_2_deny_list'
AND feature_gates.key = 'actors'
- AND feature_gates.value = concat('Group:', projects.namespace_id)
+ AND feature_gates.value = concat('Group:', namespaces.traversal_ids[1])
)"
)
end
diff --git a/app/models/packages/package_file.rb b/app/models/packages/package_file.rb
index fc7c348dfdb..ad8140ac684 100644
--- a/app/models/packages/package_file.rb
+++ b/app/models/packages/package_file.rb
@@ -49,6 +49,7 @@ class Packages::PackageFile < ApplicationRecord
scope :preload_conan_file_metadata, -> { preload(:conan_file_metadatum) }
scope :preload_debian_file_metadata, -> { preload(:debian_file_metadatum) }
scope :preload_helm_file_metadata, -> { preload(:helm_file_metadatum) }
+ scope :order_id_asc, -> { order(id: :asc) }
scope :for_rubygem_with_file_name, ->(project, file_name) do
joins(:package).merge(project.packages.rubygems).with_file_name(file_name)
diff --git a/app/services/ci/job_artifacts/destroy_associations_service.rb b/app/services/ci/job_artifacts/destroy_associations_service.rb
index 794d24eadf2..08d7f7f6f02 100644
--- a/app/services/ci/job_artifacts/destroy_associations_service.rb
+++ b/app/services/ci/job_artifacts/destroy_associations_service.rb
@@ -12,7 +12,7 @@ module Ci
def destroy_records
@job_artifacts_relation.each_batch(of: BATCH_SIZE) do |relation|
- service = Ci::JobArtifacts::DestroyBatchService.new(relation, pick_up_at: Time.current)
+ service = Ci::JobArtifacts::DestroyBatchService.new(relation, pick_up_at: Time.current, fix_expire_at: false)
result = service.execute(update_stats: false)
updates = result[:statistics_updates]
diff --git a/app/services/ci/job_artifacts/destroy_batch_service.rb b/app/services/ci/job_artifacts/destroy_batch_service.rb
index 866b40c32d8..d5a0a2dd885 100644
--- a/app/services/ci/job_artifacts/destroy_batch_service.rb
+++ b/app/services/ci/job_artifacts/destroy_batch_service.rb
@@ -17,13 +17,18 @@ module Ci
# +pick_up_at+:: When to pick up for deletion of files
# Returns:
# +Hash+:: A hash with status and destroyed_artifacts_count keys
- def initialize(job_artifacts, pick_up_at: nil)
+ def initialize(job_artifacts, pick_up_at: nil, fix_expire_at: fix_expire_at?)
@job_artifacts = job_artifacts.with_destroy_preloads.to_a
@pick_up_at = pick_up_at
+ @fix_expire_at = fix_expire_at
end
# rubocop: disable CodeReuse/ActiveRecord
def execute(update_stats: true)
+ # Detect and fix artifacts that had `expire_at` wrongly backfilled by migration
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47723
+ detect_and_fix_wrongly_expired_artifacts
+
return success(destroyed_artifacts_count: 0, statistics_updates: {}) if @job_artifacts.empty?
destroy_related_records(@job_artifacts)
@@ -89,6 +94,55 @@ module Ci
@job_artifacts.sum { |artifact| artifact.try(:size) || 0 }
end
end
+
+ # This detects and fixes job artifacts that have `expire_at` wrongly backfilled by the migration
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47723.
+ # These job artifacts will not be deleted and will have their `expire_at` removed.
+ #
+ # The migration would have backfilled `expire_at`
+ # to midnight on the 22nd of the month of the local timezone,
+ # storing it as UTC time in the database.
+ #
+ # If the timezone setting has changed since the migration,
+ # the `expire_at` stored in the database could have changed to a different local time other than midnight.
+ # For example:
+ # - changing timezone from UTC+02:00 to UTC+02:30 would change the `expire_at` in local time 00:00:00 to 00:30:00.
+ # - changing timezone from UTC+00:00 to UTC-01:00 would change the `expire_at` in local time 00:00:00 to 23:00:00 on the previous day (21st).
+ #
+ # Therefore job artifacts that have `expire_at` exactly on the 00, 30 or 45 minute mark
+ # on the dates 21, 22, 23 of the month will not be deleted.
+ # https://en.wikipedia.org/wiki/List_of_UTC_time_offsets
+ def detect_and_fix_wrongly_expired_artifacts
+ return unless @fix_expire_at
+
+ wrongly_expired_artifacts, @job_artifacts = @job_artifacts.partition { |artifact| wrongly_expired?(artifact) }
+
+ remove_expire_at(wrongly_expired_artifacts)
+ end
+
+ def fix_expire_at?
+ Feature.enabled?(:ci_detect_wrongly_expired_artifacts, default_enabled: :yaml)
+ end
+
+ def wrongly_expired?(artifact)
+ return false unless artifact.expire_at.present?
+
+ match_date?(artifact.expire_at) && match_time?(artifact.expire_at)
+ end
+
+ def match_date?(expire_at)
+ [21, 22, 23].include?(expire_at.day)
+ end
+
+ def match_time?(expire_at)
+ %w[00:00.000 30:00.000 45:00.000].include?(expire_at.strftime('%M:%S.%L'))
+ end
+
+ def remove_expire_at(artifacts)
+ Ci::JobArtifact.id_in(artifacts).update_all(expire_at: nil)
+
+ Gitlab::AppLogger.info(message: "Fixed expire_at from artifacts.", fixed_artifacts_expire_at_count: artifacts.count)
+ end
end
end
end
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 665f02ab63d..6a54eedf6c8 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,5 +1,6 @@
- @content_class = "limit-container-width" unless fluid_layout
- default_branch_name = @project.default_branch_or_main
+- escaped_default_branch_name = default_branch_name.shellescape
- @skip_current_level_breadcrumb = true
= render partial: 'flash_messages', locals: { project: @project }
@@ -42,25 +43,25 @@
:preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
cd #{h @project.path}
- git switch -c #{h default_branch_name}
+ git switch -c #{h escaped_default_branch_name}
touch README.md
git add README.md
git commit -m "add README"
- if @project.can_current_user_push_to_default_branch?
%span><
- git push -u origin #{h default_branch_name }
+ git push -u origin #{h escaped_default_branch_name }
%h5= _('Push an existing folder')
%pre.bg-light
:preserve
cd existing_folder
- git init --initial-branch=#{h default_branch_name}
+ git init --initial-branch=#{h escaped_default_branch_name}
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
git add .
git commit -m "Initial commit"
- if @project.can_current_user_push_to_default_branch?
%span><
- git push -u origin #{h default_branch_name }
+ git push -u origin #{h escaped_default_branch_name }
%h5= _('Push an existing Git repository')
%pre.bg-light
diff --git a/app/views/projects/learn_gitlab/index.html.haml b/app/views/projects/learn_gitlab/index.html.haml
index 9924b172875..0e950c26d34 100644
--- a/app/views/projects/learn_gitlab/index.html.haml
+++ b/app/views/projects/learn_gitlab/index.html.haml
@@ -5,8 +5,4 @@
= render 'projects/invite_members_modal', project: @project
-- experiment(:confetti_post_signup, actor: current_user) do |e|
- - e.control do
- #js-learn-gitlab-app{ data: data }
- - e.candidate do
- #js-learn-gitlab-app{ data: data.merge(invite_members: 'true') }
+#js-learn-gitlab-app{ data: data }
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index c70e153ae41..66a1cbb4649 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -94,7 +94,7 @@
.input-group-text /
%p.form-text.text-muted
= html_escape(_('The regular expression used to find test coverage output in the job log. For example, use %{regex} for Simplecov (Ruby). Leave blank to disable.')) % { regex: '<code>\(\d+.\d+%\)</code>'.html_safe }
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'add-test-coverage-results-to-a-merge-request-deprecated'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'add-test-coverage-results-using-project-settings-deprecated'), target: '_blank', rel: 'noopener noreferrer'
= f.submit _('Save changes'), class: "btn gl-button btn-confirm", data: { qa_selector: 'save_general_pipelines_changes_button' }
diff --git a/config/feature_flags/development/ci_detect_wrongly_expired_artifacts.yml b/config/feature_flags/development/ci_detect_wrongly_expired_artifacts.yml
new file mode 100644
index 00000000000..8340b347a96
--- /dev/null
+++ b/config/feature_flags/development/ci_detect_wrongly_expired_artifacts.yml
@@ -0,0 +1,8 @@
+---
+name: ci_detect_wrongly_expired_artifacts
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82084
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354955
+milestone: '14.9'
+type: development
+group: group::pipeline insights
+default_enabled: false
diff --git a/config/feature_flags/development/enable_new_sentry_integration.yml b/config/feature_flags/development/enable_new_sentry_integration.yml
new file mode 100644
index 00000000000..00665f80ed6
--- /dev/null
+++ b/config/feature_flags/development/enable_new_sentry_integration.yml
@@ -0,0 +1,8 @@
+---
+name: enable_new_sentry_integration
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72428
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344832
+milestone: '14.9'
+type: development
+group: group::pipeline execution
+default_enabled: false
diff --git a/config/feature_flags/development/enable_old_sentry_integration.yml b/config/feature_flags/development/enable_old_sentry_integration.yml
new file mode 100644
index 00000000000..4911dbfdc78
--- /dev/null
+++ b/config/feature_flags/development/enable_old_sentry_integration.yml
@@ -0,0 +1,8 @@
+---
+name: enable_old_sentry_integration
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72428
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344832
+milestone: '14.9'
+type: development
+group: group::pipeline execution
+default_enabled: true
diff --git a/config/feature_flags/development/fips_mode.yml b/config/feature_flags/development/fips_mode.yml
deleted file mode 100644
index cade948b886..00000000000
--- a/config/feature_flags/development/fips_mode.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: fips_mode
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81418/diffs?view=inline
-rollout_issue_url:
-milestone: '14.9'
-type: development
-group: group::source code
-default_enabled: false
diff --git a/config/feature_flags/experiment/confetti_post_signup.yml b/config/feature_flags/development/gl_avatar_for_all_user_avatars.yml
index 9f677bf252a..a3fee67a7f6 100644
--- a/config/feature_flags/experiment/confetti_post_signup.yml
+++ b/config/feature_flags/development/gl_avatar_for_all_user_avatars.yml
@@ -1,8 +1,8 @@
---
-name: confetti_post_signup
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70011
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339890
-milestone: '14.5'
-type: experiment
-group: group::expansion
+name: gl_avatar_for_all_user_avatars
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81437
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353477
+milestone: '14.9'
+type: development
+group: group::foundations
default_enabled: false
diff --git a/doc/ci/pipelines/settings.md b/doc/ci/pipelines/settings.md
index 4ae4aabbf5a..373244acbb9 100644
--- a/doc/ci/pipelines/settings.md
+++ b/doc/ci/pipelines/settings.md
@@ -195,13 +195,7 @@ Jobs that exceed the timeout are marked as failed.
You can override this value [for individual runners](../runners/configure_runners.md#set-maximum-job-timeout-for-a-runner).
-## Add test coverage results to a merge request (DEPRECATED)
-
-> [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/17633) in GitLab 14.9. Replaced by [`coverage` keyword](../yaml/index.md#coverage).
-
-WARNING:
-This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/17633)
-for use in GitLab 14.9, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/17633) in GitLab 15.0.
+## Merge request test coverage results
If you use test coverage in your code, you can use a regular expression to
find coverage results in the job log. You can then include these results
@@ -215,27 +209,38 @@ averaged.
![Build status coverage](img/pipelines_test_coverage_build.png)
-To define a coverage-parsing regular expression:
+### Add test coverage results using `coverage` keyword
+
+To add test coverage results to a merge request using the project's `.gitlab-ci.yml` file, provide a regular expression
+using the [`coverage`](../yaml/index.md#coverage) keyword.
+
+Setting the regular expression this way takes precedence over project settings.
-- Using the project's `.gitlab-ci.yml`, provide a regular expression using the [`coverage`](../yaml/index.md#coverage)
- keyword. Setting the regular expression this way takes precedence over the project's CI/CD settings.
+### Add test coverage results using project settings (DEPRECATED)
-- Using the Project's CI/CD settings:
- - Set using the GitLab UI:
+> [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/17633) in GitLab 14.9. Replaced by [`coverage` keyword](../yaml/index.md#coverage).
+
+WARNING:
+This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/17633)
+for use in GitLab 14.9, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/17633) in GitLab 15.0.
+
+You can add test coverage results to merge requests using the Project's CI/CD settings:
- 1. On the top bar, select **Menu > Projects** and find your project.
- 1. On the left sidebar, select **Settings > CI/CD**.
- 1. Expand **General pipelines**.
- 1. In the **Test coverage parsing** field, enter a regular expression. Leave blank to disable this feature.
+- Set using the GitLab UI:
- - Set when [editing a project](../../api/projects.md#edit-project) or [creating a project](../../api/projects.md#create-project)
- using the GitLab API with the `build_coverage_regex` attribute:
+ 1. On the top bar, select **Menu > Projects** and find your project.
+ 1. On the left sidebar, select **Settings > CI/CD**.
+ 1. Expand **General pipelines**.
+ 1. In the **Test coverage parsing** field, enter a regular expression. Leave blank to disable this feature.
- ```shell
- curl --request PUT --header "PRIVATE-TOKEN: <your-token>" \
- --url 'https://gitlab.com/api/v4/projects/<your-project-ID>' \
- --data "build_coverage_regex=<your-regular-expression>"
- ```
+- Set when [editing a project](../../api/projects.md#edit-project) or [creating a project](../../api/projects.md#create-project)
+ using the GitLab API with the `build_coverage_regex` attribute:
+
+ ```shell
+ curl --request PUT --header "PRIVATE-TOKEN: <your-token>" \
+ --url 'https://gitlab.com/api/v4/projects/<your-project-ID>' \
+ --data "build_coverage_regex=<your-regular-expression>"
+ ```
You can use <https://rubular.com> to test your regular expression. The regular expression returns the **last**
match found in the output.
@@ -372,9 +377,8 @@ https://gitlab.example.com/<namespace>/<project>/badges/<branch>/pipeline.svg?ig
### Test coverage report badge
-You can define the regular expression for the [coverage report](#add-test-coverage-results-to-a-merge-request-deprecated)
-that each job log is matched against. This means that each job in the
-pipeline can have the test coverage percentage value defined.
+You can define the regular expression for the [coverage report](#merge-request-test-coverage-results) that each job log
+is matched against. This means that each job in the pipeline can have the test coverage percentage value defined.
To access the test coverage badge, use the following link:
diff --git a/doc/ci/unit_test_reports.md b/doc/ci/unit_test_reports.md
index e2de47c6c62..14a2a1c810f 100644
--- a/doc/ci/unit_test_reports.md
+++ b/doc/ci/unit_test_reports.md
@@ -231,7 +231,7 @@ The [JunitXML.TestLogger](https://www.nuget.org/packages/JunitXml.TestLogger/) N
package can generate test reports for .Net Framework and .Net Core applications. The following
example expects a solution in the root folder of the repository, with one or more
project files in sub-folders. One result file is produced per test project, and each file
-is placed in a new artifacts folder. This example includes optional formatting arguments, which
+is placed in the artifacts folder. This example includes optional formatting arguments, which
improve the readability of test data in the test widget. A full .Net Core
[example is available](https://gitlab.com/Siphonophora/dot-net-cicd-test-logging-demo).
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index d9ea5498b63..e754e7081b9 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -1349,7 +1349,7 @@ In this example:
**Additional details**:
- Coverage regular expressions set in `gitlab-ci.yml` take precedence over coverage regular expression set in the
- [GitLab UI](../pipelines/settings.md#add-test-coverage-results-to-a-merge-request-deprecated).
+ [GitLab UI](../pipelines/settings.md#add-test-coverage-results-using-project-settings-deprecated).
- If there is more than one matched line in the job output, the last line is used
(the first result of reverse search).
- If there are multiple matches in a single line, the last match is searched
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index 43ef16e14d6..8dae7f16974 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -301,6 +301,21 @@ We follow the [PostgreSQL versions shipped with Omnibus GitLab](../administratio
| PG11 | `nightly` | `nightly` | `nightly` | `nightly` | `nightly` | `nightly` |
| PG13 | `nightly` | `nightly` | `nightly` | `nightly` | `nightly` | `nightly` |
+## Redis versions testing
+
+Our test suite runs against Redis 6 as GitLab.com runs on Redis 6 and
+[Omnibus defaults to Redis 6 for new installs and upgrades](https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/master/config/software/redis.rb).
+
+We do run our test suite against Redis 5 on `nightly` scheduled pipelines, specifically when running backward-compatible and forward-compatible PostgreSQL jobs.
+
+### Current versions testing
+
+| Where? | Redis version |
+| ------ | ------------------ |
+| MRs | 6 |
+| `default branch` (non-scheduled pipelines) | 6 |
+| `nightly` scheduled pipelines | 5 |
+
## Pipelines types for merge requests
In general, pipelines for an MR fall into one of the following types (from shorter to longer), depending on the changes made in the MR:
@@ -541,6 +556,8 @@ that are scoped to a single [configuration keyword](../ci/yaml/index.md#job-keyw
| `.use-pg11-ee` | Same as `.use-pg11` but also use an `elasticsearch` service (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific version of the service). |
| `.use-pg12` | Allows a job to use the `postgres` 12 and `redis` services (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific versions of the services). |
| `.use-pg12-ee` | Same as `.use-pg12` but also use an `elasticsearch` service (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific version of the service). |
+| `.use-pg13` | Allows a job to use the `postgres` 13 and `redis` services (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific versions of the services). |
+| `.use-pg13-ee` | Same as `.use-pg13` but also use an `elasticsearch` service (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific version of the service). |
| `.use-kaniko` | Allows a job to use the `kaniko` tool to build Docker images. |
| `.as-if-foss` | Simulate the FOSS project by setting the `FOSS_ONLY='1'` CI/CD variable. |
| `.use-docker-in-docker` | Allows a job to use Docker in Docker. |
diff --git a/doc/user/infrastructure/clusters/connect/new_gke_cluster.md b/doc/user/infrastructure/clusters/connect/new_gke_cluster.md
index 55fea5890b3..1ed8b0ef350 100644
--- a/doc/user/infrastructure/clusters/connect/new_gke_cluster.md
+++ b/doc/user/infrastructure/clusters/connect/new_gke_cluster.md
@@ -4,65 +4,59 @@ group: Configure
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# New GKE cluster through IaC (DEPRECATED)
-
-> [Deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
-
-WARNING:
-The process described on this page uses cluster certificates to connect the
-new cluster to GitLab, [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
-You can still create a cluster and then connect it to GitLab through the [agent](../index.md).
-[An issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/343660)
-to migrate this functionality to the [agent](../index.md).
+# Create a Google GKE cluster
Learn how to create a new cluster on Google Kubernetes Engine (GKE) through
-[Infrastructure as Code (IaC)](../../index.md).
-
-This process combines the GitLab Terraform and Google Terraform providers
-with Kubernetes to help you create GKE clusters and deploy them through
-GitLab.
-
-This document describes how to set up a [group-level cluster](../../../group/clusters/index.md) on GKE by importing an example project to get you started.
-You can then modify the project files according to your needs.
+[Infrastructure as Code (IaC)](../../index.md). This process uses the Google
+and Kubernetes Terraform providers create GKE clusters. You connect the clusters to GitLab
+by using the GitLab agent for Kubernetes.
**Prerequisites:**
-- A GitLab group.
-- A GitLab user with the Maintainer role in the group.
-- A [GitLab personal access token](../../../profile/personal_access_tokens.md) with `api` access, created by a user with at least the Maintainer role in the group.
- A [Google Cloud Platform (GCP) service account](https://cloud.google.com/docs/authentication/getting-started).
+- [A runner](https://docs.gitlab.com/runner/install/) you can use to run the GitLab CI/CD pipeline.
**Steps:**
1. [Import the example project](#import-the-example-project).
-1. [Create your GCP and GitLab credentials](#create-your-gcp-and-gitlab-credentials).
+1. [Register the agent for Kubernetes](#register-the-agent).
+1. [Create your GCP credentials](#create-your-gcp-credentials).
1. [Configure your project](#configure-your-project).
-1. [Deploy your cluster](#deploy-your-cluster).
+1. [Provision your cluster](#provision-your-cluster).
## Import the example project
-To create a new group-level cluster from GitLab using Infrastructure as Code, it is necessary
-to create a project to manage the cluster from. In this tutorial, we import a pre-configured
-sample project to help you get started.
+To create a cluster from GitLab using Infrastructure as Code, you must
+create a project to manage the cluster from. In this tutorial, you start with
+a sample project and modify it according to your needs.
-Start by [importing the example project by URL](../../../project/import/repo_by_url.md). Use `https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-gke.git` as URL.
+Start by [importing the example project by URL](../../../project/import/repo_by_url.md).
-This project provides you with the following resources:
+To import the project:
+
+1. On the top bar, select **Menu > Create new project**.
+1. Select **Import project**.
+1. Select **Repo by URL**.
+1. For the **Git repository URL**, enter `https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-gke.git`.
+1. Complete the fields and select **Create project**.
+
+This project provides you with:
- A [cluster on Google Cloud Platform (GCP)](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-gke/-/blob/master/gke.tf)
with defaults for name, location, node count, and Kubernetes version.
-- A [`gitlab-admin` K8s service account](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-gke/-/blob/master/gitlab-admin.tf) with `cluster-admin` privileges.
-- The new group-level cluster connected to GitLab.
-- Pre-configures Terraform files:
-
- ```plaintext
- ├── backend.tf # State file Location Configuration
- ├── gke.tf # Google GKE Configuration
- ├── gitlab-admin.tf # Adding kubernetes service account
- └── group_cluster.tf # Registering kubernetes cluster to GitLab `apps` Group
- ```
+- The [GitLab agent for Kubernetes](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-gke/-/blob/master/agent.tf) installed in the cluster.
-## Create your GCP and GitLab credentials
+## Register the agent
+
+To create a GitLab agent for Kubernetes:
+
+1. On the left sidebar, select **Infrastructure > Kubernetes clusters**.
+1. Select **Actions**.
+1. From the **Select an agent** dropdown list, select `gke-agent` and select **Register an agent**.
+1. GitLab generates a registration token for the agent. Securely store this secret token, as you will need it later.
+1. GitLab provides an address for the agent server (KAS), which you will also need later.
+
+## Create your GCP credentials
To set up your project to communicate to GCP and the GitLab API:
@@ -85,18 +79,14 @@ The Admin role creates a service account in the `kube-system` namespace.
## Configure your project
-**Required configuration:**
-
-Use CI/CD environment variables to configure your project as detailed below.
+Use CI/CD environment variables to configure your project.
**Required configuration:**
1. On the left sidebar, select **Settings > CI/CD**.
1. Expand **Variables**.
-1. Set the variable `TF_VAR_gitlab_token` to the GitLab personal access token you just created.
1. Set the variable `BASE64_GOOGLE_CREDENTIALS` to the `base64` encoded JSON file you just created.
1. Set the variable `TF_VAR_gcp_project` to your GCP's `project` name.
-1. Set the variable `TF_VAR_gitlab_group` to the name of the group you want to connect your cluster to. If your group's URL is `https://gitlab.example.com/my-example-group`, `my-example-group` is your group's name.
**Optional configuration:**
@@ -105,22 +95,57 @@ contains other variables that you can override according to your needs:
- `TF_VAR_gcp_region`: Set your cluster's region.
- `TF_VAR_cluster_name`: Set your cluster's name.
-- `TF_VAR_machine_type`: Set the machine type for the Kubernetes nodes.
- `TF_VAR_cluster_description`: Set a description for the cluster. We recommend setting this to `$CI_PROJECT_URL` to create a reference to your GitLab project on your GCP cluster detail page. This way you know which project was responsible for provisioning the cluster you see on the GCP dashboard.
-- `TF_VAR_base_domain`: Set to the base domain to provision resources under.
-- `TF_VAR_environment_scope`: Set to the environment scope for your cluster.
+- `TF_VAR_machine_type`: Set the machine type for the Kubernetes nodes.
+- `TF_VAR_node_count`: Set the number of Kubernetes nodes.
+- `TF_VAR_agent_version`: Set the version of the GitLab agent.
+- `TF_VAR_agent_namespace`: Set the Kubernetes namespace for the GitLab agent.
-Refer to the [GitLab Terraform provider](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs) and the [Google Terraform provider](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference) documentation for further resource options.
+Refer to the [Google Terraform provider](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference) and the [Kubernetes Terraform provider](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs) documentation for further resource options.
-## Deploy your cluster
+## Provision your cluster
-After configuring your project, manually trigger the deployment of your cluster. In GitLab:
+After configuring your project, manually trigger the provisioning of your cluster. In GitLab:
-1. From your project's sidebar, go to **CI/CD > Pipelines**.
-1. Select the dropdown icon (**{angle-down}**) next to the play icon (**{play}**).
-1. Select **deploy** to manually trigger the deployment job.
+1. On the left sidebar, go to **CI/CD > Pipelines**.
+1. Next to **Play** (**{play}**), select the dropdown icon (**{angle-down}**).
+1. Select **Deploy** to manually trigger the deployment job.
When the pipeline finishes successfully, you can see your new cluster:
- In GCP: on your [GCP console's Kubernetes list](https://console.cloud.google.com/kubernetes/list).
- In GitLab: from your project's sidebar, select **Infrastructure > Kubernetes clusters**.
+
+## Use your cluster
+
+After you provision the cluster, it is connected to GitLab and is ready for deployments. To check the connection:
+
+1. On the left sidebar, select **Infrastructure > Kubernetes clusters**.
+1. In the list, view the **Connection status** column.
+
+For more information about the capabilities of the connection, see [the GitLab agent for Kubernetes documentation](../index.md).
+
+## Remove the cluster
+
+A cleanup job is not included in your pipeline by default. To remove all created resources, you
+must modify your GitLab CI/CD template before running the cleanup job.
+
+To remove all resources:
+
+1. Add the following to your `.gitlab-ci.yml` file:
+
+ ```yaml
+ stages:
+ - init
+ - validate
+ - build
+ - deploy
+ - cleanup
+
+ destroy:
+ extends: .destroy
+ needs: []
+ ```
+
+1. On the left sidebar, select **CI/CD > Pipelines** and select the most recent pipeline.
+1. For the `destroy` job, select **Play** (**{play}**).
diff --git a/doc/user/infrastructure/iac/index.md b/doc/user/infrastructure/iac/index.md
index 90014d81848..3bc7495d4be 100644
--- a/doc/user/infrastructure/iac/index.md
+++ b/doc/user/infrastructure/iac/index.md
@@ -108,14 +108,7 @@ is available as part of the official Terraform provider documentation.
## Create a new cluster through IaC
- Learn how to [create a new cluster on Amazon Elastic Kubernetes Service (EKS)](../clusters/connect/new_eks_cluster.md).
-- Learn how to [create a new cluster on Google Kubernetes Engine (GKE)](../clusters/connect/new_gke_cluster.md) (DEPRECATED).
-
-NOTE:
-The linked GKE tutorial connects the cluster to GitLab through cluster certificates,
-and this method was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8)
-in GitLab 14.5. You can still create a cluster through IaC and then connect it to GitLab
-through the [agent](../../clusters/agent/index.md), the default and fully supported
-method to connect clusters to GitLab.
+- Learn how to [create a new cluster on Google Kubernetes Engine (GKE)](../clusters/connect/new_gke_cluster.md).
## Troubleshooting
diff --git a/doc/user/project/merge_requests/test_coverage_visualization.md b/doc/user/project/merge_requests/test_coverage_visualization.md
index d51c6e6359e..16c5dbe9199 100644
--- a/doc/user/project/merge_requests/test_coverage_visualization.md
+++ b/doc/user/project/merge_requests/test_coverage_visualization.md
@@ -52,7 +52,12 @@ from any job in any stage in the pipeline. The coverage displays for each line:
Hovering over the coverage bar provides further information, such as the number
of times the line was checked by tests.
-Uploading a test coverage report does not enable [test coverage results in Merge Requests](../../../ci/pipelines/settings.md#add-test-coverage-results-to-a-merge-request-deprecated) or [code coverage history](../../../ci/pipelines/settings.md#view-code-coverage-history). You must configure these separately.
+Uploading a test coverage report does not enable:
+
+- [Test coverage results in merge requests](../../../ci/pipelines/settings.md#merge-request-test-coverage-results).
+- [Code coverage history](../../../ci/pipelines/settings.md#view-code-coverage-history).
+
+You must configure these separately.
### Limits
diff --git a/lib/api/package_files.rb b/lib/api/package_files.rb
index e80355e80c7..4861c0c740e 100644
--- a/lib/api/package_files.rb
+++ b/lib/api/package_files.rb
@@ -29,7 +29,7 @@ module API
.new(user_project, params[:package_id]).execute
package_files = package.installable_package_files
- .preload_pipelines
+ .preload_pipelines.order_id_asc
present paginate(package_files), with: ::API::Entities::PackageFile
end
diff --git a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
index d5ca93a0a3b..f3d2e293c86 100644
--- a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_BUILD_IMAGE_VERSION: 'v1.5.0'
+ AUTO_BUILD_IMAGE_VERSION: 'v1.9.1'
build:
stage: build
@@ -7,7 +7,7 @@ build:
variables:
DOCKER_TLS_CERTDIR: ''
services:
- - name: 'docker:20.10.6-dind'
+ - name: 'docker:20.10.12-dind'
command: ['--tls=false', '--host=tcp://0.0.0.0:2375']
script:
- |
diff --git a/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml
index d5ca93a0a3b..f3d2e293c86 100644
--- a/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_BUILD_IMAGE_VERSION: 'v1.5.0'
+ AUTO_BUILD_IMAGE_VERSION: 'v1.9.1'
build:
stage: build
@@ -7,7 +7,7 @@ build:
variables:
DOCKER_TLS_CERTDIR: ''
services:
- - name: 'docker:20.10.6-dind'
+ - name: 'docker:20.10.12-dind'
command: ['--tls=false', '--host=tcp://0.0.0.0:2375']
script:
- |
diff --git a/lib/gitlab/error_tracking.rb b/lib/gitlab/error_tracking.rb
index 6a637306225..259b430a73c 100644
--- a/lib/gitlab/error_tracking.rb
+++ b/lib/gitlab/error_tracking.rb
@@ -23,7 +23,12 @@ module Gitlab
].freeze
class << self
- def configure
+ def configure(&block)
+ configure_raven(&block)
+ configure_sentry(&block)
+ end
+
+ def configure_raven
Raven.configure do |config|
config.dsn = sentry_dsn
config.release = Gitlab.revision
@@ -34,7 +39,20 @@ module Gitlab
# Sanitize authentication headers
config.sanitize_http_headers = %w[Authorization Private-Token]
- config.before_send = method(:before_send)
+ config.before_send = method(:before_send_raven)
+
+ yield config if block_given?
+ end
+ end
+
+ def configure_sentry
+ Sentry.init do |config|
+ config.dsn = new_sentry_dsn
+ config.release = Gitlab.revision
+ config.environment = new_sentry_environment
+ config.before_send = method(:before_send_sentry)
+ config.background_worker_threads = 0
+ config.send_default_pii = true
yield config if block_given?
end
@@ -96,6 +114,18 @@ module Gitlab
private
+ def before_send_raven(event, hint)
+ return unless Feature.enabled?(:enable_old_sentry_integration, default_enabled: :yaml)
+
+ before_send(event, hint)
+ end
+
+ def before_send_sentry(event, hint)
+ return unless Feature.enabled?(:enable_new_sentry_integration, default_enabled: :yaml)
+
+ before_send(event, hint)
+ end
+
def before_send(event, hint)
inject_context_for_exception(event, hint[:exception])
custom_fingerprinting(event, hint[:exception])
@@ -112,6 +142,13 @@ module Gitlab
Raven.capture_exception(exception, **context_payload)
end
+ # There is a possibility that this method is called before Sentry is
+ # configured. Since Sentry 4.0, some methods of Sentry are forwarded to
+ # to `nil`, hence we have to check the client as well.
+ if sentry && ::Sentry.get_current_client && ::Sentry.configuration.dsn
+ ::Sentry.capture_exception(exception, **context_payload)
+ end
+
if logging
formatter = Gitlab::ErrorTracking::LogFormatter.new
log_hash = formatter.generate_log(exception, context_payload)
@@ -121,12 +158,30 @@ module Gitlab
end
def sentry_dsn
- return unless Rails.env.production? || Rails.env.development?
+ return unless sentry_configurable?
return unless Gitlab.config.sentry.enabled
Gitlab.config.sentry.dsn
end
+ def new_sentry_dsn
+ return unless sentry_configurable?
+ return unless Gitlab::CurrentSettings.respond_to?(:sentry_enabled?)
+ return unless Gitlab::CurrentSettings.sentry_enabled?
+
+ Gitlab::CurrentSettings.sentry_dsn
+ end
+
+ def new_sentry_environment
+ return unless Gitlab::CurrentSettings.respond_to?(:sentry_environment)
+
+ Gitlab::CurrentSettings.sentry_environment
+ end
+
+ def sentry_configurable?
+ Rails.env.production? || Rails.env.development?
+ end
+
def should_raise_for_dev?
Rails.env.development? || Rails.env.test?
end
diff --git a/lib/gitlab/error_tracking/processor/grpc_error_processor.rb b/lib/gitlab/error_tracking/processor/grpc_error_processor.rb
index e835deeea2c..045a18f4110 100644
--- a/lib/gitlab/error_tracking/processor/grpc_error_processor.rb
+++ b/lib/gitlab/error_tracking/processor/grpc_error_processor.rb
@@ -18,7 +18,7 @@ module Gitlab
# only the first one since that's what is used for grouping.
def process_first_exception_value(event)
# Better in new version, will be event.exception.values
- exceptions = event.instance_variable_get(:@interfaces)[:exception]&.values
+ exceptions = extract_exceptions_from(event)
return unless exceptions.is_a?(Array)
@@ -37,7 +37,13 @@ module Gitlab
# instance variable
if message.present?
exceptions.each do |exception|
- exception.value = message if valid_exception?(exception)
+ next unless valid_exception?(exception)
+
+ if exception.respond_to?(:value=)
+ exception.value = message
+ else
+ exception.instance_variable_set(:@value, message)
+ end
end
end
@@ -55,6 +61,14 @@ module Gitlab
private
+ def extract_exceptions_from(event)
+ if event.is_a?(Raven::Event)
+ event.instance_variable_get(:@interfaces)[:exception]&.values
+ else
+ event.exception&.instance_variable_get(:@values)
+ end
+ end
+
def custom_grpc_fingerprint?(fingerprint)
fingerprint.is_a?(Array) && fingerprint.length == 2 && fingerprint[0].start_with?('GRPC::')
end
@@ -71,7 +85,7 @@ module Gitlab
def valid_exception?(exception)
case exception
- when Raven::SingleExceptionInterface
+ when Raven::SingleExceptionInterface, Sentry::SingleExceptionInterface
exception&.value
else
false
diff --git a/lib/gitlab/fips.rb b/lib/gitlab/fips.rb
index 3b3eac6e5e2..1dd363ceb17 100644
--- a/lib/gitlab/fips.rb
+++ b/lib/gitlab/fips.rb
@@ -10,7 +10,13 @@ module Gitlab
#
# @return [Boolean]
def enabled?
- Feature.enabled?(:fips_mode, default_enabled: :yaml)
+ # Attempt to auto-detect FIPS mode from OpenSSL
+ return true if OpenSSL.fips_mode
+
+ # Otherwise allow it to be set manually via the env vars
+ return true if ENV["FIPS_MODE"] == "true"
+
+ false
end
end
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index f5d74ad1563..c76e006bae4 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -59,6 +59,7 @@ module Gitlab
push_frontend_feature_flag(:bootstrap_confirmation_modals, default_enabled: :yaml)
push_frontend_feature_flag(:sandboxed_mermaid, default_enabled: :yaml)
push_frontend_feature_flag(:source_editor_toolbar, default_enabled: :yaml)
+ push_frontend_feature_flag(:gl_avatar_for_all_user_avatars, default_enabled: :yaml)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 44df1e5363b..257a69adfd6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -16840,6 +16840,9 @@ msgstr ""
msgid "GlobalSearch|Search GitLab"
msgstr ""
+msgid "GlobalSearch|Search for projects, issues, etc."
+msgstr ""
+
msgid "GlobalSearch|Search results are loading"
msgstr ""
@@ -16852,6 +16855,9 @@ msgstr ""
msgid "GlobalSearch|Type for new suggestions to appear below."
msgstr ""
+msgid "GlobalSearch|What are you searching for?"
+msgstr ""
+
msgid "GlobalSearch|in all GitLab"
msgstr ""
@@ -32377,6 +32383,9 @@ msgstr ""
msgid "Secret token"
msgstr ""
+msgid "Secure Code Warrior"
+msgstr ""
+
msgid "Secure Files"
msgstr ""
@@ -41416,9 +41425,6 @@ msgstr ""
msgid "What are project audit events?"
msgstr ""
-msgid "What are you searching for?"
-msgstr ""
-
msgid "What does this command do?"
msgstr ""
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index fa01304ffe0..5dd627f3b76 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe 'Project issue boards', :js do
project.add_maintainer(user2)
sign_in(user)
+ stub_feature_flags(gl_avatar_for_all_user_avatars: false)
set_cookie('sidebar_collapsed', 'true')
end
diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
index 33c5a936b8d..fca40dc7edc 100644
--- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
@@ -25,6 +25,7 @@ RSpec.describe 'Merge request > User sees avatars on diff notes', :js do
before do
project.add_maintainer(user)
sign_in user
+ stub_feature_flags(gl_avatar_for_all_user_avatars: false)
set_cookie('sidebar_collapsed', 'true')
end
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index c04a4493a9b..a0016f82f0a 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe 'User searches for code' do
it 'finds code and links to blob' do
fill_in('dashboard_search', with: 'rspec')
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
expect(page).to have_selector('.results', text: 'Update capybara, rspec-rails, poltergeist to recent versions')
@@ -52,7 +52,7 @@ RSpec.describe 'User searches for code' do
it 'finds code and links to blame' do
fill_in('dashboard_search', with: 'rspec')
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
expect(page).to have_selector('.results', text: 'Update capybara, rspec-rails, poltergeist to recent versions')
@@ -65,7 +65,7 @@ RSpec.describe 'User searches for code' do
search = 'for naming files'
fill_in('dashboard_search', with: search)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
expect(page).to have_selector('.results', text: expected_result)
@@ -94,7 +94,7 @@ RSpec.describe 'User searches for code' do
expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
end
it 'persists branch name across search' do
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
end
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index b0902096770..c23a54594d4 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'User searches for issues', :js do
def search_for_issue(search)
fill_in('dashboard_search', with: search)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Issues')
end
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index d7f490ba9bc..61c61d793db 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'User searches for merge requests', :js do
def search_for_mr(search)
fill_in('dashboard_search', with: search)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Merge requests')
end
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 7a1ec16385c..61f2e8e0c8f 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'User searches for milestones', :js do
it 'finds a milestone' do
fill_in('dashboard_search', with: milestone1.title)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Milestones')
page.within('.results') do
@@ -40,7 +40,7 @@ RSpec.describe 'User searches for milestones', :js do
end
fill_in('dashboard_search', with: milestone1.title)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Milestones')
page.within('.results') do
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 06545d8640f..9808383adb7 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe 'User searches for wiki pages', :js do
end
fill_in('dashboard_search', with: search_term)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Wiki')
page.within('.results') do
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap b/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap
index 9938357ed24..841a9bf8290 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap
@@ -58,7 +58,7 @@ exports[`Settings Form Remove regex matches snapshot 1`] = `
error=""
label="Remove tags matching:"
name="remove-regex"
- placeholder=".*"
+ placeholder=""
value="asdasdssssdfdf"
/>
`;
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js
index 625aa37fc0f..266f953c3e0 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js
@@ -49,6 +49,11 @@ describe('Settings Form', () => {
const findOlderThanDropdown = () => wrapper.find('[data-testid="older-than-dropdown"]');
const findRemoveRegexInput = () => wrapper.find('[data-testid="remove-regex-input"]');
+ const submitForm = async () => {
+ findForm().trigger('submit');
+ return waitForPromises();
+ };
+
const mountComponent = ({
props = defaultProps,
data,
@@ -318,27 +323,24 @@ describe('Settings Form', () => {
mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
});
- findForm().trigger('submit');
- await waitForPromises();
- await nextTick();
+ await submitForm();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE);
});
describe('when submit fails', () => {
describe('user recoverable errors', () => {
- it('when there is an error is shown in a toast', async () => {
+ it('when there is an error is shown in the nameRegex field t', async () => {
mountComponentWithApollo({
mutationResolver: jest
.fn()
.mockResolvedValue(expirationPolicyMutationPayload({ errors: ['foo'] })),
});
- findForm().trigger('submit');
- await waitForPromises();
- await nextTick();
+ await submitForm();
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('foo');
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE);
+ expect(findRemoveRegexInput().props('error')).toBe('foo');
});
});
@@ -348,9 +350,7 @@ describe('Settings Form', () => {
mutationResolver: jest.fn().mockRejectedValue(expirationPolicyMutationPayload()),
});
- findForm().trigger('submit');
- await waitForPromises();
- await nextTick();
+ await submitForm();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE);
});
@@ -367,9 +367,7 @@ describe('Settings Form', () => {
});
mountComponent({ mocks: { $apollo: { mutate } } });
- findForm().trigger('submit');
- await waitForPromises();
- await nextTick();
+ await submitForm();
expect(findKeepRegexInput().props('error')).toEqual('baz');
});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js
index 4d6bd65bd93..76d5f8a6659 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js
@@ -4,15 +4,15 @@ import { updateContainerExpirationPolicy } from '~/packages_and_registries/setti
describe('Registry settings cache update', () => {
let client;
- const payload = {
+ const payload = (value) => ({
data: {
updateContainerExpirationPolicy: {
containerExpirationPolicy: {
- enabled: true,
+ ...value,
},
},
},
- };
+ });
const cacheMock = {
project: {
@@ -35,12 +35,12 @@ describe('Registry settings cache update', () => {
});
describe('Registry settings cache update', () => {
it('calls readQuery', () => {
- updateContainerExpirationPolicy('foo')(client, payload);
+ updateContainerExpirationPolicy('foo')(client, payload({ enabled: true }));
expect(client.readQuery).toHaveBeenCalledWith(queryAndVariables);
});
it('writes the correct result in the cache', () => {
- updateContainerExpirationPolicy('foo')(client, payload);
+ updateContainerExpirationPolicy('foo')(client, payload({ enabled: true }));
expect(client.writeQuery).toHaveBeenCalledWith({
...queryAndVariables,
data: {
@@ -52,5 +52,20 @@ describe('Registry settings cache update', () => {
},
});
});
+
+ it('with an empty update preserves the state', () => {
+ updateContainerExpirationPolicy('foo')(client, payload());
+
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ ...queryAndVariables,
+ data: {
+ project: {
+ containerExpirationPolicy: {
+ enabled: false,
+ },
+ },
+ },
+ });
+ });
});
});
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
index ee682b18af3..5f1aff99578 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
@@ -9,7 +9,6 @@ import { testActions, testSections, testProject } from './mock_data';
describe('Learn GitLab', () => {
let wrapper;
let sidebar;
- let inviteMembers = false;
const createWrapper = () => {
wrapper = mount(LearnGitlab, {
@@ -17,7 +16,6 @@ describe('Learn GitLab', () => {
actions: testActions,
sections: testSections,
project: testProject,
- inviteMembers,
},
});
};
@@ -38,7 +36,6 @@ describe('Learn GitLab', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
- inviteMembers = false;
sidebar.remove();
});
@@ -73,7 +70,6 @@ describe('Learn GitLab', () => {
});
it('emits openModal', () => {
- inviteMembers = true;
Cookies.set(INVITE_MODAL_OPEN_COOKIE, true);
createWrapper();
@@ -86,19 +82,11 @@ describe('Learn GitLab', () => {
});
it('does not emit openModal when cookie is not set', () => {
- inviteMembers = true;
-
createWrapper();
expect(spy).not.toHaveBeenCalled();
expect(cookieSpy).toHaveBeenCalledWith(INVITE_MODAL_OPEN_COOKIE);
});
-
- it('does not emit openModal when inviteMembers is false', () => {
- createWrapper();
-
- expect(spy).not.toHaveBeenCalled();
- });
});
describe('when the showSuccessfulInvitationsAlert event is fired', () => {
diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js
index 7ce5efb3c52..0a44688bfe0 100644
--- a/spec/frontend/search/topbar/components/app_spec.js
+++ b/spec/frontend/search/topbar/components/app_spec.js
@@ -1,4 +1,4 @@
-import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui';
+import { GlSearchBoxByClick } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
@@ -36,40 +36,19 @@ describe('GlobalSearchTopbar', () => {
wrapper.destroy();
});
- const findTopbarForm = () => wrapper.find(GlForm);
- const findGlSearchBox = () => wrapper.find(GlSearchBoxByType);
+ const findGlSearchBox = () => wrapper.find(GlSearchBoxByClick);
const findGroupFilter = () => wrapper.find(GroupFilter);
const findProjectFilter = () => wrapper.find(ProjectFilter);
- const findSearchButton = () => wrapper.find(GlButton);
describe('template', () => {
beforeEach(() => {
createComponent();
});
- it('renders Topbar Form always', () => {
- expect(findTopbarForm().exists()).toBe(true);
- });
-
describe('Search box', () => {
it('renders always', () => {
expect(findGlSearchBox().exists()).toBe(true);
});
-
- describe('onSearch', () => {
- const testSearch = 'test search';
-
- beforeEach(() => {
- findGlSearchBox().vm.$emit('input', testSearch);
- });
-
- it('calls setQuery when input event is fired from GlSearchBoxByType', () => {
- expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
- key: 'search',
- value: testSearch,
- });
- });
- });
});
describe.each`
@@ -92,10 +71,6 @@ describe('GlobalSearchTopbar', () => {
expect(findProjectFilter().exists()).toBe(showFilters);
});
});
-
- it('renders SearchButton always', () => {
- expect(findSearchButton().exists()).toBe(true);
- });
});
describe('actions', () => {
@@ -103,8 +78,8 @@ describe('GlobalSearchTopbar', () => {
createComponent();
});
- it('clicking SearchButton calls applyQuery', () => {
- findTopbarForm().vm.$emit('submit', { preventDefault: () => {} });
+ it('clicking search button inside search box calls applyQuery', () => {
+ findGlSearchBox().vm.$emit('submit', { preventDefault: () => {} });
expect(actionSpies.applyQuery).toHaveBeenCalled();
});
diff --git a/spec/frontend/security_configuration/components/training_provider_list_spec.js b/spec/frontend/security_configuration/components/training_provider_list_spec.js
index 08ba4bcbf69..5e2efa2425c 100644
--- a/spec/frontend/security_configuration/components/training_provider_list_spec.js
+++ b/spec/frontend/security_configuration/components/training_provider_list_spec.js
@@ -26,6 +26,7 @@ import {
updateSecurityTrainingProvidersErrorResponse,
testProjectPath,
testProviderIds,
+ testProviderName,
tempProviderLogos,
} from '../mock_data';
@@ -207,9 +208,13 @@ describe('TrainingProviderList component', () => {
expect(findLogos().at(provider).attributes('width')).toBe('18');
});
+ it.each(providerIndexArray)('has a11y decorative attribute for provider %s', (provider) => {
+ expect(findLogos().at(provider).attributes('role')).toBe('presentation');
+ });
+
it.each(providerIndexArray)('displays the correct svg path for provider %s', (provider) => {
expect(findLogos().at(provider).attributes('src')).toBe(
- tempProviderLogos[testProviderIds[provider]].svg,
+ tempProviderLogos[testProviderName[provider]].svg,
);
});
});
diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js
index 588fac11987..3bad687740c 100644
--- a/spec/frontend/security_configuration/mock_data.js
+++ b/spec/frontend/security_configuration/mock_data.js
@@ -1,11 +1,11 @@
export const testProjectPath = 'foo/bar';
-
export const testProviderIds = [101, 102, 103];
+export const testProviderName = ['Vendor Name 1', 'Vendor Name 2', 'Vendor Name 3'];
const createSecurityTrainingProviders = ({ providerOverrides = {} }) => [
{
id: testProviderIds[0],
- name: 'Vendor Name 1',
+ name: testProviderName[0],
description: 'Interactive developer security education',
url: 'https://www.example.org/security/training',
isEnabled: false,
@@ -14,7 +14,7 @@ const createSecurityTrainingProviders = ({ providerOverrides = {} }) => [
},
{
id: testProviderIds[1],
- name: 'Vendor Name 2',
+ name: testProviderName[1],
description: 'Security training with guide and learning pathways.',
url: 'https://www.vendornametwo.com/',
isEnabled: false,
@@ -23,7 +23,7 @@ const createSecurityTrainingProviders = ({ providerOverrides = {} }) => [
},
{
id: testProviderIds[2],
- name: 'Vendor Name 3',
+ name: testProviderName[2],
description: 'Security training for the everyday developer.',
url: 'https://www.vendornamethree.com/',
isEnabled: false,
@@ -99,10 +99,10 @@ export const updateSecurityTrainingProvidersErrorResponse = {
// Will remove once this issue is resolved where the svg path will be available in the GraphQL query
// https://gitlab.com/gitlab-org/gitlab/-/issues/346899
export const tempProviderLogos = {
- [testProviderIds[0]]: {
+ [testProviderName[0]]: {
svg: '/assets/illustrations/vulnerability/vendor-1.svg',
},
- [testProviderIds[1]]: {
+ [testProviderName[1]]: {
svg: '/assets/illustrations/vulnerability/vendor-2.svg',
},
};
diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
index 2c3fc70e116..64ce210b6c8 100644
--- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
@@ -1,12 +1,13 @@
import { shallowMount } from '@vue/test-utils';
+import { GlAvatar, GlTooltip } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '~/lazy_loader';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
jest.mock('images/no_avatar.png', () => 'default-avatar-url');
-const DEFAULT_PROPS = {
- size: 99,
+const PROVIDED_PROPS = {
+ size: 32,
imgSrc: 'myavatarurl.com',
imgAlt: 'mydisplayname',
cssClasses: 'myextraavatarclass',
@@ -14,6 +15,10 @@ const DEFAULT_PROPS = {
tooltipPlacement: 'bottom',
};
+const DEFAULT_PROPS = {
+ size: 20,
+};
+
describe('User Avatar Image Component', () => {
let wrapper;
@@ -21,64 +26,149 @@ describe('User Avatar Image Component', () => {
wrapper.destroy();
});
- describe('Initialization', () => {
- beforeEach(() => {
- wrapper = shallowMount(UserAvatarImage, {
- propsData: {
- ...DEFAULT_PROPS,
- },
+ describe('`glAvatarForAllUserAvatars` feature flag enabled', () => {
+ describe('Initialization', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage, {
+ propsData: {
+ ...PROVIDED_PROPS,
+ },
+ provide: {
+ glFeatures: {
+ glAvatarForAllUserAvatars: true,
+ },
+ },
+ });
+ });
+
+ it('should render `GlAvatar` and provide correct properties to it', () => {
+ const avatar = wrapper.findComponent(GlAvatar);
+
+ expect(avatar.attributes('data-src')).toBe(
+ `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ );
+ expect(avatar.props()).toMatchObject({
+ src: `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ alt: PROVIDED_PROPS.imgAlt,
+ });
+ });
+
+ it('should add correct CSS classes', () => {
+ const classes = wrapper.findComponent(GlAvatar).classes();
+ expect(classes).toContain(PROVIDED_PROPS.cssClasses);
+ expect(classes).not.toContain('lazy');
});
});
- it('should have <img> as a child element', () => {
- const imageElement = wrapper.find('img');
+ describe('Initialization when lazy', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage, {
+ propsData: {
+ ...PROVIDED_PROPS,
+ lazy: true,
+ },
+ provide: {
+ glFeatures: {
+ glAvatarForAllUserAvatars: true,
+ },
+ },
+ });
+ });
+
+ it('should add lazy attributes', () => {
+ const avatar = wrapper.findComponent(GlAvatar);
- expect(imageElement.exists()).toBe(true);
- expect(imageElement.attributes('src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`);
- expect(imageElement.attributes('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`);
- expect(imageElement.attributes('alt')).toBe(DEFAULT_PROPS.imgAlt);
+ expect(avatar.classes()).toContain('lazy');
+ expect(avatar.attributes()).toMatchObject({
+ src: placeholderImage,
+ 'data-src': `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ });
+ });
});
- it('should properly render img css', () => {
- const classes = wrapper.find('img').classes();
- expect(classes).toEqual(expect.arrayContaining(['avatar', 's99', DEFAULT_PROPS.cssClasses]));
- expect(classes).not.toContain('lazy');
+ describe('Initialization without src', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage);
+ });
+
+ it('should have default avatar image', () => {
+ const imageElement = wrapper.find('img');
+
+ expect(imageElement.attributes('src')).toBe(
+ `${defaultAvatarUrl}?width=${DEFAULT_PROPS.size}`,
+ );
+ });
});
});
- describe('Initialization when lazy', () => {
- beforeEach(() => {
- wrapper = shallowMount(UserAvatarImage, {
- propsData: {
- ...DEFAULT_PROPS,
- lazy: true,
- },
+ describe('`glAvatarForAllUserAvatars` feature flag disabled', () => {
+ describe('Initialization', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage, {
+ propsData: {
+ ...PROVIDED_PROPS,
+ },
+ });
});
- });
- it('should add lazy attributes', () => {
- const imageElement = wrapper.find('img');
+ it('should have <img> as a child element', () => {
+ const imageElement = wrapper.find('img');
+
+ expect(imageElement.exists()).toBe(true);
+ expect(imageElement.attributes('src')).toBe(
+ `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ );
+ expect(imageElement.attributes('data-src')).toBe(
+ `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ );
+ expect(imageElement.attributes('alt')).toBe(PROVIDED_PROPS.imgAlt);
+ });
- expect(imageElement.classes()).toContain('lazy');
- expect(imageElement.attributes('src')).toBe(placeholderImage);
- expect(imageElement.attributes('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`);
+ it('should properly render img css', () => {
+ const classes = wrapper.find('img').classes();
+ expect(classes).toEqual(['avatar', 's32', PROVIDED_PROPS.cssClasses]);
+ expect(classes).not.toContain('lazy');
+ });
});
- });
- describe('Initialization without src', () => {
- beforeEach(() => {
- wrapper = shallowMount(UserAvatarImage);
+ describe('Initialization when lazy', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage, {
+ propsData: {
+ ...PROVIDED_PROPS,
+ lazy: true,
+ },
+ });
+ });
+
+ it('should add lazy attributes', () => {
+ const imageElement = wrapper.find('img');
+
+ expect(imageElement.classes()).toContain('lazy');
+ expect(imageElement.attributes('src')).toBe(placeholderImage);
+ expect(imageElement.attributes('data-src')).toBe(
+ `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ );
+ });
});
- it('should have default avatar image', () => {
- const imageElement = wrapper.find('img');
+ describe('Initialization without src', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage);
+ });
+
+ it('should have default avatar image', () => {
+ const imageElement = wrapper.find('img');
- expect(imageElement.attributes('src')).toBe(`${defaultAvatarUrl}?width=20`);
+ expect(imageElement.attributes('src')).toBe(
+ `${defaultAvatarUrl}?width=${DEFAULT_PROPS.size}`,
+ );
+ });
});
});
describe('dynamic tooltip content', () => {
- const props = DEFAULT_PROPS;
+ const props = PROVIDED_PROPS;
const slots = {
default: ['Action!'],
};
@@ -91,11 +181,11 @@ describe('User Avatar Image Component', () => {
});
it('renders the tooltip slot', () => {
- expect(wrapper.find('.js-user-avatar-image-tooltip').exists()).toBe(true);
+ expect(wrapper.findComponent(GlTooltip).exists()).toBe(true);
});
it('renders the tooltip content', () => {
- expect(wrapper.find('.js-user-avatar-image-tooltip').text()).toContain(slots.default[0]);
+ expect(wrapper.findComponent(GlTooltip).text()).toContain(slots.default[0]);
});
it('does not render tooltip data attributes for on avatar image', () => {
diff --git a/spec/lib/api/entities/ci/job_artifact_file_spec.rb b/spec/lib/api/entities/ci/job_artifact_file_spec.rb
new file mode 100644
index 00000000000..9e4ec272518
--- /dev/null
+++ b/spec/lib/api/entities/ci/job_artifact_file_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Ci::JobArtifactFile do
+ let(:artifact_file) { instance_double(JobArtifactUploader, filename: 'ci_build_artifacts.zip', cached_size: 42) }
+ let(:entity) { described_class.new(artifact_file) }
+
+ subject { entity.as_json }
+
+ it 'returns the filename' do
+ expect(subject[:filename]).to eq('ci_build_artifacts.zip')
+ end
+
+ it 'returns the size' do
+ expect(subject[:size]).to eq(42)
+ end
+end
diff --git a/spec/lib/api/entities/ci/job_request/dependency_spec.rb b/spec/lib/api/entities/ci/job_request/dependency_spec.rb
new file mode 100644
index 00000000000..fa5f3da554c
--- /dev/null
+++ b/spec/lib/api/entities/ci/job_request/dependency_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Ci::JobRequest::Dependency do
+ let(:job) { create(:ci_build, :artifacts) }
+ let(:entity) { described_class.new(job) }
+
+ subject { entity.as_json }
+
+ it 'returns the dependency id' do
+ expect(subject[:id]).to eq(job.id)
+ end
+
+ it 'returns the dependency name' do
+ expect(subject[:name]).to eq(job.name)
+ end
+
+ it 'returns the dependency token' do
+ expect(subject[:token]).to eq(job.token)
+ end
+
+ it 'returns the dependency artifacts_file', :aggregate_failures do
+ expect(subject[:artifacts_file][:filename]).to eq('ci_build_artifacts.zip')
+ expect(subject[:artifacts_file][:size]).to eq(job.artifacts_size)
+ end
+end
diff --git a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
index 9acc7fd04be..33d322d0d44 100644
--- a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
@@ -2,9 +2,9 @@
require 'spec_helper'
-RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
+RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor, :sentry do
describe '.call' do
- let(:required_options) do
+ let(:raven_required_options) do
{
configuration: Raven.configuration,
context: Raven.context,
@@ -12,7 +12,15 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
}
end
- let(:event) { Raven::Event.from_exception(exception, required_options.merge(data)) }
+ let(:raven_event) do
+ Raven::Event
+ .from_exception(exception, raven_required_options.merge(data))
+ end
+
+ let(:sentry_event) do
+ Sentry.get_current_client.event_from_exception(exception)
+ end
+
let(:result_hash) { described_class.call(event).to_hash }
let(:data) do
@@ -27,36 +35,43 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
}
end
+ before do
+ Sentry.get_current_scope.update_from_options(**data)
+ Sentry.get_current_scope.apply_to_event(sentry_event)
+ end
+
+ after do
+ Sentry.get_current_scope.clear
+ end
+
context 'when there is no GRPC exception' do
let(:exception) { RuntimeError.new }
let(:data) { { fingerprint: ['ArgumentError', 'Missing arguments'] } }
- it 'leaves data unchanged' do
- expect(result_hash).to include(data)
+ shared_examples 'leaves data unchanged' do
+ it { expect(result_hash).to include(data) }
end
- end
- context 'when there is a GRPC exception with a debug string' do
- let(:exception) { GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}') }
+ context 'with Raven event' do
+ let(:event) { raven_event }
- it 'removes the debug error string and stores it as an extra field' do
- expect(result_hash[:fingerprint])
- .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
+ it_behaves_like 'leaves data unchanged'
+ end
- expect(result_hash[:exception][:values].first)
- .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+ context 'with Sentry event' do
+ let(:event) { sentry_event }
- expect(result_hash[:extra])
- .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
+ it_behaves_like 'leaves data unchanged'
end
+ end
- context 'with no custom fingerprint' do
- let(:data) do
- { extra: { caller: 'test' } }
- end
+ context 'when there is a GRPC exception with a debug string' do
+ let(:exception) { GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}') }
+ shared_examples 'processes the exception' do
it 'removes the debug error string and stores it as an extra field' do
- expect(result_hash).not_to include(:fingerprint)
+ expect(result_hash[:fingerprint])
+ .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
expect(result_hash[:exception][:values].first)
.to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
@@ -64,11 +79,42 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
expect(result_hash[:extra])
.to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
end
+
+ context 'with no custom fingerprint' do
+ let(:data) do
+ { extra: { caller: 'test' } }
+ end
+
+ it 'removes the debug error string and stores it as an extra field' do
+ expect(result_hash[:fingerprint]).to be_blank
+
+ expect(result_hash[:exception][:values].first)
+ .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:extra])
+ .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
+ end
+ end
+ end
+
+ context 'with Raven event' do
+ let(:event) { raven_event }
+
+ it_behaves_like 'processes the exception'
+ end
+
+ context 'with Sentry event' do
+ let(:event) { sentry_event }
+
+ it_behaves_like 'processes the exception'
end
end
context 'when there is a wrapped GRPC exception with a debug string' do
- let(:inner_exception) { GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}') }
+ let(:inner_exception) do
+ GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}')
+ end
+
let(:exception) do
begin
raise inner_exception
@@ -79,27 +125,10 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
e
end
- it 'removes the debug error string and stores it as an extra field' do
- expect(result_hash[:fingerprint])
- .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
-
- expect(result_hash[:exception][:values].first)
- .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
-
- expect(result_hash[:exception][:values].second)
- .to include(type: 'StandardError', value: '4:Deadline Exceeded.')
-
- expect(result_hash[:extra])
- .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
- end
-
- context 'with no custom fingerprint' do
- let(:data) do
- { extra: { caller: 'test' } }
- end
-
+ shared_examples 'processes the exception' do
it 'removes the debug error string and stores it as an extra field' do
- expect(result_hash).not_to include(:fingerprint)
+ expect(result_hash[:fingerprint])
+ .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
expect(result_hash[:exception][:values].first)
.to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
@@ -110,6 +139,37 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
expect(result_hash[:extra])
.to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
end
+
+ context 'with no custom fingerprint' do
+ let(:data) do
+ { extra: { caller: 'test' } }
+ end
+
+ it 'removes the debug error string and stores it as an extra field' do
+ expect(result_hash[:fingerprint]).to be_blank
+
+ expect(result_hash[:exception][:values].first)
+ .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:exception][:values].second)
+ .to include(type: 'StandardError', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:extra])
+ .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
+ end
+ end
+ end
+
+ context 'with Raven event' do
+ let(:event) { raven_event }
+
+ it_behaves_like 'processes the exception'
+ end
+
+ context 'with Sentry event' do
+ let(:event) { sentry_event }
+
+ it_behaves_like 'processes the exception'
end
end
end
diff --git a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
index 3febc10831a..d33f8393904 100644
--- a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rspec-parameterized'
-RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
+RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor, :sentry do
after do
if described_class.instance_variable_defined?(:@permitted_arguments_for_worker)
described_class.remove_instance_variable(:@permitted_arguments_for_worker)
@@ -95,7 +95,9 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
end
describe '.call' do
- let(:required_options) do
+ let(:exception) { StandardError.new('Test exception') }
+
+ let(:raven_required_options) do
{
configuration: Raven.configuration,
context: Raven.context,
@@ -103,9 +105,25 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
}
end
- let(:event) { Raven::Event.new(required_options.merge(wrapped_value)) }
+ let(:raven_event) do
+ Raven::Event.new(raven_required_options.merge(wrapped_value))
+ end
+
+ let(:sentry_event) do
+ Sentry.get_current_client.event_from_exception(exception)
+ end
+
let(:result_hash) { described_class.call(event).to_hash }
+ before do
+ Sentry.get_current_scope.update_from_options(**wrapped_value)
+ Sentry.get_current_scope.apply_to_event(sentry_event)
+ end
+
+ after do
+ Sentry.get_current_scope.clear
+ end
+
context 'when there is Sidekiq data' do
let(:wrapped_value) { { extra: { sidekiq: value } } }
@@ -140,42 +158,90 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
end
context 'when processing via the default error handler' do
- include_examples 'Sidekiq arguments', args_in_job_hash: true
+ context 'with Raven events' do
+ let(:event) { raven_event}
+
+ include_examples 'Sidekiq arguments', args_in_job_hash: true
+ end
+
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ include_examples 'Sidekiq arguments', args_in_job_hash: true
+ end
end
context 'when processing via Gitlab::ErrorTracking' do
- include_examples 'Sidekiq arguments', args_in_job_hash: false
- end
+ context 'with Raven events' do
+ let(:event) { raven_event}
- context 'when a jobstr field is present' do
- let(:value) do
- {
- job: { 'args' => [1] },
- jobstr: { 'args' => [1] }.to_json
- }
+ include_examples 'Sidekiq arguments', args_in_job_hash: false
end
- it 'removes the jobstr' do
- expect(result_hash.dig(:extra, :sidekiq)).to eq(value.except(:jobstr))
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ include_examples 'Sidekiq arguments', args_in_job_hash: false
end
end
- context 'when no jobstr value is present' do
- let(:value) { { job: { 'args' => [1] } } }
+ shared_examples 'handles jobstr fields' do
+ context 'when a jobstr field is present' do
+ let(:value) do
+ {
+ job: { 'args' => [1] },
+ jobstr: { 'args' => [1] }.to_json
+ }
+ end
+
+ it 'removes the jobstr' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value.except(:jobstr))
+ end
+ end
+
+ context 'when no jobstr value is present' do
+ let(:value) { { job: { 'args' => [1] } } }
- it 'does nothing' do
- expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ it 'does nothing' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ end
end
end
+
+ context 'with Raven events' do
+ let(:event) { raven_event}
+
+ it_behaves_like 'handles jobstr fields'
+ end
+
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ it_behaves_like 'handles jobstr fields'
+ end
end
context 'when there is no Sidekiq data' do
let(:value) { { tags: { foo: 'bar', baz: 'quux' } } }
let(:wrapped_value) { value }
- it 'does nothing' do
- expect(result_hash).to include(value)
- expect(result_hash.dig(:extra, :sidekiq)).to be_nil
+ shared_examples 'does nothing' do
+ it 'does nothing' do
+ expect(result_hash).to include(value)
+ expect(result_hash.dig(:extra, :sidekiq)).to be_nil
+ end
+ end
+
+ context 'with Raven events' do
+ let(:event) { raven_event}
+
+ it_behaves_like 'does nothing'
+ end
+
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ it_behaves_like 'does nothing'
end
end
@@ -183,8 +249,22 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
let(:value) { { other: 'foo' } }
let(:wrapped_value) { { extra: { sidekiq: value } } }
- it 'does nothing' do
- expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ shared_examples 'does nothing' do
+ it 'does nothing' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ end
+ end
+
+ context 'with Raven events' do
+ let(:event) { raven_event}
+
+ it_behaves_like 'does nothing'
+ end
+
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ it_behaves_like 'does nothing'
end
end
end
diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb
index a5d44963f4b..936954fc1b6 100644
--- a/spec/lib/gitlab/error_tracking_spec.rb
+++ b/spec/lib/gitlab/error_tracking_spec.rb
@@ -3,13 +3,14 @@
require 'spec_helper'
require 'raven/transports/dummy'
+require 'sentry/transport/dummy_transport'
RSpec.describe Gitlab::ErrorTracking do
let(:exception) { RuntimeError.new('boom') }
let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' }
let(:extra) { { issue_url: issue_url, some_other_info: 'info' } }
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
let(:sentry_payload) do
{
@@ -43,17 +44,28 @@ RSpec.describe Gitlab::ErrorTracking do
}
end
- let(:sentry_event) { Gitlab::Json.parse(Raven.client.transport.events.last[1]) }
+ let(:raven_event) do
+ event = Raven.client.transport.events.last[1]
+ Gitlab::Json.parse(event)
+ end
+
+ let(:sentry_event) do
+ Sentry.get_current_client.transport.events.last
+ end
before do
+ stub_feature_flags(enable_old_sentry_integration: true)
+ stub_feature_flags(enable_new_sentry_integration: true)
stub_sentry_settings
- allow(described_class).to receive(:sentry_dsn).and_return(Gitlab.config.sentry.dsn)
+ allow(described_class).to receive(:sentry_configurable?) { true }
+
allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid')
allow(I18n).to receive(:locale).and_return('en')
described_class.configure do |config|
- config.encoding = 'json'
+ config.encoding = 'json' if config.respond_to?(:encoding=)
+ config.transport.transport_class = Sentry::DummyTransport if config.respond_to?(:transport)
end
end
@@ -63,6 +75,10 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
+ after do
+ Sentry.get_current_scope.clear
+ end
+
describe '.track_and_raise_for_dev_exception' do
context 'when exceptions for dev should be raised' do
before do
@@ -71,6 +87,7 @@ RSpec.describe Gitlab::ErrorTracking do
it 'raises the exception' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
+ expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
expect do
described_class.track_and_raise_for_dev_exception(
@@ -89,6 +106,7 @@ RSpec.describe Gitlab::ErrorTracking do
it 'logs the exception with all attributes passed' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
+ expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
described_class.track_and_raise_for_dev_exception(
exception,
@@ -112,6 +130,7 @@ RSpec.describe Gitlab::ErrorTracking do
describe '.track_and_raise_exception' do
it 'always raises the exception' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
+ expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
expect do
described_class.track_and_raise_for_dev_exception(
@@ -136,20 +155,24 @@ RSpec.describe Gitlab::ErrorTracking do
end
describe '.track_exception' do
- subject(:track_exception) { described_class.track_exception(exception, extra) }
+ subject(:track_exception) do
+ described_class.track_exception(exception, extra)
+ end
before do
allow(Raven).to receive(:capture_exception).and_call_original
+ allow(Sentry).to receive(:capture_exception).and_call_original
allow(Gitlab::ErrorTracking::Logger).to receive(:error)
end
it 'calls Raven.capture_exception' do
track_exception
- expect(Raven).to have_received(:capture_exception).with(
- exception,
- sentry_payload
- )
+ expect(Raven)
+ .to have_received(:capture_exception).with(exception, sentry_payload)
+
+ expect(Sentry)
+ .to have_received(:capture_exception).with(exception, sentry_payload)
end
it 'calls Gitlab::ErrorTracking::Logger.error with formatted payload' do
@@ -172,7 +195,10 @@ RSpec.describe Gitlab::ErrorTracking do
context 'the exception implements :sentry_extra_data' do
let(:extra_info) { { event: 'explosion', size: :massive } }
- let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info, backtrace: caller, cause: nil) }
+
+ before do
+ allow(exception).to receive(:sentry_extra_data).and_return(extra_info)
+ end
it 'includes the extra data from the exception in the tracking information' do
track_exception
@@ -180,29 +206,30 @@ RSpec.describe Gitlab::ErrorTracking do
expect(Raven).to have_received(:capture_exception).with(
exception, a_hash_including(extra: a_hash_including(extra_info))
)
+
+ expect(Sentry).to have_received(:capture_exception).with(
+ exception, a_hash_including(extra: a_hash_including(extra_info))
+ )
end
end
context 'the exception implements :sentry_extra_data, which returns nil' do
- let(:exception) { double(message: 'bang!', sentry_extra_data: nil, backtrace: caller, cause: nil) }
let(:extra) { { issue_url: issue_url } }
+ before do
+ allow(exception).to receive(:sentry_extra_data).and_return(nil)
+ end
+
it 'just includes the other extra info' do
track_exception
expect(Raven).to have_received(:capture_exception).with(
exception, a_hash_including(extra: a_hash_including(extra))
)
- end
- end
-
- context 'when the error is kind of an `ActiveRecord::StatementInvalid`' do
- let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }
- it 'injects the normalized sql query into extra' do
- track_exception
-
- expect(sentry_event.dig('extra', 'sql')).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
+ expect(Sentry).to have_received(:capture_exception).with(
+ exception, a_hash_including(extra: a_hash_including(extra))
+ )
end
end
end
@@ -212,32 +239,65 @@ RSpec.describe Gitlab::ErrorTracking do
before do
allow(Raven).to receive(:capture_exception).and_call_original
+ allow(Sentry).to receive(:capture_exception).and_call_original
allow(Gitlab::ErrorTracking::Logger).to receive(:error)
end
context 'custom GitLab context when using Raven.capture_exception directly' do
- subject(:raven_capture_exception) { Raven.capture_exception(exception) }
+ subject(:track_exception) { Raven.capture_exception(exception) }
it 'merges a default set of tags into the existing tags' do
allow(Raven.context).to receive(:tags).and_return(foo: 'bar')
- raven_capture_exception
+ track_exception
- expect(sentry_event['tags']).to include('correlation_id', 'feature_category', 'foo', 'locale', 'program')
+ expect(raven_event['tags']).to include('correlation_id', 'feature_category', 'foo', 'locale', 'program')
end
it 'merges the current user information into the existing user information' do
Raven.user_context(id: -1)
- raven_capture_exception
+ track_exception
- expect(sentry_event['user']).to eq('id' => -1, 'username' => user.username)
+ expect(raven_event['user']).to eq('id' => -1, 'username' => user.username)
+ end
+ end
+
+ context 'custom GitLab context when using Sentry.capture_exception directly' do
+ subject(:track_exception) { Sentry.capture_exception(exception) }
+
+ it 'merges a default set of tags into the existing tags' do
+ Sentry.set_tags(foo: 'bar')
+
+ track_exception
+
+ expect(sentry_event.tags).to include(:correlation_id, :feature_category, :foo, :locale, :program)
+ end
+
+ it 'merges the current user information into the existing user information' do
+ Sentry.set_user(id: -1)
+
+ track_exception
+
+ expect(sentry_event.user).to eq(id: -1, username: user.username)
end
end
context 'with sidekiq args' do
context 'when the args does not have anything sensitive' do
- let(:extra) { { sidekiq: { 'class' => 'PostReceive', 'args' => [1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'] } } }
+ let(:extra) do
+ {
+ sidekiq: {
+ 'class' => 'PostReceive',
+ 'args' => [
+ 1,
+ { 'id' => 2, 'name' => 'hello' },
+ 'some-value',
+ 'another-value'
+ ]
+ }
+ }
+ end
it 'ensures extra.sidekiq.args is a string' do
track_exception
@@ -254,8 +314,10 @@ RSpec.describe Gitlab::ErrorTracking do
it 'does not filter parameters when sending to Sentry' do
track_exception
+ expected_data = [1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value']
- expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq([1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'])
+ expect(raven_event.dig('extra', 'sidekiq', 'args')).to eq(expected_data)
+ expect(sentry_event.extra[:sidekiq]['args']).to eq(expected_data)
end
end
@@ -265,7 +327,8 @@ RSpec.describe Gitlab::ErrorTracking do
it 'filters sensitive arguments before sending and logging' do
track_exception
- expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2])
+ expect(raven_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2])
+ expect(sentry_event.extra[:sidekiq]['args']).to eq(['[FILTERED]', 1, 2])
expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with(
hash_including(
'extra.sidekiq' => {
@@ -285,8 +348,10 @@ RSpec.describe Gitlab::ErrorTracking do
it 'sets the GRPC debug error string in the Sentry event and adds a custom fingerprint' do
track_exception
- expect(sentry_event.dig('extra', 'grpc_debug_error_string')).to eq('{"hello":1}')
- expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
+ expect(raven_event.dig('extra', 'grpc_debug_error_string')).to eq('{"hello":1}')
+ expect(raven_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
+ expect(sentry_event.extra[:grpc_debug_error_string]).to eq('{"hello":1}')
+ expect(sentry_event.fingerprint).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
end
end
@@ -296,8 +361,10 @@ RSpec.describe Gitlab::ErrorTracking do
it 'does not do any processing on the event' do
track_exception
- expect(sentry_event['extra']).not_to include('grpc_debug_error_string')
- expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
+ expect(raven_event['extra']).not_to include('grpc_debug_error_string')
+ expect(raven_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
+ expect(sentry_event.extra).not_to include(:grpc_debug_error_string)
+ expect(sentry_event.fingerprint).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
end
end
end
diff --git a/spec/lib/gitlab/fips_spec.rb b/spec/lib/gitlab/fips_spec.rb
index 2ede2e3adf3..4d19a44f617 100644
--- a/spec/lib/gitlab/fips_spec.rb
+++ b/spec/lib/gitlab/fips_spec.rb
@@ -6,16 +6,46 @@ RSpec.describe Gitlab::FIPS do
describe ".enabled?" do
subject { described_class.enabled? }
- context "feature flag is enabled" do
- it { is_expected.to be_truthy }
+ let(:openssl_fips_mode) { false }
+ let(:fips_mode_env_var) { nil }
+
+ before do
+ expect(OpenSSL).to receive(:fips_mode).and_return(openssl_fips_mode)
+ stub_env("FIPS_MODE", fips_mode_env_var)
+ end
+
+ describe "OpenSSL auto-detection" do
+ context "OpenSSL is in FIPS mode" do
+ let(:openssl_fips_mode) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context "OpenSSL is not in FIPS mode" do
+ let(:openssl_fips_mode) { false }
+
+ it { is_expected.to be_falsey }
+ end
end
- context "feature flag is disabled" do
- before do
- stub_feature_flags(fips_mode: false)
+ describe "manual configuration via env var" do
+ context "env var is not set" do
+ let(:fips_mode_env_var) { nil }
+
+ it { is_expected.to be_falsey }
end
- it { is_expected.to be_falsey }
+ context "env var is set to true" do
+ let(:fips_mode_env_var) { "true" }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context "env var is set to false" do
+ let(:fips_mode_env_var) { "false" }
+
+ it { is_expected.to be_falsey }
+ end
end
end
end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index e27fee9286b..c8d86edc55f 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -1255,7 +1255,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
subject { described_class.ready_for_import }
before do
- stub_application_setting(container_registry_import_target_plan: project.namespace.actual_plan_name)
+ stub_application_setting(container_registry_import_target_plan: root_group.actual_plan_name)
end
it 'works' do
diff --git a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
index 0e7230c042e..67d664a617b 100644
--- a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
+++ b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
@@ -102,5 +102,81 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
is_expected.to eq(destroyed_artifacts_count: 0, statistics_updates: {}, status: :success)
end
end
+
+ context 'with artifacts that has backfilled expire_at' do
+ let!(:created_on_00_30_45_minutes_on_21_22_23) do
+ [
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-21 00:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-21 01:30:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-22 12:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-22 12:30:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-23 23:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-23 23:30:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-23 06:45:00.000'))
+ ]
+ end
+
+ let!(:created_close_to_00_or_30_minutes) do
+ [
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-21 00:00:00.001')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-21 00:30:00.999'))
+ ]
+ end
+
+ let!(:created_on_00_or_30_minutes_on_other_dates) do
+ [
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-01 00:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-19 12:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-24 23:30:00.000'))
+ ]
+ end
+
+ let!(:created_at_other_times) do
+ [
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-19 00:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-19 00:30:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-24 00:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-24 00:30:00.000'))
+ ]
+ end
+
+ let(:artifacts_to_keep) { created_on_00_30_45_minutes_on_21_22_23 }
+ let(:artifacts_to_delete) { created_close_to_00_or_30_minutes + created_on_00_or_30_minutes_on_other_dates + created_at_other_times }
+ let(:all_artifacts) { artifacts_to_keep + artifacts_to_delete }
+
+ let(:artifacts) { Ci::JobArtifact.where(id: all_artifacts.map(&:id)) }
+
+ it 'deletes job artifacts that do not have expire_at on 00, 30 or 45 minute of 21, 22, 23 of the month' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(artifacts_to_delete.size * -1)
+ end
+
+ it 'keeps job artifacts that have expire_at on 00, 30 or 45 minute of 21, 22, 23 of the month' do
+ expect { subject }.not_to change { Ci::JobArtifact.where(id: artifacts_to_keep.map(&:id)).count }
+ end
+
+ it 'removes expire_at on job artifacts that have expire_at on 00, 30 or 45 minute of 21, 22, 23 of the month' do
+ subject
+
+ expect(artifacts_to_keep.all? { |artifact| artifact.reload.expire_at.nil? }).to be(true)
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_detect_wrongly_expired_artifacts: false)
+ end
+
+ it 'deletes all job artifacts' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(all_artifacts.size * -1)
+ end
+ end
+
+ context 'when fix_expire_at is false' do
+ let(:service) { described_class.new(artifacts, pick_up_at: Time.current, fix_expire_at: false) }
+
+ it 'deletes all job artifacts' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(all_artifacts.size * -1)
+ end
+ end
+ end
end
end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 8c60dc30cdb..20f46396424 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -90,10 +90,18 @@ module StubConfiguration
allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages))
end
- def stub_sentry_settings
- allow(Gitlab.config.sentry).to receive(:enabled).and_return(true)
- allow(Gitlab.config.sentry).to receive(:dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42')
- allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/43')
+ def stub_sentry_settings(enabled: true)
+ allow(Gitlab.config.sentry).to receive(:enabled) { enabled }
+ allow(Gitlab::CurrentSettings).to receive(:sentry_enabled?) { enabled }
+
+ dsn = 'dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42'
+ allow(Gitlab.config.sentry).to receive(:dsn) { dsn }
+ allow(Gitlab::CurrentSettings).to receive(:sentry_dsn) { dsn }
+
+ clientside_dsn = 'dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/43'
+ allow(Gitlab.config.sentry).to receive(:clientside_dsn) { clientside_dsn }
+ allow(Gitlab::CurrentSettings)
+ .to receive(:sentry_clientside_dsn) { clientside_dsn }
end
def stub_kerberos_setting(messages)
diff --git a/spec/support/sentry.rb b/spec/support/sentry.rb
new file mode 100644
index 00000000000..c439b6c0fd9
--- /dev/null
+++ b/spec/support/sentry.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ config.around(:example, :sentry) do |example|
+ dsn = Sentry.get_current_client.configuration.dsn
+ Sentry.get_current_client.configuration.dsn = 'dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42'
+ begin
+ example.run
+ ensure
+ Sentry.get_current_client.configuration.dsn = dsn.to_s.presence
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/container_repositories_shared_context.rb b/spec/support/shared_contexts/container_repositories_shared_context.rb
index 7f61631dce0..9a9f80a3cbd 100644
--- a/spec/support/shared_contexts/container_repositories_shared_context.rb
+++ b/spec/support/shared_contexts/container_repositories_shared_context.rb
@@ -1,13 +1,16 @@
# frozen_string_literal: true
RSpec.shared_context 'importable repositories' do
- let_it_be(:project) { create(:project) }
+ let_it_be(:root_group) { create(:group) }
+ let_it_be(:group) { create(:group, parent_id: root_group.id) }
+ let_it_be(:project) { create(:project, namespace: group) }
let_it_be(:valid_container_repository) { create(:container_repository, project: project, created_at: 2.days.ago) }
let_it_be(:valid_container_repository2) { create(:container_repository, project: project, created_at: 1.year.ago) }
let_it_be(:importing_container_repository) { create(:container_repository, :importing, project: project, created_at: 2.days.ago) }
let_it_be(:new_container_repository) { create(:container_repository, project: project) }
- let_it_be(:denied_group) { create(:group) }
+ let_it_be(:denied_root_group) { create(:group) }
+ let_it_be(:denied_group) { create(:group, parent_id: denied_root_group.id) }
let_it_be(:denied_project) { create(:project, group: denied_group) }
let_it_be(:denied_container_repository) { create(:container_repository, project: denied_project, created_at: 2.days.ago) }
@@ -21,7 +24,7 @@ RSpec.shared_context 'importable repositories' do
Feature::FlipperGate.create!(
feature_key: 'container_registry_phase_2_deny_list',
key: 'actors',
- value: "Group:#{denied_group.id}"
+ value: "Group:#{denied_root_group.id}"
)
end
end
diff --git a/spec/views/projects/empty.html.haml_spec.rb b/spec/views/projects/empty.html.haml_spec.rb
index 416dfc10174..6077dda3c98 100644
--- a/spec/views/projects/empty.html.haml_spec.rb
+++ b/spec/views/projects/empty.html.haml_spec.rb
@@ -25,6 +25,21 @@ RSpec.describe 'projects/empty' do
expect(rendered).to have_content("git clone")
end
+
+ context 'when default branch name contains special shell characters' do
+ let(:branch_name) { ';rm -rf /' }
+
+ before do
+ allow(project).to receive(:default_branch_or_main).and_return(branch_name)
+ end
+
+ it 'escapes the default branch name' do
+ render
+
+ expect(rendered).not_to have_content(branch_name)
+ expect(rendered).to have_content(branch_name.shellescape)
+ end
+ end
end
context 'when user can not push code on the project' do