summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-06-15 00:10:11 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-06-15 00:10:11 +0000
commit94668f5cca234c8132d1da1f5e91a3c76f5337fb (patch)
tree7d0127de84e51ba06ac3ec53b3c42e2beb35df97
parent5b829393a732143e31e2f9a62b6ca2cfb7ebb147 (diff)
downloadgitlab-ce-94668f5cca234c8132d1da1f5e91a3c76f5337fb.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/images/aws-cloud-formation.pngbin0 -> 2545 bytes
-rw-r--r--app/assets/javascripts/ide/constants.js2
-rw-r--r--app/assets/javascripts/ide/ide_router.js10
-rw-r--r--app/assets/javascripts/ide/index.js3
-rw-r--r--app/assets/javascripts/ide/utils.js2
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js2
-rw-r--r--app/assets/javascripts/pages/shared/mount_runner_aws_deployments.js17
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue77
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue6
-rw-r--r--app/assets/javascripts/pipelines/pipelines_index.js2
-rw-r--r--app/assets/javascripts/static_site_editor/rich_content_editor/services/renderers/render_identifier_instance_text.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js49
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue43
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue97
-rw-r--r--app/assets/stylesheets/components/rich_content_editor.scss2
-rw-r--r--app/controllers/groups_controller.rb3
-rw-r--r--app/controllers/projects/pipelines_controller.rb62
-rw-r--r--app/helpers/groups_helper.rb12
-rw-r--r--app/helpers/ide_helper.rb1
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/namespace_setting.rb3
-rw-r--r--app/policies/group_policy.rb1
-rw-r--r--app/services/namespace_settings/update_service.rb10
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml30
-rw-r--r--app/views/admin/application_settings/ci_cd.html.haml2
-rw-r--r--app/views/ci/runner/_setup_runner_in_aws.html.haml16
-rw-r--r--app/views/groups/runners/_group_runners.html.haml4
-rw-r--r--app/views/groups/settings/_permissions.html.haml12
-rw-r--r--app/views/projects/pipelines/index.html.haml3
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml2
-rw-r--r--config/feature_flags/experiment/ci_runner_templates.yml8
-rw-r--r--db/post_migrate/20210610141711_disable_expiration_policies_linked_to_no_container_images.rb32
-rw-r--r--db/schema_migrations/202106101417111
-rw-r--r--doc/administration/compliance.md2
-rw-r--r--doc/administration/geo/replication/datatypes.md4
-rw-r--r--doc/api/settings.md2
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/ci/environments/deployment_safety.md2
-rw-r--r--doc/ci/pipelines/settings.md8
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/user/admin_area/custom_project_templates.md10
-rw-r--r--doc/user/admin_area/index.md11
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md42
-rw-r--r--doc/user/admin_area/settings/img/continuous_integration_shared_runner_details_v14_0.pngbin0 -> 80106 bytes
-rw-r--r--doc/user/admin_area/settings/import_export_rate_limits.md2
-rw-r--r--doc/user/admin_area/settings/package_registry_rate_limits.md5
-rw-r--r--doc/user/application_security/sast/index.md1
-rw-r--r--doc/user/group/devops_adoption/index.md2
-rw-r--r--doc/user/group/index.md24
-rw-r--r--doc/user/group/settings/import_export.md6
-rw-r--r--doc/user/project/settings/import_export.md6
-rw-r--r--doc/user/project/time_tracking.md4
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml43
-rw-r--r--lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml261
-rw-r--r--locale/gitlab.pot89
-rw-r--r--spec/controllers/groups_controller_spec.rb39
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb26
-rw-r--r--spec/features/groups/group_settings_spec.rb20
-rw-r--r--spec/frontend/ide/components/branches/item_spec.js2
-rw-r--r--spec/frontend/ide/components/commit_sidebar/actions_spec.js14
-rw-r--r--spec/frontend/ide/components/commit_sidebar/form_spec.js2
-rw-r--r--spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js6
-rw-r--r--spec/frontend/ide/components/ide_review_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_status_bar_spec.js2
-rw-r--r--spec/frontend/ide/components/ide_tree_list_spec.js6
-rw-r--r--spec/frontend/ide/components/ide_tree_spec.js4
-rw-r--r--spec/frontend/ide/components/merge_requests/list_spec.js2
-rw-r--r--spec/frontend/ide/components/nav_dropdown_spec.js4
-rw-r--r--spec/frontend/ide/components/new_dropdown/index_spec.js2
-rw-r--r--spec/frontend/ide/components/repo_commit_section_spec.js6
-rw-r--r--spec/frontend/ide/components/repo_editor_spec.js6
-rw-r--r--spec/frontend/ide/ide_router_spec.js12
-rw-r--r--spec/frontend/ide/mock_data.js14
-rw-r--r--spec/frontend/ide/services/index_spec.js2
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js16
-rw-r--r--spec/frontend/ide/stores/actions/merge_request_spec.js4
-rw-r--r--spec/frontend/ide/stores/actions/project_spec.js32
-rw-r--r--spec/frontend/ide/stores/actions/tree_spec.js22
-rw-r--r--spec/frontend/ide/stores/actions_spec.js6
-rw-r--r--spec/frontend/ide/stores/getters_spec.js36
-rw-r--r--spec/frontend/ide/stores/modules/commit/actions_spec.js22
-rw-r--r--spec/frontend/ide/stores/modules/commit/getters_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js2
-rw-r--r--spec/frontend/ide/stores/mutations/branch_spec.js10
-rw-r--r--spec/frontend/ide/stores/mutations/file_spec.js6
-rw-r--r--spec/frontend/ide/stores/mutations/tree_spec.js24
-rw-r--r--spec/frontend/ide/stores/mutations_spec.js22
-rw-r--r--spec/frontend/ide/stores/utils_spec.js18
-rw-r--r--spec/frontend/ide/utils_spec.js34
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js28
-rw-r--r--spec/frontend/vue_shared/components/runner_aws_deployments/__snapshots__/runner_aws_deployments_modal_spec.js.snap110
-rw-r--r--spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js75
-rw-r--r--spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_spec.js41
-rw-r--r--spec/frontend/vue_shared/components/web_ide_link_spec.js4
-rw-r--r--spec/lib/gitlab/ci/templates/templates_spec.rb162
-rw-r--r--spec/migrations/disable_expiration_policies_linked_to_no_container_images_spec.rb46
-rw-r--r--spec/services/namespace_settings/update_service_spec.rb32
-rw-r--r--spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb20
103 files changed, 1378 insertions, 631 deletions
diff --git a/app/assets/images/aws-cloud-formation.png b/app/assets/images/aws-cloud-formation.png
new file mode 100644
index 00000000000..1d078309d86
--- /dev/null
+++ b/app/assets/images/aws-cloud-formation.png
Binary files differ
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index 6304423a3c0..4845b667b40 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -112,3 +112,5 @@ export const LIVE_PREVIEW_DEBOUNCE = 2000;
// This is the maximum number of files to auto open when opening the Web IDE
// from a merge request
export const MAX_MR_FILES_AUTO_OPEN = 10;
+
+export const DEFAULT_BRANCH = 'main';
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index cb59cd7a8df..5f60bf0269d 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -16,8 +16,8 @@ Vue.use(IdeRouter);
/**
* Routes below /-/ide/:
-/project/h5bp/html5-boilerplate/blob/master
-/project/h5bp/html5-boilerplate/blob/master/app/js/test.js
+/project/h5bp/html5-boilerplate/blob/main
+/project/h5bp/html5-boilerplate/blob/main/app/js/test.js
/project/h5bp/html5-boilerplate/mr/123
/project/h5bp/html5-boilerplate/mr/123/app/js/test.js
@@ -39,7 +39,7 @@ const EmptyRouterComponent = {
},
};
-export const createRouter = (store) => {
+export const createRouter = (store, defaultBranch) => {
const router = new IdeRouter({
mode: 'history',
base: joinPaths(gon.relative_url_root || '', '/-/ide/'),
@@ -58,7 +58,7 @@ export const createRouter = (store) => {
},
{
path: ':targetmode(edit|tree|blob)',
- redirect: (to) => joinPaths(to.path, '/master/-/'),
+ redirect: (to) => joinPaths(to.path, `/${defaultBranch}/-/`),
},
{
path: 'merge_requests/:mrid',
@@ -66,7 +66,7 @@ export const createRouter = (store) => {
},
{
path: '',
- redirect: (to) => joinPaths(to.path, '/edit/master/-/'),
+ redirect: (to) => joinPaths(to.path, `/edit/${defaultBranch}/-/`),
},
],
},
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index 7109c45a3fe..e8c726d6184 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -1,6 +1,7 @@
import { identity } from 'lodash';
import Vue from 'vue';
import { mapActions } from 'vuex';
+import { DEFAULT_BRANCH } from '~/ide/constants';
import PerformancePlugin from '~/performance/vue_performance_plugin';
import Translate from '~/vue_shared/translate';
import { parseBoolean } from '../lib/utils/common_utils';
@@ -38,7 +39,7 @@ export function initIde(el, options = {}) {
const { rootComponent = ide, extendStore = identity } = options;
const store = createStore();
- const router = createRouter(store);
+ const router = createRouter(store, el.dataset.defaultBranch || DEFAULT_BRANCH);
return new Vue({
el,
diff --git a/app/assets/javascripts/ide/utils.js b/app/assets/javascripts/ide/utils.js
index 63c53737119..275fecc5a32 100644
--- a/app/assets/javascripts/ide/utils.js
+++ b/app/assets/javascripts/ide/utils.js
@@ -146,7 +146,7 @@ export function getFileEOL(content = '') {
* hello.md -> hello-1.md
* hello_2.md -> hello_3.md
* hello_ -> hello_1
- * master-patch-22432 -> master-patch-22433
+ * main-patch-22432 -> main-patch-22433
* patch_332 -> patch_333
*
* @param {string} filename File name or branch name
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index 636eea5d7ac..a8d7a83cdd6 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -3,6 +3,7 @@ import GroupRunnersFilteredSearchTokenKeys from '~/filtered_search/group_runners
import initSharedRunnersForm from '~/group_settings/mount_shared_runners';
import { FILTERED_SEARCH } from '~/pages/constants';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import { initRunnerAwsDeployments } from '~/pages/shared/mount_runner_aws_deployments';
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
import initSettingsPanels from '~/settings_panels';
@@ -20,3 +21,4 @@ initSharedRunnersForm();
initVariableList();
initInstallRunner();
+initRunnerAwsDeployments();
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index 10105af3561..db7b3bad6ed 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -4,6 +4,7 @@ import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
import initVariableList from '~/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze';
import registrySettingsApp from '~/packages_and_registries/settings/project/registry_settings_bundle';
+import { initRunnerAwsDeployments } from '~/pages/shared/mount_runner_aws_deployments';
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle';
import initSettingsPanels from '~/settings_panels';
@@ -38,4 +39,5 @@ document.addEventListener('DOMContentLoaded', () => {
initArtifactsSettings();
initSharedRunnersToggle();
initInstallRunner();
+ initRunnerAwsDeployments();
});
diff --git a/app/assets/javascripts/pages/shared/mount_runner_aws_deployments.js b/app/assets/javascripts/pages/shared/mount_runner_aws_deployments.js
new file mode 100644
index 00000000000..f3807a33a2b
--- /dev/null
+++ b/app/assets/javascripts/pages/shared/mount_runner_aws_deployments.js
@@ -0,0 +1,17 @@
+import Vue from 'vue';
+import RunnerAwsDeployments from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue';
+
+export function initRunnerAwsDeployments(componentId = 'js-runner-aws-deployments') {
+ const el = document.getElementById(componentId);
+
+ if (!el) {
+ return null;
+ }
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(RunnerAwsDeployments);
+ },
+ });
+}
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
index e9773f055a7..104a3caab4c 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
@@ -2,6 +2,7 @@
import { GlEmptyState, GlButton } from '@gitlab/ui';
import { startCodeQualityWalkthrough, track } from '~/code_quality_walkthrough/utils';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
+import ExperimentTracking from '~/experimentation/experiment_tracking';
import { getExperimentData } from '~/experimentation/utils';
import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
@@ -13,7 +14,9 @@ export default {
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.`),
- btnText: s__('Pipelines|Get started with CI/CD'),
+ aboutRunnersBtnText: s__('Pipelines|Learn about Runners'),
+ installRunnersBtnText: s__('Pipelines|Install GitLab Runners'),
+ getStartedBtnText: s__('Pipelines|Get started with CI/CD'),
codeQualityTitle: s__('Pipelines|Improve code quality with GitLab CI/CD'),
codeQualityDescription: s__(`Pipelines|To keep your codebase simple,
readable, and accessible to contributors, use GitLab CI/CD
@@ -42,6 +45,11 @@ export default {
required: false,
default: null,
},
+ ciRunnerSettingsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
ciHelpPagePath() {
@@ -50,6 +58,12 @@ export default {
isPipelineEmptyStateTemplatesExperimentActive() {
return this.canSetCi && Boolean(getExperimentData('pipeline_empty_state_templates'));
},
+ isCodeQualityExperimentActive() {
+ return this.canSetCi && Boolean(getExperimentData('code_quality_walkthrough'));
+ },
+ isCiRunnerTemplatesExperimentActive() {
+ return this.canSetCi && Boolean(getExperimentData('ci_runner_templates'));
+ },
},
mounted() {
startCodeQualityWalkthrough();
@@ -58,6 +72,10 @@ export default {
trackClick() {
track('cta_clicked');
},
+ trackCiRunnerTemplatesClick(action) {
+ const tracking = new ExperimentTracking('ci_runner_templates');
+ tracking.event(action);
+ },
},
};
</script>
@@ -72,7 +90,7 @@ export default {
:title="$options.i18n.title"
:svg-path="emptyStateSvgPath"
:description="$options.i18n.description"
- :primary-button-text="$options.i18n.btnText"
+ :primary-button-text="$options.i18n.getStartedBtnText"
:primary-button-link="ciHelpPagePath"
/>
</template>
@@ -80,7 +98,7 @@ export default {
<pipelines-ci-templates />
</template>
</gitlab-experiment>
- <gitlab-experiment v-else-if="canSetCi" name="code_quality_walkthrough">
+ <gitlab-experiment v-else-if="isCodeQualityExperimentActive" name="code_quality_walkthrough">
<template #control>
<gl-empty-state
:title="$options.i18n.title"
@@ -89,7 +107,7 @@ export default {
>
<template #actions>
<gl-button :href="ciHelpPagePath" variant="confirm" @click="trackClick()">
- {{ $options.i18n.btnText }}
+ {{ $options.i18n.getStartedBtnText }}
</gl-button>
</template>
</gl-empty-state>
@@ -108,6 +126,57 @@ export default {
</gl-empty-state>
</template>
</gitlab-experiment>
+ <gitlab-experiment v-else-if="isCiRunnerTemplatesExperimentActive" name="ci_runner_templates">
+ <template #control>
+ <gl-empty-state
+ :title="$options.i18n.title"
+ :svg-path="emptyStateSvgPath"
+ :description="$options.i18n.description"
+ >
+ <template #actions>
+ <gl-button
+ :href="ciHelpPagePath"
+ variant="confirm"
+ @click="trackCiRunnerTemplatesClick('get_started_button_clicked')"
+ >
+ {{ $options.i18n.getStartedBtnText }}
+ </gl-button>
+ </template>
+ </gl-empty-state>
+ </template>
+ <template #candidate>
+ <gl-empty-state
+ :title="$options.i18n.title"
+ :svg-path="emptyStateSvgPath"
+ :description="$options.i18n.description"
+ >
+ <template #actions>
+ <gl-button
+ :href="ciRunnerSettingsPath"
+ variant="confirm"
+ @click="trackCiRunnerTemplatesClick('install_runners_button_clicked')"
+ >
+ {{ $options.i18n.installRunnersBtnText }}
+ </gl-button>
+ <gl-button
+ :href="ciHelpPagePath"
+ variant="default"
+ @click="trackCiRunnerTemplatesClick('learn_button_clicked')"
+ >
+ {{ $options.i18n.aboutRunnersBtnText }}
+ </gl-button>
+ </template>
+ </gl-empty-state>
+ </template>
+ </gitlab-experiment>
+ <gl-empty-state
+ v-else-if="canSetCi"
+ :title="$options.i18n.title"
+ :svg-path="emptyStateSvgPath"
+ :description="$options.i18n.description"
+ :primary-button-text="$options.i18n.getStartedBtnText"
+ :primary-button-link="ciHelpPagePath"
+ />
<gl-empty-state
v-else
title=""
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index 612df3fdf31..8bb2657c161 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -99,6 +99,11 @@ export default {
required: false,
default: null,
},
+ ciRunnerSettingsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -345,6 +350,7 @@ export default {
:empty-state-svg-path="emptyStateSvgPath"
:can-set-ci="canCreatePipeline"
:code-quality-page-path="codeQualityPagePath"
+ :ci-runner-settings-path="ciRunnerSettingsPath"
/>
<gl-empty-state
diff --git a/app/assets/javascripts/pipelines/pipelines_index.js b/app/assets/javascripts/pipelines/pipelines_index.js
index c892311782c..925a96ea1aa 100644
--- a/app/assets/javascripts/pipelines/pipelines_index.js
+++ b/app/assets/javascripts/pipelines/pipelines_index.js
@@ -38,6 +38,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
projectId,
params,
codeQualityPagePath,
+ ciRunnerSettingsPath,
} = el.dataset;
return new Vue({
@@ -76,6 +77,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
projectId,
params: JSON.parse(params),
codeQualityPagePath,
+ ciRunnerSettingsPath,
},
});
},
diff --git a/app/assets/javascripts/static_site_editor/rich_content_editor/services/renderers/render_identifier_instance_text.js b/app/assets/javascripts/static_site_editor/rich_content_editor/services/renderers/render_identifier_instance_text.js
index d7716543b53..d770dd18d7f 100644
--- a/app/assets/javascripts/static_site_editor/rich_content_editor/services/renderers/render_identifier_instance_text.js
+++ b/app/assets/javascripts/static_site_editor/rich_content_editor/services/renderers/render_identifier_instance_text.js
@@ -4,7 +4,7 @@ import { buildTextToken, buildUneditableInlineTokens } from './build_uneditable_
Use case examples:
- Majority: two bracket pairs, back-to-back, each with content (including spaces)
- `[environment terraform plans][terraform]`
- - `[an issue labelled `~"master:broken"`][broken-master-issues]`
+ - `[an issue labelled `~"main:broken"`][broken-main-issues]`
- Minority: two bracket pairs the latter being empty or only one pair with content (including spaces)
- `[this link][]`
- `[this link]`
diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js
new file mode 100644
index 00000000000..46361c6eb32
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js
@@ -0,0 +1,49 @@
+import { s__, sprintf } from '~/locale';
+
+export const EXPERIMENT_NAME = 'ci_runner_templates';
+
+export const README_URL =
+ 'https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/easybuttons.md';
+
+export const CF_BASE_URL =
+ 'https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?';
+
+export const TEMPLATES_BASE_URL = 'https://gl-public-templates.s3.amazonaws.com/cfn/experimental/';
+
+export const EASY_BUTTONS = [
+ {
+ stackName: 'linux-docker-nonspot',
+ templateName:
+ 'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-ondemandonly.cf.yml',
+ description: s__(
+ 'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor.',
+ ),
+ },
+ {
+ stackName: 'linux-docker-spotonly',
+ templateName: 'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-spotonly.cf.yml',
+ description: sprintf(
+ s__(
+ 'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. %{percentage} spot.',
+ ),
+ { percentage: '100%' },
+ ),
+ },
+ {
+ stackName: 'win2019-shell-non-spot',
+ templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-ondemandonly.cf.yml',
+ description: s__(
+ 'Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor.',
+ ),
+ },
+ {
+ stackName: 'win2019-shell-spot',
+ templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-spotonly.cf.yml',
+ description: sprintf(
+ s__(
+ 'Runners|Windows 2019 Shell with manual scaling and optional scheduling. %{percentage} spot.',
+ ),
+ { percentage: '100%' },
+ ),
+ },
+];
diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue
new file mode 100644
index 00000000000..e3e3b9abc3c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue
@@ -0,0 +1,43 @@
+<script>
+import { GlButton, GlModalDirective } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import RunnerAwsDeploymentsModal from './runner_aws_deployments_modal.vue';
+
+export default {
+ components: {
+ GlButton,
+ RunnerAwsDeploymentsModal,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ modalId: 'runner-aws-deployments-modal',
+ i18n: {
+ buttonText: s__('Runners|Deploy GitLab Runner in AWS'),
+ },
+ data() {
+ return {
+ opened: false,
+ };
+ },
+ methods: {
+ onClick() {
+ this.opened = true;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-button
+ v-gl-modal-directive="$options.modalId"
+ class="gl-mt-4"
+ data-testid="show-modal-button"
+ variant="confirm"
+ @click="onClick"
+ >
+ {{ $options.i18n.buttonText }}
+ </gl-button>
+ <runner-aws-deployments-modal v-if="opened" :modal-id="$options.modalId" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
new file mode 100644
index 00000000000..f21dea468cb
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
@@ -0,0 +1,97 @@
+<script>
+import { GlModal, GlSprintf, GlLink } from '@gitlab/ui';
+import ExperimentTracking from '~/experimentation/experiment_tracking';
+import { getBaseURL, objectToQuery } from '~/lib/utils/url_utility';
+import { __, s__ } from '~/locale';
+import {
+ EXPERIMENT_NAME,
+ README_URL,
+ CF_BASE_URL,
+ TEMPLATES_BASE_URL,
+ EASY_BUTTONS,
+} from './constants';
+
+export default {
+ components: {
+ GlModal,
+ GlSprintf,
+ GlLink,
+ },
+ props: {
+ modalId: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ easyButtonUrl(easyButton) {
+ const params = {
+ templateURL: TEMPLATES_BASE_URL + easyButton.templateName,
+ stackName: easyButton.stackName,
+ param_3GITLABRunnerInstanceURL: getBaseURL(),
+ };
+ return CF_BASE_URL + objectToQuery(params);
+ },
+ trackCiRunnerTemplatesClick(stackName) {
+ const tracking = new ExperimentTracking(EXPERIMENT_NAME);
+ tracking.event(`template_clicked_${stackName}`);
+ },
+ },
+ i18n: {
+ title: s__('Runners|Deploy GitLab Runner in AWS'),
+ instructions: s__(
+ 'Runners|For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet.',
+ ),
+ dont_see_what_you_are_looking_for: s__(
+ "Rnners|Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}.",
+ ),
+ note: s__(
+ 'Runners|If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account.',
+ ),
+ },
+ closeButton: {
+ text: __('Cancel'),
+ attributes: [{ variant: 'default' }],
+ },
+ readmeUrl: README_URL,
+ easyButtons: EASY_BUTTONS,
+};
+</script>
+<template>
+ <gl-modal
+ :modal-id="modalId"
+ :title="$options.i18n.title"
+ :action-secondary="$options.closeButton"
+ size="sm"
+ >
+ <p>{{ $options.i18n.instructions }}</p>
+ <ul class="gl-list-style-none gl-p-0 gl-mb-0">
+ <li v-for="easyButton in $options.easyButtons" :key="easyButton.templateName">
+ <gl-link
+ :href="easyButtonUrl(easyButton)"
+ target="_blank"
+ class="gl-display-flex gl-font-weight-bold"
+ @click="trackCiRunnerTemplatesClick(easyButton.stackName)"
+ >
+ <img
+ :title="easyButton.stackName"
+ :alt="easyButton.stackName"
+ src="/assets/aws-cloud-formation.png"
+ width="46"
+ height="46"
+ class="gl-mt-2 gl-mr-5 gl-mb-6"
+ />
+ {{ easyButton.description }}
+ </gl-link>
+ </li>
+ </ul>
+ <p>
+ <gl-sprintf :message="$options.i18n.dont_see_what_you_are_looking_for">
+ <template #link="{ content }">
+ <gl-link :href="$options.readmeUrl" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ <p class="gl-font-sm gl-mb-0">{{ $options.i18n.note }}</p>
+ </gl-modal>
+</template>
diff --git a/app/assets/stylesheets/components/rich_content_editor.scss b/app/assets/stylesheets/components/rich_content_editor.scss
index d97a9bc227d..59bd69955d3 100644
--- a/app/assets/stylesheets/components/rich_content_editor.scss
+++ b/app/assets/stylesheets/components/rich_content_editor.scss
@@ -47,7 +47,7 @@
/**
* Styling below ensures that YouTube videos are displayed in the editor the same as they would in about.gitlab.com
-* https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/master/source/stylesheets/_base.scss#L977
+* https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/main/source/stylesheets/_base.scss#L977
*/
.video_container {
padding-bottom: 56.25%;
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index cf6d34b2042..136b674f0a9 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -265,7 +265,8 @@ class GroupsController < Groups::ApplicationController
:default_branch_protection,
:default_branch_name,
:allow_mfa_for_subgroups,
- :resource_access_token_creation_allowed
+ :resource_access_token_creation_allowed,
+ :prevent_sharing_groups_outside_hierarchy
]
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 116e7970bbf..7779f3c3b65 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -49,26 +49,9 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format|
format.html do
- experiment(:pipeline_empty_state_templates, namespace: project.root_ancestor) do |e|
- e.exclude! unless current_user
- e.exclude! if @pipelines_count.to_i > 0
- e.exclude! if helpers.has_gitlab_ci?(project)
-
- e.use {}
- e.try {}
- e.record!
- end
- experiment(:code_quality_walkthrough, namespace: project.root_ancestor) do |e|
- e.exclude! unless current_user
- e.exclude! unless can?(current_user, :create_pipeline, project)
- e.exclude! unless project.root_ancestor.recent?
- e.exclude! if @pipelines_count.to_i > 0
- e.exclude! if helpers.has_gitlab_ci?(project)
-
- e.use {}
- e.try {}
- e.record!
- end
+ enable_pipeline_empty_state_templates_experiment
+ enable_code_quality_walkthrough_experiment
+ enable_ci_runner_templates_experiment
end
format.json do
Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
@@ -317,6 +300,45 @@ class Projects::PipelinesController < Projects::ApplicationController
def index_params
params.permit(:scope, :username, :ref, :status)
end
+
+ def enable_pipeline_empty_state_templates_experiment
+ experiment(:pipeline_empty_state_templates, namespace: project.root_ancestor) do |e|
+ e.exclude! unless current_user
+ e.exclude! if @pipelines_count.to_i > 0
+ e.exclude! if helpers.has_gitlab_ci?(project)
+
+ e.control {}
+ e.candidate {}
+ e.record!
+ end
+ end
+
+ def enable_code_quality_walkthrough_experiment
+ experiment(:code_quality_walkthrough, namespace: project.root_ancestor) do |e|
+ e.exclude! unless current_user
+ e.exclude! unless can?(current_user, :create_pipeline, project)
+ e.exclude! unless project.root_ancestor.recent?
+ e.exclude! if @pipelines_count.to_i > 0
+ e.exclude! if helpers.has_gitlab_ci?(project)
+
+ e.control {}
+ e.candidate {}
+ e.record!
+ end
+ end
+
+ def enable_ci_runner_templates_experiment
+ experiment(:ci_runner_templates, namespace: project.root_ancestor) do |e|
+ e.exclude! unless current_user
+ e.exclude! unless can?(current_user, :create_pipeline, project)
+ e.exclude! if @pipelines_count.to_i > 0
+ e.exclude! if helpers.has_gitlab_ci?(project)
+
+ e.control {}
+ e.candidate {}
+ e.record!
+ end
+ end
end
Projects::PipelinesController.prepend_mod_with('Projects::PipelinesController')
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 21f96c01f98..26a5df321cd 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -77,6 +77,10 @@ module GroupsHelper
can?(current_user, :change_share_with_group_lock, group)
end
+ def can_change_prevent_sharing_groups_outside_hierarchy?(group)
+ can?(current_user, :change_prevent_sharing_groups_outside_hierarchy, group)
+ end
+
def can_disable_group_emails?(group)
can?(current_user, :set_emails_disabled, group) && !group.parent&.emails_disabled?
end
@@ -188,6 +192,14 @@ module GroupsHelper
end
end
+ def link_to_group(group)
+ link_to(group.name, group_path(group))
+ end
+
+ def prevent_sharing_groups_outside_hierarchy_help_text(group)
+ s_("GroupSettings|This setting is only available on the top-level group and it applies to all subgroups. Groups that have already been shared with a group outside %{group} will still be shared, and this access will have to be revoked manually.").html_safe % { group: link_to_group(group) }
+ end
+
def parent_group_options(current_group)
exclude_groups = current_group.self_and_descendants.pluck_primary_key
exclude_groups << current_group.parent_id if current_group.parent_id
diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb
index d1c84bd4141..b92e418006b 100644
--- a/app/helpers/ide_helper.rb
+++ b/app/helpers/ide_helper.rb
@@ -14,6 +14,7 @@ module IdeHelper
'render-whitespace-in-code': current_user.render_whitespace_in_code.to_s,
'codesandbox-bundler-url': Gitlab::CurrentSettings.web_ide_clientside_preview_bundler_url,
'branch-name' => @branch,
+ 'default-branch' => @project && @project.default_branch,
'file-path' => @path,
'merge-request' => @merge_request,
'fork-info' => @fork_info&.to_json,
diff --git a/app/models/group.rb b/app/models/group.rb
index 41f8cfa8b26..6c04bbdb032 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -80,6 +80,8 @@ class Group < Namespace
# debian_distributions and associated component_files must be destroyed by ruby code in order to properly remove carrierwave uploads
has_many :debian_distributions, class_name: 'Packages::Debian::GroupDistribution', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ delegate :prevent_sharing_groups_outside_hierarchy, to: :namespace_settings
+
accepts_nested_attributes_for :variables, allow_destroy: true
validate :visibility_level_allowed_by_projects
diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb
index 75b8169b58e..600abc33471 100644
--- a/app/models/namespace_setting.rb
+++ b/app/models/namespace_setting.rb
@@ -14,7 +14,8 @@ class NamespaceSetting < ApplicationRecord
before_validation :normalize_default_branch_name
NAMESPACE_SETTINGS_PARAMS = [:default_branch_name, :delayed_project_removal,
- :lock_delayed_project_removal, :resource_access_token_creation_allowed].freeze
+ :lock_delayed_project_removal, :resource_access_token_creation_allowed,
+ :prevent_sharing_groups_outside_hierarchy].freeze
self.primary_key = :namespace_id
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 41c5757854e..ba06b98e906 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -155,6 +155,7 @@ class GroupPolicy < BasePolicy
enable :set_note_created_at
enable :set_emails_disabled
+ enable :change_prevent_sharing_groups_outside_hierarchy
enable :update_default_branch_protection
enable :create_deploy_token
enable :destroy_deploy_token
diff --git a/app/services/namespace_settings/update_service.rb b/app/services/namespace_settings/update_service.rb
index de54eb87cc0..80f15f7cc22 100644
--- a/app/services/namespace_settings/update_service.rb
+++ b/app/services/namespace_settings/update_service.rb
@@ -14,6 +14,7 @@ module NamespaceSettings
def execute
validate_resource_access_token_creation_allowed_param
+ validate_prevent_sharing_groups_outside_hierarchy_param
if group.namespace_settings
group.namespace_settings.attributes = settings_params
@@ -32,6 +33,15 @@ module NamespaceSettings
group.namespace_settings.errors.add(:resource_access_token_creation_allowed, _('can only be changed by a group admin.'))
end
end
+
+ def validate_prevent_sharing_groups_outside_hierarchy_param
+ return if settings_params[:prevent_sharing_groups_outside_hierarchy].nil?
+
+ unless can?(current_user, :change_prevent_sharing_groups_outside_hierarchy, group)
+ settings_params.delete(:prevent_sharing_groups_outside_hierarchy)
+ group.namespace_settings.errors.add(:prevent_sharing_groups_outside_hierarchy, _('can only be changed by a group admin.'))
+ end
+ end
end
end
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index 0af244d54f3..5ae45d5a9da 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -3,15 +3,13 @@
%fieldset
.form-group
- .card.auto-devops-card
- .card-body
- .form-check
- = f.check_box :auto_devops_enabled, class: 'form-check-input'
- = f.label :auto_devops_enabled, class: 'form-check-label' do
- = s_('CICD|Default to Auto DevOps pipeline for all projects')
- .form-text.text-muted
- = s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
- = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
+ .form-check
+ = f.check_box :auto_devops_enabled, class: 'form-check-input'
+ = f.label :auto_devops_enabled, class: 'form-check-label' do
+ %strong= s_('CICD|Default to Auto DevOps pipeline for all projects')
+ .form-text.text-muted
+ = s_('CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file.')
+ = link_to _('What is Auto DevOps?'), help_page_path('topics/autodevops/index.md'), target: '_blank'
.form-group
= f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold'
= f.text_field :auto_devops_domain, class: 'form-control gl-form-input', placeholder: 'domain.com'
@@ -26,9 +24,9 @@
= render_if_exists 'admin/application_settings/shared_runners_minutes_setting', form: f
.form-group
- = f.label :shared_runners_text, class: 'label-bold'
+ = f.label :shared_runners_text, _('Shared runners details'), class: 'label-bold'
= f.text_area :shared_runners_text, class: 'form-control gl-form-input', rows: 4
- .form-text.text-muted= _("Markdown enabled")
+ .form-text.text-muted= _("Add a custom message with details about the instance's shared runners. The message is visible in group and project CI/CD settings, in the Runners section. Markdown is supported.")
.form-group
= f.label :max_artifacts_size, _('Maximum artifacts size (MB)'), class: 'label-bold'
= f.number_field :max_artifacts_size, class: 'form-control gl-form-input'
@@ -58,14 +56,14 @@
.form-check
= f.check_box :protected_ci_variables, class: 'form-check-input'
= f.label :protected_ci_variables, class: 'form-check-label' do
- = s_('AdminSettings|Environment variables are protected by default')
+ %strong= s_('AdminSettings|Protect CI/CD variables by default')
.form-text.text-muted
- = s_('AdminSettings|When creating a new environment variable it will be protected by default.')
+ = s_('AdminSettings|New CI/CD variables in projects and groups default to protected.')
.form-group
- = f.label :ci_config_path, _('Default CI configuration path'), class: 'label-bold'
+ = f.label :ci_config_path, _('Default CI/CD configuration file'), class: 'label-bold'
= f.text_field :default_ci_config_path, class: 'form-control gl-form-input', placeholder: '.gitlab-ci.yml'
%p.form-text.text-muted
- = _("The default CI configuration path for new projects.").html_safe
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'custom-cicd-configuration-path'), target: '_blank'
+ = _("The default CI/CD configuration file and path for new projects.").html_safe
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'custom-cicd-configuration-file'), target: '_blank'
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
diff --git a/app/views/admin/application_settings/ci_cd.html.haml b/app/views/admin/application_settings/ci_cd.html.haml
index d38b4cba40a..127ab8ea1f4 100644
--- a/app/views/admin/application_settings/ci_cd.html.haml
+++ b/app/views/admin/application_settings/ci_cd.html.haml
@@ -19,7 +19,7 @@
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
- = _('Auto DevOps, runners and job artifacts')
+ = _('Customize CI/CD settings, including Auto DevOps, shared runners, and job artifacts.')
.settings-content
= render 'ci_cd'
diff --git a/app/views/ci/runner/_setup_runner_in_aws.html.haml b/app/views/ci/runner/_setup_runner_in_aws.html.haml
new file mode 100644
index 00000000000..b0a5b40f2ad
--- /dev/null
+++ b/app/views/ci/runner/_setup_runner_in_aws.html.haml
@@ -0,0 +1,16 @@
+%h5= _('Use GitLab Runner in AWS')
+
+%p
+ = _('Use an AWS CloudFormation Template (CFT) to install and configure GitLab Runner in AWS.')
+
+%ol
+ %li
+ = _('Copy this registration token.')
+ %br
+ %code#registration_token{ data: { testid: 'registration_token' } }= registration_token
+ = clipboard_button(target: '#registration_token', title: _('Copy token'), class: 'btn-transparent btn-clipboard')
+ %li
+ = _('Choose the preferred Runner and populate the AWS CFT.')
+ = link_to _('Learn more.'), 'https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg', target: '_blank', rel: 'noopener noreferrer'
+
+#js-runner-aws-deployments
diff --git a/app/views/groups/runners/_group_runners.html.haml b/app/views/groups/runners/_group_runners.html.haml
index 910b36770f1..823d908c5e2 100644
--- a/app/views/groups/runners/_group_runners.html.haml
+++ b/app/views/groups/runners/_group_runners.html.haml
@@ -13,6 +13,10 @@
= render partial: 'ci/runner/how_to_setup_runner_automatically',
locals: { type: 'group',
clusters_path: group_clusters_path(@group) }
+ - if params[:ci_runner_templates]
+ %hr
+ = render partial: 'ci/runner/setup_runner_in_aws',
+ locals: { registration_token: @group.runners_token }
%hr
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: @group.runners_token,
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index fcfe70bd694..d1f356ed665 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -7,13 +7,21 @@
.form-group
= render 'shared/allow_request_access', form: f
+ - if @group.root?
+ .form-group.gl-mb-3
+ .gl-form-checkbox.custom-control.custom-checkbox
+ = f.check_box :prevent_sharing_groups_outside_hierarchy, disabled: !can_change_prevent_sharing_groups_outside_hierarchy?(@group), class: 'custom-control-input'
+ = f.label :prevent_sharing_groups_outside_hierarchy, class: 'custom-control-label' do
+ %span
+ = s_('GroupSettings|Prevent members from sending invitations to groups outside of %{group} and its subgroups.').html_safe % { group: link_to_group(@group) }
+ %p.js-descr.help-text= prevent_sharing_groups_outside_hierarchy_help_text(@group)
+
.form-group.gl-mb-3
.gl-form-checkbox.custom-control.custom-checkbox
= f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'custom-control-input'
= f.label :share_with_group_lock, class: 'custom-control-label' do
%span
- - group_link = link_to @group.name, group_path(@group)
- = s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
+ = s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: link_to_group(@group) }
%p.js-descr.help-text= share_with_group_lock_help_text(@group)
.form-group.gl-mb-3
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 6a626d445ee..9669b2e72dc 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -24,4 +24,5 @@
"has-gitlab-ci" => has_gitlab_ci?(@project).to_s,
"add-ci-yml-path" => can?(current_user, :create_pipeline, @project) && @project.present(current_user: current_user).add_ci_yml_path,
"suggested-ci-templates" => experiment_suggested_ci_templates.to_json,
- "code-quality-page-path" => @project.present(current_user: current_user).add_code_quality_ci_yml_path } }
+ "code-quality-page-path" => @project.present(current_user: current_user).add_code_quality_ci_yml_path,
+ "ci-runner-settings-path" => project_settings_ci_cd_path(@project, ci_runner_templates: true, anchor: 'js-runners-settings') } }
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 88895634990..210cc414007 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -7,6 +7,10 @@
= render partial: 'ci/runner/how_to_setup_runner_automatically',
locals: { type: 'specific',
clusters_path: project_clusters_path(@project) }
+ - if params[:ci_runner_templates]
+ %hr
+ = render partial: 'ci/runner/setup_runner_in_aws',
+ locals: { registration_token: @project.runners_token }
%hr
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: @project.runners_token,
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index ec5d594abda..c89c9879f4b 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -36,7 +36,7 @@
= f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
%p.form-text.text-muted
= html_escape(_("The name of the CI/CD configuration file. A path relative to the root directory is optional (for example %{code_open}my/path/.myfile.yml%{code_close}).")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'custom-cicd-configuration-path'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'custom-cicd-configuration-file'), target: '_blank'
%hr
.form-group
diff --git a/config/feature_flags/experiment/ci_runner_templates.yml b/config/feature_flags/experiment/ci_runner_templates.yml
new file mode 100644
index 00000000000..e791581f67a
--- /dev/null
+++ b/config/feature_flags/experiment/ci_runner_templates.yml
@@ -0,0 +1,8 @@
+---
+name: ci_runner_templates
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58357
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326725
+milestone: "14.0"
+type: experiment
+group: group::activation
+default_enabled: false
diff --git a/db/post_migrate/20210610141711_disable_expiration_policies_linked_to_no_container_images.rb b/db/post_migrate/20210610141711_disable_expiration_policies_linked_to_no_container_images.rb
new file mode 100644
index 00000000000..f4827c0bbc0
--- /dev/null
+++ b/db/post_migrate/20210610141711_disable_expiration_policies_linked_to_no_container_images.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class DisableExpirationPoliciesLinkedToNoContainerImages < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ BATCH_SIZE = 1000
+
+ class ContainerExpirationPolicy < ActiveRecord::Base
+ include ::EachBatch
+ self.table_name = 'container_expiration_policies'
+ end
+
+ def up
+ ContainerExpirationPolicy.where(enabled: true).each_batch(of: BATCH_SIZE) do |batch, _|
+ sql = <<-SQL
+ WITH batched_relation AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (#{batch.limit(BATCH_SIZE).to_sql})
+ UPDATE container_expiration_policies
+ SET enabled = FALSE
+ FROM batched_relation
+ WHERE container_expiration_policies.project_id = batched_relation.project_id
+ AND NOT EXISTS (SELECT 1 FROM "container_repositories" WHERE container_repositories.project_id = container_expiration_policies.project_id)
+ SQL
+ execute(sql)
+ end
+ end
+
+ def down
+ # no-op
+
+ # we can't accuretaly know which policies were previously enabled during `#up`
+ end
+end
diff --git a/db/schema_migrations/20210610141711 b/db/schema_migrations/20210610141711
new file mode 100644
index 00000000000..786ca655eeb
--- /dev/null
+++ b/db/schema_migrations/20210610141711
@@ -0,0 +1 @@
+b8bfe0d67516f0fe71c166a616c2279fc9f45769c369d8ddd86e9705dbc55097 \ No newline at end of file
diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md
index c97227e2b3e..6b80ddbcdb5 100644
--- a/doc/administration/compliance.md
+++ b/doc/administration/compliance.md
@@ -26,7 +26,7 @@ relevant compliance standards.
|**[Audit events](audit_events.md)**<br>To maintain the integrity of your code, GitLab Enterprise Edition Premium gives administrators the ability to view any modifications made within the GitLab server in an advanced audit events system, so you can control, analyze, and track every change. | Premium+ | **{check-circle}** Yes | Instance, Group, Project |
|**[Auditor users](auditor_users.md)**<br>Auditor users are users who are given read-only access to all projects, groups, and other resources on the GitLab instance. | Premium+ | **{dotted-circle}** No | Instance |
|**[Credentials inventory](../user/admin_area/credentials_inventory.md)**<br>With a credentials inventory, GitLab administrators can keep track of the credentials used by all of the users in their GitLab instance. | Ultimate | **{dotted-circle}** No | Instance |
-|**Separation of Duties using [Protected branches](../user/project/protected_branches.md#protected-branches-approval-by-code-owners) and [custom CI Configuration Paths](../ci/pipelines/settings.md#custom-cicd-configuration-path)**<br> GitLab Premium users can leverage the GitLab cross-project YAML configurations to define deployers of code and developers of code. View the [Separation of Duties Deploy Project](https://gitlab.com/guided-explorations/separation-of-duties-deploy/blob/master/README.md) and [Separation of Duties Project](https://gitlab.com/guided-explorations/separation-of-duties/blob/master/README.md) to see how to use this set up to define these roles. | Premium+ | **{check-circle}** Yes | Project |
+|**Separation of Duties using [Protected branches](../user/project/protected_branches.md#protected-branches-approval-by-code-owners) and [custom CI Configuration Paths](../ci/pipelines/settings.md#custom-cicd-configuration-file)**<br> GitLab Premium users can leverage the GitLab cross-project YAML configurations to define deployers of code and developers of code. View the [Separation of Duties Deploy Project](https://gitlab.com/guided-explorations/separation-of-duties-deploy/blob/master/README.md) and [Separation of Duties Project](https://gitlab.com/guided-explorations/separation-of-duties/blob/master/README.md) to see how to use this set up to define these roles. | Premium+ | **{check-circle}** Yes | Project |
|**[Compliance frameworks](../user/project/settings/index.md#compliance-frameworks)**<br>Create a custom compliance framework at the group level to describe the type of compliance requirements any child project needs to follow. | Premium+ | **{check-circle}** Yes | Group |
|**[Compliance pipelines](../user/project/settings/index.md#compliance-pipeline-configuration)**<br>Define a pipeline configuration to run for any projects with a given compliance framework. | Ultimate | **{check-circle}** Yes | Group |
|**[Compliance dashboard](../user/compliance/compliance_dashboard/index.md)**<br>Quickly get visibility into the compliance posture of your organization. | Ultimate | **{check-circle}** Yes | Group |
diff --git a/doc/administration/geo/replication/datatypes.md b/doc/administration/geo/replication/datatypes.md
index 2d66de3d5da..aef152ecf37 100644
--- a/doc/administration/geo/replication/datatypes.md
+++ b/doc/administration/geo/replication/datatypes.md
@@ -197,8 +197,8 @@ successfully, you must replicate their data using some other means.
|[Package Registry for PyPI](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|[Package Registry for Composer](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|[Package Registry for generic packages](../../../user/packages/generic_packages/index.md) | **Yes** (13.5) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
-|[Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.12) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_terraform_state_version_replication`, enabled by default. |
-|[External merge request diffs](../../merge_request_diffs.md) | **Yes** (13.5) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_merge_request_diff_replication`, enabled by default. |
+|[Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.12) | Via Object Storage provider if supported. Native Geo support (Beta). | Replication is behind the feature flag `geo_terraform_state_version_replication`, enabled by default. Verification was behind the feature flag `geo_terraform_state_version_verification`, which was removed in 14.0|
+|[External merge request diffs](../../merge_request_diffs.md) | **Yes** (13.5) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Replication is behind the feature flag `geo_merge_request_diff_replication`, enabled by default. Verification is under development, behind the feature flag `geo_merge_request_diff_verification`, introduced in 14.0.|
|[Versioned snippets](../../../user/snippets.md#versioned-snippets) | [**Yes** (13.7)](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2810) | No | |
|[Server-side Git hooks](../../server_hooks.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1867) | No | No | |
|[Elasticsearch integration](../../../integration/elasticsearch.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/1186) | No | No | |
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 406688732fe..76ae0174496 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -244,7 +244,7 @@ listed in the descriptions of the relevant settings.
| `deactivate_dormant_users` | boolean | no | Enable [atomatic deactivation of dormant users](../user/admin_area/moderate_users.md#automatically-deactivate-dormant-users). |
| `default_artifacts_expire_in` | string | no | Set the default expiration time for each job's artifacts. |
| `default_branch_protection` | integer | no | Determine if developers can push to the default branch. Can take: `0` _(not protected, both developers and maintainers can push new commits, force push, or delete the branch)_, `1` _(partially protected, developers and maintainers can push new commits, but cannot force push, or delete, the branch)_ or `2` _(fully protected, developers cannot push new commits, but maintainers can; no-one can force push or delete the branch)_ as a parameter. Default is `2`. |
-| `default_ci_config_path` | string | no | Default CI configuration path for new projects (`.gitlab-ci.yml` if not set). |
+| `default_ci_config_path` | string | no | Default CI/CD configuration file and path for new projects (`.gitlab-ci.yml` if not set). |
| `default_group_visibility` | string | no | What visibility level new groups receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
| `default_project_creation` | integer | no | Default project creation protection. Can take: `0` _(No one)_, `1` _(Maintainers)_ or `2` _(Developers + Maintainers)_|
| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 85c9bb97ff2..dc4312250ca 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -70,7 +70,7 @@ GitLab CI/CD supports numerous configuration options:
| Configuration | Description |
|:----------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------|
| [Schedule pipelines](pipelines/schedules.md) | Schedule pipelines to run as often as you need. |
-| [Custom path for `.gitlab-ci.yml`](pipelines/settings.md#custom-cicd-configuration-path) | Define a custom path for the CI/CD configuration file. |
+| [Custom path for `.gitlab-ci.yml`](pipelines/settings.md#custom-cicd-configuration-file) | Define a custom path for the CI/CD configuration file. |
| [Git submodules for CI/CD](git_submodules.md) | Configure jobs for using Git submodules. |
| [SSH keys for CI/CD](ssh_keys/index.md) | Using SSH keys in your CI pipelines. |
| [Pipeline triggers](triggers/README.md) | Trigger pipelines through the API. |
diff --git a/doc/ci/environments/deployment_safety.md b/doc/ci/environments/deployment_safety.md
index 86e004322bc..c57dc99f341 100644
--- a/doc/ci/environments/deployment_safety.md
+++ b/doc/ci/environments/deployment_safety.md
@@ -141,7 +141,7 @@ reference a file in another project with a completely different set of permissio
In this scenario, the `gitlab-ci.yml` is publicly accessible, but can only be edited by users with
appropriate permissions in the other project.
-For more information, see [Custom CI/CD configuration path](../pipelines/settings.md#custom-cicd-configuration-path).
+For more information, see [Custom CI/CD configuration path](../pipelines/settings.md#custom-cicd-configuration-file).
## Troubleshooting
diff --git a/doc/ci/pipelines/settings.md b/doc/ci/pipelines/settings.md
index ab3f4169c18..2e842856e55 100644
--- a/doc/ci/pipelines/settings.md
+++ b/doc/ci/pipelines/settings.md
@@ -73,7 +73,7 @@ Project defined timeout (either specific timeout set by user or the default
For information about setting a maximum artifact size for a project, see
[Maximum artifacts size](../../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size).
-## Custom CI/CD configuration path
+## Custom CI/CD configuration file
> [Support for external `.gitlab-ci.yml` locations](https://gitlab.com/gitlab-org/gitlab/-/issues/14376) introduced in GitLab 12.6.
@@ -87,7 +87,7 @@ To customize the path:
1. Provide a value in the **CI/CD configuration file** field.
1. Click **Save changes**.
-If the CI configuration is stored in the repository in a non-default
+If the CI/CD configuration file is stored in the repository in a non-default
location, the path must be relative to the root directory. Examples of valid
paths and file names include:
@@ -96,11 +96,11 @@ paths and file names include:
- `my/path/.gitlab-ci.yml`
- `my/path/.my-custom-file.yml`
-If hosting the CI configuration on an external site, the URL link must end with `.yml`:
+If hosting the CI/CD configuration file on an external site, the URL link must end with `.yml`:
- `http://example.com/generate/ci/config.yml`
-If hosting the CI configuration in a different project in GitLab, the path must be relative
+If hosting the CI/CD configuration file in a different project in GitLab, the path must be relative
to the root directory in the other project. Include the group and project name at the end:
- `.gitlab-ci.yml@mygroup/another-project`
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 8b416dd0da9..1db2d0dd888 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -602,7 +602,7 @@ You can grant permission to override variables to [maintainers](../../user/permi
with overridden variables, they receive the `Insufficient permissions to set pipeline variables`
error message.
-If you [store your CI/CD configurations in a different repository](../../ci/pipelines/settings.md#custom-cicd-configuration-path),
+If you [store your CI/CD configurations in a different repository](../../ci/pipelines/settings.md#custom-cicd-configuration-file),
use this setting for control over the environment the pipeline runs in.
You can enable this feature by using [the projects API](../../api/projects.md#edit-project)
diff --git a/doc/user/admin_area/custom_project_templates.md b/doc/user/admin_area/custom_project_templates.md
index b4b33df37bf..6cf3c5bbd7d 100644
--- a/doc/user/admin_area/custom_project_templates.md
+++ b/doc/user/admin_area/custom_project_templates.md
@@ -30,12 +30,12 @@ see [Custom group-level project templates](../group/custom_project_templates.md)
## Configuring
GitLab administrators can configure a GitLab group that serves as template
-source for an entire GitLab instance by:
+source for an entire GitLab instance:
-1. Navigating to **Admin Area > Settings > Templates**.
-1. Expanding **Custom project templates**.
-1. Selecting a group to use.
-1. Pressing **Save changes**.
+1. On the top bar, navigate to **Menu > Admin > Settings > Templates**.
+1. Expand **Custom project templates**.
+1. Select a group to use.
+1. Select **Save changes**.
NOTE:
Projects below subgroups of the template group are **not** supported.
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index 0da868b78ce..2eaf043f9a9 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -7,12 +7,13 @@ type: reference
# GitLab Admin Area **(FREE SELF)**
-The Admin Area provides a web UI for administering some features of GitLab self-managed instances.
+The Admin Area provides a web UI to manage and configure some features of GitLab
+self-managed instances. If you are an Admin user, you can access the Admin Area
+by visiting `/admin` on your self-managed instance. You can also access it through
+the UI:
-To access the Admin Area, either:
-
-- Click the Admin Area icon (**{admin}**).
-- Visit `/admin` on your self-managed instance.
+- GitLab versions 14.0 and later: on the top bar, select **Menu >** **{admin}** **Admin**.
+- GitLab versions 13.12 and earlier: on the top bar, select the Admin Area icon (**{admin}**).
NOTE:
Only admin users can access the Admin Area.
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index bf695f85ad3..ffe969a6799 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -28,7 +28,26 @@ From now on, every existing project and newly created ones that don't have a
If you want to disable it for a specific project, you can do so in
[its settings](../../../topics/autodevops/index.md#enable-or-disable-auto-devops).
-## Maximum artifacts size
+## Shared runner details
+
+To display details about the instance's shared runners in all projects'
+runner settings:
+
+1. On the top bar, select **Menu >** **{admin}** **Admin**.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **Continuous Integration and Deployment**.
+1. Enter your shared runner details in the **Shared runner details** field.
+
+You can use [Markdown](../../markdown.md) for improved formatting. To see the rendered
+details:
+
+1. On the top bar, select **Menu > Project** and select any group or project.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **Runners**.
+
+![Shared runner details example](img/continuous_integration_shared_runner_details_v14_0.png)
+
+## Maximum artifacts size **(FREE SELF)**
The maximum size of the [job artifacts](../../../administration/job_artifacts.md)
can be set at:
@@ -174,19 +193,28 @@ for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.
As of June 22, 2020 the [value is set](../../gitlab_com/index.md#gitlab-cicd) to 3 months on GitLab.com. Jobs created before that date were archived after September 22, 2020.
-## Default CI configuration path
+## Protect CI/CD variables by default
+
+To set all new [CI/CD variables](../../../ci/variables/README.md) as
+[protected](../../../ci/variables/README.md#protect-a-cicd-variable) by default:
+
+1. On the top bar, select **Menu >** **{admin}** **Admin**.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Select **Protect CI/CD variables by default**.
+
+## Default CI/CD configuration file
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18073) in GitLab 12.5.
-The default CI configuration file path for new projects can be set in the Admin
-Area of your GitLab instance (`.gitlab-ci.yml` if not set):
+The default CI/CD configuration file and path for new projects can be set in the Admin Area
+of your GitLab instance (`.gitlab-ci.yml` if not set):
1. On the top bar, select **Menu >** **{admin}** **Admin**.
-1. On the left sidebar, select **Admin Area > Settings > CI/CD**.
-1. Input the new path in the **Default CI configuration path** field.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Input the new file and path in the **Default CI/CD configuration file** field.
1. Hit **Save changes** for the changes to take effect.
-It is also possible to specify a [custom CI/CD configuration path for a specific project](../../../ci/pipelines/settings.md#custom-cicd-configuration-path).
+It is also possible to specify a [custom CI/CD configuration file for a specific project](../../../ci/pipelines/settings.md#custom-cicd-configuration-file).
## Required pipeline configuration **(PREMIUM SELF)**
diff --git a/doc/user/admin_area/settings/img/continuous_integration_shared_runner_details_v14_0.png b/doc/user/admin_area/settings/img/continuous_integration_shared_runner_details_v14_0.png
new file mode 100644
index 00000000000..d8bc3deccd4
--- /dev/null
+++ b/doc/user/admin_area/settings/img/continuous_integration_shared_runner_details_v14_0.png
Binary files differ
diff --git a/doc/user/admin_area/settings/import_export_rate_limits.md b/doc/user/admin_area/settings/import_export_rate_limits.md
index b62e6eb0c3d..12235bdb5ef 100644
--- a/doc/user/admin_area/settings/import_export_rate_limits.md
+++ b/doc/user/admin_area/settings/import_export_rate_limits.md
@@ -23,7 +23,7 @@ per minute per user basis:
All rate limits are:
-- Configurable at **(admin)** **Admin Area > Settings > Network > Import/Export Rate Limits**
+- Configurable through the top bar at **Menu > Admin > Settings > Network > Import/Export Rate Limits**
- Applied per minute per user
- Not applied per IP address
- Active by default. To disable, set the option to `0`
diff --git a/doc/user/admin_area/settings/package_registry_rate_limits.md b/doc/user/admin_area/settings/package_registry_rate_limits.md
index 578b7cd1236..6e7b9b0da30 100644
--- a/doc/user/admin_area/settings/package_registry_rate_limits.md
+++ b/doc/user/admin_area/settings/package_registry_rate_limits.md
@@ -9,7 +9,8 @@ type: reference
Rate limiting is a common technique used to improve the security and durability of a web
application. For more details, see [Rate limits](../../../security/rate_limits.md). General user and
-IP rate limits can be enforced in **Admin Area > Settings > Network > User and IP rate limits**.
+IP rate limits can be enforced from the top bar at
+**Menu > Admin > Settings > Network > User and IP rate limits**.
For more details, see [User and IP rate limits](user_and_ip_rate_limits.md).
With the [GitLab Package Registry](../../packages/package_registry/index.md),
@@ -20,7 +21,7 @@ the [Packages API](../../../api/packages.md).
When downloading such dependencies in downstream projects, many requests are made through the
Packages API. You may therefore reach enforced user and IP rate limits. To address this issue, you
can define specific rate limits for the Packages API in
-**Admin Area > Settings > Network > Package Registry Rate Limits**:
+**Menu > Admin > Settings > Network > Package Registry Rate Limits**:
- Unauthenticated Packages API requests
- Authenticated Packages API requests
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index da9b09e92c1..c11e367a688 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -473,6 +473,7 @@ The following are Docker image-related CI/CD variables.
|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). |
| `SAST_EXCLUDED_ANALYZERS` | Names of default images that should never run. Read more about [customizing analyzers](analyzers.md). |
+| `SAST_ANALYZER_IMAGE_TAG` | Override the default version of analyzer image. Read more about [pinning the analyzer image version](#pinning-to-minor-image-version). |
#### Vulnerability filters
diff --git a/doc/user/group/devops_adoption/index.md b/doc/user/group/devops_adoption/index.md
index ab10ad17ecd..8ea9c3df062 100644
--- a/doc/user/group/devops_adoption/index.md
+++ b/doc/user/group/devops_adoption/index.md
@@ -18,7 +18,7 @@ Refer to this feature's version history for more details.
Prerequisites:
-- A minimum of [Reporter access](../../permissions.md) to the group.
+- You must have at least the [Reporter role](../../permissions.md) for the group.
To access Group DevOps Adoption, go to your group and select **Analytics > DevOps Adoption**.
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index cb6c3f44f7f..104ea57db4a 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -425,6 +425,30 @@ To restore a group that is marked for deletion:
1. Expand the **Path, transfer, remove** section.
1. In the Restore group section, select **Restore group**.
+## Prevent group sharing outside the group hierarchy
+
+This setting is only available on top-level groups. It affects all subgroups.
+
+When checked, any group within the top-level group hierarchy can be shared only with other groups within the hierarchy.
+
+For example, with these groups:
+
+- **Animals > Dogs**
+- **Animals > Cats**
+- **Plants > Trees**
+
+If you select this setting in the **Animals** group:
+
+- **Dogs** can be shared with **Cats**.
+- **Dogs** cannot be shared with **Trees**.
+
+To prevent sharing outside of the group's hierarchy:
+
+1. Go to the group's **Settings > General** page.
+1. Expand the **Permissions, LFS, 2FA** section.
+1. Select **Prevent members from sending invitations to groups outside of `<group_name>` and its subgroups**.
+1. Select **Save changes**.
+
## Prevent a project from being shared with groups
Prevent projects in a group from [sharing
diff --git a/doc/user/group/settings/import_export.md b/doc/user/group/settings/import_export.md
index eca1be81725..c097790ef16 100644
--- a/doc/user/group/settings/import_export.md
+++ b/doc/user/group/settings/import_export.md
@@ -21,9 +21,9 @@ See also:
To enable GitLab import/export:
-1. Navigate to **Admin Area > Settings > Visibility and access controls**.
-1. Scroll to **Import sources**
-1. Enable desired **Import sources**
+1. On the top bar, go to **Menu > Admin > Settings > General > Visibility and access controls**.
+1. Scroll to **Import sources**.
+1. Enable the desired **Import sources**.
## Important Notes
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 8faecbee1de..890784cecf5 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -24,9 +24,9 @@ See also:
To set up a project import/export:
- 1. Navigate to **Admin Area > Settings > Visibility and access controls**.
- 1. Scroll to **Import sources**
- 1. Enable desired **Import sources**
+ 1. On the top bar, go to **Menu > Admin > Settings > General > Visibility and access controls**.
+ 1. Scroll to **Import sources**.
+ 1. Enable the desired **Import sources**.
## Important notes
diff --git a/doc/user/project/time_tracking.md b/doc/user/project/time_tracking.md
index b2eb6c15a81..3c9b0341661 100644
--- a/doc/user/project/time_tracking.md
+++ b/doc/user/project/time_tracking.md
@@ -20,7 +20,7 @@ Time Tracking allows you to:
- Record the time spent working on an issue or a merge request.
- Add an estimate of the amount of time needed to complete an issue or a merge
request.
-- View a breakdown of time spent working on an issue or a merge request.
+- View a breakdown of time spent working on an issue or a merge request.
You don't have to indicate an estimate to enter the time spent, and vice versa.
@@ -84,7 +84,7 @@ You can view a breakdown of time spent on an issue or merge request.
Prerequisites:
-- A minimum of [Reporter](../permissions.md#project-members-permissions) access to a private project in GitLab.
+- You must have at least the [Reporter role](../permissions.md#project-members-permissions) for a project.
To view a time tracking report, go to an issue or a merge request and select **Time tracking report**
in the right sidebar.
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 372bc7b3d8f..b4f8320cb74 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -46,7 +46,7 @@ module API
optional :asset_proxy_allowlist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically allowed.'
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
- optional :default_ci_config_path, type: String, desc: 'The instance default CI configuration path for new projects'
+ optional :default_ci_config_path, type: String, desc: 'The instance default CI/CD configuration file and path for new projects'
optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group'
optional :default_branch_protection, type: Integer, values: ::Gitlab::Access.protection_values, desc: 'Determine if developers can push to master'
optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
diff --git a/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
index 9dc77d52e11..6af79728dc8 100644
--- a/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
@@ -8,7 +8,6 @@ variables:
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
- SAST_DEFAULT_ANALYZERS: "bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, sobelow, pmd-apex, kubesec, mobsf, semgrep"
SAST_EXCLUDED_ANALYZERS: ""
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
SCAN_KUBERNETES_MANIFESTS: "false"
@@ -49,8 +48,7 @@ bandit-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /bandit/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /bandit/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.py'
@@ -69,8 +67,7 @@ brakeman-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /brakeman/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /brakeman/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.rb'
- '**/Gemfile'
@@ -90,8 +87,7 @@ eslint-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /eslint/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /eslint/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.html'
- '**/*.js'
@@ -114,8 +110,7 @@ flawfinder-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /flawfinder/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /flawfinder/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.c'
- '**/*.cpp'
@@ -136,7 +131,6 @@ kubesec-sast:
- if: $SAST_EXCLUDED_ANALYZERS =~ /kubesec/
when: never
- if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /kubesec/ &&
$SCAN_KUBERNETES_MANIFESTS == 'true'
gosec-sast:
@@ -154,8 +148,7 @@ gosec-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /gosec/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /gosec/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.go'
@@ -178,7 +171,6 @@ mobsf-android-sast:
- if: $SAST_EXCLUDED_ANALYZERS =~ /mobsf/
when: never
- if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /mobsf/ &&
$SAST_EXPERIMENTAL_FEATURES == 'true'
exists:
- '**/*.apk'
@@ -192,7 +184,6 @@ mobsf-ios-sast:
- if: $SAST_EXCLUDED_ANALYZERS =~ /mobsf/
when: never
- if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /mobsf/ &&
$SAST_EXPERIMENTAL_FEATURES == 'true'
exists:
- '**/*.ipa'
@@ -213,8 +204,7 @@ nodejs-scan-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /nodejs-scan/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /nodejs-scan/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/package.json'
@@ -233,8 +223,7 @@ phpcs-security-audit-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /phpcs-security-audit/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /phpcs-security-audit/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.php'
@@ -253,8 +242,7 @@ pmd-apex-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /pmd-apex/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /pmd-apex/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.cls'
@@ -273,8 +261,7 @@ security-code-scan-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /security-code-scan/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /security-code-scan/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.csproj'
- '**/*.vbproj'
@@ -294,8 +281,7 @@ semgrep-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /semgrep/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /semgrep/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.py'
- '**/*.js'
@@ -318,8 +304,7 @@ sobelow-sast:
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /sobelow/
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /sobelow/
+ - if: $CI_COMMIT_BRANCH
exists:
- 'mix.exs'
@@ -336,15 +321,13 @@ spotbugs-sast:
rules:
- if: $SAST_EXCLUDED_ANALYZERS =~ /spotbugs/
when: never
- - if: $SAST_DEFAULT_ANALYZERS =~ /mobsf/ &&
- $SAST_EXPERIMENTAL_FEATURES == 'true'
+ - if: $SAST_EXPERIMENTAL_FEATURES == 'true'
exists:
- '**/AndroidManifest.xml'
when: never
- if: $SAST_DISABLED
when: never
- - if: $CI_COMMIT_BRANCH &&
- $SAST_DEFAULT_ANALYZERS =~ /spotbugs/
+ - if: $CI_COMMIT_BRANCH
exists:
- '**/*.groovy'
- '**/*.java'
diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
index f3c9a93d9fb..0c4c39cbcd6 100644
--- a/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
@@ -3,276 +3,31 @@
# Configure API fuzzing with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#available-cicd-variables
-stages:
- - build
- - test
- - deploy
- - fuzz
-
variables:
+ FUZZAPI_VERSION: "1"
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
- FUZZAPI_PROFILE: Quick
- FUZZAPI_VERSION: "1.6"
- FUZZAPI_CONFIG: .gitlab-api-fuzzing.yml
- FUZZAPI_TIMEOUT: 30
- FUZZAPI_REPORT: gl-api-fuzzing-report.json
- FUZZAPI_REPORT_ASSET_PATH: assets
- #
- FUZZAPI_D_NETWORK: testing-net
- #
- # Wait up to 5 minutes for API Fuzzer and target url to become
- # available (non 500 response to HTTP(s))
- FUZZAPI_SERVICE_START_TIMEOUT: "300"
- #
FUZZAPI_IMAGE: ${SECURE_ANALYZERS_PREFIX}/api-fuzzing:${FUZZAPI_VERSION}
- #
-
-apifuzzer_fuzz_unlicensed:
- stage: fuzz
- allow_failure: true
- rules:
- - if: '$GITLAB_FEATURES !~ /\bapi_fuzzing\b/ && $API_FUZZING_DISABLED == null'
- - when: never
- script:
- - |
- echo "Error: Your GitLab project is not licensed for API Fuzzing."
- - exit 1
apifuzzer_fuzz:
stage: fuzz
- image:
- name: $FUZZAPI_IMAGE
- entrypoint: ["/bin/bash", "-l", "-c"]
- variables:
- FUZZAPI_PROJECT: $CI_PROJECT_PATH
- FUZZAPI_API: http://localhost:5000
- FUZZAPI_NEW_REPORT: 1
- FUZZAPI_LOG_SCANNER: gl-apifuzzing-api-scanner.log
- TZ: America/Los_Angeles
+ image: $FUZZAPI_IMAGE
allow_failure: true
rules:
- - if: $FUZZAPI_D_TARGET_IMAGE
- when: never
- - if: $FUZZAPI_D_WORKER_IMAGE
- when: never
- - if: $API_FUZZING_DISABLED
- when: never
- - if: $API_FUZZING_DISABLED_FOR_DEFAULT_BRANCH &&
- $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
- when: never
- - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bapi_fuzzing\b/
- script:
- #
- # Validate options
- - |
- if [ "$FUZZAPI_HAR$FUZZAPI_OPENAPI$FUZZAPI_POSTMAN_COLLECTION" == "" ]; then \
- echo "Error: One of FUZZAPI_HAR, FUZZAPI_OPENAPI, or FUZZAPI_POSTMAN_COLLECTION must be provided."; \
- echo "See https://docs.gitlab.com/ee/user/application_security/api_fuzzing/ for information on how to configure API Fuzzing."; \
- exit 1; \
- fi
- #
- # Run user provided pre-script
- - sh -c "$FUZZAPI_PRE_SCRIPT"
- #
- # Make sure asset path exists
- - mkdir -p $FUZZAPI_REPORT_ASSET_PATH
- #
- # Start API Security background process
- - dotnet /peach/Peach.Web.dll &> $FUZZAPI_LOG_SCANNER &
- - APISEC_PID=$!
- #
- # Start scanning
- - worker-entry
- #
- # Run user provided post-script
- - sh -c "$FUZZAPI_POST_SCRIPT"
- #
- # Shutdown API Security
- - kill $APISEC_PID
- - wait $APISEC_PID
- #
- artifacts:
- when: always
- paths:
- - $FUZZAPI_REPORT_ASSET_PATH
- - $FUZZAPI_REPORT
- - $FUZZAPI_LOG_SCANNER
- reports:
- api_fuzzing: $FUZZAPI_REPORT
-
-apifuzzer_fuzz_dnd:
- stage: fuzz
- image: docker:19.03.12
- variables:
- DOCKER_DRIVER: overlay2
- DOCKER_TLS_CERTDIR: ""
- FUZZAPI_PROJECT: $CI_PROJECT_PATH
- FUZZAPI_API: http://apifuzzer:5000
- allow_failure: true
- rules:
- - if: $FUZZAPI_D_TARGET_IMAGE == null && $FUZZAPI_D_WORKER_IMAGE == null
- when: never
- if: $API_FUZZING_DISABLED
when: never
- if: $API_FUZZING_DISABLED_FOR_DEFAULT_BRANCH &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bapi_fuzzing\b/
- services:
- - docker:19.03.12-dind
+ - if: $CI_COMMIT_BRANCH
script:
- #
- #
- - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- #
- - docker network create --driver bridge $FUZZAPI_D_NETWORK
- #
- # Run user provided pre-script
- - sh -c "$FUZZAPI_PRE_SCRIPT"
- #
- # Make sure asset path exists
- - mkdir -p $FUZZAPI_REPORT_ASSET_PATH
- #
- # Start peach testing engine container
- - |
- docker run -d \
- --name apifuzzer \
- --network $FUZZAPI_D_NETWORK \
- -e Proxy:Port=8000 \
- -e TZ=America/Los_Angeles \
- -e GITLAB_FEATURES \
- -p 80:80 \
- -p 5000:5000 \
- -p 8000:8000 \
- -p 514:514 \
- --restart=no \
- $FUZZAPI_IMAGE \
- dotnet /peach/Peach.Web.dll
- #
- # Start target container
- - |
- if [ "$FUZZAPI_D_TARGET_IMAGE" != "" ]; then \
- docker run -d \
- --name target \
- --network $FUZZAPI_D_NETWORK \
- $FUZZAPI_D_TARGET_ENV \
- $FUZZAPI_D_TARGET_PORTS \
- $FUZZAPI_D_TARGET_VOLUME \
- --restart=no \
- $FUZZAPI_D_TARGET_IMAGE \
- ; fi
- #
- # Start worker container if provided
- - |
- if [ "$FUZZAPI_D_WORKER_IMAGE" != "" ]; then \
- echo "Starting worker image $FUZZAPI_D_WORKER_IMAGE"; \
- docker run \
- --name worker \
- --network $FUZZAPI_D_NETWORK \
- -e FUZZAPI_API=http://apifuzzer:5000 \
- -e FUZZAPI_PROJECT \
- -e FUZZAPI_PROFILE \
- -e FUZZAPI_CONFIG \
- -e FUZZAPI_REPORT \
- -e FUZZAPI_REPORT_ASSET_PATH \
- -e FUZZAPI_NEW_REPORT=1 \
- -e FUZZAPI_HAR \
- -e FUZZAPI_OPENAPI \
- -e FUZZAPI_POSTMAN_COLLECTION \
- -e FUZZAPI_POSTMAN_COLLECTION_VARIABLES \
- -e FUZZAPI_TARGET_URL \
- -e FUZZAPI_OVERRIDES_FILE \
- -e FUZZAPI_OVERRIDES_ENV \
- -e FUZZAPI_OVERRIDES_CMD \
- -e FUZZAPI_OVERRIDES_INTERVAL \
- -e FUZZAPI_TIMEOUT \
- -e FUZZAPI_VERBOSE \
- -e FUZZAPI_SERVICE_START_TIMEOUT \
- -e FUZZAPI_HTTP_USERNAME \
- -e FUZZAPI_HTTP_PASSWORD \
- -e CI_PROJECT_URL \
- -e CI_JOB_ID \
- -e CI_COMMIT_BRANCH=${CI_COMMIT_BRANCH} \
- $FUZZAPI_D_WORKER_ENV \
- $FUZZAPI_D_WORKER_PORTS \
- $FUZZAPI_D_WORKER_VOLUME \
- --restart=no \
- $FUZZAPI_D_WORKER_IMAGE \
- ; fi
- #
- # Start API Fuzzing provided worker if no other worker present
- - |
- if [ "$FUZZAPI_D_WORKER_IMAGE" == "" ]; then \
- if [ "$FUZZAPI_HAR$FUZZAPI_OPENAPI$FUZZAPI_POSTMAN_COLLECTION" == "" ]; then \
- echo "Error: One of FUZZAPI_HAR, FUZZAPI_OPENAPI, or FUZZAPI_POSTMAN_COLLECTION must be provided."; \
- echo "See https://docs.gitlab.com/ee/user/application_security/api_fuzzing/ for information on how to configure API Fuzzing."; \
- exit 1; \
- fi; \
- docker run \
- --name worker \
- --network $FUZZAPI_D_NETWORK \
- -e TZ=America/Los_Angeles \
- -e FUZZAPI_API=http://apifuzzer:5000 \
- -e FUZZAPI_PROJECT \
- -e FUZZAPI_PROFILE \
- -e FUZZAPI_CONFIG \
- -e FUZZAPI_REPORT \
- -e FUZZAPI_REPORT_ASSET_PATH \
- -e FUZZAPI_NEW_REPORT=1 \
- -e FUZZAPI_HAR \
- -e FUZZAPI_OPENAPI \
- -e FUZZAPI_POSTMAN_COLLECTION \
- -e FUZZAPI_POSTMAN_COLLECTION_VARIABLES \
- -e FUZZAPI_TARGET_URL \
- -e FUZZAPI_OVERRIDES_FILE \
- -e FUZZAPI_OVERRIDES_ENV \
- -e FUZZAPI_OVERRIDES_CMD \
- -e FUZZAPI_OVERRIDES_INTERVAL \
- -e FUZZAPI_TIMEOUT \
- -e FUZZAPI_VERBOSE \
- -e FUZZAPI_SERVICE_START_TIMEOUT \
- -e FUZZAPI_HTTP_USERNAME \
- -e FUZZAPI_HTTP_PASSWORD \
- -e CI_PROJECT_URL \
- -e CI_JOB_ID \
- -v $CI_PROJECT_DIR:/app \
- -v `pwd`/$FUZZAPI_REPORT_ASSET_PATH:/app/$FUZZAPI_REPORT_ASSET_PATH:rw \
- -p 81:80 \
- -p 5001:5000 \
- -p 8001:8000 \
- -p 515:514 \
- --restart=no \
- $FUZZAPI_IMAGE \
- worker-entry \
- ; fi
- #
- # Propagate exit code from api fuzzing scanner (if any)
- - if [[ $(docker inspect apifuzzer --format='{{.State.ExitCode}}') != "0" ]]; then echo "API Fuzzing scanner exited with an error. Logs are available as job artifacts."; exit 1; fi
- #
- # Run user provided post-script
- - sh -c "$FUZZAPI_POST_SCRIPT"
- #
- after_script:
- #
- # Shutdown all containers
- - echo "Stopping all containers"
- - if [ "$FUZZAPI_D_TARGET_IMAGE" != "" ]; then docker stop target; fi
- - docker stop worker
- - docker stop apifuzzer
- #
- # Save docker logs
- - docker logs apifuzzer &> gl-api_fuzzing-logs.log
- - if [ "$FUZZAPI_D_TARGET_IMAGE" != "" ]; then docker logs target &> gl-api_fuzzing-target-logs.log; fi
- - docker logs worker &> gl-api_fuzzing-worker-logs.log
- #
+ - /peach/analyzer-fuzz-api
artifacts:
when: always
paths:
- - ./gl-api_fuzzing*.log
- - ./gl-api_fuzzing*.zip
- - $FUZZAPI_REPORT_ASSET_PATH
- - $FUZZAPI_REPORT
+ - gl-assets
+ - gl-api-fuzzing-report.json
+ - gl-*.log
reports:
- api_fuzzing: $FUZZAPI_REPORT
+ api_fuzzing: gl-api-fuzzing-report.json
# end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b4c82b69b43..3058acadaeb 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1897,6 +1897,9 @@ msgstr ""
msgid "Add a comment to this line or drag for multiple lines"
msgstr ""
+msgid "Add a custom message with details about the instance's shared runners. The message is visible in group and project CI/CD settings, in the Runners section. Markdown is supported."
+msgstr ""
+
msgid "Add a general comment to this %{noteableDisplayName}."
msgstr ""
@@ -2332,9 +2335,6 @@ msgstr ""
msgid "AdminSettings|Enable shared runners for new projects"
msgstr ""
-msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
-
msgid "AdminSettings|Feed token"
msgstr ""
@@ -2347,9 +2347,15 @@ msgstr ""
msgid "AdminSettings|Moved to integrations"
msgstr ""
+msgid "AdminSettings|New CI/CD variables in projects and groups default to protected."
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
+msgid "AdminSettings|Protect CI/CD variables by default"
+msgstr ""
+
msgid "AdminSettings|Required pipeline configuration"
msgstr ""
@@ -2380,9 +2386,6 @@ msgstr ""
msgid "AdminSettings|The template for the required pipeline configuration can be one of the GitLab-provided templates, or a custom template added to an instance template repository. %{link_start}How do I create an instance template repository?%{link_end}"
msgstr ""
-msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
-
msgid "AdminSettings|You can't add new templates. To migrate or remove a Service template, create a new integration at %{settings_link_start}Settings &gt; Integrations%{link_end}. Learn more about %{doc_link_start}Project integration management%{link_end}."
msgstr ""
@@ -4709,9 +4712,6 @@ msgstr ""
msgid "Auto DevOps enabled"
msgstr ""
-msgid "Auto DevOps, runners and job artifacts"
-msgstr ""
-
msgid "Auto stop successfully canceled."
msgstr ""
@@ -5770,10 +5770,10 @@ msgstr ""
msgid "CICD|Jobs"
msgstr ""
-msgid "CICD|The Auto DevOps pipeline runs if no alternative CI configuration file is found."
+msgid "CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file."
msgstr ""
-msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgid "CICD|The Auto DevOps pipeline runs if no alternative CI configuration file is found."
msgstr ""
msgid "CICD|group enabled"
@@ -6466,6 +6466,9 @@ msgstr ""
msgid "Choose specific groups or storage shards"
msgstr ""
+msgid "Choose the preferred Runner and populate the AWS CFT."
+msgstr ""
+
msgid "Choose the top-level group for your repository imports."
msgstr ""
@@ -9030,6 +9033,9 @@ msgstr ""
msgid "Copy the code below to implement tracking in your application:"
msgstr ""
+msgid "Copy this registration token."
+msgstr ""
+
msgid "Copy this value"
msgstr ""
@@ -9741,6 +9747,9 @@ msgstr ""
msgid "Customizable by owners."
msgstr ""
+msgid "Customize CI/CD settings, including Auto DevOps, shared runners, and job artifacts."
+msgstr ""
+
msgid "Customize colors"
msgstr ""
@@ -10455,7 +10464,7 @@ msgstr ""
msgid "Default"
msgstr ""
-msgid "Default CI configuration path"
+msgid "Default CI/CD configuration file"
msgstr ""
msgid "Default Timeout Period"
@@ -15898,6 +15907,9 @@ msgstr ""
msgid "GroupSettings|Prevent forking setting was not saved"
msgstr ""
+msgid "GroupSettings|Prevent members from sending invitations to groups outside of %{group} and its subgroups."
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
@@ -15934,6 +15946,9 @@ msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
msgstr ""
+msgid "GroupSettings|This setting is only available on the top-level group and it applies to all subgroups. Groups that have already been shared with a group outside %{group} will still be shared, and this access will have to be revoked manually."
+msgstr ""
+
msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
msgstr ""
@@ -21791,6 +21806,9 @@ msgstr ""
msgid "NetworkPolicies|Save changes"
msgstr ""
+msgid "NetworkPolicies|Scan Execution"
+msgstr ""
+
msgid "NetworkPolicies|Something went wrong, failed to update policy"
msgstr ""
@@ -24158,12 +24176,18 @@ msgstr ""
msgid "Pipelines|Improve code quality with GitLab CI/CD"
msgstr ""
+msgid "Pipelines|Install GitLab Runners"
+msgstr ""
+
msgid "Pipelines|It is recommended the code is reviewed thoroughly before running this pipeline with the parent project's CI resource."
msgstr ""
msgid "Pipelines|Last Used"
msgstr ""
+msgid "Pipelines|Learn about Runners"
+msgstr ""
+
msgid "Pipelines|Lint"
msgstr ""
@@ -28090,6 +28114,9 @@ msgstr ""
msgid "RightSidebar|deleting the"
msgstr ""
+msgid "Rnners|Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}."
+msgstr ""
+
msgid "Roadmap"
msgstr ""
@@ -28168,6 +28195,12 @@ msgstr ""
msgid "Runners|Active"
msgstr ""
+msgid "Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. %{percentage} spot."
+msgstr ""
+
+msgid "Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor."
+msgstr ""
+
msgid "Runners|An error has occurred fetching instructions"
msgstr ""
@@ -28186,6 +28219,9 @@ msgstr ""
msgid "Runners|Copy instructions"
msgstr ""
+msgid "Runners|Deploy GitLab Runner in AWS"
+msgstr ""
+
msgid "Runners|Description"
msgstr ""
@@ -28198,9 +28234,15 @@ msgstr ""
msgid "Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project."
msgstr ""
+msgid "Runners|For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet."
+msgstr ""
+
msgid "Runners|IP Address"
msgstr ""
+msgid "Runners|If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account."
+msgstr ""
+
msgid "Runners|Install a runner"
msgstr ""
@@ -28294,6 +28336,12 @@ msgstr ""
msgid "Runners|View installation instructions"
msgstr ""
+msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. %{percentage} spot."
+msgstr ""
+
+msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor."
+msgstr ""
+
msgid "Runners|You can set up a specific runner to be used by multiple projects but you cannot make this a shared runner."
msgstr ""
@@ -29906,6 +29954,9 @@ msgstr ""
msgid "Shared runners are disabled on group level"
msgstr ""
+msgid "Shared runners details"
+msgstr ""
+
msgid "Shared runners help link"
msgstr ""
@@ -32344,7 +32395,7 @@ msgstr ""
msgid "The data source is connected, but there is no data to display. %{documentationLink}"
msgstr ""
-msgid "The default CI configuration path for new projects."
+msgid "The default CI/CD configuration file and path for new projects."
msgstr ""
msgid "The dependency list details information about the components used within your project."
@@ -35352,9 +35403,15 @@ msgstr ""
msgid "Use .gitlab-ci.yml"
msgstr ""
+msgid "Use GitLab Runner in AWS"
+msgstr ""
+
msgid "Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
msgstr ""
+msgid "Use an AWS CloudFormation Template (CFT) to install and configure GitLab Runner in AWS."
+msgstr ""
+
msgid "Use cURL"
msgstr ""
@@ -36667,6 +36724,9 @@ msgstr ""
msgid "What does this command do?"
msgstr ""
+msgid "What is Auto DevOps?"
+msgstr ""
+
msgid "What is repository mirroring?"
msgstr ""
@@ -38036,6 +38096,9 @@ msgstr ""
msgid "can only be changed by a group admin."
msgstr ""
+msgid "can only have one escalation policy"
+msgstr ""
+
msgid "can't be enabled because signed commits are required for this project"
msgstr ""
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index f47eac7ac25..07d8332bfd0 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -651,6 +651,45 @@ RSpec.describe GroupsController, factory_default: :keep do
end
end
+ describe 'updating :prevent_sharing_groups_outside_hierarchy' do
+ subject do
+ put :update,
+ params: {
+ id: group.to_param,
+ group: { prevent_sharing_groups_outside_hierarchy: true }
+ }
+ end
+
+ context 'when user is a group owner' do
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ end
+
+ it 'updates the attribute' do
+ expect { subject }
+ .to change { group.namespace_settings.reload.prevent_sharing_groups_outside_hierarchy }
+ .from(false)
+ .to(true)
+
+ expect(response).to have_gitlab_http_status(:found)
+ end
+ end
+
+ context 'when not a group owner' do
+ before do
+ group.add_maintainer(user)
+ sign_in(user)
+ end
+
+ it 'does not update the attribute' do
+ expect { subject }.not_to change { group.namespace_settings.reload.prevent_sharing_groups_outside_hierarchy }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
describe '#ensure_canonical_path' do
before do
sign_in(user)
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 050d5e00670..a80c5fa82f6 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -278,26 +278,22 @@ RSpec.describe Projects::PipelinesController do
stub_application_setting(auto_devops_enabled: false)
end
- context 'pipeline_empty_state_templates experiment' do
- it 'tracks the assignment', :experiment do
- expect(experiment(:pipeline_empty_state_templates))
- .to track(:assignment)
- .with_context(namespace: project.root_ancestor)
- .on_next_instance
+ def action
+ get :index, params: { namespace_id: project.namespace, project_id: project }
+ end
- get :index, params: { namespace_id: project.namespace, project_id: project }
- end
+ subject { project.namespace }
+
+ context 'pipeline_empty_state_templates experiment' do
+ it_behaves_like 'tracks assignment and records the subject', :pipeline_empty_state_templates, :namespace
end
context 'code_quality_walkthrough experiment' do
- it 'tracks the assignment', :experiment do
- expect(experiment(:code_quality_walkthrough))
- .to track(:assignment)
- .with_context(namespace: project.root_ancestor)
- .on_next_instance
+ it_behaves_like 'tracks assignment and records the subject', :code_quality_walkthrough, :namespace
+ end
- get :index, params: { namespace_id: project.namespace, project_id: project }
- end
+ context 'ci_runner_templates experiment' do
+ it_behaves_like 'tracks assignment and records the subject', :ci_runner_templates, :namespace
end
end
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 00ad1006037..161a8a7a203 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -153,6 +153,26 @@ RSpec.describe 'Edit group settings' do
end
end
+ describe 'prevent sharing outside group hierarchy setting' do
+ it 'updates the setting' do
+ visit edit_group_path(group)
+
+ check 'group_prevent_sharing_groups_outside_hierarchy'
+
+ expect { save_permissions_group }.to change {
+ group.reload.namespace_settings.prevent_sharing_groups_outside_hierarchy
+ }.to(true)
+ end
+
+ it 'is not present for a subgroup' do
+ subgroup = create(:group, parent: group)
+ visit edit_group_path(subgroup)
+
+ expect(page).to have_text "Permissions"
+ expect(page).not_to have_selector('#group_prevent_sharing_groups_outside_hierarchy')
+ end
+ end
+
def update_path(new_group_path)
visit edit_group_path(group)
diff --git a/spec/frontend/ide/components/branches/item_spec.js b/spec/frontend/ide/components/branches/item_spec.js
index f90c298c401..271d0600e16 100644
--- a/spec/frontend/ide/components/branches/item_spec.js
+++ b/spec/frontend/ide/components/branches/item_spec.js
@@ -7,7 +7,7 @@ import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
import { projectData } from '../../mock_data';
const TEST_BRANCH = {
- name: 'master',
+ name: 'main',
committedDate: '2018-01-05T05:50Z',
};
const TEST_PROJECT_ID = projectData.name_with_namespace;
diff --git a/spec/frontend/ide/components/commit_sidebar/actions_spec.js b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
index c4dccf26af3..ed9d11246ae 100644
--- a/spec/frontend/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
@@ -10,7 +10,7 @@ import {
const ACTION_UPDATE_COMMIT_ACTION = 'commit/updateCommitAction';
-const BRANCH_DEFAULT = 'master';
+const BRANCH_DEFAULT = 'main';
const BRANCH_PROTECTED = 'protected/access';
const BRANCH_PROTECTED_NO_ACCESS = 'protected/no-access';
const BRANCH_REGULAR = 'regular';
@@ -20,11 +20,7 @@ describe('IDE commit sidebar actions', () => {
let store;
let vm;
- const createComponent = ({
- hasMR = false,
- currentBranchId = 'master',
- emptyRepo = false,
- } = {}) => {
+ const createComponent = ({ hasMR = false, currentBranchId = 'main', emptyRepo = false } = {}) => {
const Component = Vue.extend(commitActions);
vm = createComponentWithStore(Component, store);
@@ -72,7 +68,7 @@ describe('IDE commit sidebar actions', () => {
it('renders current branch text', () => {
createComponent();
- expect(findText()).toContain('Commit to master branch');
+ expect(findText()).toContain('Commit to main branch');
});
it('hides merge request option when project merge requests are disabled', (done) => {
@@ -112,7 +108,7 @@ describe('IDE commit sidebar actions', () => {
it('calls again after staged changes', (done) => {
createComponent({ currentBranchId: null });
- vm.$store.state.currentBranchId = 'master';
+ vm.$store.state.currentBranchId = 'main';
vm.$store.state.changedFiles.push({});
vm.$store.state.stagedFiles.push({});
@@ -158,7 +154,7 @@ describe('IDE commit sidebar actions', () => {
it('only renders commit to current branch', () => {
expect(findRadios().length).toBe(1);
- expect(findText()).toContain('Commit to master branch');
+ expect(findText()).toContain('Commit to main branch');
});
});
});
diff --git a/spec/frontend/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js
index f5916b021aa..83d1bbb842e 100644
--- a/spec/frontend/ide/components/commit_sidebar/form_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/form_spec.js
@@ -67,7 +67,7 @@ describe('IDE commit form', () => {
store = createStore();
store.state.stagedFiles.push('test');
store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
Vue.set(store.state.projects, 'abcproject', {
...projectData,
userPermissions: { pushCode: true },
diff --git a/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js
index 253c2a426ee..4474647552d 100644
--- a/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js
@@ -24,7 +24,7 @@ describe('create new MR checkbox', () => {
store.state.projects[store.state.currentProjectId].userPermissions = permissions;
};
- const createComponent = ({ currentBranchId = 'master', createNewBranch = false } = {}) => {
+ const createComponent = ({ currentBranchId = 'main', createNewBranch = false } = {}) => {
const Component = Vue.extend(NewMergeRequestOption);
vm = createComponentWithStore(Component, store);
@@ -63,7 +63,7 @@ describe('create new MR checkbox', () => {
describe('is rendered when pushing to a new branch', () => {
beforeEach(() => {
createComponent({
- currentBranchId: 'master',
+ currentBranchId: 'main',
createNewBranch: true,
});
});
@@ -87,7 +87,7 @@ describe('create new MR checkbox', () => {
describe('is NOT rendered when pushing to the same branch', () => {
beforeEach(() => {
createComponent({
- currentBranchId: 'master',
+ currentBranchId: 'main',
createNewBranch: false,
});
});
diff --git a/spec/frontend/ide/components/ide_review_spec.js b/spec/frontend/ide/components/ide_review_spec.js
index 740b7ada521..7a92f59641f 100644
--- a/spec/frontend/ide/components/ide_review_spec.js
+++ b/spec/frontend/ide/components/ide_review_spec.js
@@ -19,9 +19,9 @@ describe('IDE review mode', () => {
beforeEach(() => {
store = createStore();
store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
store.state.projects.abcproject = { ...projectData };
- Vue.set(store.state.trees, 'abcproject/master', {
+ Vue.set(store.state.trees, 'abcproject/main', {
tree: [file('fileName')],
loading: false,
});
diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js
index b23a78a035d..f8d29fc7b47 100644
--- a/spec/frontend/ide/components/ide_spec.js
+++ b/spec/frontend/ide/components/ide_spec.js
@@ -22,9 +22,9 @@ describe('WebIDE', () => {
const createComponent = ({ projData = emptyProjData, state = {} } = {}) => {
store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
store.state.projects.abcproject = projData && { ...projData };
- store.state.trees['abcproject/master'] = {
+ store.state.trees['abcproject/main'] = {
tree: [],
loading: false,
};
diff --git a/spec/frontend/ide/components/ide_status_bar_spec.js b/spec/frontend/ide/components/ide_status_bar_spec.js
index 9d33a1e2554..f1a0b64caf2 100644
--- a/spec/frontend/ide/components/ide_status_bar_spec.js
+++ b/spec/frontend/ide/components/ide_status_bar_spec.js
@@ -24,7 +24,7 @@ describe('ideStatusBar', () => {
store = createStore();
store.state.currentProjectId = TEST_PROJECT_ID;
store.state.projects[TEST_PROJECT_ID] = _.clone(projectData);
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
});
afterEach(() => {
diff --git a/spec/frontend/ide/components/ide_tree_list_spec.js b/spec/frontend/ide/components/ide_tree_list_spec.js
index c8153ea339e..85d9feb0c09 100644
--- a/spec/frontend/ide/components/ide_tree_list_spec.js
+++ b/spec/frontend/ide/components/ide_tree_list_spec.js
@@ -14,9 +14,9 @@ describe('IDE tree list', () => {
const bootstrapWithTree = (tree = normalBranchTree) => {
store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
store.state.projects.abcproject = { ...projectData };
- Vue.set(store.state.trees, 'abcproject/master', {
+ Vue.set(store.state.trees, 'abcproject/main', {
tree,
loading: false,
});
@@ -42,7 +42,7 @@ describe('IDE tree list', () => {
});
it('renders loading indicator', (done) => {
- store.state.trees['abcproject/master'].loading = true;
+ store.state.trees['abcproject/main'].loading = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull();
diff --git a/spec/frontend/ide/components/ide_tree_spec.js b/spec/frontend/ide/components/ide_tree_spec.js
index 6eef646b012..0792b88aeb6 100644
--- a/spec/frontend/ide/components/ide_tree_spec.js
+++ b/spec/frontend/ide/components/ide_tree_spec.js
@@ -18,9 +18,9 @@ describe('IdeTree', () => {
store = createStore();
store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
store.state.projects.abcproject = { ...projectData };
- Vue.set(store.state.trees, 'abcproject/master', {
+ Vue.set(store.state.trees, 'abcproject/main', {
tree: [file('fileName')],
loading: false,
});
diff --git a/spec/frontend/ide/components/merge_requests/list_spec.js b/spec/frontend/ide/components/merge_requests/list_spec.js
index 85acabca38b..610e20d5868 100644
--- a/spec/frontend/ide/components/merge_requests/list_spec.js
+++ b/spec/frontend/ide/components/merge_requests/list_spec.js
@@ -21,7 +21,7 @@ describe('IDE merge requests list', () => {
const fakeStore = new Vuex.Store({
state: {
currentMergeRequestId: '1',
- currentProjectId: 'project/master',
+ currentProjectId: 'project/main',
...restOfState,
},
modules: {
diff --git a/spec/frontend/ide/components/nav_dropdown_spec.js b/spec/frontend/ide/components/nav_dropdown_spec.js
index 4ddb3930764..6a1be7ee964 100644
--- a/spec/frontend/ide/components/nav_dropdown_spec.js
+++ b/spec/frontend/ide/components/nav_dropdown_spec.js
@@ -14,14 +14,14 @@ describe('IDE NavDropdown', () => {
store = createStore();
Object.assign(store.state, {
currentProjectId: TEST_PROJECT_ID,
- currentBranchId: 'master',
+ currentBranchId: 'main',
projects: {
[TEST_PROJECT_ID]: {
userPermissions: {
[PERMISSION_READ_MR]: true,
},
branches: {
- master: { id: 'master' },
+ main: { id: 'main' },
},
},
},
diff --git a/spec/frontend/ide/components/new_dropdown/index_spec.js b/spec/frontend/ide/components/new_dropdown/index_spec.js
index 5a1c0471206..fa34d1b257f 100644
--- a/spec/frontend/ide/components/new_dropdown/index_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/index_spec.js
@@ -13,7 +13,7 @@ describe('new dropdown component', () => {
const component = Vue.extend(newDropdown);
vm = createComponentWithStore(component, store, {
- branch: 'master',
+ branch: 'main',
path: '',
mouseOver: false,
type: 'tree',
diff --git a/spec/frontend/ide/components/repo_commit_section_spec.js b/spec/frontend/ide/components/repo_commit_section_spec.js
index c174f5e2006..db4181395d3 100644
--- a/spec/frontend/ide/components/repo_commit_section_spec.js
+++ b/spec/frontend/ide/components/repo_commit_section_spec.js
@@ -22,11 +22,11 @@ describe('RepoCommitSection', () => {
store.state.noChangesStateSvgPath = 'svg';
store.state.committedStateSvgPath = 'commitsvg';
store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
store.state.projects.abcproject = {
web_url: '',
branches: {
- master: {
+ main: {
workingReference: '1',
},
},
@@ -39,7 +39,7 @@ describe('RepoCommitSection', () => {
}),
);
- store.state.currentBranch = 'master';
+ store.state.currentBranch = 'main';
store.state.changedFiles = [];
store.state.stagedFiles = [{ ...files[0] }, { ...files[1] }];
store.state.stagedFiles.forEach((f) =>
diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js
index 41a93c7ba5a..8e8fb31b15a 100644
--- a/spec/frontend/ide/components/repo_editor_spec.js
+++ b/spec/frontend/ide/components/repo_editor_spec.js
@@ -63,8 +63,8 @@ const prepareStore = (state, activeFile) => {
projects: {
'gitlab-org/gitlab': {
branches: {
- master: {
- name: 'master',
+ main: {
+ name: 'main',
commit: {
id: 'abcdefgh',
},
@@ -73,7 +73,7 @@ const prepareStore = (state, activeFile) => {
},
},
currentProjectId: 'gitlab-org/gitlab',
- currentBranchId: 'master',
+ currentBranchId: 'main',
entries: {
[activeFile.path]: activeFile,
},
diff --git a/spec/frontend/ide/ide_router_spec.js b/spec/frontend/ide/ide_router_spec.js
index acab2c6aeef..3fb7781b176 100644
--- a/spec/frontend/ide/ide_router_spec.js
+++ b/spec/frontend/ide/ide_router_spec.js
@@ -18,14 +18,14 @@ describe('IDE router', () => {
});
[
- `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/master/-/src/blob/`,
- `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/master/-/src/blob`,
+ `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/main/-/src/blob/`,
+ `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/main/-/src/blob`,
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/blob/-/src/blob`,
- `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/master/-/src/tree/`,
+ `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/main/-/src/tree/`,
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/weird:branch/name-123/-/src/tree/`,
- `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/master/-/src/blob`,
- `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/master/-/src/edit`,
- `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/master/-/src/merge_requests/2`,
+ `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/main/-/src/blob`,
+ `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/main/-/src/edit`,
+ `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/main/-/src/merge_requests/2`,
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/blob/-/src/blob`,
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/edit/blob/-/src/blob`,
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/merge_requests/2`,
diff --git a/spec/frontend/ide/mock_data.js b/spec/frontend/ide/mock_data.js
index c8925e6745d..557626b3cca 100644
--- a/spec/frontend/ide/mock_data.js
+++ b/spec/frontend/ide/mock_data.js
@@ -8,8 +8,8 @@ export const projectData = {
path: '',
name_with_namespace: 'namespace/abcproject',
branches: {
- master: {
- treeId: 'abcproject/master',
+ main: {
+ treeId: 'abcproject/main',
can_push: true,
commit: {
id: '123',
@@ -19,13 +19,13 @@ export const projectData = {
mergeRequests: {},
merge_requests_enabled: true,
userPermissions: {},
- default_branch: 'master',
+ default_branch: 'main',
};
export const pipelines = [
{
id: 1,
- ref: 'master',
+ ref: 'main',
sha: '123',
details: {
status: {
@@ -38,7 +38,7 @@ export const pipelines = [
},
{
id: 2,
- ref: 'master',
+ ref: 'main',
sha: '213',
details: {
status: {
@@ -178,9 +178,9 @@ export const mergeRequests = [
export const branches = [
{
id: 1,
- name: 'master',
+ name: 'main',
commit: {
- message: 'Update master branch',
+ message: 'Update main branch',
committed_date: '2018-08-01T00:20:05Z',
},
can_push: true,
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index 4a726cff3b6..925446aa280 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -15,7 +15,7 @@ jest.mock('~/ide/services/gql');
const TEST_NAMESPACE = 'alice';
const TEST_PROJECT = 'wonderland';
const TEST_PROJECT_ID = `${TEST_NAMESPACE}/${TEST_PROJECT}`;
-const TEST_BRANCH = 'master-patch-123';
+const TEST_BRANCH = 'main-patch-123';
const TEST_COMMIT_SHA = '123456789';
const TEST_FILE_PATH = 'README2.md';
const TEST_FILE_OLD_PATH = 'OLD_README2.md';
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index 6178fb08d8c..6b94d7cf6f1 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -29,7 +29,7 @@ describe('IDE store file actions', () => {
store = createStore();
store.state.currentProjectId = 'test/test';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
router = createRouter(store);
@@ -85,7 +85,7 @@ describe('IDE store file actions', () => {
.dispatch('closeFile', localFile)
.then(Vue.nextTick)
.then(() => {
- expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/master/-/newOpenFile/');
+ expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/main/-/newOpenFile/');
});
});
@@ -177,11 +177,11 @@ describe('IDE store file actions', () => {
store.state.entries[localFile.path] = localFile;
store.state.currentProjectId = 'test/test';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
store.state.projects['test/test'] = {
branches: {
- master: {
+ main: {
commit: {
id: '7297abc',
},
@@ -260,7 +260,7 @@ describe('IDE store file actions', () => {
it('sets document title with the branchId', () => {
return store.dispatch('getFileData', { path: localFile.path }).then(() => {
- expect(document.title).toBe(`${localFile.path} · master · test/test · GitLab`);
+ expect(document.title).toBe(`${localFile.path} · main · test/test · GitLab`);
});
});
@@ -329,7 +329,7 @@ describe('IDE store file actions', () => {
it('sets document title considering `prevPath` on a file', () => {
return store.dispatch('getFileData', { path: localFile.path }).then(() => {
- expect(document.title).toBe(`new-shiny-file · master · test/test · GitLab`);
+ expect(document.title).toBe(`new-shiny-file · main · test/test · GitLab`);
});
});
});
@@ -702,7 +702,7 @@ describe('IDE store file actions', () => {
});
it('pushes route for active file', () => {
- expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/master/-/tempFile/');
+ expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/main/-/tempFile/');
});
});
});
@@ -778,7 +778,7 @@ describe('IDE store file actions', () => {
it('pushes router URL when added', () => {
return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(() => {
- expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/master/');
+ expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/main/');
});
});
});
diff --git a/spec/frontend/ide/stores/actions/merge_request_spec.js b/spec/frontend/ide/stores/actions/merge_request_spec.js
index a923d0df99f..e62811a4517 100644
--- a/spec/frontend/ide/stores/actions/merge_request_spec.js
+++ b/spec/frontend/ide/stores/actions/merge_request_spec.js
@@ -463,11 +463,11 @@ describe('IDE store merge request actions', () => {
};
store.state.currentProjectId = 'test/test';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
store.state.projects['test/test'] = {
branches: {
- master: {
+ main: {
commit: {
id: '7297abc',
},
diff --git a/spec/frontend/ide/stores/actions/project_spec.js b/spec/frontend/ide/stores/actions/project_spec.js
index 23ffb5ff56b..ca6f7169059 100644
--- a/spec/frontend/ide/stores/actions/project_spec.js
+++ b/spec/frontend/ide/stores/actions/project_spec.js
@@ -37,11 +37,11 @@ describe('IDE store project actions', () => {
describe('refreshLastCommitData', () => {
beforeEach(() => {
store.state.currentProjectId = 'abc/def';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
store.state.projects['abc/def'] = {
id: 4,
branches: {
- master: {
+ main: {
commit: null,
},
},
@@ -60,7 +60,7 @@ describe('IDE store project actions', () => {
branchId: store.state.currentBranchId,
})
.then(() => {
- expect(service.getBranchData).toHaveBeenCalledWith('abc/def', 'master');
+ expect(service.getBranchData).toHaveBeenCalledWith('abc/def', 'main');
done();
})
@@ -81,7 +81,7 @@ describe('IDE store project actions', () => {
type: 'SET_BRANCH_COMMIT',
payload: {
projectId: TEST_PROJECT_ID,
- branchId: 'master',
+ branchId: 'main',
commit: { id: '123' },
},
},
@@ -97,17 +97,17 @@ describe('IDE store project actions', () => {
it('dispatches setErrorMessage', (done) => {
testAction(
showBranchNotFoundError,
- 'master',
+ 'main',
null,
[],
[
{
type: 'setErrorMessage',
payload: {
- text: "Branch <strong>master</strong> was not found in this project's repository.",
+ text: "Branch <strong>main</strong> was not found in this project's repository.",
action: expect.any(Function),
actionText: 'Create branch',
- actionPayload: 'master',
+ actionPayload: 'main',
},
},
],
@@ -131,7 +131,7 @@ describe('IDE store project actions', () => {
},
getters: {
currentProject: {
- default_branch: 'master',
+ default_branch: 'main',
},
},
dispatch() {},
@@ -140,7 +140,7 @@ describe('IDE store project actions', () => {
)
.then(() => {
expect(api.createBranch).toHaveBeenCalledWith('project-path', {
- ref: 'master',
+ ref: 'main',
branch: 'new-branch-name',
});
})
@@ -158,7 +158,7 @@ describe('IDE store project actions', () => {
},
getters: {
currentProject: {
- default_branch: 'master',
+ default_branch: 'main',
},
},
dispatch: dispatchSpy,
@@ -180,7 +180,7 @@ describe('IDE store project actions', () => {
},
getters: {
currentProject: {
- default_branch: 'master',
+ default_branch: 'main',
},
},
dispatch() {},
@@ -199,13 +199,13 @@ describe('IDE store project actions', () => {
it('creates a blank tree and sets loading state to false', (done) => {
testAction(
loadEmptyBranch,
- { projectId: TEST_PROJECT_ID, branchId: 'master' },
+ { projectId: TEST_PROJECT_ID, branchId: 'main' },
store.state,
[
- { type: 'CREATE_TREE', payload: { treePath: `${TEST_PROJECT_ID}/master` } },
+ { type: 'CREATE_TREE', payload: { treePath: `${TEST_PROJECT_ID}/main` } },
{
type: 'TOGGLE_LOADING',
- payload: { entry: store.state.trees[`${TEST_PROJECT_ID}/master`], forceValue: false },
+ payload: { entry: store.state.trees[`${TEST_PROJECT_ID}/main`], forceValue: false },
},
],
expect.any(Object),
@@ -214,11 +214,11 @@ describe('IDE store project actions', () => {
});
it('does nothing, if tree already exists', (done) => {
- const trees = { [`${TEST_PROJECT_ID}/master`]: [] };
+ const trees = { [`${TEST_PROJECT_ID}/main`]: [] };
testAction(
loadEmptyBranch,
- { projectId: TEST_PROJECT_ID, branchId: 'master' },
+ { projectId: TEST_PROJECT_ID, branchId: 'main' },
{ trees },
[],
[],
diff --git a/spec/frontend/ide/stores/actions/tree_spec.js b/spec/frontend/ide/stores/actions/tree_spec.js
index 8de2188a5f4..8d7328725e9 100644
--- a/spec/frontend/ide/stores/actions/tree_spec.js
+++ b/spec/frontend/ide/stores/actions/tree_spec.js
@@ -18,8 +18,8 @@ describe('Multi-file store tree actions', () => {
const basicCallParameters = {
endpoint: 'rootEndpoint',
projectId: 'abcproject',
- branch: 'master',
- branchId: 'master',
+ branch: 'main',
+ branchId: 'main',
ref: '12345678',
};
@@ -31,7 +31,7 @@ describe('Multi-file store tree actions', () => {
mock = new MockAdapter(axios);
store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
store.state.projects.abcproject = {
web_url: '',
path_with_namespace: 'foo/abcproject',
@@ -66,7 +66,7 @@ describe('Multi-file store tree actions', () => {
store
.dispatch('getFiles', basicCallParameters)
.then(() => {
- projectTree = store.state.trees['abcproject/master'];
+ projectTree = store.state.trees['abcproject/main'];
expect(projectTree.tree.length).toBe(2);
expect(projectTree.tree[0].type).toBe('tree');
@@ -89,7 +89,7 @@ describe('Multi-file store tree actions', () => {
'abc/def': {
web_url: `${TEST_HOST}/files`,
branches: {
- 'master-testing': {
+ 'main-testing': {
commit: {
id: '12345',
},
@@ -98,7 +98,7 @@ describe('Multi-file store tree actions', () => {
},
};
const getters = {
- findBranch: () => store.state.projects['abc/def'].branches['master-testing'],
+ findBranch: () => store.state.projects['abc/def'].branches['main-testing'],
};
mock.onGet(/(.*)/).replyOnce(500);
@@ -112,7 +112,7 @@ describe('Multi-file store tree actions', () => {
},
{
projectId: 'abc/def',
- branchId: 'master-testing',
+ branchId: 'main-testing',
},
)
.then(done.fail)
@@ -121,7 +121,7 @@ describe('Multi-file store tree actions', () => {
text: 'An error occurred while loading all the files.',
action: expect.any(Function),
actionText: 'Please try again',
- actionPayload: { projectId: 'abc/def', branchId: 'master-testing' },
+ actionPayload: { projectId: 'abc/def', branchId: 'main-testing' },
});
done();
});
@@ -178,17 +178,17 @@ describe('Multi-file store tree actions', () => {
describe('setDirectoryData', () => {
it('sets tree correctly if there are no opened files yet', (done) => {
const treeFile = file({ name: 'README.md' });
- store.state.trees['abcproject/master'] = {};
+ store.state.trees['abcproject/main'] = {};
testAction(
setDirectoryData,
- { projectId: 'abcproject', branchId: 'master', treeList: [treeFile] },
+ { projectId: 'abcproject', branchId: 'main', treeList: [treeFile] },
store.state,
[
{
type: types.SET_DIRECTORY_DATA,
payload: {
- treePath: 'abcproject/master',
+ treePath: 'abcproject/main',
data: [treeFile],
},
},
diff --git a/spec/frontend/ide/stores/actions_spec.js b/spec/frontend/ide/stores/actions_spec.js
index ad55313da93..e575667b8c6 100644
--- a/spec/frontend/ide/stores/actions_spec.js
+++ b/spec/frontend/ide/stores/actions_spec.js
@@ -777,7 +777,7 @@ describe('Multi-file store actions', () => {
it('routes to the renamed file if the original file has been opened', (done) => {
store.state.currentProjectId = 'test/test';
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
Object.assign(store.state.entries.orig, {
opened: true,
@@ -790,7 +790,7 @@ describe('Multi-file store actions', () => {
})
.then(() => {
expect(router.push.mock.calls).toHaveLength(1);
- expect(router.push).toHaveBeenCalledWith(`/project/test/test/tree/master/-/renamed/`);
+ expect(router.push).toHaveBeenCalledWith(`/project/test/test/tree/main/-/renamed/`);
})
.then(done)
.catch(done.fail);
@@ -1019,7 +1019,7 @@ describe('Multi-file store actions', () => {
},
{
projectId: 'abc/def',
- branchId: 'master-testing',
+ branchId: 'main-testing',
},
];
dispatch = jest.fn();
diff --git a/spec/frontend/ide/stores/getters_spec.js b/spec/frontend/ide/stores/getters_spec.js
index 06456cdb12a..53d161ae5c9 100644
--- a/spec/frontend/ide/stores/getters_spec.js
+++ b/spec/frontend/ide/stores/getters_spec.js
@@ -209,12 +209,12 @@ describe('IDE store getters', () => {
describe('currentBranch', () => {
it('returns current projects branch', () => {
localState.currentProjectId = 'abcproject';
- localState.currentBranchId = 'master';
+ localState.currentBranchId = 'main';
localState.projects.abcproject = {
name: 'abcproject',
branches: {
- master: {
- name: 'master',
+ main: {
+ name: 'main',
},
},
};
@@ -223,7 +223,7 @@ describe('IDE store getters', () => {
};
getters.currentBranch(localState, localGetters);
- expect(localGetters.findBranch).toHaveBeenCalledWith('abcproject', 'master');
+ expect(localGetters.findBranch).toHaveBeenCalledWith('abcproject', 'main');
});
});
@@ -243,12 +243,12 @@ describe('IDE store getters', () => {
it('returns the selected branch from a project', () => {
localState.currentProjectId = 'abcproject';
- localState.currentBranchId = 'master';
+ localState.currentBranchId = 'main';
localState.projects.abcproject = {
name: 'abcproject',
branches: {
- master: {
- name: 'master',
+ main: {
+ name: 'main',
},
},
};
@@ -256,9 +256,9 @@ describe('IDE store getters', () => {
findProject: () => localState.projects.abcproject,
};
- result = getters.findBranch(localState, localGetters)('abcproject', 'master');
+ result = getters.findBranch(localState, localGetters)('abcproject', 'main');
- expect(result.name).toBe('master');
+ expect(result.name).toBe('main');
});
});
@@ -274,9 +274,9 @@ describe('IDE store getters', () => {
it("returns true when project's default branch matches current branch", () => {
const localGetters = {
currentProject: {
- default_branch: 'master',
+ default_branch: 'main',
},
- branchName: 'master',
+ branchName: 'main',
};
expect(getters.isOnDefaultBranch({}, localGetters)).toBeTruthy();
@@ -285,7 +285,7 @@ describe('IDE store getters', () => {
it("returns false when project's default branch doesn't match current branch", () => {
const localGetters = {
currentProject: {
- default_branch: 'master',
+ default_branch: 'main',
},
branchName: 'feature',
};
@@ -620,10 +620,10 @@ describe('IDE store getters', () => {
describe('getUrlForPath', () => {
it('returns a route url for the given path', () => {
localState.currentProjectId = 'test/test';
- localState.currentBranchId = 'master';
+ localState.currentBranchId = 'main';
expect(localStore.getters.getUrlForPath('path/to/foo/bar-1.jpg')).toBe(
- `/project/test/test/tree/master/-/path/to/foo/bar-1.jpg/`,
+ `/project/test/test/tree/main/-/path/to/foo/bar-1.jpg/`,
);
});
});
@@ -631,13 +631,13 @@ describe('IDE store getters', () => {
describe('getJsonSchemaForPath', () => {
beforeEach(() => {
localState.currentProjectId = 'path/to/some/project';
- localState.currentBranchId = 'master';
+ localState.currentBranchId = 'main';
});
it('returns a json schema uri and match config for a json/yaml file that can be loaded by monaco', () => {
expect(localStore.getters.getJsonSchemaForPath('.gitlab-ci.yml')).toEqual({
fileMatch: ['*.gitlab-ci.yml'],
- uri: `${TEST_HOST}/path/to/some/project/-/schema/master/.gitlab-ci.yml`,
+ uri: `${TEST_HOST}/path/to/some/project/-/schema/main/.gitlab-ci.yml`,
});
});
@@ -645,8 +645,8 @@ describe('IDE store getters', () => {
localState.projects['path/to/some/project'] = {
name: 'project',
branches: {
- master: {
- name: 'master',
+ main: {
+ name: 'main',
commit: {
id: 'abcdef123456',
},
diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js
index b124eb391f3..cb6bb7c1202 100644
--- a/spec/frontend/ide/stores/modules/commit/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js
@@ -47,7 +47,7 @@ describe('IDE commit module actions', () => {
jest.spyOn(router, 'push').mockImplementation();
mock
- .onGet('/api/v1/projects/abcproject/repository/branches/master')
+ .onGet('/api/v1/projects/abcproject/repository/branches/main')
.reply(200, { commit: COMMIT_RESPONSE });
});
@@ -101,7 +101,7 @@ describe('IDE commit module actions', () => {
originalGon = window.gon;
window.gon = { current_username: 'johndoe' };
- store.state.currentBranchId = 'master';
+ store.state.currentBranchId = 'main';
});
afterEach(() => {
@@ -177,7 +177,7 @@ describe('IDE commit module actions', () => {
committed_date: '123',
committer_name: 'root',
};
- const branch = 'master';
+ const branch = 'main';
let f;
beforeEach(() => {
@@ -192,12 +192,12 @@ describe('IDE commit module actions', () => {
Object.assign(store.state, {
currentProjectId: 'abcproject',
- currentBranchId: 'master',
+ currentBranchId: 'main',
projects: {
abcproject: {
web_url: 'web_url',
branches: {
- master: {
+ main: {
workingReference: '',
commit: {
short_id: TEST_COMMIT_SHA,
@@ -228,7 +228,7 @@ describe('IDE commit module actions', () => {
branch,
})
.then(() => {
- expect(store.state.projects.abcproject.branches.master.workingReference).toBe(data.id);
+ expect(store.state.projects.abcproject.branches.main.workingReference).toBe(data.id);
})
.then(done)
.catch(done.fail);
@@ -310,14 +310,14 @@ describe('IDE commit module actions', () => {
changedFiles: [f],
openFiles: [f],
currentProjectId: 'abcproject',
- currentBranchId: 'master',
+ currentBranchId: 'main',
projects: {
abcproject: {
- default_branch: 'master',
+ default_branch: 'main',
web_url: 'webUrl',
branches: {
- master: {
- name: 'master',
+ main: {
+ name: 'main',
workingReference: '1',
commit: {
id: TEST_COMMIT_SHA,
@@ -460,7 +460,7 @@ describe('IDE commit module actions', () => {
.dispatch('commit/commitChanges')
.then(() => {
expect(visitUrl).toHaveBeenCalledWith(
- `webUrl/-/merge_requests/new?merge_request[source_branch]=${store.getters['commit/placeholderBranchName']}&merge_request[target_branch]=master&nav_source=webide`,
+ `webUrl/-/merge_requests/new?merge_request[source_branch]=${store.getters['commit/placeholderBranchName']}&merge_request[target_branch]=main&nav_source=webide`,
);
done();
diff --git a/spec/frontend/ide/stores/modules/commit/getters_spec.js b/spec/frontend/ide/stores/modules/commit/getters_spec.js
index 0dc938bb637..7a07ed05201 100644
--- a/spec/frontend/ide/stores/modules/commit/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/getters_spec.js
@@ -46,7 +46,7 @@ describe('IDE commit module getters', () => {
describe('branchName', () => {
const rootState = {
- currentBranchId: 'master',
+ currentBranchId: 'main',
};
const localGetters = {
placeholderBranchName: 'placeholder-branch-name',
@@ -61,7 +61,7 @@ describe('IDE commit module getters', () => {
it('defaults to currentBranchId when not committing to a new branch', () => {
localGetters.isCreatingNewBranch = false;
- expect(getters.branchName(state, localGetters, rootState)).toBe('master');
+ expect(getters.branchName(state, localGetters, rootState)).toBe('main');
});
describe('commit to a new branch', () => {
diff --git a/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js
index e5887ca0a33..fc00bd075e7 100644
--- a/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js
@@ -13,7 +13,7 @@ import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
const TEST_PROJECT_PATH = 'lorem/root';
-const TEST_BRANCH_ID = 'master';
+const TEST_BRANCH_ID = 'main';
const TEST_YAML_HELP_PATH = `${TEST_HOST}/test/yaml/help`;
const TEST_RUNNERS_HELP_PATH = `${TEST_HOST}/test/runners/help`;
diff --git a/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js
index 3bb67cb3341..ecda7f304ba 100644
--- a/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js
@@ -11,7 +11,7 @@ import httpStatus from '~/lib/utils/http_status';
jest.mock('~/flash');
const TEST_PROJECT_PATH = 'lorem/root';
-const TEST_BRANCH_ID = 'master';
+const TEST_BRANCH_ID = 'main';
const TEST_SESSION = {
id: 7,
status: PENDING,
diff --git a/spec/frontend/ide/stores/mutations/branch_spec.js b/spec/frontend/ide/stores/mutations/branch_spec.js
index 0900b25d5d3..30a688d2bb0 100644
--- a/spec/frontend/ide/stores/mutations/branch_spec.js
+++ b/spec/frontend/ide/stores/mutations/branch_spec.js
@@ -10,9 +10,9 @@ describe('Multi-file store branch mutations', () => {
describe('SET_CURRENT_BRANCH', () => {
it('sets currentBranch', () => {
- mutations.SET_CURRENT_BRANCH(localState, 'master');
+ mutations.SET_CURRENT_BRANCH(localState, 'main');
- expect(localState.currentBranchId).toBe('master');
+ expect(localState.currentBranchId).toBe('main');
});
});
@@ -21,20 +21,20 @@ describe('Multi-file store branch mutations', () => {
localState.projects = {
Example: {
branches: {
- master: {},
+ main: {},
},
},
};
mutations.SET_BRANCH_COMMIT(localState, {
projectId: 'Example',
- branchId: 'master',
+ branchId: 'main',
commit: {
title: 'Example commit',
},
});
- expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit');
+ expect(localState.projects.Example.branches.main.commit.title).toBe('Example commit');
});
});
diff --git a/spec/frontend/ide/stores/mutations/file_spec.js b/spec/frontend/ide/stores/mutations/file_spec.js
index 825d2a546cd..1453f26c1d9 100644
--- a/spec/frontend/ide/stores/mutations/file_spec.js
+++ b/spec/frontend/ide/stores/mutations/file_spec.js
@@ -319,8 +319,8 @@ describe('IDE store file mutations', () => {
localFile.content = 'test';
localFile.changed = true;
localState.currentProjectId = 'gitlab-ce';
- localState.currentBranchId = 'master';
- localState.trees['gitlab-ce/master'] = {
+ localState.currentBranchId = 'main';
+ localState.trees['gitlab-ce/main'] = {
tree: [],
};
});
@@ -337,7 +337,7 @@ describe('IDE store file mutations', () => {
mutations.DISCARD_FILE_CHANGES(localState, localFile.path);
- expect(localState.trees['gitlab-ce/master'].tree).toEqual([{ ...localFile, deleted: false }]);
+ expect(localState.trees['gitlab-ce/main'].tree).toEqual([{ ...localFile, deleted: false }]);
});
it('adds to parent tree if deleted', () => {
diff --git a/spec/frontend/ide/stores/mutations/tree_spec.js b/spec/frontend/ide/stores/mutations/tree_spec.js
index a4b98aa9d5a..6935e57578f 100644
--- a/spec/frontend/ide/stores/mutations/tree_spec.js
+++ b/spec/frontend/ide/stores/mutations/tree_spec.js
@@ -33,16 +33,16 @@ describe('Multi-file store tree mutations', () => {
});
it('adds directory data', () => {
- localState.trees['project/master'] = {
+ localState.trees['project/main'] = {
tree: [],
};
mutations.SET_DIRECTORY_DATA(localState, {
data,
- treePath: 'project/master',
+ treePath: 'project/main',
});
- const tree = localState.trees['project/master'];
+ const tree = localState.trees['project/main'];
expect(tree.tree.length).toBe(3);
expect(tree.tree[0].name).toBe('tree');
@@ -52,30 +52,30 @@ describe('Multi-file store tree mutations', () => {
it('keeps loading state', () => {
mutations.CREATE_TREE(localState, {
- treePath: 'project/master',
+ treePath: 'project/main',
});
mutations.SET_DIRECTORY_DATA(localState, {
data,
- treePath: 'project/master',
+ treePath: 'project/main',
});
- expect(localState.trees['project/master'].loading).toBe(true);
+ expect(localState.trees['project/main'].loading).toBe(true);
});
it('does not override tree already in state, but merges the two with correct order', () => {
const openedFile = file('new');
- localState.trees['project/master'] = {
+ localState.trees['project/main'] = {
loading: true,
tree: [openedFile],
};
mutations.SET_DIRECTORY_DATA(localState, {
data,
- treePath: 'project/master',
+ treePath: 'project/main',
});
- const { tree } = localState.trees['project/master'];
+ const { tree } = localState.trees['project/main'];
expect(tree.length).toBe(4);
expect(tree[0].name).toBe('blob');
@@ -86,17 +86,17 @@ describe('Multi-file store tree mutations', () => {
it('returns tree unchanged if the opened file is already in the tree', () => {
const openedFile = file('foo');
- localState.trees['project/master'] = {
+ localState.trees['project/main'] = {
loading: true,
tree: [openedFile],
};
mutations.SET_DIRECTORY_DATA(localState, {
data,
- treePath: 'project/master',
+ treePath: 'project/main',
});
- const { tree } = localState.trees['project/master'];
+ const { tree } = localState.trees['project/main'];
expect(tree.length).toBe(3);
diff --git a/spec/frontend/ide/stores/mutations_spec.js b/spec/frontend/ide/stores/mutations_spec.js
index 09e9481e5d4..23fe23bdef9 100644
--- a/spec/frontend/ide/stores/mutations_spec.js
+++ b/spec/frontend/ide/stores/mutations_spec.js
@@ -98,8 +98,8 @@ describe('Multi-file store mutations', () => {
describe('CREATE_TMP_ENTRY', () => {
beforeEach(() => {
localState.currentProjectId = 'gitlab-ce';
- localState.currentBranchId = 'master';
- localState.trees['gitlab-ce/master'] = {
+ localState.currentBranchId = 'main';
+ localState.trees['gitlab-ce/main'] = {
tree: [],
};
});
@@ -115,7 +115,7 @@ describe('Multi-file store mutations', () => {
},
});
- expect(localState.trees['gitlab-ce/master'].tree.length).toEqual(1);
+ expect(localState.trees['gitlab-ce/main'].tree.length).toEqual(1);
expect(localState.entries.test.tempFile).toEqual(true);
});
});
@@ -163,8 +163,8 @@ describe('Multi-file store mutations', () => {
describe('DELETE_ENTRY', () => {
beforeEach(() => {
localState.currentProjectId = 'gitlab-ce';
- localState.currentBranchId = 'master';
- localState.trees['gitlab-ce/master'] = {
+ localState.currentBranchId = 'main';
+ localState.trees['gitlab-ce/main'] = {
tree: [],
};
});
@@ -184,11 +184,11 @@ describe('Multi-file store mutations', () => {
path: 'filePath',
deleted: false,
};
- localState.trees['gitlab-ce/master'].tree.push(localState.entries.filePath);
+ localState.trees['gitlab-ce/main'].tree.push(localState.entries.filePath);
mutations.DELETE_ENTRY(localState, 'filePath');
- expect(localState.trees['gitlab-ce/master'].tree).toEqual([]);
+ expect(localState.trees['gitlab-ce/main'].tree).toEqual([]);
});
it('removes from parent tree', () => {
@@ -279,12 +279,12 @@ describe('Multi-file store mutations', () => {
describe('RENAME_ENTRY', () => {
beforeEach(() => {
localState.trees = {
- 'gitlab-ce/master': {
+ 'gitlab-ce/main': {
tree: [],
},
};
localState.currentProjectId = 'gitlab-ce';
- localState.currentBranchId = 'master';
+ localState.currentBranchId = 'main';
localState.entries = {
oldPath: file('oldPath', 'oldPath', 'blob'),
};
@@ -462,7 +462,7 @@ describe('Multi-file store mutations', () => {
gamma,
};
- localState.trees['gitlab-ce/master'].tree = [alpha, beta, gamma];
+ localState.trees['gitlab-ce/main'].tree = [alpha, beta, gamma];
mutations.RENAME_ENTRY(localState, {
path: 'alpha',
@@ -471,7 +471,7 @@ describe('Multi-file store mutations', () => {
parentPath: '',
});
- expect(localState.trees['gitlab-ce/master'].tree).toEqual([
+ expect(localState.trees['gitlab-ce/main'].tree).toEqual([
expect.objectContaining({
name: 'beta',
}),
diff --git a/spec/frontend/ide/stores/utils_spec.js b/spec/frontend/ide/stores/utils_spec.js
index 46a0794b2e6..8f7b8c5e311 100644
--- a/spec/frontend/ide/stores/utils_spec.js
+++ b/spec/frontend/ide/stores/utils_spec.js
@@ -18,13 +18,13 @@ describe('Multi-file store utils', () => {
};
const state = {
- currentBranchId: 'master',
+ currentBranchId: 'main',
currentProjectId: 'test/test',
};
utils.setPageTitleForFile(state, f);
- expect(document.title).toBe('README.md · master · test/test · GitLab');
+ expect(document.title).toBe('README.md · main · test/test · GitLab');
});
});
@@ -52,10 +52,10 @@ describe('Multi-file store utils', () => {
{ ...file('deletedFile'), path: 'deletedFile', deleted: true },
{ ...file('renamedFile'), path: 'renamedFile', prevPath: 'prevPath' },
],
- currentBranchId: 'master',
+ currentBranchId: 'main',
};
const payload = utils.createCommitPayload({
- branch: 'master',
+ branch: 'main',
newBranch: false,
state,
rootState,
@@ -63,7 +63,7 @@ describe('Multi-file store utils', () => {
});
expect(payload).toEqual({
- branch: 'master',
+ branch: 'main',
commit_message: 'commit message',
actions: [
{
@@ -122,10 +122,10 @@ describe('Multi-file store utils', () => {
lastCommitSha: '123456789',
},
],
- currentBranchId: 'master',
+ currentBranchId: 'main',
};
const payload = utils.createCommitPayload({
- branch: 'master',
+ branch: 'main',
newBranch: false,
state: {},
rootState,
@@ -135,7 +135,7 @@ describe('Multi-file store utils', () => {
});
expect(payload).toEqual({
- branch: 'master',
+ branch: 'main',
commit_message: 'prebuilt test commit message',
actions: [
{
@@ -377,7 +377,7 @@ describe('Multi-file store utils', () => {
let localState;
let branchInfo;
const currentProjectId = '123-foo';
- const currentBranchId = 'master';
+ const currentBranchId = 'main';
beforeEach(() => {
localState = {
diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js
index f467d174eeb..00733615f81 100644
--- a/spec/frontend/ide/utils_spec.js
+++ b/spec/frontend/ide/utils_spec.js
@@ -274,33 +274,33 @@ describe('WebIDE utils', () => {
* hello.md -> hello-1.md
* hello_2.md -> hello_3.md
* hello_ -> hello_1
- * master-patch-22432 -> master-patch-22433
+ * main-patch-22432 -> main-patch-22433
* patch_332 -> patch_333
*/
describe('addNumericSuffix', () => {
it.each`
- input | output
- ${'hello'} | ${'hello-1'}
- ${'hello2'} | ${'hello-3'}
- ${'hello.md'} | ${'hello-1.md'}
- ${'hello_2.md'} | ${'hello_3.md'}
- ${'hello_'} | ${'hello_1'}
- ${'master-patch-22432'} | ${'master-patch-22433'}
- ${'patch_332'} | ${'patch_333'}
+ input | output
+ ${'hello'} | ${'hello-1'}
+ ${'hello2'} | ${'hello-3'}
+ ${'hello.md'} | ${'hello-1.md'}
+ ${'hello_2.md'} | ${'hello_3.md'}
+ ${'hello_'} | ${'hello_1'}
+ ${'main-patch-22432'} | ${'main-patch-22433'}
+ ${'patch_332'} | ${'patch_333'}
`('adds a numeric suffix to a given filename/branch name: $input', ({ input, output }) => {
expect(addNumericSuffix(input)).toBe(output);
});
it.each`
- input | output
- ${'hello'} | ${'hello-39135'}
- ${'hello2'} | ${'hello-39135'}
- ${'hello.md'} | ${'hello-39135.md'}
- ${'hello_2.md'} | ${'hello_39135.md'}
- ${'hello_'} | ${'hello_39135'}
- ${'master-patch-22432'} | ${'master-patch-39135'}
- ${'patch_332'} | ${'patch_39135'}
+ input | output
+ ${'hello'} | ${'hello-39135'}
+ ${'hello2'} | ${'hello-39135'}
+ ${'hello.md'} | ${'hello-39135.md'}
+ ${'hello_2.md'} | ${'hello_39135.md'}
+ ${'hello_'} | ${'hello_39135'}
+ ${'main-patch-22432'} | ${'main-patch-39135'}
+ ${'patch_332'} | ${'patch_39135'}
`('adds a random suffix if randomize=true is passed for name: $input', ({ input, output }) => {
jest.spyOn(Math, 'random').mockReturnValue(0.391352525);
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index bc544439b47..874ecbccf82 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -7,7 +7,7 @@ import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
-import { getExperimentVariant } from '~/experimentation/utils';
+import { getExperimentData, getExperimentVariant } from '~/experimentation/utils';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
@@ -23,6 +23,7 @@ import { stageReply, users, mockSearch, branches } from './mock_data';
jest.mock('~/flash');
jest.mock('~/experimentation/utils', () => ({
...jest.requireActual('~/experimentation/utils'),
+ getExperimentData: jest.fn().mockReturnValue(false),
getExperimentVariant: jest.fn().mockReturnValue('control'),
}));
@@ -48,6 +49,7 @@ describe('Pipelines', () => {
resetCachePath: `${mockProjectPath}/settings/ci_cd/reset_cache`,
newPipelinePath: `${mockProjectPath}/pipelines/new`,
codeQualityPagePath: `${mockProjectPath}/-/new/master?commit_message=Add+.gitlab-ci.yml+and+create+a+code+quality+job&file_name=.gitlab-ci.yml&template=Code-Quality`,
+ ciRunnerSettingsPath: `${mockProjectPath}/-/settings/ci_cd#js-runners-settings`,
};
const noPermissions = {
@@ -563,6 +565,7 @@ describe('Pipelines', () => {
describe('when the code_quality_walkthrough experiment is active', () => {
beforeAll(() => {
+ getExperimentData.mockImplementation((name) => name === 'code_quality_walkthrough');
getExperimentVariant.mockReturnValue('candidate');
});
@@ -574,6 +577,29 @@ describe('Pipelines', () => {
});
});
+ describe('when the ci_runner_templates experiment is active', () => {
+ beforeAll(() => {
+ getExperimentData.mockImplementation((name) => name === 'ci_runner_templates');
+ getExperimentVariant.mockReturnValue('candidate');
+ });
+
+ it('renders two buttons', () => {
+ expect(findEmptyState().findAllComponents(GlButton).length).toBe(2);
+ expect(findEmptyState().findAllComponents(GlButton).at(0).text()).toBe(
+ 'Install GitLab Runners',
+ );
+ expect(findEmptyState().findAllComponents(GlButton).at(0).attributes('href')).toBe(
+ paths.ciRunnerSettingsPath,
+ );
+ expect(findEmptyState().findAllComponents(GlButton).at(1).text()).toBe(
+ 'Learn about Runners',
+ );
+ expect(findEmptyState().findAllComponents(GlButton).at(1).attributes('href')).toBe(
+ '/help/ci/quick_start/index.md',
+ );
+ });
+ });
+
it('does not render filtered search', () => {
expect(findFilteredSearch().exists()).toBe(false);
});
diff --git a/spec/frontend/vue_shared/components/runner_aws_deployments/__snapshots__/runner_aws_deployments_modal_spec.js.snap b/spec/frontend/vue_shared/components/runner_aws_deployments/__snapshots__/runner_aws_deployments_modal_spec.js.snap
new file mode 100644
index 00000000000..b2906973dbd
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_aws_deployments/__snapshots__/runner_aws_deployments_modal_spec.js.snap
@@ -0,0 +1,110 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RunnerAwsDeploymentsModal renders the modal 1`] = `
+<gl-modal-stub
+ actionsecondary="[object Object]"
+ dismisslabel="Close"
+ modalclass=""
+ modalid="runner-aws-deployments-modal"
+ size="sm"
+ title="Deploy GitLab Runner in AWS"
+ titletag="h4"
+>
+ <p>
+ For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet.
+ </p>
+
+ <ul
+ class="gl-list-style-none gl-p-0 gl-mb-0"
+ >
+ <li>
+ <gl-link-stub
+ class="gl-display-flex gl-font-weight-bold"
+ href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-amazon-linux-2-docker-manual-scaling-with-schedule-ondemandonly.cf.yml&stackName=linux-docker-nonspot&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
+ target="_blank"
+ >
+ <img
+ alt="linux-docker-nonspot"
+ class="gl-mt-2 gl-mr-5 gl-mb-6"
+ height="46"
+ src="/assets/aws-cloud-formation.png"
+ title="linux-docker-nonspot"
+ width="46"
+ />
+
+ Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor.
+
+ </gl-link-stub>
+ </li>
+ <li>
+ <gl-link-stub
+ class="gl-display-flex gl-font-weight-bold"
+ href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-amazon-linux-2-docker-manual-scaling-with-schedule-spotonly.cf.yml&stackName=linux-docker-spotonly&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
+ target="_blank"
+ >
+ <img
+ alt="linux-docker-spotonly"
+ class="gl-mt-2 gl-mr-5 gl-mb-6"
+ height="46"
+ src="/assets/aws-cloud-formation.png"
+ title="linux-docker-spotonly"
+ width="46"
+ />
+
+ Amazon Linux 2 Docker HA with manual scaling and optional scheduling. 100% spot.
+
+ </gl-link-stub>
+ </li>
+ <li>
+ <gl-link-stub
+ class="gl-display-flex gl-font-weight-bold"
+ href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-windows2019-shell-manual-scaling-with-scheduling-ondemandonly.cf.yml&stackName=win2019-shell-non-spot&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
+ target="_blank"
+ >
+ <img
+ alt="win2019-shell-non-spot"
+ class="gl-mt-2 gl-mr-5 gl-mb-6"
+ height="46"
+ src="/assets/aws-cloud-formation.png"
+ title="win2019-shell-non-spot"
+ width="46"
+ />
+
+ Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor.
+
+ </gl-link-stub>
+ </li>
+ <li>
+ <gl-link-stub
+ class="gl-display-flex gl-font-weight-bold"
+ href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-windows2019-shell-manual-scaling-with-scheduling-spotonly.cf.yml&stackName=win2019-shell-spot&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
+ target="_blank"
+ >
+ <img
+ alt="win2019-shell-spot"
+ class="gl-mt-2 gl-mr-5 gl-mb-6"
+ height="46"
+ src="/assets/aws-cloud-formation.png"
+ title="win2019-shell-spot"
+ width="46"
+ />
+
+ Windows 2019 Shell with manual scaling and optional scheduling. 100% spot.
+
+ </gl-link-stub>
+ </li>
+ </ul>
+
+ <p>
+ <gl-sprintf-stub
+ message="Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}."
+ />
+ </p>
+
+ <p
+ class="gl-font-sm gl-mb-0"
+ >
+ If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account.
+ </p>
+</gl-modal-stub>
+`;
diff --git a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js
new file mode 100644
index 00000000000..69db3ec7132
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js
@@ -0,0 +1,75 @@
+import { GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import ExperimentTracking from '~/experimentation/experiment_tracking';
+import { getBaseURL } from '~/lib/utils/url_utility';
+import {
+ EXPERIMENT_NAME,
+ CF_BASE_URL,
+ TEMPLATES_BASE_URL,
+ EASY_BUTTONS,
+} from '~/vue_shared/components/runner_aws_deployments/constants';
+import RunnerAwsDeploymentsModal from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue';
+
+jest.mock('~/experimentation/experiment_tracking');
+
+describe('RunnerAwsDeploymentsModal', () => {
+ let wrapper;
+
+ const findEasyButtons = () => wrapper.findAllComponents(GlLink);
+
+ const createComponent = () => {
+ wrapper = shallowMount(RunnerAwsDeploymentsModal, {
+ propsData: {
+ modalId: 'runner-aws-deployments-modal',
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the modal', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should contain all easy buttons', () => {
+ expect(findEasyButtons()).toHaveLength(EASY_BUTTONS.length);
+ });
+
+ describe('first easy button', () => {
+ const findFirstButton = () => findEasyButtons().at(0);
+
+ it('should contain the correct description', () => {
+ expect(findFirstButton().text()).toBe(EASY_BUTTONS[0].description);
+ });
+
+ it('should contain the correct link', () => {
+ const link = findFirstButton().attributes('href');
+
+ expect(link.startsWith(CF_BASE_URL)).toBe(true);
+ expect(
+ link.includes(
+ `templateURL=${encodeURIComponent(TEMPLATES_BASE_URL + EASY_BUTTONS[0].templateName)}`,
+ ),
+ ).toBe(true);
+ expect(link.includes(`stackName=${EASY_BUTTONS[0].stackName}`)).toBe(true);
+ expect(
+ link.includes(`param_3GITLABRunnerInstanceURL=${encodeURIComponent(getBaseURL())}`),
+ ).toBe(true);
+ });
+
+ it('should track an event when clicked', () => {
+ findFirstButton().vm.$emit('click');
+
+ expect(ExperimentTracking).toHaveBeenCalledWith(EXPERIMENT_NAME);
+ expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
+ `template_clicked_${EASY_BUTTONS[0].stackName}`,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_spec.js b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_spec.js
new file mode 100644
index 00000000000..639668761ea
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_spec.js
@@ -0,0 +1,41 @@
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import RunnerAwsDeployments from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue';
+import RunnerAwsDeploymentsModal from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue';
+
+describe('RunnerAwsDeployments component', () => {
+ let wrapper;
+
+ const findModalButton = () => wrapper.findByTestId('show-modal-button');
+ const findModal = () => wrapper.findComponent(RunnerAwsDeploymentsModal);
+
+ const createComponent = () => {
+ wrapper = extendedWrapper(shallowMount(RunnerAwsDeployments));
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should show the "Deploy GitLab Runner in AWS" button', () => {
+ expect(findModalButton().exists()).toBe(true);
+ expect(findModalButton().text()).toBe('Deploy GitLab Runner in AWS');
+ });
+
+ it('should not render the modal once mounted', () => {
+ expect(findModal().exists()).toBe(false);
+ });
+
+ it('should render the modal once clicked', async () => {
+ findModalButton().vm.$emit('click');
+
+ await nextTick();
+
+ expect(findModal().exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js
index eb23a8ef457..5a6c91bda9f 100644
--- a/spec/frontend/vue_shared/components/web_ide_link_spec.js
+++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js
@@ -3,8 +3,8 @@ import ActionsButton from '~/vue_shared/components/actions_button.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
-const TEST_EDIT_URL = '/gitlab-test/test/-/edit/master/';
-const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/master/-/';
+const TEST_EDIT_URL = '/gitlab-test/test/-/edit/main/';
+const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/main/-/';
const TEST_GITPOD_URL = 'https://gitpod.test/';
const ACTION_EDIT = {
diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb
index 5eb85601982..dc94b9967ec 100644
--- a/spec/lib/gitlab/ci/templates/templates_spec.rb
+++ b/spec/lib/gitlab/ci/templates/templates_spec.rb
@@ -6,33 +6,104 @@ RSpec.describe 'CI YML Templates' do
subject { Gitlab::Ci::YamlProcessor.new(content).execute }
let(:all_templates) { Gitlab::Template::GitlabCiYmlTemplate.all.map(&:full_name) }
-
let(:excluded_templates) do
all_templates.select do |name|
Gitlab::Template::GitlabCiYmlTemplate.excluded_patterns.any? { |pattern| pattern.match?(name) }
end
end
- context 'when including available templates in a CI YAML configuration' do
- using RSpec::Parameterized::TableSyntax
+ before do
+ stub_feature_flags(
+ redirect_to_latest_template_terraform: false,
+ redirect_to_latest_template_security_api_fuzzing: false,
+ redirect_to_latest_template_security_dast: false)
+ end
+
+ shared_examples 'require default stages to be included' do
+ it 'require default stages to be included' do
+ expect(subject.stages).to include(*Gitlab::Ci::Config::Entry::Stages.default)
+ end
+ end
+
+ context 'that support autodevops' do
+ non_autodevops_templates = [
+ 'Security/DAST-API.gitlab-ci.yml',
+ 'Security/API-Fuzzing.gitlab-ci.yml'
+ ]
+
+ context 'when including available templates in a CI YAML configuration' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:template_name) do
+ all_templates - excluded_templates - non_autodevops_templates
+ end
+
+ with_them do
+ let(:content) do
+ <<~EOS
+ include:
+ - template: #{template_name}
+
+ concrete_build_implemented_by_a_user:
+ stage: test
+ script: do something
+ EOS
+ end
+
+ it { is_expected.to be_valid }
- where(:template_name) do
- all_templates - excluded_templates
+ include_examples 'require default stages to be included'
+ end
end
- before do
- stub_feature_flags(
- redirect_to_latest_template_terraform: false,
- redirect_to_latest_template_security_api_fuzzing: false,
- redirect_to_latest_template_security_dast: false)
+ context 'when including unavailable templates in a CI YAML configuration' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:template_name) do
+ excluded_templates
+ end
+
+ with_them do
+ let(:content) do
+ <<~EOS
+ include:
+ - template: #{template_name}
+
+ concrete_build_implemented_by_a_user:
+ stage: test
+ script: do something
+ EOS
+ end
+
+ it { is_expected.not_to be_valid }
+ end
end
+ end
+
+ describe 'that do not support autodevops' do
+ context 'when DAST API template' do
+ # The DAST API template purposly excludes a stages
+ # definition.
+
+ let(:template_name) { 'Security/DAST-API.gitlab-ci.yml' }
- with_them do
- let(:content) do
- if template_name == 'Security/DAST-API.gitlab-ci.yml'
- # The DAST-API template purposly excludes a stages
- # definition.
+ context 'with default stages' do
+ let(:content) do
+ <<~EOS
+ include:
+ - template: #{template_name}
+
+ concrete_build_implemented_by_a_user:
+ stage: test
+ script: do something
+ EOS
+ end
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'with defined stages' do
+ let(:content) do
<<~EOS
include:
- template: #{template_name}
@@ -47,7 +118,22 @@ RSpec.describe 'CI YML Templates' do
stage: test
script: do something
EOS
- else
+ end
+
+ it { is_expected.to be_valid }
+
+ include_examples 'require default stages to be included'
+ end
+ end
+
+ context 'when API Fuzzing template' do
+ # The API Fuzzing template purposly excludes a stages
+ # definition.
+
+ let(:template_name) { 'Security/API-Fuzzing.gitlab-ci.yml' }
+
+ context 'with default stages' do
+ let(:content) do
<<~EOS
include:
- template: #{template_name}
@@ -57,39 +143,31 @@ RSpec.describe 'CI YML Templates' do
script: do something
EOS
end
- end
-
- it 'is valid' do
- expect(subject).to be_valid
- end
- it 'require default stages to be included' do
- expect(subject.stages).to include(*Gitlab::Ci::Config::Entry::Stages.default)
+ it { is_expected.not_to be_valid }
end
- end
- end
- context 'when including unavailable templates in a CI YAML configuration' do
- using RSpec::Parameterized::TableSyntax
+ context 'with defined stages' do
+ let(:content) do
+ <<~EOS
+ include:
+ - template: #{template_name}
- where(:template_name) do
- excluded_templates
- end
+ stages:
+ - build
+ - test
+ - deploy
+ - fuzz
- with_them do
- let(:content) do
- <<~EOS
- include:
- - template: #{template_name}
+ concrete_build_implemented_by_a_user:
+ stage: test
+ script: do something
+ EOS
+ end
- concrete_build_implemented_by_a_user:
- stage: test
- script: do something
- EOS
- end
+ it { is_expected.to be_valid }
- it 'is not valid' do
- expect(subject).not_to be_valid
+ include_examples 'require default stages to be included'
end
end
end
diff --git a/spec/migrations/disable_expiration_policies_linked_to_no_container_images_spec.rb b/spec/migrations/disable_expiration_policies_linked_to_no_container_images_spec.rb
new file mode 100644
index 00000000000..f2be06f1ed6
--- /dev/null
+++ b/spec/migrations/disable_expiration_policies_linked_to_no_container_images_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe DisableExpirationPoliciesLinkedToNoContainerImages do
+ let(:projects) { table(:projects) }
+ let(:container_expiration_policies) { table(:container_expiration_policies) }
+ let(:container_repositories) { table(:container_repositories) }
+ let(:namespaces) { table(:namespaces) }
+
+ let!(:namespace) { namespaces.create!(name: 'test', path: 'test') }
+ let!(:project) { projects.create!(id: 1, namespace_id: namespace.id, name: 'gitlab1') }
+ let!(:container_expiration_policy) { container_expiration_policies.create!(project_id: project.id, enabled: true) }
+
+ before do
+ projects.create!(id: 2, namespace_id: namespace.id, name: 'gitlab2')
+ container_expiration_policies.create!(project_id: 2, enabled: true)
+ container_repositories.create!(id: 1, project_id: 2, name: 'image2')
+
+ projects.create!(id: 3, namespace_id: namespace.id, name: 'gitlab3')
+ container_expiration_policies.create!(project_id: 3, enabled: false)
+ container_repositories.create!(id: 2, project_id: 3, name: 'image3')
+ end
+
+ it 'correctly disable expiration policies linked to no container images' do
+ expect(enabled_policies.count).to eq 2
+ expect(disabled_policies.count).to eq 1
+ expect(container_expiration_policy.enabled).to eq true
+
+ migrate!
+
+ expect(enabled_policies.count).to eq 1
+ expect(disabled_policies.count).to eq 2
+ expect(container_expiration_policy.reload.enabled).to eq false
+ end
+
+ def enabled_policies
+ container_expiration_policies.where(enabled: true)
+ end
+
+ def disabled_policies
+ container_expiration_policies.where(enabled: false)
+ end
+end
diff --git a/spec/services/namespace_settings/update_service_spec.rb b/spec/services/namespace_settings/update_service_spec.rb
index 887d56df099..8e176dbc6cd 100644
--- a/spec/services/namespace_settings/update_service_spec.rb
+++ b/spec/services/namespace_settings/update_service_spec.rb
@@ -75,5 +75,37 @@ RSpec.describe NamespaceSettings::UpdateService do
end
end
end
+
+ context "updating :prevent_sharing_groups_outside_hierarchy" do
+ let(:settings) { { prevent_sharing_groups_outside_hierarchy: true } }
+
+ context 'when user is a group owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'changes settings' do
+ expect { service.execute }
+ .to change { group.namespace_settings.prevent_sharing_groups_outside_hierarchy }
+ .from(false).to(true)
+ end
+ end
+
+ context 'when user is not a group owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'does not change settings' do
+ expect { service.execute }.not_to change { group.namespace_settings.prevent_sharing_groups_outside_hierarchy }
+ end
+
+ it 'returns the group owner error' do
+ service.execute
+
+ expect(group.namespace_settings.errors.messages[:prevent_sharing_groups_outside_hierarchy]).to include('can only be changed by a group admin.')
+ end
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb
new file mode 100644
index 00000000000..5baa6478225
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'tracks assignment and records the subject' do |experiment, subject_type|
+ it 'tracks the assignment', :experiment do
+ expect(experiment(experiment))
+ .to track(:assignment)
+ .with_context(subject_type => subject)
+ .on_next_instance
+
+ action
+ end
+
+ it 'records the subject' do
+ stub_experiments(experiment => :candidate)
+
+ expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: :experimental, subject: subject)
+
+ action
+ end
+end