Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-10-29 09:08:38 +00:00
parent 4bc1e04a7a
commit c3ea5eada6
28 changed files with 473 additions and 54 deletions

View file

@ -90,6 +90,7 @@ export default () => {
labelsFetchPath: $boardApp.dataset.labelsFetchPath,
labelsManagePath: $boardApp.dataset.labelsManagePath,
labelsFilterBasePath: $boardApp.dataset.labelsFilterBasePath,
timeTrackingLimitToHours: parseBoolean($boardApp.dataset.timeTrackingLimitToHours),
},
store,
apolloProvider,

View file

@ -54,7 +54,7 @@ export default {
<ide-tree-list>
<template #header>
{{ __('Edit') }}
<div class="ide-tree-actions ml-auto d-flex">
<div class="ide-tree-actions ml-auto d-flex" data-testid="ide-root-actions">
<new-entry-button
:label="__('New file')"
:show-label="false"

View file

@ -57,7 +57,6 @@ export default {
:human-time-estimate="store.humanTimeEstimate"
:human-time-spent="store.humanTotalTimeSpent"
:limit-to-hours="store.timeTrackingLimitToHours"
:root-path="store.rootPath"
/>
</div>
</template>

View file

@ -44,6 +44,21 @@ export default {
default: false,
required: false,
},
/*
In issue list, "time-tracking-collapsed-state" is always rendered even if the sidebar isn't collapsed.
The actual hiding is controlled with css classes:
Hide "time-tracking-collapsed-state"
if .right-sidebar .right-sidebar-collapsed .sidebar-collapsed-icon
Show "time-tracking-collapsed-state"
if .right-sidebar .right-sidebar-expanded .sidebar-collapsed-icon
In Swimlanes sidebar, we do not use collapsed state at all.
*/
showCollapsed: {
type: Boolean,
default: true,
required: false,
},
},
data() {
return {
@ -93,8 +108,9 @@ export default {
</script>
<template>
<div v-cloak class="time_tracker time-tracking-component-wrap">
<div v-cloak class="time-tracker time-tracking-component-wrap" data-testid="time-tracker">
<time-tracking-collapsed-state
v-if="showCollapsed"
:show-comparison-state="showComparisonState"
:show-no-time-tracking-state="showNoTimeTrackingState"
:show-help-state="showHelpState"
@ -103,7 +119,7 @@ export default {
:time-spent-human-readable="humanTimeSpent"
:time-estimate-human-readable="humanTimeEstimate"
/>
<div class="title hide-collapsed">
<div class="title hide-collapsed gl-mb-3">
{{ __('Time tracking') }}
<div
v-if="!showHelpState"

View file

@ -808,11 +808,7 @@
}
}
.time_tracker {
padding-bottom: 0;
border-bottom: 0;
.time-tracker {
.sidebar-collapsed-icon {
> .stopwatch-svg {
display: inline-block;

View file

@ -0,0 +1,5 @@
---
title: Fix IDE issues with special characters
merge_request: 46398
author:
type: fixed

View file

@ -0,0 +1,6 @@
---
title: Show tar warning message when file/folder changed during backup instead of
failing whole backup operation
merge_request: 42197
author:
type: fixed

View file

@ -0,0 +1,7 @@
---
name: coverage_fuzzing_mr_widget
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43545
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/257839
type: development
group: group::fuzz testing
default_enabled: false

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
class AddEsCustomAnalyzersSettings < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :application_settings, :elasticsearch_analyzers_smartcn_enabled, :bool, null: false, default: false
add_column :application_settings, :elasticsearch_analyzers_smartcn_search, :bool, null: false, default: false
add_column :application_settings, :elasticsearch_analyzers_kuromoji_enabled, :bool, null: false, default: false
add_column :application_settings, :elasticsearch_analyzers_kuromoji_search, :bool, null: false, default: false
end
end

View file

@ -0,0 +1 @@
1bd99d7d6b972ea66495f21358e3b8731532219fcf75731bf643c312eb56820d

View file

@ -9297,6 +9297,10 @@ CREATE TABLE application_settings (
encrypted_ci_jwt_signing_key text,
encrypted_ci_jwt_signing_key_iv text,
container_registry_expiration_policies_worker_capacity integer DEFAULT 0 NOT NULL,
elasticsearch_analyzers_smartcn_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_smartcn_search boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_search boolean DEFAULT false NOT NULL,
secret_detection_token_revocation_enabled boolean DEFAULT false NOT NULL,
secret_detection_token_revocation_url text,
encrypted_secret_detection_token_revocation_token text,

View file

@ -38,7 +38,7 @@ To start multiple processes:
process, and values in each item determine the queues it works on.
For example, the following setting creates three Sidekiq processes, one to run on
`elastic_indexer`, one to run on `mailers`, and one process running all on queues:
`elastic_indexer`, one to run on `mailers`, and one process running on all queues:
```ruby
sidekiq['queue_groups'] = [
@ -327,9 +327,9 @@ Running Sidekiq cluster is the default in GitLab 13.0 and later.
```
`min_concurrency` and `max_concurrency` are independent; one can be set without
the other. Setting `min_concurrency` to 0 will disable the limit.
the other. Setting `min_concurrency` to `0` will disable the limit.
For each queue group, let N be one more than the number of queues. The
For each queue group, let `N` be one more than the number of queues. The
concurrency factor will be set to:
1. `N`, if it's between `min_concurrency` and `max_concurrency`.

View file

@ -1273,6 +1273,11 @@ tested for within the unit test of `Types::MutationType`. The merge request
can be referred to as an example of this, including the method of testing
deprecated aliased mutations.
#### Deprecating EE mutations
EE mutations should follow the same process. For an example of the merge request
process, read [merge request !42588](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42588).
## Pagination implementation
To learn more, visit [GraphQL pagination](graphql_guide/pagination.md).

View file

@ -426,6 +426,38 @@ module EE
end
```
### Code in `app/graphql/`
EE-specific mutations, resolvers, and types should be added to
`ee/app/graphql/{mutations,resolvers,types}`.
To override a CE mutation, resolver, or type, create the file in
`ee/app/graphql/ee/{mutations,resolvers,types}` and add new code to a
`prepended` block.
For example, if CE has a mutation called `Mutations::Tanukis::Create` and you
wanted to add a new argument, place the EE override in
`ee/app/graphql/ee/mutations/tanukis/create.rb`:
```ruby
module EE
module Mutations
module Tanukis
module Create
extend ActiveSupport::Concern
prepended do
argument :name,
GraphQL::STRING_TYPE,
required: false,
description: 'Tanuki name'
end
end
end
end
end
```
#### Using `render_if_exists`
Instead of using regular `render`, we should use `render_if_exists`, which

View file

@ -246,6 +246,29 @@ for filtering to work correctly. To do this run the Rake tasks `gitlab:elastic:r
`gitlab:elastic:clear_index_status`. Afterwards, removing a namespace or a project from the list will delete the data
from the Elasticsearch index as expected.
## Enabling custom language analyzers
You can improve the language support for Chinese and Japanese languages by utilizing [smartcn](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) and/or [kuromoji](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) analysis plugins from Elastic.
To enable language(s) support:
1. Install the desired plugin(s), please refer to [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/plugins/7.9/installation.html) for plugins installation instructions. The plugin(s) must be installed on every node in the cluster, and each node must be restarted after installation. For a list of plugins, see the table later in this section.
1. Navigate to the **Admin Area** (wrench icon), then **Settings > General**..
1. Expand the **Advanced Search** section and locate **Custom analyzers: language support**.
1. Enable plugin(s) support for **Indexing**.
1. Click **Save changes** for the changes to take effect.
1. Trigger [Zero downtime reindexing](#zero-downtime-reindexing) or reindex everything from scratch to create a new index with updated mappings.
1. Enable plugin(s) support for **Searching** after the previous step is completed.
For guidance on what to install, see the following Elasticsearch language plugin options:
| Parameter | Description |
|-------------------------------------------------------|-------------|
| `Enable Chinese (smartcn) custom analyzer: Indexing` | Enables or disables Chinese language support using [smartcn](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) custom analyzer for newly created indices.|
| `Enable Chinese (smartcn) custom analyzer: Search` | Enables or disables using [smartcn](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) fields for Advanced Search. Please only enable this after [installing the plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html), enabling custom analyzer indexing and recreating the index.|
| `Enable Japanese (kuromoji) custom analyzer: Indexing` | Enables or disables Japanese language support using [kuromoji](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) custom analyzer for newly created indices.|
| `Enable Japanese (kuromoji) custom analyzer: Search` | Enables or disables using [kuromoji](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) fields for Advanced Search. Please only enable this after [installing the plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html), enabling custom analyzer indexing and recreating the index.|
## Disabling Advanced Search
To disable the Elasticsearch integration:

View file

@ -26,7 +26,7 @@ module Backup
FileUtils.rm_f(backup_tarball)
if ENV['STRATEGY'] == 'copy'
cmd = [%w(rsync -a), exclude_dirs(:rsync), %W(#{app_files_dir} #{Gitlab.config.backup.path})].flatten
cmd = [%w[rsync -a], exclude_dirs(:rsync), %W[#{app_files_dir} #{Gitlab.config.backup.path}]].flatten
output, status = Gitlab::Popen.popen(cmd)
unless status == 0
@ -34,19 +34,27 @@ module Backup
raise Backup::Error, 'Backup failed'
end
tar_cmd = [tar, exclude_dirs(:tar), %W(-C #{@backup_files_dir} -cf - .)].flatten
run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{@backup_files_dir} -cf - .]].flatten
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
FileUtils.rm_rf(@backup_files_dir)
else
tar_cmd = [tar, exclude_dirs(:tar), %W(-C #{app_files_dir} -cf - .)].flatten
run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{app_files_dir} -cf - .]].flatten
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
end
unless pipeline_succeeded?(tar_status: status_list[0], gzip_status: status_list[1], output: output)
raise Backup::Error, "Backup operation failed: #{output}"
end
end
def restore
backup_existing_files_dir
run_pipeline!([%w(gzip -cd), %W(#{tar} --unlink-first --recursive-unlink -C #{app_files_dir} -xf -)], in: backup_tarball)
cmd_list = [%w[gzip -cd], %W[#{tar} --unlink-first --recursive-unlink -C #{app_files_dir} -xf -]]
status_list, output = run_pipeline!(cmd_list, in: backup_tarball)
unless pipeline_succeeded?(gzip_status: status_list[0], tar_status: status_list[1], output: output)
raise Backup::Error, "Restore operation failed: #{output}"
end
end
def tar
@ -78,13 +86,44 @@ module Backup
def run_pipeline!(cmd_list, options = {})
err_r, err_w = IO.pipe
options[:err] = err_w
status = Open3.pipeline(*cmd_list, options)
status_list = Open3.pipeline(*cmd_list, options)
err_w.close
return if status.compact.all?(&:success?)
regex = /^g?tar: \.: Cannot mkdir: No such file or directory$/
error = err_r.read
raise Backup::Error, "Backup failed. #{error}" unless error =~ regex
[status_list, err_r.read]
end
def noncritical_warning?(warning)
noncritical_warnings = [
/^g?tar: \.: Cannot mkdir: No such file or directory$/
]
noncritical_warnings.map { |w| warning =~ w }.any?
end
def pipeline_succeeded?(tar_status:, gzip_status:, output:)
return false unless gzip_status&.success?
tar_status&.success? || tar_ignore_non_success?(tar_status.exitstatus, output)
end
def tar_ignore_non_success?(exitstatus, output)
# tar can exit with nonzero code:
# 1 - if some files changed (i.e. a CI job is currently writes to log)
# 2 - if it cannot create `.` directory (see issue https://gitlab.com/gitlab-org/gitlab/-/issues/22442)
# http://www.gnu.org/software/tar/manual/html_section/tar_19.html#Synopsis
# so check tar status 1 or stderr output against some non-critical warnings
if exitstatus == 1
$stdout.puts "Ignoring tar exit status 1 'Some files differ': #{output}"
return true
end
# allow tar to fail with other non-success status if output contain non-critical warning
if noncritical_warning?(output)
$stdout.puts "Ignoring non-success exit status #{exitstatus} due to output of non-critical warning(s): #{output}"
return true
end
false
end
def exclude_dirs(fmt)

View file

@ -5164,6 +5164,9 @@ msgstr ""
msgid "Child epic doesn't exist."
msgstr ""
msgid "Chinese language support using"
msgstr ""
msgid "Choose %{strong_open}Create archive%{strong_close} and wait for archiving to complete."
msgstr ""
@ -7910,6 +7913,9 @@ msgstr ""
msgid "Custom Git clone URL for HTTP(S)"
msgstr ""
msgid "Custom analyzers: language support"
msgstr ""
msgid "Custom hostname (for private commit emails)"
msgstr ""
@ -9924,6 +9930,12 @@ msgstr ""
msgid "Enable integration"
msgstr ""
msgid "Enable kuromoji custom analyzer: Indexing"
msgstr ""
msgid "Enable kuromoji custom analyzer: Search"
msgstr ""
msgid "Enable maintenance mode"
msgstr ""
@ -9960,6 +9972,12 @@ msgstr ""
msgid "Enable shared runners for this group"
msgstr ""
msgid "Enable smartcn custom analyzer: Indexing"
msgstr ""
msgid "Enable smartcn custom analyzer: Search"
msgstr ""
msgid "Enable snowplow tracking"
msgstr ""
@ -14892,6 +14910,9 @@ msgstr ""
msgid "January"
msgstr ""
msgid "Japanese language support using"
msgstr ""
msgid "Jira Issues"
msgstr ""
@ -19795,6 +19816,9 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
msgid "Please only enable search after installing the plugin, enabling indexing and recreating the index"
msgstr ""
msgid "Please provide a name"
msgstr ""
@ -31139,9 +31163,6 @@ msgstr ""
msgid "ciReport|Coverage Fuzzing"
msgstr ""
msgid "ciReport|Coverage Fuzzing Title"
msgstr ""
msgid "ciReport|Coverage fuzzing"
msgstr ""
@ -31589,6 +31610,9 @@ msgstr ""
msgid "jigsaw is not defined"
msgstr ""
msgid "kuromoji custom analyzer"
msgstr ""
msgid "last commit:"
msgstr ""
@ -32216,6 +32240,9 @@ msgstr ""
msgid "sign in"
msgstr ""
msgid "smartcn custom analyzer"
msgstr ""
msgid "sort:"
msgstr ""

View file

@ -146,7 +146,7 @@
"vue": "^2.6.12",
"vue-apollo": "^3.0.3",
"vue-loader": "^15.9.3",
"vue-router": "^3.4.7",
"vue-router": "3.4.5",
"vue-template-compiler": "^2.6.12",
"vue-virtual-scroll-list": "^1.4.4",
"vuedraggable": "^2.23.0",

View file

@ -105,7 +105,8 @@ describe('Monitoring router', () => {
path | currentDashboard
${'/panel/new'} | ${undefined}
${'/dashboard.yml/panel/new'} | ${'dashboard.yml'}
${'/config%2Fprometheus%2Fcommon_metrics.yml/panel/new'} | ${'config%2Fprometheus%2Fcommon_metrics.yml'}
${'/config/prometheus/common_metrics.yml/panel/new'} | ${'config/prometheus/common_metrics.yml'}
${'/config%2Fprometheus%2Fcommon_metrics.yml/panel/new'} | ${'config/prometheus/common_metrics.yml'}
`('"$path" renders page with dashboard "$currentDashboard"', ({ path, currentDashboard }) => {
const wrapper = createWrapper(BASE_PATH, path);

View file

@ -16,7 +16,6 @@ describe('Issuable Time Tracker', () => {
humanTimeEstimate: '2h 46m',
humanTimeSpent: '1h 23m',
limitToHours: false,
rootPath: '/',
};
const mountComponent = ({ props = {} } = {}) =>
@ -52,6 +51,24 @@ describe('Issuable Time Tracker', () => {
});
describe('Content panes', () => {
describe('Collapsed state', () => {
it('should render "time-tracking-collapsed-state" by default when "showCollapsed" prop is not specified', () => {
wrapper = mountComponent();
expect(findCollapsedState().exists()).toBe(true);
});
it('should not render "time-tracking-collapsed-state" when "showCollapsed" is false', () => {
wrapper = mountComponent({
props: {
showCollapsed: false,
},
});
expect(findCollapsedState().exists()).toBe(false);
});
});
describe('Comparison pane', () => {
beforeEach(() => {
wrapper = mountComponent({

View file

@ -1,6 +1,6 @@
import { findAllByText, fireEvent, getByLabelText, screen } from '@testing-library/dom';
const isFileRowOpen = row => row.matches('.is-open');
const isFolderRowOpen = row => row.matches('.folder.is-open');
const getLeftSidebar = () => screen.getByTestId('left-sidebar');
@ -24,6 +24,8 @@ const findAndSetEditorValue = async value => {
const findTreeBody = () => screen.findByTestId('ide-tree-body', {}, { timeout: 5000 });
const findRootActions = () => screen.findByTestId('ide-root-actions', {}, { timeout: 7000 });
const findFileRowContainer = (row = null) =>
row ? Promise.resolve(row.parentElement) : findTreeBody();
@ -35,7 +37,7 @@ const findFileChild = async (row, name, index = 0) => {
};
const openFileRow = row => {
if (!row || isFileRowOpen(row)) {
if (!row || isFolderRowOpen(row)) {
return;
}
@ -74,6 +76,19 @@ const findAndSetFileName = async value => {
createButton.click();
};
const findAndClickRootAction = async name => {
const container = await findRootActions();
const button = getByLabelText(container, name);
button.click();
};
export const openFile = async path => {
const row = await findAndTraverseToPath(path);
openFileRow(row);
};
export const createFile = async (path, content) => {
const parentPath = path
.split('/')
@ -81,7 +96,12 @@ export const createFile = async (path, content) => {
.join('/');
const parentRow = await findAndTraverseToPath(parentPath);
clickFileRowAction(parentRow, 'New file');
if (parentRow) {
clickFileRowAction(parentRow, 'New file');
} else {
await findAndClickRootAction('New file');
}
await findAndSetFileName(path);
await findAndSetEditorValue(content);

View file

@ -1,5 +1,6 @@
import { TEST_HOST } from 'helpers/test_constants';
import { waitForText } from 'helpers/wait_for_text';
import waitForPromises from 'helpers/wait_for_promises';
import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
import { createCommitId } from 'test_helpers/factories/commit_id';
import { initIde } from '~/ide';
@ -86,4 +87,18 @@ describe('WebIDE', () => {
],
});
});
it('user adds file that starts with +', async () => {
createComponent();
await ideHelper.createFile('+test', 'Hello world!');
await ideHelper.openFile('+test');
// Wait for monaco things
await waitForPromises();
// Assert that +test is the only open tab
const tabs = Array.from(document.querySelectorAll('.multi-file-tab'));
expect(tabs.map(x => x.textContent.trim())).toEqual(['+test']);
});
});

View file

@ -6,7 +6,7 @@ beforeEach(() => {
relative_url_root: '',
};
setTestTimeout(5000);
setTestTimeout(7000);
jest.useRealTimers();
});

View file

@ -30,7 +30,8 @@ RSpec.describe Backup::Artifacts do
it 'excludes tmp from backup tar' do
expect(backup).to receive(:tar).and_return('blabla-tar')
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/gitlab-artifacts -cf - .), 'gzip -c -1'], any_args)
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/gitlab-artifacts -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
backup.dump
end
end

View file

@ -6,6 +6,10 @@ RSpec.describe Backup::Files do
let(:progress) { StringIO.new }
let!(:project) { create(:project) }
let(:status_0) { double('exit 0', success?: true, exitstatus: 0) }
let(:status_1) { double('exit 1', success?: false, exitstatus: 1) }
let(:status_2) { double('exit 2', success?: false, exitstatus: 2) }
before do
allow(progress).to receive(:puts)
allow(progress).to receive(:print)
@ -24,6 +28,20 @@ RSpec.describe Backup::Files do
allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
end
RSpec::Matchers.define :eq_statuslist do |expected|
match do |actual|
actual.map(&:exitstatus) == expected.map(&:exitstatus)
end
description do
'be an Array of Process::Status with equal exitstatus against expected'
end
failure_message do |actual|
"expected #{actual} exitstatuses list to be equal #{expected} exitstatuses list"
end
end
describe '#restore' do
subject { described_class.new('registry', '/var/gitlab-registry') }
@ -35,8 +53,9 @@ RSpec.describe Backup::Files do
describe 'folders with permission' do
before do
allow(subject).to receive(:run_pipeline!).and_return(true)
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
allow(subject).to receive(:backup_existing_files).and_return(true)
allow(subject).to receive(:pipeline_succeeded?).and_return(true)
allow(Dir).to receive(:glob).with("/var/gitlab-registry/*", File::FNM_DOTMATCH).and_return(["/var/gitlab-registry/.", "/var/gitlab-registry/..", "/var/gitlab-registry/sample1"])
end
@ -54,14 +73,22 @@ RSpec.describe Backup::Files do
expect(subject).to receive(:tar).and_return('blabla-tar')
expect(subject).to receive(:run_pipeline!).with([%w(gzip -cd), %w(blabla-tar --unlink-first --recursive-unlink -C /var/gitlab-registry -xf -)], any_args)
expect(subject).to receive(:pipeline_succeeded?).and_return(true)
subject.restore
end
it 'raises an error on failure' do
expect(subject).to receive(:pipeline_succeeded?).and_return(false)
expect { subject.restore }.to raise_error(/Restore operation failed:/)
end
end
describe 'folders without permissions' do
before do
allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
allow(subject).to receive(:run_pipeline!).and_return(true)
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
allow(subject).to receive(:pipeline_succeeded?).and_return(true)
end
it 'shows error message' do
@ -73,7 +100,8 @@ RSpec.describe Backup::Files do
describe 'folders that are a mountpoint' do
before do
allow(FileUtils).to receive(:mv).and_raise(Errno::EBUSY)
allow(subject).to receive(:run_pipeline!).and_return(true)
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
allow(subject).to receive(:pipeline_succeeded?).and_return(true)
end
it 'shows error message' do
@ -89,7 +117,8 @@ RSpec.describe Backup::Files do
subject { described_class.new('pages', '/var/gitlab-pages', excludes: ['@pages.tmp']) }
before do
allow(subject).to receive(:run_pipeline!).and_return(true)
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
allow(subject).to receive(:pipeline_succeeded?).and_return(true)
end
it 'raises no errors' do
@ -103,29 +132,190 @@ RSpec.describe Backup::Files do
subject.dump
end
it 'raises an error on failure' do
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
expect(subject).to receive(:pipeline_succeeded?).and_return(false)
expect do
subject.dump
end.to raise_error(/Backup operation failed:/)
end
describe 'with STRATEGY=copy' do
before do
stub_env('STRATEGY', 'copy')
allow(Gitlab.config.backup).to receive(:path) { '/var/gitlab-backup' }
allow(File).to receive(:realpath).with("/var/gitlab-backup").and_return("/var/gitlab-backup")
end
it 'excludes tmp dirs from rsync' do
allow(Gitlab.config.backup).to receive(:path) { '/var/gitlab-backup' }
allow(File).to receive(:realpath).with("/var/gitlab-backup").and_return("/var/gitlab-backup")
expect(Gitlab::Popen).to receive(:popen).with(%w(rsync -a --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup)).and_return(['', 0])
subject.dump
end
end
describe '#exclude_dirs' do
it 'prepends a leading dot slash to tar excludes' do
expect(subject.exclude_dirs(:tar)).to eq(['--exclude=lost+found', '--exclude=./@pages.tmp'])
end
it 'raises an error and outputs an error message if rsync failed' do
allow(Gitlab::Popen).to receive(:popen).with(%w(rsync -a --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup)).and_return(['rsync failed', 1])
it 'prepends a leading slash to rsync excludes' do
expect(subject.exclude_dirs(:rsync)).to eq(['--exclude=lost+found', '--exclude=/@pages.tmp'])
expect do
subject.dump
end.to output(/rsync failed/).to_stdout
.and raise_error(/Backup failed/)
end
end
end
describe '#exclude_dirs' do
subject { described_class.new('pages', '/var/gitlab-pages', excludes: ['@pages.tmp']) }
it 'prepends a leading dot slash to tar excludes' do
expect(subject.exclude_dirs(:tar)).to eq(['--exclude=lost+found', '--exclude=./@pages.tmp'])
end
it 'prepends a leading slash to rsync excludes' do
expect(subject.exclude_dirs(:rsync)).to eq(['--exclude=lost+found', '--exclude=/@pages.tmp'])
end
end
describe '#run_pipeline!' do
subject { described_class.new('registry', '/var/gitlab-registry') }
it 'executes an Open3.pipeline for cmd_list' do
expect(Open3).to receive(:pipeline).with(%w[whew command], %w[another cmd], any_args)
subject.run_pipeline!([%w[whew command], %w[another cmd]])
end
it 'returns an empty output on success pipeline' do
expect(subject.run_pipeline!(%w[true true])[1]).to eq('')
end
it 'returns the stderr for failed pipeline' do
expect(
subject.run_pipeline!(['echo OMG: failed command present 1>&2; false', 'true'])[1]
).to match(/OMG: failed/)
end
it 'returns the success status list on success pipeline' do
expect(
subject.run_pipeline!(%w[true true])[0]
).to eq_statuslist([status_0, status_0])
end
it 'returns the failed status in status list for failed commands in pipeline' do
expect(subject.run_pipeline!(%w[false true true])[0]).to eq_statuslist([status_1, status_0, status_0])
expect(subject.run_pipeline!(%w[true false true])[0]).to eq_statuslist([status_0, status_1, status_0])
expect(subject.run_pipeline!(%w[false false true])[0]).to eq_statuslist([status_1, status_1, status_0])
expect(subject.run_pipeline!(%w[false true false])[0]).to eq_statuslist([status_1, status_0, status_1])
expect(subject.run_pipeline!(%w[false false false])[0]).to eq_statuslist([status_1, status_1, status_1])
end
end
describe '#pipeline_succeeded?' do
subject { described_class.new('registry', '/var/gitlab-registry') }
it 'returns true if both tar and gzip succeeeded' do
expect(
subject.pipeline_succeeded?(tar_status: status_0, gzip_status: status_0, output: 'any_output')
).to be_truthy
end
it 'returns false if gzip failed' do
expect(
subject.pipeline_succeeded?(tar_status: status_1, gzip_status: status_1, output: 'any_output')
).to be_falsey
end
context 'if gzip succeeded and tar failed non-critically' do
before do
allow(subject).to receive(:tar_ignore_non_success?).and_return(true)
end
it 'returns true' do
expect(
subject.pipeline_succeeded?(tar_status: status_1, gzip_status: status_0, output: 'any_output')
).to be_truthy
end
end
context 'if gzip succeeded and tar failed in other cases' do
before do
allow(subject).to receive(:tar_ignore_non_success?).and_return(false)
end
it 'returns false' do
expect(
subject.pipeline_succeeded?(tar_status: status_1, gzip_status: status_0, output: 'any_output')
).to be_falsey
end
end
end
describe '#tar_ignore_non_success?' do
subject { described_class.new('registry', '/var/gitlab-registry') }
context 'if `tar` command exits with 1 exitstatus' do
it 'returns true' do
expect(
subject.tar_ignore_non_success?(1, 'any_output')
).to be_truthy
end
it 'outputs a warning' do
expect do
subject.tar_ignore_non_success?(1, 'any_output')
end.to output(/Ignoring tar exit status 1/).to_stdout
end
end
context 'if `tar` command exits with 2 exitstatus with non-critical warning' do
before do
allow(subject).to receive(:noncritical_warning?).and_return(true)
end
it 'returns true' do
expect(
subject.tar_ignore_non_success?(2, 'any_output')
).to be_truthy
end
it 'outputs a warning' do
expect do
subject.tar_ignore_non_success?(2, 'any_output')
end.to output(/Ignoring non-success exit status/).to_stdout
end
end
context 'if `tar` command exits with any other unlisted error' do
before do
allow(subject).to receive(:noncritical_warning?).and_return(false)
end
it 'returns false' do
expect(
subject.tar_ignore_non_success?(2, 'any_output')
).to be_falsey
end
end
end
describe '#noncritical_warning?' do
subject { described_class.new('registry', '/var/gitlab-registry') }
it 'returns true if given text matches noncritical warnings list' do
expect(
subject.noncritical_warning?('tar: .: Cannot mkdir: No such file or directory')
).to be_truthy
expect(
subject.noncritical_warning?('gtar: .: Cannot mkdir: No such file or directory')
).to be_truthy
end
it 'returns false otherwize' do
expect(
subject.noncritical_warning?('unknown message')
).to be_falsey
end
end
end

View file

@ -23,7 +23,8 @@ RSpec.describe Backup::Pages do
allow(Gitlab.config.pages).to receive(:path) { '/var/gitlab-pages' }
expect(subject).to receive(:tar).and_return('blabla-tar')
expect(subject).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./@pages.tmp -C /var/gitlab-pages -cf - .), 'gzip -c -1'], any_args)
expect(subject).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./@pages.tmp -C /var/gitlab-pages -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
expect(subject).to receive(:pipeline_succeeded?).and_return(true)
subject.dump
end
end

View file

@ -32,7 +32,8 @@ RSpec.describe Backup::Uploads do
it 'excludes tmp from backup tar' do
expect(backup).to receive(:tar).and_return('blabla-tar')
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/uploads -cf - .), 'gzip -c -1'], any_args)
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/uploads -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
backup.dump
end
end

View file

@ -12363,10 +12363,10 @@ vue-loader@^15.9.3:
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
vue-router@^3.4.7:
version "3.4.7"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.7.tgz#bf189bafd16f4e4ef783c4a6250a3090f2c1fa1b"
integrity sha512-CbHXue5BLrDivOk5O4eZ0WT4Yj8XwdXa4kCnsEIOzYUPF/07ZukayA2jGxDCJxLc9SgVQX9QX0OuGOwGlVB4Qg==
vue-router@3.4.5:
version "3.4.5"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.5.tgz#d396ec037b35931bdd1e9b7edd86f9788dc15175"
integrity sha512-ioRY5QyDpXM9TDjOX6hX79gtaMXSVDDzSlbIlyAmbHNteIL81WIVB2e+jbzV23vzxtoV0krdS2XHm+GxFg+Nxg==
vue-runtime-helpers@^1.1.2:
version "1.1.2"