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-18 09:08:29 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-18 09:08:29 +0300
commit654859099919ed5fd1896956460ba00568a2d90e (patch)
tree94b7ac45a50f75d674dc9a32d24639bee73bf8ed
parent5ea8a46ef44de37afd98447e8a38f36f925d0af8 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue56
-rw-r--r--config/feature_flags/development/incremental_repository_backup.yml8
-rw-r--r--doc/api/environments.md87
-rw-r--r--doc/raketasks/backup_restore.md19
-rw-r--r--doc/user/application_security/coverage_fuzzing/index.md2
-rw-r--r--doc/user/application_security/dast/checks/598.1.md31
-rw-r--r--doc/user/application_security/dast/checks/index.md1
-rw-r--r--doc/user/application_security/iac_scanning/index.md7
-rw-r--r--lib/backup/gitaly_backup.rb12
-rw-r--r--lib/backup/manager.rb7
-rw-r--r--locale/gitlab.pot21
-rw-r--r--spec/frontend/boards/components/board_form_spec.js14
-rw-r--r--spec/lib/backup/gitaly_backup_spec.rb99
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb20
15 files changed, 343 insertions, 51 deletions
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index f39f9751c83..5fcf9514708 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -3,6 +3,7 @@ import { GlModal, GlAlert } from '@gitlab/ui';
import { mapGetters, mapActions, mapState } from 'vuex';
import { getParameterByName, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { formType } from '../constants';
import createBoardMutation from '../graphql/board_create.mutation.graphql';
@@ -15,6 +16,7 @@ const boardDefaults = {
name: '',
labels: [],
milestone: {},
+ iterationCadence: {},
iteration: {},
assignee: {},
weight: null,
@@ -41,6 +43,7 @@ export default {
BoardConfigurationOptions,
GlAlert,
},
+ mixins: [glFeatureFlagMixin()],
inject: {
fullPath: {
default: '',
@@ -231,9 +234,12 @@ export default {
this.board = { ...boardDefaults, ...this.currentBoard };
}
},
- setIteration(iterationId) {
+ setIteration(iteration) {
+ if (this.glFeatures.iterationCadences) {
+ this.board.iterationCadenceId = iteration.iterationCadenceId;
+ }
this.$set(this.board, 'iteration', {
- id: iterationId,
+ id: iteration.id,
});
},
setBoardLabels(labels) {
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
index 153b0981813..2a79ccc2648 100644
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
+++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
@@ -1,22 +1,28 @@
<script>
import {
+ GlIcon,
GlLoadingIcon,
GlDropdown,
GlDropdownForm,
GlDropdownDivider,
GlDropdownItem,
+ GlDropdownSectionHeader,
GlSearchBoxByType,
} from '@gitlab/ui';
import { __ } from '~/locale';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
export default {
components: {
+ GlIcon,
GlLoadingIcon,
GlDropdown,
GlDropdownForm,
GlDropdownDivider,
GlDropdownItem,
+ GlDropdownSectionHeader,
GlSearchBoxByType,
+ TooltipOnTruncate,
},
props: {
selectText: {
@@ -39,6 +45,11 @@ export default {
required: false,
default: () => [],
},
+ groupedOptions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
isLoading: {
type: Boolean,
required: false,
@@ -79,11 +90,7 @@ export default {
if (Array.isArray(this.selected)) {
return this.selected.some((label) => label.title === option.title);
}
- return (
- this.selected &&
- ((option.name && this.selected.name === option.name) ||
- (option.title && this.selected.title === option.title))
- );
+ return this.selected && option.id && this.selected.id === option.id;
},
showDropdown() {
this.$refs.dropdown.show();
@@ -101,6 +108,9 @@ export default {
// TODO: this has some knowledge of the context where the component is used. We could later rework it.
return option.username || null;
},
+ optionKey(option) {
+ return option.key ? option.key : option.id;
+ },
},
i18n: {
noMatchingResults: __('No matching results'),
@@ -154,10 +164,10 @@ export default {
</template>
<gl-dropdown-item
v-for="option in options"
- :key="option.id"
+ :key="optionKey(option)"
:is-checked="isSelected(option)"
- :is-check-centered="true"
- :is-check-item="true"
+ is-check-centered
+ is-check-item
:avatar-url="avatarUrl(option)"
:secondary-text="secondaryText(option)"
data-testid="unselected-option"
@@ -167,6 +177,36 @@ export default {
{{ option.title }}
</slot>
</gl-dropdown-item>
+ <template v-for="(optionGroup, index) in groupedOptions">
+ <gl-dropdown-divider v-if="index !== 0" :key="index" />
+ <gl-dropdown-section-header :key="optionGroup.id">
+ <div class="gl-display-flex gl-max-w-full">
+ <tooltip-on-truncate
+ :title="optionGroup.title"
+ class="gl-text-truncate gl-flex-grow-1"
+ >
+ {{ optionGroup.title }}
+ </tooltip-on-truncate>
+ <span v-if="optionGroup.secondaryText" class="gl-float-right gl-font-weight-normal">
+ <gl-icon name="clock" class="gl-mr-2" />
+ {{ optionGroup.secondaryText }}
+ </span>
+ </div>
+ </gl-dropdown-section-header>
+ <gl-dropdown-item
+ v-for="option in optionGroup.options"
+ :key="optionKey(option)"
+ :is-checked="isSelected(option)"
+ is-check-centered
+ is-check-item
+ data-testid="unselected-option"
+ @click="selectOption(option)"
+ >
+ <slot name="item" :item="option">
+ {{ option.title }}
+ </slot>
+ </gl-dropdown-item>
+ </template>
<gl-dropdown-item v-if="noOptionsFound" class="gl-pl-6!">
{{ $options.i18n.noMatchingResults }}
</gl-dropdown-item>
diff --git a/config/feature_flags/development/incremental_repository_backup.yml b/config/feature_flags/development/incremental_repository_backup.yml
new file mode 100644
index 00000000000..d9eb97ba327
--- /dev/null
+++ b/config/feature_flags/development/incremental_repository_backup.yml
@@ -0,0 +1,8 @@
+---
+name: incremental_repository_backup
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79589
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/355945
+milestone: '14.9'
+type: development
+group: group::gitaly
+default_enabled: false
diff --git a/doc/api/environments.md b/doc/api/environments.md
index 8188e0e7b85..40d161485ff 100644
--- a/doc/api/environments.md
+++ b/doc/api/environments.md
@@ -35,7 +35,90 @@ Example response:
"name": "review/fix-foo",
"slug": "review-fix-foo-dfjre3",
"external_url": "https://review-fix-foo-dfjre3.gitlab.example.com",
- "state": "available"
+ "state": "available",
+ "created_at": "2019-05-25T18:55:13.252Z",
+ "updated_at": "2019-05-27T18:55:13.252Z",
+ "enable_advanced_logs_querying": false,
+ "logs_api_path": "/project/-/logs/k8s.json?environment_name=review%2Ffix-foo",
+ "last_deployment": {
+ "id": 100,
+ "iid": 34,
+ "ref": "fdroid",
+ "sha": "416d8ea11849050d3d1f5104cf8cf51053e790ab",
+ "created_at": "2019-03-25T18:55:13.252Z",
+ "status": "success",
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "state": "active",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://localhost:3000/root"
+ },
+ "deployable": {
+ "id": 710,
+ "status": "success",
+ "stage": "deploy",
+ "name": "staging",
+ "ref": "fdroid",
+ "tag": false,
+ "coverage": null,
+ "created_at": "2019-03-25T18:55:13.215Z",
+ "started_at": "2019-03-25T12:54:50.082Z",
+ "finished_at": "2019-03-25T18:55:13.216Z",
+ "duration": 21623.13423,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.dev/root",
+ "created_at": "2015-12-21T13:14:24.077Z",
+ "bio": null,
+ "location": null,
+ "public_email": "",
+ "skype": "",
+ "linkedin": "",
+ "twitter": "",
+ "website_url": "",
+ "organization": null
+ },
+ "commit": {
+ "id": "416d8ea11849050d3d1f5104cf8cf51053e790ab",
+ "short_id": "416d8ea1",
+ "created_at": "2016-01-02T15:39:18.000Z",
+ "parent_ids": [
+ "e9a4449c95c64358840902508fc827f1a2eab7df"
+ ],
+ "title": "Removed fabric to fix #40",
+ "message": "Removed fabric to fix #40\n",
+ "author_name": "Administrator",
+ "author_email": "admin@example.com",
+ "authored_date": "2016-01-02T15:39:18.000Z",
+ "committer_name": "Administrator",
+ "committer_email": "admin@example.com",
+ "committed_date": "2016-01-02T15:39:18.000Z"
+ },
+ "pipeline": {
+ "id": 34,
+ "sha": "416d8ea11849050d3d1f5104cf8cf51053e790ab",
+ "ref": "fdroid",
+ "status": "success",
+ "web_url": "http://localhost:3000/Commit451/lab-coat/pipelines/34"
+ },
+ "web_url": "http://localhost:3000/Commit451/lab-coat/-/jobs/710",
+ "artifacts": [
+ {
+ "file_type": "trace",
+ "size": 1305,
+ "filename": "job.log",
+ "file_format": null
+ }
+ ],
+ "runner": null,
+ "artifacts_expire_at": null
+ }
}
]
```
@@ -66,6 +149,8 @@ Example of response
"state": "available",
"created_at": "2019-05-25T18:55:13.252Z",
"updated_at": "2019-05-27T18:55:13.252Z",
+ "enable_advanced_logs_querying": false,
+ "logs_api_path": "/project/-/logs/k8s.json?environment_name=review%2Ffix-foo",
"last_deployment": {
"id": 100,
"iid": 34,
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 39162230cc2..cef52cc61d7 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -1854,3 +1854,22 @@ To enable it:
```ruby
Feature.enable(:gitaly_backup)
```
+
+### Incremental repository backups
+
+> Introduced in GitLab 14.9 [with a flag](../administration/feature_flags.md) named `incremental_repository_backup`. Disabled by default.
+
+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 `incremental_repository_backup`.
+On GitLab.com, this feature is not available.
+This feature is not ready for production use.
+
+Incremental backups can be faster than full backups because they only pack changes since the last backup into the backup
+bundle for each repository. Because incremental backups require access to the previous backup, you can't use incremental
+backups with tar files.
+
+To create an incremental backup, run:
+
+```shell
+sudo gitlab-backup create SKIP=tar INCREMENTAL=yes
+```
diff --git a/doc/user/application_security/coverage_fuzzing/index.md b/doc/user/application_security/coverage_fuzzing/index.md
index a893106f52a..14e98766f0f 100644
--- a/doc/user/application_security/coverage_fuzzing/index.md
+++ b/doc/user/application_security/coverage_fuzzing/index.md
@@ -121,7 +121,7 @@ Use the following variables to configure coverage-guided fuzz testing in your CI
| `COVFUZZ_URL_PREFIX` | Path to the `gitlab-cov-fuzz` repository cloned for use with an offline environment. You should only change this value when using an offline environment. Default: `https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw`. |
| `COVFUZZ_USE_REGISTRY` | Set to `true` to have the corpus stored in the GitLab corpus registry. The variables `COVFUZZ_CORPUS_NAME` and `COVFUZZ_GITLAB_TOKEN` are required if this variable is set to `true`. Default: `false`. [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5017) in GitLab 14.8. |
| `COVFUZZ_CORPUS_NAME` | Name of the corpus to be used in the job. [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5017) in GitLab 14.8. |
-| `COVFUZZ_GITLAB_TOKEN` | Environment variable configured with [Personal Access Token](../../../user/profile/personal_access_tokens.md#create-a-personal-access-token) with API read/write access. [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5017) in GitLab 14.8. |
+| `COVFUZZ_GITLAB_TOKEN` | Environment variable configured with [Personal Access Token](../../../user/profile/personal_access_tokens.md#create-a-personal-access-token) or [Project Access Token](../../../user/project/settings/project_access_tokens.md#create-a-project-access-token) with API read/write access. [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5017) in GitLab 14.8. |
#### Seed corpus
diff --git a/doc/user/application_security/dast/checks/598.1.md b/doc/user/application_security/dast/checks/598.1.md
new file mode 100644
index 00000000000..817e20ec413
--- /dev/null
+++ b/doc/user/application_security/dast/checks/598.1.md
@@ -0,0 +1,31 @@
+---
+stage: Secure
+group: Dynamic Analysis
+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
+---
+
+# Use of GET request method with sensitive query strings (session ID)
+
+## Description
+
+A session ID was identified in the request URL as well as a cookie value. Session
+IDs should not be sent in GET requests as they maybe captured by proxy systems, stored in
+browser history, or stored in log files. If an attacker were to get access to the session
+ID they would potentially be able to gain access to the target account.
+
+## Remediation
+
+As request headers are rarely logged or captured by third party systems, ensure session ID
+values are only sent in cookies (assigned via `Set-Cookie` response headers) and never sent
+in the request URL.
+
+## Details
+
+| ID | Aggregated | CWE | Type | Risk |
+|:---|:--------|:--------|:--------|:--------|
+| 598.1 | true | 598 | Passive | Medium |
+
+## Links
+
+- [OWASP](https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url)
+- [CWE](https://cwe.mitre.org/data/definitions/598.html)
diff --git a/doc/user/application_security/dast/checks/index.md b/doc/user/application_security/dast/checks/index.md
index 97224554723..435bc28c4aa 100644
--- a/doc/user/application_security/dast/checks/index.md
+++ b/doc/user/application_security/dast/checks/index.md
@@ -19,5 +19,6 @@ The [DAST browser-based crawler](../browser_based.md) provides a number of vulne
| [16.6](16.6.md) | AspNetMvc header exposes version information | Low | Passive |
| [200.1](200.1.md) | Exposure of sensitive information to an unauthorized actor (private IP address) | Low | Passive |
| [548.1](548.1.md) | Exposure of information through directory listing | Low | Passive |
+| [598.1](598.1.md) | Use of GET request method with sensitive query strings (session ID) | Medium | Passive |
| [614.1](614.1.md) | Sensitive cookie without Secure attribute | Low | Passive |
| [693.1](693.1.md) | Missing X-Content-Type-Options: nosniff | Low | Passive |
diff --git a/doc/user/application_security/iac_scanning/index.md b/doc/user/application_security/iac_scanning/index.md
index 884dc24e20f..b72f54b4493 100644
--- a/doc/user/application_security/iac_scanning/index.md
+++ b/doc/user/application_security/iac_scanning/index.md
@@ -76,7 +76,12 @@ To configure IaC Scanning for a project you can:
### Configure IaC Scanning manually
To enable IaC Scanning you must [include](../../../ci/yaml/index.md#includetemplate) the
-[`SAST-IaC.latest.gitlab-ci.yml template`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST-IaC.latest.gitlab-ci.yml) provided as part of your GitLab installation.
+[`SAST-IaC.latest.gitlab-ci.yml template`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST-IaC.latest.gitlab-ci.yml) provided as part of your GitLab installation. Here is an example of how to include it:
+
+```yaml
+include:
+ - template: Security/SAST-IaC.latest.gitlab-ci.yml
+```
The included template creates IaC scanning jobs in your CI/CD pipeline and scans
your project's configuration files for possible vulnerabilities.
diff --git a/lib/backup/gitaly_backup.rb b/lib/backup/gitaly_backup.rb
index 149aa00c2ce..b688ff7f13b 100644
--- a/lib/backup/gitaly_backup.rb
+++ b/lib/backup/gitaly_backup.rb
@@ -9,10 +9,13 @@ module Backup
# @param [StringIO] progress IO interface to output progress
# @param [Integer] max_parallelism max parallelism when running backups
# @param [Integer] storage_parallelism max parallelism per storage (is affected by max_parallelism)
- def initialize(progress, max_parallelism: nil, storage_parallelism: nil)
+ # @param [String] backup_id unique identifier for the backup
+ def initialize(progress, max_parallelism: nil, storage_parallelism: nil, incremental: false, backup_id: nil)
@progress = progress
@max_parallelism = max_parallelism
@storage_parallelism = storage_parallelism
+ @incremental = incremental
+ @backup_id = backup_id
end
def start(type, backup_repos_path)
@@ -30,6 +33,13 @@ module Backup
args = []
args += ['-parallel', @max_parallelism.to_s] if @max_parallelism
args += ['-parallel-storage', @storage_parallelism.to_s] if @storage_parallelism
+ if Feature.enabled?(:incremental_repository_backup, default_enabled: :yaml)
+ args += ['-layout', 'pointer']
+ if type == :create
+ args += ['-incremental'] if @incremental
+ args += ['-id', @backup_id] if @backup_id
+ end
+ end
@input_stream, stdout, @thread = Open3.popen2(build_env, bin_path, command, '-path', backup_repos_path, *args)
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index a19fcd6fede..6e90824fce2 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -21,6 +21,7 @@ module Backup
max_concurrency = ENV.fetch('GITLAB_BACKUP_MAX_CONCURRENCY', 1).to_i
max_storage_concurrency = ENV.fetch('GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY', 1).to_i
force = ENV['force'] == 'yes'
+ incremental = Gitlab::Utils.to_boolean(ENV['INCREMENTAL'], default: false)
@definitions = definitions || {
'db' => TaskDefinition.new(
@@ -32,7 +33,7 @@ module Backup
destination_path: 'repositories',
destination_optional: true,
task: Repositories.new(progress,
- strategy: repository_backup_strategy,
+ strategy: repository_backup_strategy(incremental),
max_concurrency: max_concurrency,
max_storage_concurrency: max_storage_concurrency)
),
@@ -481,11 +482,11 @@ module Backup
Gitlab.config.backup.upload.connection&.provider&.downcase == 'google'
end
- def repository_backup_strategy
+ def repository_backup_strategy(incremental)
if Feature.enabled?(:gitaly_backup, default_enabled: :yaml)
max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence
max_storage_concurrency = ENV['GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY'].presence
- Backup::GitalyBackup.new(progress, max_parallelism: max_concurrency, storage_parallelism: max_storage_concurrency)
+ Backup::GitalyBackup.new(progress, incremental: incremental, max_parallelism: max_concurrency, storage_parallelism: max_storage_concurrency)
else
Backup::GitalyRpcBackup.new(progress)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 95267f78373..e8075deb16c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5852,6 +5852,9 @@ msgstr ""
msgid "BoardNewIssue|Select a project"
msgstr ""
+msgid "BoardScope|An error occurred while getting iterations. Please try again."
+msgstr ""
+
msgid "BoardScope|An error occurred while getting milestones, please try again."
msgstr ""
@@ -5867,6 +5870,9 @@ msgstr ""
msgid "BoardScope|Any assignee"
msgstr ""
+msgid "BoardScope|Any iteration"
+msgstr ""
+
msgid "BoardScope|Any label"
msgstr ""
@@ -5876,24 +5882,39 @@ msgstr ""
msgid "BoardScope|Choose labels"
msgstr ""
+msgid "BoardScope|Current iteration"
+msgstr ""
+
msgid "BoardScope|Edit"
msgstr ""
+msgid "BoardScope|Iteration"
+msgstr ""
+
msgid "BoardScope|Labels"
msgstr ""
msgid "BoardScope|Milestone"
msgstr ""
+msgid "BoardScope|No iteration"
+msgstr ""
+
msgid "BoardScope|No milestone"
msgstr ""
+msgid "BoardScope|Search iterations"
+msgstr ""
+
msgid "BoardScope|Search milestones"
msgstr ""
msgid "BoardScope|Select assignee"
msgstr ""
+msgid "BoardScope|Select iteration"
+msgstr ""
+
msgid "BoardScope|Select labels"
msgstr ""
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index 5678da2a246..c976ba7525b 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -1,6 +1,6 @@
import { GlModal } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import setWindowLocation from 'helpers/set_window_location_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import BoardForm from '~/boards/components/board_form.vue';
@@ -22,6 +22,8 @@ const currentBoard = {
labels: [],
milestone: {},
assignee: {},
+ iteration: {},
+ iterationCadence: {},
weight: null,
hideBacklogList: false,
hideClosedList: false,
@@ -37,11 +39,11 @@ describe('BoardForm', () => {
let wrapper;
let mutate;
- const findModal = () => wrapper.find(GlModal);
+ const findModal = () => wrapper.findComponent(GlModal);
const findModalActionPrimary = () => findModal().props('actionPrimary');
- const findForm = () => wrapper.find('[data-testid="board-form"]');
- const findFormWrapper = () => wrapper.find('[data-testid="board-form-wrapper"]');
- const findDeleteConfirmation = () => wrapper.find('[data-testid="delete-confirmation-message"]');
+ const findForm = () => wrapper.findByTestId('board-form');
+ const findFormWrapper = () => wrapper.findByTestId('board-form-wrapper');
+ const findDeleteConfirmation = () => wrapper.findByTestId('delete-confirmation-message');
const findInput = () => wrapper.find('#board-new-name');
const store = createStore({
@@ -52,7 +54,7 @@ describe('BoardForm', () => {
});
const createComponent = (props, data) => {
- wrapper = shallowMount(BoardForm, {
+ wrapper = shallowMountExtended(BoardForm, {
propsData: { ...defaultProps, ...props },
data() {
return {
diff --git a/spec/lib/backup/gitaly_backup_spec.rb b/spec/lib/backup/gitaly_backup_spec.rb
index 84ee75e27ac..f5295c2b04c 100644
--- a/spec/lib/backup/gitaly_backup_spec.rb
+++ b/spec/lib/backup/gitaly_backup_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Backup::GitalyBackup do
let(:max_parallelism) { nil }
let(:storage_parallelism) { nil }
let(:destination) { File.join(Gitlab.config.backup.path, 'repositories') }
+ let(:backup_id) { '20220101' }
let(:progress) do
Tempfile.new('progress').tap do |progress|
@@ -24,7 +25,7 @@ RSpec.describe Backup::GitalyBackup do
progress.close
end
- subject { described_class.new(progress, max_parallelism: max_parallelism, storage_parallelism: storage_parallelism) }
+ subject { described_class.new(progress, max_parallelism: max_parallelism, storage_parallelism: storage_parallelism, backup_id: backup_id) }
context 'unknown' do
it 'fails to start unknown' do
@@ -41,7 +42,7 @@ RSpec.describe Backup::GitalyBackup do
project_snippet = create(:project_snippet, :repository, project: project)
personal_snippet = create(:personal_snippet, :repository, author: project.first_owner)
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything).and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-layout', 'pointer', '-id', backup_id).and_call_original
subject.start(:create, destination)
subject.enqueue(project, Gitlab::GlRepository::PROJECT)
@@ -51,18 +52,18 @@ RSpec.describe Backup::GitalyBackup do
subject.enqueue(project_snippet, Gitlab::GlRepository::SNIPPET)
subject.finish!
- expect(File).to exist(File.join(destination, project.disk_path + '.bundle'))
- expect(File).to exist(File.join(destination, project.disk_path + '.wiki.bundle'))
- expect(File).to exist(File.join(destination, project.disk_path + '.design.bundle'))
- expect(File).to exist(File.join(destination, personal_snippet.disk_path + '.bundle'))
- expect(File).to exist(File.join(destination, project_snippet.disk_path + '.bundle'))
+ expect(File).to exist(File.join(destination, project.disk_path, backup_id, '001.bundle'))
+ expect(File).to exist(File.join(destination, project.disk_path + '.wiki', backup_id, '001.bundle'))
+ expect(File).to exist(File.join(destination, project.disk_path + '.design', backup_id, '001.bundle'))
+ expect(File).to exist(File.join(destination, personal_snippet.disk_path, backup_id, '001.bundle'))
+ expect(File).to exist(File.join(destination, project_snippet.disk_path, backup_id, '001.bundle'))
end
context 'parallel option set' do
let(:max_parallelism) { 3 }
it 'passes parallel option through' do
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-parallel', '3').and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-parallel', '3', '-layout', 'pointer', '-id', backup_id).and_call_original
subject.start(:create, destination)
subject.finish!
@@ -73,7 +74,7 @@ RSpec.describe Backup::GitalyBackup do
let(:storage_parallelism) { 3 }
it 'passes parallel option through' do
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-parallel-storage', '3').and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-parallel-storage', '3', '-layout', 'pointer', '-id', backup_id).and_call_original
subject.start(:create, destination)
subject.finish!
@@ -86,6 +87,36 @@ RSpec.describe Backup::GitalyBackup do
subject.start(:create, destination)
expect { subject.finish! }.to raise_error(::Backup::Error, 'gitaly-backup exit status 1')
end
+
+ context 'feature flag incremental_repository_backup disabled' do
+ before do
+ stub_feature_flags(incremental_repository_backup: false)
+ end
+
+ it 'creates repository bundles', :aggregate_failures do
+ # Add data to the wiki, design repositories, and snippets, so they will be included in the dump.
+ create(:wiki_page, container: project)
+ create(:design, :with_file, issue: create(:issue, project: project))
+ project_snippet = create(:project_snippet, :repository, project: project)
+ personal_snippet = create(:personal_snippet, :repository, author: project.first_owner)
+
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything).and_call_original
+
+ subject.start(:create, destination)
+ subject.enqueue(project, Gitlab::GlRepository::PROJECT)
+ subject.enqueue(project, Gitlab::GlRepository::WIKI)
+ subject.enqueue(project, Gitlab::GlRepository::DESIGN)
+ subject.enqueue(personal_snippet, Gitlab::GlRepository::SNIPPET)
+ subject.enqueue(project_snippet, Gitlab::GlRepository::SNIPPET)
+ subject.finish!
+
+ expect(File).to exist(File.join(destination, project.disk_path + '.bundle'))
+ expect(File).to exist(File.join(destination, project.disk_path + '.wiki.bundle'))
+ expect(File).to exist(File.join(destination, project.disk_path + '.design.bundle'))
+ expect(File).to exist(File.join(destination, personal_snippet.disk_path + '.bundle'))
+ expect(File).to exist(File.join(destination, project_snippet.disk_path + '.bundle'))
+ end
+ end
end
context 'hashed storage' do
@@ -113,7 +144,7 @@ RSpec.describe Backup::GitalyBackup do
end
it 'passes through SSL envs' do
- expect(Open3).to receive(:popen2).with(ssl_env, anything, 'create', '-path', anything).and_call_original
+ expect(Open3).to receive(:popen2).with(ssl_env, anything, 'create', '-path', anything, '-layout', 'pointer', '-id', backup_id).and_call_original
subject.start(:create, destination)
subject.finish!
@@ -138,7 +169,7 @@ RSpec.describe Backup::GitalyBackup do
copy_bundle_to_backup_path('personal_snippet_repo.bundle', personal_snippet.disk_path + '.bundle')
copy_bundle_to_backup_path('project_snippet_repo.bundle', project_snippet.disk_path + '.bundle')
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything).and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer').and_call_original
subject.start(:restore, destination)
subject.enqueue(project, Gitlab::GlRepository::PROJECT)
@@ -150,18 +181,18 @@ RSpec.describe Backup::GitalyBackup do
collect_commit_shas = -> (repo) { repo.commits('master', limit: 10).map(&:sha) }
- expect(collect_commit_shas.call(project.repository)).to eq(['393a7d860a5a4c3cc736d7eb00604e3472bb95ec'])
- expect(collect_commit_shas.call(project.wiki.repository)).to eq(['c74b9948d0088d703ee1fafeddd9ed9add2901ea'])
- expect(collect_commit_shas.call(project.design_repository)).to eq(['c3cd4d7bd73a51a0f22045c3a4c871c435dc959d'])
- expect(collect_commit_shas.call(personal_snippet.repository)).to eq(['3b3c067a3bc1d1b695b51e2be30c0f8cf698a06e'])
- expect(collect_commit_shas.call(project_snippet.repository)).to eq(['6e44ba56a4748be361a841e759c20e421a1651a1'])
+ expect(collect_commit_shas.call(project.repository)).to match_array(['393a7d860a5a4c3cc736d7eb00604e3472bb95ec'])
+ expect(collect_commit_shas.call(project.wiki.repository)).to match_array(['c74b9948d0088d703ee1fafeddd9ed9add2901ea'])
+ expect(collect_commit_shas.call(project.design_repository)).to match_array(['c3cd4d7bd73a51a0f22045c3a4c871c435dc959d'])
+ expect(collect_commit_shas.call(personal_snippet.repository)).to match_array(['3b3c067a3bc1d1b695b51e2be30c0f8cf698a06e'])
+ expect(collect_commit_shas.call(project_snippet.repository)).to match_array(['6e44ba56a4748be361a841e759c20e421a1651a1'])
end
context 'parallel option set' do
let(:max_parallelism) { 3 }
it 'passes parallel option through' do
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-parallel', '3').and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-parallel', '3', '-layout', 'pointer').and_call_original
subject.start(:restore, destination)
subject.finish!
@@ -172,13 +203,45 @@ RSpec.describe Backup::GitalyBackup do
let(:storage_parallelism) { 3 }
it 'passes parallel option through' do
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-parallel-storage', '3').and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-parallel-storage', '3', '-layout', 'pointer').and_call_original
subject.start(:restore, destination)
subject.finish!
end
end
+ context 'feature flag incremental_repository_backup disabled' do
+ before do
+ stub_feature_flags(incremental_repository_backup: false)
+ end
+
+ it 'restores from repository bundles', :aggregate_failures do
+ copy_bundle_to_backup_path('project_repo.bundle', project.disk_path + '.bundle')
+ copy_bundle_to_backup_path('wiki_repo.bundle', project.disk_path + '.wiki.bundle')
+ copy_bundle_to_backup_path('design_repo.bundle', project.disk_path + '.design.bundle')
+ copy_bundle_to_backup_path('personal_snippet_repo.bundle', personal_snippet.disk_path + '.bundle')
+ copy_bundle_to_backup_path('project_snippet_repo.bundle', project_snippet.disk_path + '.bundle')
+
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything).and_call_original
+
+ subject.start(:restore, destination)
+ subject.enqueue(project, Gitlab::GlRepository::PROJECT)
+ subject.enqueue(project, Gitlab::GlRepository::WIKI)
+ subject.enqueue(project, Gitlab::GlRepository::DESIGN)
+ subject.enqueue(personal_snippet, Gitlab::GlRepository::SNIPPET)
+ subject.enqueue(project_snippet, Gitlab::GlRepository::SNIPPET)
+ subject.finish!
+
+ collect_commit_shas = -> (repo) { repo.commits('master', limit: 10).map(&:sha) }
+
+ expect(collect_commit_shas.call(project.repository)).to match_array(['393a7d860a5a4c3cc736d7eb00604e3472bb95ec'])
+ expect(collect_commit_shas.call(project.wiki.repository)).to match_array(['c74b9948d0088d703ee1fafeddd9ed9add2901ea'])
+ expect(collect_commit_shas.call(project.design_repository)).to match_array(['c3cd4d7bd73a51a0f22045c3a4c871c435dc959d'])
+ expect(collect_commit_shas.call(personal_snippet.repository)).to match_array(['3b3c067a3bc1d1b695b51e2be30c0f8cf698a06e'])
+ expect(collect_commit_shas.call(project_snippet.repository)).to match_array(['6e44ba56a4748be361a841e759c20e421a1651a1'])
+ end
+ end
+
it 'raises when the exit code not zero' do
expect(subject).to receive(:bin_path).and_return(Gitlab::Utils.which('false'))
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 3b64034fc2d..df9f2a0d3bb 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -176,8 +176,8 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect(exit_status).to eq(0)
expect(tar_contents).to match(user_backup_path)
- expect(tar_contents).to match("#{user_backup_path}/custom_hooks.tar")
- expect(tar_contents).to match("#{user_backup_path}.bundle")
+ expect(tar_contents).to match("#{user_backup_path}/.+/001.custom_hooks.tar")
+ expect(tar_contents).to match("#{user_backup_path}/.+/001.bundle")
end
it 'restores files correctly' do
@@ -360,14 +360,14 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect(exit_status).to eq(0)
[
- "#{project_a.disk_path}.bundle",
- "#{project_a.disk_path}.wiki.bundle",
- "#{project_a.disk_path}.design.bundle",
- "#{project_b.disk_path}.bundle",
- "#{project_snippet_a.disk_path}.bundle",
- "#{project_snippet_b.disk_path}.bundle"
+ "#{project_a.disk_path}/.+/001.bundle",
+ "#{project_a.disk_path}.wiki/.+/001.bundle",
+ "#{project_a.disk_path}.design/.+/001.bundle",
+ "#{project_b.disk_path}/.+/001.bundle",
+ "#{project_snippet_a.disk_path}/.+/001.bundle",
+ "#{project_snippet_b.disk_path}/.+/001.bundle"
].each do |repo_name|
- expect(tar_lines.grep(/#{repo_name}/).size).to eq 1
+ expect(tar_lines).to include(a_string_matching(repo_name))
end
end
@@ -428,7 +428,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect(::Backup::Repositories).to receive(:new)
.with(anything, strategy: anything, max_concurrency: 5, max_storage_concurrency: 2)
.and_call_original
- expect(::Backup::GitalyBackup).to receive(:new).with(anything, max_parallelism: 5, storage_parallelism: 2).and_call_original
+ expect(::Backup::GitalyBackup).to receive(:new).with(anything, max_parallelism: 5, storage_parallelism: 2, incremental: false).and_call_original
expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout_from_any_process
end