Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-23 09:10:20 +00:00
parent 22622fab4a
commit b563a5209a
31 changed files with 673 additions and 155 deletions

View File

@ -0,0 +1,35 @@
<script>
import { GlEmptyState } from '@gitlab/ui';
import { mapState } from 'vuex';
import { s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export default {
components: {
GlEmptyState,
},
computed: {
...mapState(['pipelinesEmptyStateSvgPath']),
ciHelpPagePath() {
return helpPagePath('ci/quick_start/index.md');
},
},
i18n: {
title: s__('Pipelines|Build with confidence'),
description: s__(`Pipelines|GitLab CI/CD can automatically build,
test, and deploy your code. Let GitLab take care of time
consuming tasks, so you can spend more time creating.`),
primaryButtonText: s__('Pipelines|Get started with GitLab CI/CD'),
},
};
</script>
<template>
<gl-empty-state
:title="$options.i18n.title"
:svg-path="pipelinesEmptyStateSvgPath"
:description="$options.i18n.description"
:primary-button-text="$options.i18n.primaryButtonText"
:primary-button-link="ciHelpPagePath"
/>
</template>

View File

@ -11,10 +11,17 @@ import {
import { escape } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
import IDEServices from '~/ide/services';
import { sprintf, __ } from '../../../locale';
import EmptyState from '../../../pipelines/components/pipelines_list/empty_state.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
import { sprintf, __ } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import JobsList from '../jobs/list.vue';
import EmptyState from './empty_state.vue';
const CLASSES_FLEX_VERTICAL_CENTER = [
'gl-h-full',
'gl-display-flex',
'gl-flex-direction-column',
'gl-justify-content-center',
];
export default {
components: {
@ -32,7 +39,6 @@ export default {
SafeHtml,
},
computed: {
...mapState(['pipelinesEmptyStateSvgPath']),
...mapGetters(['currentProject']),
...mapGetters('pipelines', ['jobsCount', 'failedJobsCount', 'failedStages', 'pipelineFailed']),
...mapState('pipelines', [
@ -63,12 +69,15 @@ export default {
methods: {
...mapActions('pipelines', ['fetchLatestPipeline']),
},
CLASSES_FLEX_VERTICAL_CENTER,
};
</script>
<template>
<div class="ide-pipeline">
<gl-loading-icon v-if="showLoadingIcon" size="lg" class="gl-mt-3" />
<div v-if="showLoadingIcon" :class="$options.CLASSES_FLEX_VERTICAL_CENTER">
<gl-loading-icon size="lg" />
</div>
<template v-else-if="hasLoadedPipeline">
<header v-if="latestPipeline" class="ide-tree-header ide-pipeline-header">
<ci-icon :status="latestPipeline.details.status" :size="24" class="d-flex" />
@ -83,12 +92,9 @@ export default {
</a>
</span>
</header>
<empty-state
v-if="!latestPipeline"
:empty-state-svg-path="pipelinesEmptyStateSvgPath"
:can-set-ci="true"
class="gl-p-5"
/>
<div v-if="!latestPipeline" :class="$options.CLASSES_FLEX_VERTICAL_CENTER">
<empty-state />
</div>
<gl-alert
v-else-if="latestPipeline.yamlError"
variant="danger"

View File

@ -164,10 +164,25 @@ module AuthHelper
end
def google_tag_manager_enabled?
Gitlab.com? &&
extra_config.has_key?('google_tag_manager_id') &&
extra_config.google_tag_manager_id.present? &&
!current_user
return false unless Gitlab.dev_env_or_com?
has_config_key = if Feature.enabled?(:gtm_nonce, type: :ops)
extra_config.has_key?('google_tag_manager_nonce_id') &&
extra_config.google_tag_manager_nonce_id.present?
else
extra_config.has_key?('google_tag_manager_id') &&
extra_config.google_tag_manager_id.present?
end
has_config_key && !current_user
end
def google_tag_manager_id
return unless google_tag_manager_enabled?
return extra_config.google_tag_manager_nonce_id if Feature.enabled?(:gtm_nonce, type: :ops)
extra_config.google_tag_manager_id
end
def auth_app_owner_text(owner)

View File

@ -1,4 +1,4 @@
- return unless google_tag_manager_enabled?
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=#{extra_config.google_tag_manager_id}"
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=#{google_tag_manager_id}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>

View File

@ -1,8 +1,19 @@
- if google_tag_manager_enabled?
- return unless google_tag_manager_enabled?
- if Feature.enabled?(:gtm_nonce, type: :ops)
= javascript_tag do
:plain
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;var n=d.querySelector('[nonce]');
n&&j.setAttribute('nonce',n.nonce||n.getAttribute('nonce'));f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','#{google_tag_manager_id}');
- else
= javascript_tag do
:plain
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','#{extra_config.google_tag_manager_id}');
})(window,document,'script','dataLayer','#{google_tag_manager_id}');

View File

@ -0,0 +1,8 @@
---
name: gtm_nonce
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58494
rollout_issue_url:
milestone: '14.6'
type: ops
group: group::product intelligence
default_enabled: false

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class RemoveVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0]
BATCH_SIZE = 50_000
MIGRATION = 'RemoveVulnerabilityFindingLinks'
disable_ddl_transaction!
def up
queue_background_migration_jobs_by_range_at_intervals(
define_batchable_model('vulnerability_finding_links'),
MIGRATION,
2.minutes,
batch_size: BATCH_SIZE
)
end
def down
# no ops
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class AddNotNullConstraintToSecurityFindingsUuid < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
add_not_null_constraint(
:security_findings,
:uuid,
validate: false
)
end
def down
remove_not_null_constraint(
:security_findings,
:uuid
)
end
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class AddTemporaryIndexOnSecurityFindingsUuid < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
INDEX_NAME = "tmp_index_uuid_is_null"
def up
add_concurrent_index(
:security_findings,
:id,
where: "uuid IS NULL",
name: INDEX_NAME
)
end
def down
remove_concurrent_index_by_name(
:security_findings,
INDEX_NAME
)
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class ScheduleDropInvalidSecurityFindings < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
MIGRATION = "DropInvalidSecurityFindings"
DELAY_INTERVAL = 2.minutes.to_i
BATCH_SIZE = 100_000
SUB_BATCH_SIZE = 10_000
def up
queue_background_migration_jobs_by_range_at_intervals(
define_batchable_model('security_findings').where(uuid: nil),
MIGRATION,
DELAY_INTERVAL,
batch_size: BATCH_SIZE,
other_job_arguments: [SUB_BATCH_SIZE],
track_jobs: true
)
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
52625ff0a6117724cc1d7c6417ef95fe8dbcbb394486bb4734e28d3b41d23fd2

View File

@ -0,0 +1 @@
7724e5a2c52be99b1b40c449f25abdc23f279f5b0bdaebcfd897c39d295fda41

View File

@ -0,0 +1 @@
dab6123f19fb44a1566a8de9c760dedec5548dd64e472a180e7748cd7c93eea9

View File

@ -0,0 +1 @@
f5e69502e582c5f30ba686f8b668d8f0ce5cf8078b0833d2eda67f5ed97ac074

View File

@ -22613,6 +22613,9 @@ ALTER TABLE ONLY chat_teams
ALTER TABLE vulnerability_scanners
ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
ALTER TABLE security_findings
ADD CONSTRAINT check_6c2851a8c9 CHECK ((uuid IS NOT NULL)) NOT VALID;
ALTER TABLE sprints
ADD CONSTRAINT check_ccd8a1eae0 CHECK ((start_date IS NOT NULL)) NOT VALID;
@ -27722,6 +27725,8 @@ CREATE UNIQUE INDEX tmp_index_on_tmp_project_id_on_namespaces ON namespaces USIN
CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING btree (id) WHERE (state <> 2);
CREATE INDEX tmp_index_uuid_is_null ON security_findings USING btree (id) WHERE (uuid IS NULL);
CREATE UNIQUE INDEX uniq_pkgs_deb_grp_architectures_on_distribution_id_and_name ON packages_debian_group_architectures USING btree (distribution_id, name);
CREATE UNIQUE INDEX uniq_pkgs_deb_grp_components_on_distribution_id_and_name ON packages_debian_group_components USING btree (distribution_id, name);

View File

@ -219,27 +219,6 @@ This ordering also affects [issue lists](issues/sorting_issue_lists.md).
Changing the order in an issue board changes the ordering in an issue list,
and vice versa.
### GraphQL-based issue boards
<!-- This anchor is linked from #blocked-issues as well. -->
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285074) in GitLab 13.9.
> - [Deployed behind a feature flag](../feature_flags.md), enabled by default.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/248908) in GitLab 14.1
> - [Feature flag `graphql_board_lists`](https://gitlab.com/gitlab-org/gitlab/-/issues/248908) removed in GitLab 14.3
There can be
[risks when disabling released features](../../administration/feature_flags.md#risks-when-disabling-released-features).
Refer to this feature's version history for more details.
Using GraphQL-based boards gives you these
additional features:
- [Edit more issue attributes](#edit-an-issue)
- [View blocked issues](#blocked-issues)
Learn more about the known issues in [epic 5596](https://gitlab.com/groups/gitlab-org/-/epics/5596).
## GitLab Enterprise features for issue boards
GitLab issue boards are available on the GitLab Free tier, but some
@ -334,10 +313,7 @@ As in other list types, click the trash icon to remove a list.
### Iteration lists **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250479) in GitLab 13.11.
> - Enabled on GitLab.com and is ready for production use.
> - Enabled with `iteration_board_lists` flag for self-managed GitLab and is ready for production use.
> GitLab administrators can opt to [disable the feature flag](#enable-or-disable-iteration-lists-in-boards).
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250479) in GitLab 13.11 [with a flag](../../administration/feature_flags.md) named `iteration_board_lists`. Enabled by default.
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature, ask an
@ -345,8 +321,9 @@ administrator to [disable the `iteration_board_lists` flag](../../administration
On GitLab.com, this feature is available.
You're also able to create lists of an iteration.
These are lists that filter issues by the assigned
iteration. To add an iteration list:
These lists filter issues by the assigned iteration.
To add an iteration list:
1. Select **Create list**.
1. Select **Iteration**.
@ -434,8 +411,6 @@ status.
When you hover over the blocked icon (**{issue-block}**), a detailed information popover is displayed.
This feature is only supported when using the [GraphQL-based boards](#graphql-based-issue-boards). The feature is enabled by default regardless when you use group issue boards in epic swimlanes mode.
![Blocked issues](img/issue_boards_blocked_icon_v13_10.png)
## Actions you can take on an issue board
@ -457,25 +432,25 @@ If you're not able to do some of the things above, make sure you have the right
### Edit an issue
> Editing title, iteration, and confidentiality [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/248908) in GitLab 14.1.
You can edit an issue without leaving the board view.
To open the right sidebar, select an issue card (not its title).
You can edit the following issue attributes in the right sidebar:
- Assignees
- [Epic](../group/epics/index.md)
- Milestone
- Time tracking value (view only)
- Due date
- Labels
- [Weight](issues/issue_weight.md)
- Notifications setting
When you use [GraphQL-based boards](#graphql-based-issue-boards), you can also edit the following issue attributes:
- Title
- [Iteration](../group/iterations/index.md)
- Confidentiality
- Due date
- [Epic](../group/epics/index.md)
- [Iteration](../group/iterations/index.md)
- Labels
- Milestone
- Notifications setting
- Title
- [Weight](issues/issue_weight.md)
Additionally, you can also see the time tracking value.
### Create a new list
@ -620,13 +595,12 @@ and the target list.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18954) in GitLab 12.4.
> - [Placed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61955) behind a [feature flag](../feature_flags.md), disabled by default in GitLab 14.0.
> - Disabled on GitLab.com.
> - Not recommended for production use.
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-multi-selecting-issue-cards). **(FREE SELF)**
This in-development feature might not be available for your use. There can be
[risks when enabling features still in development](../../administration/feature_flags.md#risks-when-enabling-features-still-in-development).
Refer to this feature's version history for more details.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, ask an
administrator to [enable the feature flag](../../administration/feature_flags.md) named `board_multi_select`.
On GitLab.com, this feature is not available.
The feature is not ready for production use.
You can select multiple issue cards, then drag the group to another position within the list, or to
another list. This makes it faster to reorder many issues at once.
@ -668,41 +642,3 @@ A few things to remember:
- For performance and visibility reasons, each list shows the first 20 issues
by default. If you have more than 20 issues, start scrolling down and the next
20 appear.
### Enable or disable iteration lists in boards **(PREMIUM SELF)**
The iteration list is under development but ready for production use. It is
deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can disable it.
To enable it:
```ruby
Feature.enable(:iteration_board_lists)
```
To disable it:
```ruby
Feature.disable(:iteration_board_lists)
```
### Enable or disable multi-selecting issue cards **(FREE SELF)**
Multi-selecting issue cards is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:board_multi_select)
```
To disable it:
```ruby
Feature.disable(:board_multi_select)
```

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Drop rows from security_findings where the uuid is NULL
class DropInvalidSecurityFindings
# rubocop:disable Style/Documentation
class SecurityFinding < ActiveRecord::Base
include ::EachBatch
self.table_name = 'security_findings'
scope :no_uuid, -> { where(uuid: nil) }
end
# rubocop:enable Style/Documentation
PAUSE_SECONDS = 0.1
def perform(start_id, end_id, sub_batch_size)
ranged_query = SecurityFinding
.where(id: start_id..end_id)
.no_uuid
ranged_query.each_batch(of: sub_batch_size) do |sub_batch|
first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
# The query need to be reconstructed because .each_batch modifies the default scope
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330510
SecurityFinding.unscoped
.where(id: first..last)
.no_uuid
.delete_all
sleep PAUSE_SECONDS
end
mark_job_as_succeeded(start_id, end_id, sub_batch_size)
end
private
def mark_job_as_succeeded(*arguments)
Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
self.class.name.demodulize,
arguments
)
end
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Remove vulnerability finding link records
# The records will be repopulated from the `raw_metadata`
# column of `vulnerability_occurrences` once the unique
# index is in place.
class RemoveVulnerabilityFindingLinks
include Gitlab::Database::DynamicModelHelpers
def perform(start_id, stop_id)
define_batchable_model('vulnerability_finding_links').where(id: start_id..stop_id).delete_all
end
end
end
end

View File

@ -8,7 +8,7 @@ module Gitlab
module ContentSecurityPolicy
module Directives
def self.frame_src
"https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com"
"https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com https://www.googletagmanager.com/ns.html"
end
def self.script_src

View File

@ -6,10 +6,10 @@ exports[`IDE pipelines list when loaded renders empty state when no latestPipeli
>
<!---->
<empty-state-stub
cansetci="true"
class="gl-p-5"
emptystatesvgpath="http://test.host"
/>
<div
class="gl-h-full gl-display-flex gl-flex-direction-column gl-justify-content-center"
>
<empty-state-stub />
</div>
</div>
`;

View File

@ -0,0 +1,44 @@
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import EmptyState from '~/ide/components/pipelines/empty_state.vue';
import { createStore } from '~/ide/stores';
const TEST_PIPELINES_EMPTY_STATE_SVG_PATH = 'illustrations/test/pipelines.svg';
describe('~/ide/components/pipelines/empty_state.vue', () => {
let store;
let wrapper;
const createComponent = () => {
wrapper = shallowMount(EmptyState, {
store,
});
};
beforeEach(() => {
store = createStore();
store.dispatch('setEmptyStateSvgs', {
pipelinesEmptyStateSvgPath: TEST_PIPELINES_EMPTY_STATE_SVG_PATH,
});
});
afterEach(() => {
wrapper.destroy();
});
describe('default', () => {
beforeEach(() => {
createComponent();
});
it('renders empty state', () => {
expect(wrapper.find(GlEmptyState).props()).toMatchObject({
title: EmptyState.i18n.title,
description: EmptyState.i18n.description,
primaryButtonText: EmptyState.i18n.primaryButtonText,
primaryButtonLink: '/help/ci/quick_start/index.md',
svgPath: TEST_PIPELINES_EMPTY_STATE_SVG_PATH,
});
});
});
});

View File

@ -2,10 +2,10 @@ import { GlLoadingIcon, GlTab } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
import { pipelines } from 'jest/ide/mock_data';
import JobsList from '~/ide/components/jobs/list.vue';
import List from '~/ide/components/pipelines/list.vue';
import EmptyState from '~/ide/components/pipelines/empty_state.vue';
import IDEServices from '~/ide/services';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
@ -18,9 +18,6 @@ jest.mock('~/ide/services', () => ({
describe('IDE pipelines list', () => {
let wrapper;
const defaultState = {
pipelinesEmptyStateSvgPath: TEST_HOST,
};
const defaultPipelinesState = {
stages: [],
failedStages: [],
@ -38,7 +35,6 @@ describe('IDE pipelines list', () => {
currentProject: () => ({ web_url: 'some/url ', path_with_namespace: fakeProjectPath }),
},
state: {
...defaultState,
...rootState,
},
modules: {
@ -131,6 +127,8 @@ describe('IDE pipelines list', () => {
it('renders empty state when no latestPipeline', () => {
createComponent({}, { ...defaultPipelinesLoadedState, latestPipeline: null });
expect(wrapper.find(EmptyState).exists()).toBe(true);
expect(wrapper.element).toMatchSnapshot();
});

View File

@ -283,35 +283,84 @@ RSpec.describe AuthHelper do
before do
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
stub_config(extra: { google_tag_manager_id: 'key' })
allow(helper).to receive(:current_user).and_return(user)
end
subject(:google_tag_manager_enabled?) { helper.google_tag_manager_enabled? }
context 'on gitlab.com and a key set without a current user' do
it { is_expected.to be_truthy }
end
subject(:google_tag_manager_enabled) { helper.google_tag_manager_enabled? }
context 'when not on gitlab.com' do
let(:is_gitlab_com) { false }
it { is_expected.to be_falsey }
it { is_expected.to eq(false) }
end
context 'when current user is set' do
let(:user) { instance_double('User') }
context 'regular and nonce versions' do
using RSpec::Parameterized::TableSyntax
it { is_expected.to be_falsey }
where(:gtm_nonce_enabled, :gtm_key) do
false | 'google_tag_manager_id'
true | 'google_tag_manager_nonce_id'
end
with_them do
before do
stub_feature_flags(gtm_nonce: gtm_nonce_enabled)
stub_config(extra: { gtm_key => 'key' })
end
context 'on gitlab.com and a key set without a current user' do
it { is_expected.to be_truthy }
end
context 'when current user is set' do
let(:user) { instance_double('User') }
it { is_expected.to eq(false) }
end
context 'when no key is set' do
before do
stub_config(extra: {})
end
it { is_expected.to eq(false) }
end
end
end
end
describe '#google_tag_manager_id' do
subject(:google_tag_manager_id) { helper.google_tag_manager_id }
before do
stub_config(extra: { 'google_tag_manager_nonce_id': 'nonce', 'google_tag_manager_id': 'gtm' })
end
context 'when no key is set' do
context 'when google tag manager is disabled' do
before do
stub_config(extra: {})
allow(helper).to receive(:google_tag_manager_enabled?).and_return(false)
end
it { is_expected.to be_falsey }
end
context 'when google tag manager is enabled' do
before do
allow(helper).to receive(:google_tag_manager_enabled?).and_return(true)
end
context 'when nonce feature flag is enabled' do
it { is_expected.to eq('nonce') }
end
context 'when nonce feature flag is disabled' do
before do
stub_feature_flags(gtm_nonce: false)
end
it { is_expected.to eq('gtm') }
end
end
end
describe '#auth_app_owner_text' do

View File

@ -0,0 +1,56 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::DropInvalidSecurityFindings, schema: 20211108211434 do
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:project) { table(:projects).create!(namespace_id: namespace.id) }
let(:pipelines) { table(:ci_pipelines) }
let!(:pipeline) { pipelines.create!(project_id: project.id) }
let(:ci_builds) { table(:ci_builds) }
let!(:ci_build) { ci_builds.create! }
let(:security_scans) { table(:security_scans) }
let!(:security_scan) do
security_scans.create!(
scan_type: 1,
status: 1,
build_id: ci_build.id,
project_id: project.id,
pipeline_id: pipeline.id
)
end
let(:vulnerability_scanners) { table(:vulnerability_scanners) }
let!(:vulnerability_scanner) { vulnerability_scanners.create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let(:security_findings) { table(:security_findings) }
let!(:security_finding_without_uuid) do
security_findings.create!(
severity: 1,
confidence: 1,
scan_id: security_scan.id,
scanner_id: vulnerability_scanner.id,
uuid: nil
)
end
let!(:security_finding_with_uuid) do
security_findings.create!(
severity: 1,
confidence: 1,
scan_id: security_scan.id,
scanner_id: vulnerability_scanner.id,
uuid: 'bd95c085-71aa-51d7-9bb6-08ae669c262e'
)
end
let(:sub_batch_size) { 10_000 }
subject { described_class.new.perform(security_finding_without_uuid.id, security_finding_with_uuid.id, sub_batch_size) }
it 'drops Security::Finding objects with no UUID' do
expect { subject }.to change(security_findings, :count).from(2).to(1)
end
end

View File

@ -0,0 +1,66 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::RemoveVulnerabilityFindingLinks, :migration, schema: 20211104165220 do
let(:vulnerability_findings) { table(:vulnerability_occurrences) }
let(:finding_links) { table(:vulnerability_finding_links) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:project) { table(:projects).create!(namespace_id: namespace.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'scanner', name: 'scanner') }
let(:vulnerability_identifier) do
table(:vulnerability_identifiers).create!(
project_id: project.id,
external_type: 'vulnerability-identifier',
external_id: 'vulnerability-identifier',
fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
name: 'vulnerability identifier')
end
# vulnerability findings
let!(:findings) do
Array.new(2) do |id|
vulnerability_findings.create!(
project_id: project.id,
name: 'Vulnerability Name',
severity: 7,
confidence: 7,
report_type: 0,
project_fingerprint: '123qweasdzxc',
scanner_id: scanner.id,
primary_identifier_id: vulnerability_identifier.id,
location_fingerprint: "location_fingerprint_#{id}",
metadata_version: 'metadata_version',
raw_metadata: 'raw_metadata',
uuid: "uuid_#{id}"
)
end
end
# vulnerability finding links
let!(:links) do
{
findings.first => Array.new(5) { |id| finding_links.create!(vulnerability_occurrence_id: findings.first.id, name: "Link Name 1", url: "link_url1.example") },
findings.second => Array.new(5) { |id| finding_links.create!(vulnerability_occurrence_id: findings.second.id, name: "Link Name 2", url: "link_url2.example") }
}
end
it 'removes vulnerability links' do
expect do
subject.perform(links[findings.first].first.id, links[findings.second].last.id)
end.to change { finding_links.count }.from(10).to(0)
expect(finding_links.all).to be_empty
end
it 'only deletes vulnerability links for the current batch' do
expected_links = [finding_links.where(vulnerability_occurrence_id: findings.second.id)].flatten
expect do
subject.perform(links[findings.first].first.id, links[findings.first].last.id)
end.to change { finding_links.count }.from(10).to(5)
expect(finding_links.all).to match_array(expected_links)
end
end

View File

@ -487,25 +487,9 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
end
end
describe 'primary connection re-use', :reestablished_active_record_base do
describe 'primary connection re-use', :reestablished_active_record_base, :add_ci_connection do
let(:model) { Ci::ApplicationRecord }
around do |example|
if Gitlab::Database.has_config?(:ci)
example.run
else
# fake additional Database
model.establish_connection(
ActiveRecord::DatabaseConfigurations::HashConfig.new(Rails.env, 'ci', ActiveRecord::Base.connection_db_config.configuration_hash)
)
example.run
# Cleanup connection_specification_name for Ci::ApplicationRecord
model.remove_connection
end
end
describe '#read' do
it 'returns ci replica connection' do
expect { |b| lb.read(&b) }.to yield_with_args do |args|

View File

@ -17,7 +17,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana
process_sql(ActiveRecord::Base, "SELECT 1 FROM projects")
end
context 'properly observes all queries', :mocked_ci_connection do
context 'properly observes all queries', :add_ci_connection do
using RSpec::Parameterized::TableSyntax
where do

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddNotNullConstraintToSecurityFindingsUuid do
let_it_be(:security_findings) { table(:security_findings) }
let_it_be(:migration) { described_class.new }
before do
allow(migration).to receive(:transaction_open?).and_return(false)
allow(migration).to receive(:with_lock_retries).and_yield
end
it 'adds a check constraint' do
constraint = security_findings.connection.check_constraints(:security_findings).find { |constraint| constraint.expression == "uuid IS NOT NULL" }
expect(constraint).to be_nil
migration.up
constraint = security_findings.connection.check_constraints(:security_findings).find { |constraint| constraint.expression == "uuid IS NOT NULL" }
expect(constraint).to be_a(ActiveRecord::ConnectionAdapters::CheckConstraintDefinition)
end
end

View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe ScheduleDropInvalidSecurityFindings, :migration, schema: 20211108211434 do
let_it_be(:background_migration_jobs) { table(:background_migration_jobs) }
let_it_be(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let_it_be(:project) { table(:projects).create!(namespace_id: namespace.id) }
let_it_be(:pipelines) { table(:ci_pipelines) }
let_it_be(:pipeline) { pipelines.create!(project_id: project.id) }
let_it_be(:ci_builds) { table(:ci_builds) }
let_it_be(:ci_build) { ci_builds.create! }
let_it_be(:security_scans) { table(:security_scans) }
let_it_be(:security_scan) do
security_scans.create!(
scan_type: 1,
status: 1,
build_id: ci_build.id,
project_id: project.id,
pipeline_id: pipeline.id
)
end
let_it_be(:vulnerability_scanners) { table(:vulnerability_scanners) }
let_it_be(:vulnerability_scanner) { vulnerability_scanners.create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let_it_be(:security_findings) { table(:security_findings) }
let_it_be(:security_finding_without_uuid) do
security_findings.create!(
severity: 1,
confidence: 1,
scan_id: security_scan.id,
scanner_id: vulnerability_scanner.id,
uuid: nil
)
end
let_it_be(:security_finding_with_uuid) do
security_findings.create!(
severity: 1,
confidence: 1,
scan_id: security_scan.id,
scanner_id: vulnerability_scanner.id,
uuid: 'bd95c085-71aa-51d7-9bb6-08ae669c262e'
)
end
before do
stub_const("#{described_class}::BATCH_SIZE", 1)
stub_const("#{described_class}::SUB_BATCH_SIZE", 1)
end
around do |example|
freeze_time { Sidekiq::Testing.fake! { example.run } }
end
it 'schedules background migrations' do
migrate!
expect(background_migration_jobs.count).to eq(1)
expect(background_migration_jobs.first.arguments).to match_array([security_finding_without_uuid.id, security_finding_without_uuid.id, described_class::SUB_BATCH_SIZE])
expect(BackgroundMigrationWorker.jobs.size).to eq(1)
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, security_finding_without_uuid.id, security_finding_without_uuid.id, described_class::SUB_BATCH_SIZE)
end
end

View File

@ -6,6 +6,10 @@ module Database
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
end
def skip_if_multiple_databases_are_setup
skip 'Skipping because multiple databases are set up' if Gitlab::Database.has_config?(:ci)
end
def reconfigure_db_connection(name: nil, config_hash: {}, model: ActiveRecord::Base, config_model: nil)
db_config = (config_model || model).connection_db_config
@ -46,6 +50,26 @@ module Database
new_handler&.clear_all_connections!
end
# rubocop:enable Database/MultipleDatabases
def with_added_ci_connection
if Gitlab::Database.has_config?(:ci)
# No need to add a ci: connection if we already have one
yield
else
with_reestablished_active_record_base(reconnect: true) do
reconfigure_db_connection(
name: :ci,
model: Ci::ApplicationRecord,
config_model: ActiveRecord::Base
)
yield
# Cleanup connection_specification_name for Ci::ApplicationRecord
Ci::ApplicationRecord.remove_connection
end
end
end
end
module ActiveRecordBaseEstablishConnection
@ -69,18 +93,9 @@ RSpec.configure do |config|
end
end
config.around(:each, :mocked_ci_connection) do |example|
with_reestablished_active_record_base(reconnect: true) do
reconfigure_db_connection(
name: :ci,
model: Ci::ApplicationRecord,
config_model: ActiveRecord::Base
)
config.around(:each, :add_ci_connection) do |example|
with_added_ci_connection do
example.run
# Cleanup connection_specification_name for Ci::ApplicationRecord
Ci::ApplicationRecord.remove_connection
end
end
end

View File

@ -56,4 +56,43 @@ RSpec.describe 'Database::MultipleDatabases' do
end
end
end
describe '.with_added_ci_connection' do
context 'when only a single database is setup' do
before do
skip_if_multiple_databases_are_setup
end
it 'connects Ci::ApplicationRecord to the main database for the duration of the block', :aggregate_failures do
main_database = current_database(ActiveRecord::Base)
original_database = current_database(Ci::ApplicationRecord)
with_added_ci_connection do
expect(current_database(Ci::ApplicationRecord)).to eq(main_database)
end
expect(current_database(Ci::ApplicationRecord)).to eq(original_database)
end
end
context 'when multiple databases are setup' do
before do
skip_if_multiple_databases_not_setup
end
it 'does not mock the original Ci::ApplicationRecord connection', :aggregate_failures do
original_database = current_database(Ci::ApplicationRecord)
with_added_ci_connection do
expect(current_database(Ci::ApplicationRecord)).to eq(original_database)
end
expect(current_database(Ci::ApplicationRecord)).to eq(original_database)
end
end
def current_database(connection_class)
connection_class.retrieve_connection.execute('select current_database()').first
end
end
end