summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClement Ho <ClemMakesApps@gmail.com>2018-02-16 10:16:48 -0600
committerClement Ho <ClemMakesApps@gmail.com>2018-02-16 10:16:48 -0600
commit3f6d3dc11a8c2406b8a8c7e8b81005c31f7caa4f (patch)
tree3a6407e4ce1a21163c213273f9950d79e4c88f97
parent1c2d283d506f2ceb4777622837fffe381e34cb9d (diff)
parent46d59a5d24c0cdbfab42587426dba3ab7b85be03 (diff)
downloadgitlab-ce-3f6d3dc11a8c2406b8a8c7e8b81005c31f7caa4f.tar.gz
Merge branch 'master' into dispatcher-mr-haml
-rw-r--r--.eslintrc2
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js1
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js4
-rw-r--r--app/assets/javascripts/dispatcher.js5
-rw-r--r--app/assets/javascripts/importer_status.js49
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue26
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/index.js37
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/charts/index.js56
-rw-r--r--app/assets/javascripts/pages/projects/services/edit/index.js7
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js (renamed from app/assets/javascripts/users/activity_calendar.js)0
-rw-r--r--app/assets/javascripts/pages/users/index.js (renamed from app/assets/javascripts/users/index.js)2
-rw-r--r--app/assets/javascripts/pages/users/show/index.js3
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js (renamed from app/assets/javascripts/users/user_tabs.js)10
-rw-r--r--app/assets/javascripts/pipelines/pipelines_charts.js38
-rw-r--r--app/assets/javascripts/pipelines/pipelines_times.js27
-rw-r--r--app/assets/javascripts/prometheus_metrics/index.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue106
-rw-r--r--app/services/issues/move_service.rb25
-rw-r--r--app/views/admin/jobs/index.html.haml7
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/pipelines/charts.html.haml3
-rw-r--r--app/views/projects/pipelines/charts/_pipeline_times.haml3
-rw-r--r--app/views/projects/pipelines/charts/_pipelines.haml3
-rw-r--r--app/views/projects/services/prometheus/_show.html.haml3
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/users/show.html.haml3
-rw-r--r--changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml5
-rw-r--r--changelogs/unreleased/42929-hide-new-variable-values.yml5
-rw-r--r--changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml5
-rw-r--r--changelogs/unreleased/asciidoc_inter_document_cross_references.yml5
-rw-r--r--changelogs/unreleased/dm-escape-commit-message.yml5
-rw-r--r--changelogs/unreleased/winh-new-modal-component.yml5
-rw-r--r--config/application.rb2
-rw-r--r--config/webpack.config.js4
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/project_import_export.md74
-rw-r--r--doc/api/projects.md4
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/development/fe_guide/components.md61
-rw-r--r--doc/development/fe_guide/dropdowns.md33
-rw-r--r--doc/development/fe_guide/img/gl-modal.pngbin0 -> 25893 bytes
-rw-r--r--doc/development/fe_guide/index.md9
-rw-r--r--doc/development/profiling.md11
-rw-r--r--doc/install/google_cloud_platform/img/boot_disk.pngbin0 -> 124175 bytes
-rw-r--r--doc/install/google_cloud_platform/img/change_admin_passwd_email.pngbin7193 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/chrome_not_secure_page.pngbin21705 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/first_signin.pngbin0 -> 69516 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.pngbin23486 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_gitlab_overview.pngbin42028 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_landing.pngbin59912 -> 15025 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.pngbin42090 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_search_for_gitlab.pngbin7648 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gitlab_deployed_page.pngbin35573 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gitlab_first_sign_in.pngbin20054 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gitlab_launch_button.pngbin5198 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/launch_vm.pngbin0 -> 47201 bytes
-rw-r--r--doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.pngbin50014 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/ssh_terminal.pngbin0 -> 94161 bytes
-rw-r--r--doc/install/google_cloud_platform/img/ssh_via_button.pngbin3062 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/vm_created.pngbin0 -> 13809 bytes
-rw-r--r--doc/install/google_cloud_platform/img/vm_details.pngbin0 -> 76519 bytes
-rw-r--r--doc/install/google_cloud_platform/index.md86
-rw-r--r--doc/user/project/clusters/index.md117
-rw-r--r--doc/user/project/settings/import_export.md1
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb7
-rw-r--r--lib/api/project_import.rb65
-rw-r--r--lib/banzai/filter/html_entity_filter.rb2
-rw-r--r--lib/gitlab/asciidoc.rb3
-rw-r--r--lib/gitlab/profiler.rb1
-rw-r--r--spec/helpers/events_helper_spec.rb4
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js46
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js41
-rw-r--r--spec/javascripts/importer_status_spec.js74
-rw-r--r--spec/javascripts/vue_shared/components/gl_modal_spec.js192
-rw-r--r--spec/lib/banzai/filter/html_entity_filter_spec.rb9
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb8
-rw-r--r--spec/lib/gitlab/profiler_spec.rb9
-rw-r--r--spec/requests/api/project_import_spec.rb102
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml10
80 files changed, 1075 insertions, 364 deletions
diff --git a/.eslintrc b/.eslintrc
index ad5eaebccae..8f9cdfb14ac 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -36,7 +36,7 @@
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
- "no-underscore-dangle": ["error", { "allow": ["__"]}],
+ "no-underscore-dangle": ["error", { "allow": ["__", "_links"]}],
"vue/html-self-closing": ["error", {
"html": {
"void": "always",
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
index 76f93e5c6bd..b33adff609f 100644
--- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -75,6 +75,7 @@ export default class AjaxVariableList {
if (res.status === statusCodes.OK && res.data) {
this.updateRowsWithPersistedVariables(res.data.variables);
+ this.variableList.hideValues();
} else if (res.status === statusCodes.BAD_REQUEST) {
// Validation failed
this.errorBox.innerHTML = generateErrorBoxContent(res.data);
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index 3467e88119b..745f3404295 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -178,6 +178,10 @@ export default class VariableList {
this.$container.find('.js-row-remove-button').attr('disabled', !isEnabled);
}
+ hideValues() {
+ this.secretValues.updateDom(false);
+ }
+
getAllData() {
// Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems.
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index e4288dc1317..8f708dde063 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -467,11 +467,6 @@ var Dispatcher;
.then(callDefault)
.catch(fail);
break;
- case 'users:show':
- import('./pages/users/show')
- .then(callDefault)
- .catch(fail);
- break;
case 'admin:conversational_development_index:show':
import('./pages/admin/conversational_development_index/show')
.then(callDefault)
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 134a503864e..35094f8e73b 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -59,29 +59,36 @@ class ImporterStatus {
.catch(() => flash(__('An error occurred while importing project')));
}
- setAutoUpdate() {
- return setInterval(() => $.get(this.jobsUrl, data => $.each(data, (i, job) => {
- const jobItem = $(`#project_${job.id}`);
- const statusField = jobItem.find('.job-status');
+ autoUpdate() {
+ return axios.get(this.jobsUrl)
+ .then(({ data = [] }) => {
+ data.forEach((job) => {
+ const jobItem = $(`#project_${job.id}`);
+ const statusField = jobItem.find('.job-status');
+
+ const spinner = '<i class="fa fa-spinner fa-spin"></i>';
- const spinner = '<i class="fa fa-spinner fa-spin"></i>';
+ switch (job.import_status) {
+ case 'finished':
+ jobItem.removeClass('active').addClass('success');
+ statusField.html('<span><i class="fa fa-check"></i> done</span>');
+ break;
+ case 'scheduled':
+ statusField.html(`${spinner} scheduled`);
+ break;
+ case 'started':
+ statusField.html(`${spinner} started`);
+ break;
+ default:
+ statusField.html(job.import_status);
+ break;
+ }
+ });
+ });
+ }
- switch (job.import_status) {
- case 'finished':
- jobItem.removeClass('active').addClass('success');
- statusField.html('<span><i class="fa fa-check"></i> done</span>');
- break;
- case 'scheduled':
- statusField.html(`${spinner} scheduled`);
- break;
- case 'started':
- statusField.html(`${spinner} started`);
- break;
- default:
- statusField.html(job.import_status);
- break;
- }
- })), 4000);
+ setAutoUpdate() {
+ setInterval(this.autoUpdate.bind(this), 4000);
}
}
diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
index 555725cbe12..ba1d8e4d8db 100644
--- a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
+++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
@@ -1,13 +1,13 @@
<script>
import axios from '~/lib/utils/axios_utils';
- import Flash from '~/flash';
- import modal from '~/vue_shared/components/modal.vue';
- import { s__ } from '~/locale';
+ import createFlash from '~/flash';
+ import GlModal from '~/vue_shared/components/gl_modal.vue';
import { redirectTo } from '~/lib/utils/url_utility';
+ import { s__ } from '~/locale';
export default {
components: {
- modal,
+ GlModal,
},
props: {
url: {
@@ -17,7 +17,7 @@
},
computed: {
text() {
- return s__('AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running.');
+ return s__('AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running.');
},
},
methods: {
@@ -28,7 +28,7 @@
redirectTo(response.request.responseURL);
})
.catch((error) => {
- Flash(s__('AdminArea|Stopping jobs failed'));
+ createFlash(s__('AdminArea|Stopping jobs failed'));
throw error;
});
},
@@ -37,11 +37,13 @@
</script>
<template>
- <modal
+ <gl-modal
id="stop-jobs-modal"
- :title="s__('AdminArea|Stop all jobs?')"
- :text="text"
- kind="danger"
- :primary-button-label="s__('AdminArea|Stop jobs')"
- @submit="onSubmit" />
+ :header-title-text="s__('AdminArea|Stop all jobs?')"
+ footer-primary-button-variant="danger"
+ :footer-primary-button-text="s__('AdminArea|Stop jobs')"
+ @submit="onSubmit"
+ >
+ {{ text }}
+ </gl-modal>
</template>
diff --git a/app/assets/javascripts/pages/admin/jobs/index/index.js b/app/assets/javascripts/pages/admin/jobs/index/index.js
index 0e004bd9174..31d58eabaaf 100644
--- a/app/assets/javascripts/pages/admin/jobs/index/index.js
+++ b/app/assets/javascripts/pages/admin/jobs/index/index.js
@@ -8,22 +8,23 @@ Vue.use(Translate);
export default () => {
const stopJobsButton = document.getElementById('stop-jobs-button');
-
- // eslint-disable-next-line no-new
- new Vue({
- el: '#stop-jobs-modal',
- components: {
- stopJobsModal,
- },
- mounted() {
- stopJobsButton.classList.remove('disabled');
- },
- render(createElement) {
- return createElement('stop-jobs-modal', {
- props: {
- url: stopJobsButton.dataset.url,
- },
- });
- },
- });
+ if (stopJobsButton) {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#stop-jobs-modal',
+ components: {
+ stopJobsModal,
+ },
+ mounted() {
+ stopJobsButton.classList.remove('disabled');
+ },
+ render(createElement) {
+ return createElement('stop-jobs-modal', {
+ props: {
+ url: stopJobsButton.dataset.url,
+ },
+ });
+ },
+ });
+ }
};
diff --git a/app/assets/javascripts/pages/projects/pipelines/charts/index.js b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
new file mode 100644
index 00000000000..c1dafda0e24
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
@@ -0,0 +1,56 @@
+import Chart from 'vendor/Chart';
+
+const options = {
+ scaleOverlay: true,
+ responsive: true,
+ maintainAspectRatio: false,
+};
+
+const buildChart = (chartScope) => {
+ const data = {
+ labels: chartScope.labels,
+ datasets: [{
+ fillColor: '#707070',
+ strokeColor: '#707070',
+ pointColor: '#707070',
+ pointStrokeColor: '#EEE',
+ data: chartScope.totalValues,
+ },
+ {
+ fillColor: '#1aaa55',
+ strokeColor: '#1aaa55',
+ pointColor: '#1aaa55',
+ pointStrokeColor: '#fff',
+ data: chartScope.successValues,
+ },
+ ],
+ };
+ const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
+
+ new Chart(ctx).Line(data, options);
+};
+
+document.addEventListener('DOMContentLoaded', () => {
+ const chartTimesData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
+ const chartsData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
+ const data = {
+ labels: chartTimesData.labels,
+ datasets: [{
+ fillColor: 'rgba(220,220,220,0.5)',
+ strokeColor: 'rgba(220,220,220,1)',
+ barStrokeWidth: 1,
+ barValueSpacing: 1,
+ barDatasetSpacing: 1,
+ data: chartTimesData.values,
+ }],
+ };
+
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8;
+ }
+
+ new Chart($('#build_timesChart').get(0).getContext('2d')).Bar(data, options);
+
+ chartsData.forEach(scope => buildChart(scope));
+});
diff --git a/app/assets/javascripts/pages/projects/services/edit/index.js b/app/assets/javascripts/pages/projects/services/edit/index.js
index 434a7e44277..5c73171e62e 100644
--- a/app/assets/javascripts/pages/projects/services/edit/index.js
+++ b/app/assets/javascripts/pages/projects/services/edit/index.js
@@ -1,6 +1,13 @@
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
+import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
export default () => {
+ const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring');
const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
integrationSettingsForm.init();
+
+ if (prometheusSettingsWrapper) {
+ const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
+ prometheusMetrics.loadActiveMetrics();
+ }
};
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 57306322aa4..57306322aa4 100644
--- a/app/assets/javascripts/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
diff --git a/app/assets/javascripts/users/index.js b/app/assets/javascripts/pages/users/index.js
index 9fd8452a2b6..899dcd42e37 100644
--- a/app/assets/javascripts/users/index.js
+++ b/app/assets/javascripts/pages/users/index.js
@@ -1,3 +1,4 @@
+import UserCallout from '~/user_callout';
import Cookies from 'js-cookie';
import UserTabs from './user_tabs';
@@ -22,4 +23,5 @@ document.addEventListener('DOMContentLoaded', () => {
const page = $('body').attr('data-page');
const action = page.split(':')[1];
initUserProfile(action);
+ new UserCallout(); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/users/show/index.js b/app/assets/javascripts/pages/users/show/index.js
deleted file mode 100644
index f18f98b4e9a..00000000000
--- a/app/assets/javascripts/pages/users/show/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import UserCallout from '~/user_callout';
-
-export default () => new UserCallout();
diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index e13b9839a20..c1217623467 100644
--- a/app/assets/javascripts/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -1,9 +1,9 @@
-import axios from '../lib/utils/axios_utils';
-import Activities from '../activities';
+import axios from '~/lib/utils/axios_utils';
+import Activities from '~/activities';
+import { localTimeAgo } from '~/lib/utils/datetime_utility';
+import { __ } from '~/locale';
+import flash from '~/flash';
import ActivityCalendar from './activity_calendar';
-import { localTimeAgo } from '../lib/utils/datetime_utility';
-import { __ } from '../locale';
-import flash from '../flash';
/**
* UserTabs
diff --git a/app/assets/javascripts/pipelines/pipelines_charts.js b/app/assets/javascripts/pipelines/pipelines_charts.js
deleted file mode 100644
index 821aa7e229f..00000000000
--- a/app/assets/javascripts/pipelines/pipelines_charts.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import Chart from 'vendor/Chart';
-
-document.addEventListener('DOMContentLoaded', () => {
- const chartData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
- const buildChart = (chartScope) => {
- const data = {
- labels: chartScope.labels,
- datasets: [{
- fillColor: '#707070',
- strokeColor: '#707070',
- pointColor: '#707070',
- pointStrokeColor: '#EEE',
- data: chartScope.totalValues,
- },
- {
- fillColor: '#1aaa55',
- strokeColor: '#1aaa55',
- pointColor: '#1aaa55',
- pointStrokeColor: '#fff',
- data: chartScope.successValues,
- },
- ],
- };
- const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
- const options = {
- scaleOverlay: true,
- responsive: true,
- maintainAspectRatio: false,
- };
- if (window.innerWidth < 768) {
- // Scale fonts if window width lower than 768px (iPad portrait)
- options.scaleFontSize = 8;
- }
- new Chart(ctx).Line(data, options);
- };
-
- chartData.forEach(scope => buildChart(scope));
-});
diff --git a/app/assets/javascripts/pipelines/pipelines_times.js b/app/assets/javascripts/pipelines/pipelines_times.js
deleted file mode 100644
index b5e7a0e53d9..00000000000
--- a/app/assets/javascripts/pipelines/pipelines_times.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import Chart from 'vendor/Chart';
-
-document.addEventListener('DOMContentLoaded', () => {
- const chartData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
- const data = {
- labels: chartData.labels,
- datasets: [{
- fillColor: 'rgba(220,220,220,0.5)',
- strokeColor: 'rgba(220,220,220,1)',
- barStrokeWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data: chartData.values,
- }],
- };
- const ctx = $('#build_timesChart').get(0).getContext('2d');
- const options = {
- scaleOverlay: true,
- responsive: true,
- maintainAspectRatio: false,
- };
- if (window.innerWidth < 768) {
- // Scale fonts if window width lower than 768px (iPad portrait)
- options.scaleFontSize = 8;
- }
- new Chart(ctx).Bar(data, options);
-});
diff --git a/app/assets/javascripts/prometheus_metrics/index.js b/app/assets/javascripts/prometheus_metrics/index.js
deleted file mode 100644
index a0c43c5abe1..00000000000
--- a/app/assets/javascripts/prometheus_metrics/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import PrometheusMetrics from './prometheus_metrics';
-
-$(() => {
- const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
- prometheusMetrics.loadActiveMetrics();
-});
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
new file mode 100644
index 00000000000..67c9181c7b1
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -0,0 +1,106 @@
+<script>
+ const buttonVariants = [
+ 'danger',
+ 'primary',
+ 'success',
+ 'warning',
+ ];
+
+ export default {
+ name: 'GlModal',
+
+ props: {
+ id: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ headerTitleText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ footerPrimaryButtonVariant: {
+ type: String,
+ required: false,
+ default: 'primary',
+ validator: value => buttonVariants.indexOf(value) !== -1,
+ },
+ footerPrimaryButtonText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
+ methods: {
+ emitCancel(event) {
+ this.$emit('cancel', event);
+ },
+ emitSubmit(event) {
+ this.$emit('submit', event);
+ },
+ },
+ };
+</script>
+
+<template>
+ <div
+ :id="id"
+ class="modal fade"
+ tabindex="-1"
+ role="dialog"
+ >
+ <div
+ class="modal-dialog"
+ role="document"
+ >
+ <div class="modal-content">
+ <div class="modal-header">
+ <slot name="header">
+ <button
+ type="button"
+ class="close"
+ data-dismiss="modal"
+ :aria-label="s__('Modal|Close')"
+ @click="emitCancel($event)"
+ >
+ <span aria-hidden="true">&times;</span>
+ </button>
+ <h4 class="modal-title">
+ <slot name="title">
+ {{ headerTitleText }}
+ </slot>
+ </h4>
+ </slot>
+ </div>
+
+ <div class="modal-body">
+ <slot></slot>
+ </div>
+
+ <div class="modal-footer">
+ <slot name="footer">
+ <button
+ type="button"
+ class="btn"
+ data-dismiss="modal"
+ @click="emitCancel($event)"
+ >
+ {{ s__('Modal|Cancel') }}
+ </button>
+ <button
+ type="button"
+ class="btn"
+ :class="`btn-${footerPrimaryButtonVariant}`"
+ data-dismiss="modal"
+ @click="emitSubmit($event)"
+ >
+ {{ footerPrimaryButtonText }}
+ </button>
+ </slot>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 2f511ab44b7..299b9c6215f 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -19,19 +19,10 @@ module Issues
# on rewriting notes (unfolding references)
#
ActiveRecord::Base.transaction do
- # New issue tasks
- #
@new_issue = create_new_issue
- rewrite_notes
- rewrite_issue_award_emoji
- add_note_moved_from
-
- # Old issue tasks
- #
- add_note_moved_to
- close_issue
- mark_as_moved
+ update_new_issue
+ update_old_issue
end
notify_participants
@@ -41,6 +32,18 @@ module Issues
private
+ def update_new_issue
+ rewrite_notes
+ rewrite_issue_award_emoji
+ add_note_moved_from
+ end
+
+ def update_old_issue
+ add_note_moved_to
+ close_issue
+ mark_as_moved
+ end
+
def create_new_issue
new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids,
milestone_id: cloneable_milestone_id,
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index a01676d82a8..4e3e2f7a475 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -7,10 +7,9 @@
- build_path_proc = ->(scope) { admin_jobs_path(scope: scope) }
= render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
- .nav-controls
- - if @all_builds.running_or_pending.any?
- #stop-jobs-modal
-
+ - if @all_builds.running_or_pending.any?
+ #stop-jobs-modal
+ .nav-controls
%button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal',
target: '#stop-jobs-modal',
url: cancel_all_admin_jobs_path } }
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index e16d132f869..0931ceb1512 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -58,7 +58,7 @@
- if @project.avatar?
%hr
= link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _("Avatar will be removed. Are you sure?") }, method: :delete, class: "btn btn-danger btn-inverted"
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit 'Save changes', class: "btn btn-success js-btn-save-general-project-settings"
%section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index ba55bc23add..a86cb14960a 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,9 +1,6 @@
- @no_container = true
- breadcrumb_title "CI / CD Charts"
- page_title _("Charts"), _("Pipelines")
-- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('common_d3')
- = page_specific_javascript_bundle_tag('graphs')
%div{ class: container_class }
.sub-header-block
diff --git a/app/views/projects/pipelines/charts/_pipeline_times.haml b/app/views/projects/pipelines/charts/_pipeline_times.haml
index a5dbd1b1532..510697c2ae9 100644
--- a/app/views/projects/pipelines/charts/_pipeline_times.haml
+++ b/app/views/projects/pipelines/charts/_pipeline_times.haml
@@ -1,6 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('pipelines_times')
-
%div
%p.light
= _("Commit duration in minutes for last 30 commits")
diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml
index 41dc2f6cf9d..2f4b6def155 100644
--- a/app/views/projects/pipelines/charts/_pipelines.haml
+++ b/app/views/projects/pipelines/charts/_pipelines.haml
@@ -1,6 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('pipelines_charts')
-
%h4= _("Pipelines charts")
%p
&nbsp;
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index b0cb5ce5e8f..5f38ecd6820 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -1,6 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('prometheus_metrics')
-
.row.prepend-top-default.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
.col-lg-3
%h4.prepend-top-0
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 2c05ebe52ae..1a353953838 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -6,4 +6,4 @@
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
$('.project-edit-container').show();
- $('.edit-project .btn-save').enable();
+ $('.edit-project .js-btn-save-general-project-settings').enable();
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index c9a77d668a2..a396d1007a7 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -4,9 +4,6 @@
- page_description @user.bio
- header_title @user.name, user_path(@user)
- @no_container = true
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag 'common_d3'
- = webpack_bundle_tag 'users'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
diff --git a/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml b/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml
new file mode 100644
index 00000000000..29ab7cc7cab
--- /dev/null
+++ b/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml
@@ -0,0 +1,5 @@
+---
+title: API endpoint for importing a project export
+merge_request: 17025
+author:
+type: added
diff --git a/changelogs/unreleased/42929-hide-new-variable-values.yml b/changelogs/unreleased/42929-hide-new-variable-values.yml
new file mode 100644
index 00000000000..68decd25b5a
--- /dev/null
+++ b/changelogs/unreleased/42929-hide-new-variable-values.yml
@@ -0,0 +1,5 @@
+---
+title: Hide CI secret variable values after saving
+merge_request: 17044
+author:
+type: changed
diff --git a/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml b/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml
new file mode 100644
index 00000000000..b527000332e
--- /dev/null
+++ b/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml
@@ -0,0 +1,5 @@
+---
+title: Allows project rename after validation error
+merge_request: 17150
+author:
+type: fixed
diff --git a/changelogs/unreleased/asciidoc_inter_document_cross_references.yml b/changelogs/unreleased/asciidoc_inter_document_cross_references.yml
new file mode 100644
index 00000000000..34b26753312
--- /dev/null
+++ b/changelogs/unreleased/asciidoc_inter_document_cross_references.yml
@@ -0,0 +1,5 @@
+---
+title: Asciidoc now support inter-document cross references between files in repository
+merge_request: 17125
+author: Turo Soisenniemi
+type: changed
diff --git a/changelogs/unreleased/dm-escape-commit-message.yml b/changelogs/unreleased/dm-escape-commit-message.yml
new file mode 100644
index 00000000000..89af2da3484
--- /dev/null
+++ b/changelogs/unreleased/dm-escape-commit-message.yml
@@ -0,0 +1,5 @@
+---
+title: Escape HTML entities in commit messages
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/winh-new-modal-component.yml b/changelogs/unreleased/winh-new-modal-component.yml
new file mode 100644
index 00000000000..bcc0d489c88
--- /dev/null
+++ b/changelogs/unreleased/winh-new-modal-component.yml
@@ -0,0 +1,5 @@
+---
+title: Add new modal Vue component
+merge_request: 17108
+author:
+type: changed
diff --git a/config/application.rb b/config/application.rb
index c914e34b9c3..918bd4d57cf 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -69,6 +69,7 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key)
+ # - Secret variable values (:value)
config.filter_parameters += [/token$/, /password/, /secret/]
config.filter_parameters += %i(
certificate
@@ -80,6 +81,7 @@ module Gitlab
sentry_dsn
trace
variables
+ value
)
# Enable escaping HTML in JSON.
diff --git a/config/webpack.config.js b/config/webpack.config.js
index b31529591e9..62ed3d31e1f 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -77,12 +77,9 @@ var config = {
notes: './notes/index.js',
pdf_viewer: './blob/pdf_viewer.js',
pipelines: './pipelines/pipelines_bundle.js',
- pipelines_charts: './pipelines/pipelines_charts.js',
pipelines_details: './pipelines/pipeline_details_bundle.js',
- pipelines_times: './pipelines/pipelines_times.js',
profile: './profile/profile_bundle.js',
project_import_gl: './projects/project_import_gitlab_project.js',
- prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches',
protected_tags: './protected_tags',
registry_list: './registry/index.js',
@@ -98,7 +95,6 @@ var config = {
vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js',
two_factor_auth: './two_factor_auth.js',
- users: './users/index.js',
webpack_runtime: './webpack.js',
},
diff --git a/doc/api/README.md b/doc/api/README.md
index 88710eae4fe..b193ef4ab7f 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -43,6 +43,7 @@ following locations:
- [Pipeline Schedules](pipeline_schedules.md)
- [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md)
+- [Project import/export](project_import_export.md)
- [Project Members](members.md)
- [Project Snippets](project_snippets.md)
- [Protected Branches](protected_branches.md)
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
new file mode 100644
index 00000000000..e442442c750
--- /dev/null
+++ b/doc/api/project_import_export.md
@@ -0,0 +1,74 @@
+# Project import API
+
+[Introduced][ce-41899] in GitLab 10.6
+
+[See also the project import/export documentation](../user/project/settings/import_export.md)
+
+## Import a file
+
+```http
+POST /projects/import
+```
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ---------------------------------------- |
+| `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace |
+| `file` | string | yes | The file to be uploaded |
+| `path` | string | yes | Name and path for new project |
+
+To upload a file from your filesystem, use the `--form` argument. This causes
+cURL to post data using the header `Content-Type: multipart/form-data`.
+The `file=` parameter must point to a file on your filesystem and be preceded
+by `@`. For example:
+
+```console
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import
+```
+
+```json
+{
+ "id": 1,
+ "description": null,
+ "name": "api-project",
+ "name_with_namespace": "Administrator / api-project",
+ "path": "api-project",
+ "path_with_namespace": "root/api-project",
+ "created_at": "2018-02-13T09:05:58.023Z",
+ "import_status": "scheduled"
+}
+```
+
+## Import status
+
+Get the status of an import.
+
+```http
+GET /projects/:id/import
+```
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ---------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+
+```console
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/import
+```
+
+Status can be one of `none`, `scheduled`, `failed`, `started`, or `finished`.
+
+If the status is `failed`, it will include the import error message under `import_error`.
+
+```json
+{
+ "id": 1,
+ "description": "Itaque perspiciatis minima aspernatur corporis consequatur.",
+ "name": "Gitlab Test",
+ "name_with_namespace": "Gitlab Org / Gitlab Test",
+ "path": "gitlab-test",
+ "path_with_namespace": "gitlab-org/gitlab-test",
+ "created_at": "2017-08-29T04:36:44.383Z",
+ "import_status": "started"
+}
+```
+
+[ce-41899]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41899
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 05d2f2af00b..9e649efea9c 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -1330,6 +1330,10 @@ POST /projects/:id/housekeeping
Read more in the [Branches](branches.md) documentation.
+## Project Import/Export
+
+Read more in the [Project import/export](project_import_export.md) documentation.
+
## Project members
Read more in the [Project members](members.md) documentation.
diff --git a/doc/ci/README.md b/doc/ci/README.md
index eabeb4510db..532ae52a184 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -70,6 +70,8 @@ learn how to leverage its potential even more.
- [Use SSH keys in your build environment](ssh_keys/README.md)
- [Trigger pipelines through the GitLab API](triggers/README.md)
- [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md)
+- [Kubernetes clusters](../user/project/clusters/index.md) - Integrate one or
+ more Kubernetes clusters to your project
## GitLab CI/CD for Docker
diff --git a/doc/development/fe_guide/components.md b/doc/development/fe_guide/components.md
new file mode 100644
index 00000000000..66a8abe42f7
--- /dev/null
+++ b/doc/development/fe_guide/components.md
@@ -0,0 +1,61 @@
+# Components
+
+## Contents
+* [Dropdowns](#dropdowns)
+* [Modals](#modals)
+
+## Dropdowns
+
+See also the [corresponding UX guide](../ux_guide/components.md#dropdowns).
+
+### How to style a bootstrap dropdown
+1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
+1. Add a specific class to the top level `.dropdown` element
+
+
+ ```Haml
+ .dropdown.my-dropdown
+ %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
+ %span.dropdown-toggle-text
+ Toggle Dropdown
+ = icon('chevron-down')
+
+ %ul.dropdown-menu
+ %li
+ %a
+ item!
+ ```
+
+ Or use the helpers
+ ```Haml
+ .dropdown.my-dropdown
+ = dropdown_toggle('Toogle!', { toggle: 'dropdown' })
+ = dropdown_content
+ %li
+ %a
+ item!
+ ```
+
+[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
+
+## Modals
+
+See also the [corresponding UX guide](../ux_guide/components.md#modals).
+
+We have a reusable Vue component for modals: [vue_shared/components/gl-modal.vue](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/vue_shared/components/gl-modal.vue)
+
+Here is an example of how to use it:
+
+```html
+ <gl-modal
+ id="dogs-out-modal"
+ :header-title-text="s__('ModalExample|Let the dogs out?')"
+ footer-primary-button-variant="danger"
+ :footer-primary-button-text="s__('ModalExample|Let them out')"
+ @submit="letOut(theDogs)"
+ >
+ {{ s__('ModalExample|You’re about to let the dogs out.') }}
+ </gl-modal>
+```
+
+![example modal](img/gl-modal.png)
diff --git a/doc/development/fe_guide/dropdowns.md b/doc/development/fe_guide/dropdowns.md
index 6314f8f38d2..e9d6244355c 100644
--- a/doc/development/fe_guide/dropdowns.md
+++ b/doc/development/fe_guide/dropdowns.md
@@ -1,32 +1 @@
-# Dropdowns
-
-
-## How to style a bootstrap dropdown
-1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
-1. Add a specific class to the top level `.dropdown` element
-
-
- ```Haml
- .dropdown.my-dropdown
- %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
- %span.dropdown-toggle-text
- Toggle Dropdown
- = icon('chevron-down')
-
- %ul.dropdown-menu
- %li
- %a
- item!
- ```
-
- Or use the helpers
- ```Haml
- .dropdown.my-dropdown
- = dropdown_toggle('Toogle!', { toggle: 'dropdown' })
- = dropdown_content
- %li
- %a
- item!
- ```
-
-[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
+This page has moved [here](components.md#dropdowns).
diff --git a/doc/development/fe_guide/img/gl-modal.png b/doc/development/fe_guide/img/gl-modal.png
new file mode 100644
index 00000000000..47302e857bc
--- /dev/null
+++ b/doc/development/fe_guide/img/gl-modal.png
Binary files differ
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 72cb557d054..12dfc10812b 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -21,6 +21,8 @@ Working with our frontend assets requires Node (v4.3 or greater) and Yarn
[jQuery][jquery] is used throughout the application's JavaScript, with
[Vue.js][vue] for particularly advanced, dynamic elements.
+We also use [Axios][axios] to handle all of our network requests.
+
### Browser Support
For our currently-supported browsers, see our [requirements][requirements].
@@ -77,8 +79,10 @@ Axios specific practices and gotchas.
## [Icons](icons.md)
How we use SVG for our Icons.
-## [Dropdowns](dropdowns.md)
-How we use dropdowns.
+## [Components](components.md)
+
+How we use UI components.
+
---
## Style Guides
@@ -122,6 +126,7 @@ The [externalization part of the guide](../i18n/externalization.md) explains the
[webpack]: https://webpack.js.org/
[jquery]: https://jquery.com/
[vue]: http://vuejs.org/
+[axios]: https://github.com/axios/axios
[airbnb-js-style-guide]: https://github.com/airbnb/javascript
[scss-lint]: https://github.com/brigade/scss-lint
[install]: ../../install/installation.md#4-node
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index 97c997e0568..11878b4009b 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -27,6 +27,17 @@ Gitlab::Profiler.profile('/my-user')
# Returns a RubyProf::Profile where 100 seconds is spent in UsersController#show
```
+For routes that require authorization you will need to provide a user to
+`Gitlab::Profiler`. You can do this like so:
+
+```ruby
+Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first)
+```
+
+The user you provide will need to have a [personal access
+token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) in
+the GitLab instance.
+
Passing a `logger:` keyword argument to `Gitlab::Profiler.profile` will send
ActiveRecord and ActionController log output to that logger. Further options are
documented with the method source.
diff --git a/doc/install/google_cloud_platform/img/boot_disk.png b/doc/install/google_cloud_platform/img/boot_disk.png
new file mode 100644
index 00000000000..37b2d9eaae7
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/boot_disk.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/change_admin_passwd_email.png b/doc/install/google_cloud_platform/img/change_admin_passwd_email.png
deleted file mode 100644
index 1ffe14f60ff..00000000000
--- a/doc/install/google_cloud_platform/img/change_admin_passwd_email.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/chrome_not_secure_page.png b/doc/install/google_cloud_platform/img/chrome_not_secure_page.png
deleted file mode 100644
index e732066908f..00000000000
--- a/doc/install/google_cloud_platform/img/chrome_not_secure_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/first_signin.png b/doc/install/google_cloud_platform/img/first_signin.png
new file mode 100644
index 00000000000..6eb3392d674
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/first_signin.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png b/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png
deleted file mode 100644
index 2a1859da6e3..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png b/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png
deleted file mode 100644
index 1c4c870dbc9..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_landing.png b/doc/install/google_cloud_platform/img/gcp_landing.png
index 6398d247ba0..d6390c4dd4f 100644
--- a/doc/install/google_cloud_platform/img/gcp_landing.png
+++ b/doc/install/google_cloud_platform/img/gcp_landing.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png b/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png
deleted file mode 100644
index f492888ea4a..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png b/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png
deleted file mode 100644
index b38af3966e2..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gitlab_deployed_page.png b/doc/install/google_cloud_platform/img/gitlab_deployed_page.png
deleted file mode 100644
index fef9ae45f32..00000000000
--- a/doc/install/google_cloud_platform/img/gitlab_deployed_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png b/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png
deleted file mode 100644
index 381c0fe48a5..00000000000
--- a/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gitlab_launch_button.png b/doc/install/google_cloud_platform/img/gitlab_launch_button.png
deleted file mode 100644
index 50f66f66118..00000000000
--- a/doc/install/google_cloud_platform/img/gitlab_launch_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/launch_vm.png b/doc/install/google_cloud_platform/img/launch_vm.png
new file mode 100644
index 00000000000..3fd13f232bb
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/launch_vm.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png b/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png
deleted file mode 100644
index 00060841619..00000000000
--- a/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/ssh_terminal.png b/doc/install/google_cloud_platform/img/ssh_terminal.png
new file mode 100644
index 00000000000..6a1a418d8e9
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/ssh_terminal.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/ssh_via_button.png b/doc/install/google_cloud_platform/img/ssh_via_button.png
deleted file mode 100644
index 26106f159ad..00000000000
--- a/doc/install/google_cloud_platform/img/ssh_via_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/vm_created.png b/doc/install/google_cloud_platform/img/vm_created.png
new file mode 100644
index 00000000000..fb467f40838
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/vm_created.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/vm_details.png b/doc/install/google_cloud_platform/img/vm_details.png
new file mode 100644
index 00000000000..2d230416a4b
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/vm_details.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md
index c6b767fff02..3389f0260f9 100644
--- a/doc/install/google_cloud_platform/index.md
+++ b/doc/install/google_cloud_platform/index.md
@@ -2,12 +2,7 @@
![GCP landing page](img/gcp_landing.png)
-The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through
-the [Google Cloud Launcher][launcher] program.
-
-GitLab's official Google Launcher apps:
-1. [GitLab Community Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-community-edition?project=gitlab-public)
-2. [GitLab Enterprise Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-enterprise-edition?project=gitlab-public)
+Gettung started with GitLab on a [Google Cloud Platform (GCP)][gcp] instance is quick and easy.
## Prerequisites
@@ -17,84 +12,52 @@ There are only two prerequisites in order to install GitLab on GCP:
1. You need to sign up for the GCP program. If this is your first time, Google
gives you [$300 credit for free][freetrial] to consume over a 60-day period.
-Once you have performed those two steps, you can visit the
-[GCP launcher console][console] which has a list of all the things you can
-deploy on GCP.
-
-![GCP launcher console](img/gcp_launcher_console_home_page.png)
-
-The next step is to find and install GitLab.
+Once you have performed those two steps, you can [create a VM](#creating-the-vm).
-## Configuring and deploying the VM
+## Creating the VM
To deploy GitLab on GCP you need to follow five simple steps:
-1. Go to https://cloud.google.com/launcher and login with your Google credentials
-1. Search for GitLab from GitLab Inc. (not the same as Bitnami) and click on
- the tile.
+1. Go to https://console.cloud.google.com/compute/instances and login with your Google credentials.
- ![Search for GitLab](img/gcp_search_for_gitlab.png)
+1. Click on **Create**
-1. In the next page, you can see an overview of the GitLab VM as well as some
- estimated costs. Click the **Launch on Compute Engine** button to choose the
- hardware and network settings.
+ ![Search for GitLab](img/launch_vm.png)
- ![Launch on Compute Engine](img/gcp_gitlab_overview.png)
+1. On the next page, you can select the type of VM as well as the
+ estimated costs. Provide the name of the instance, desired datacenter, and machine type. Note that GitLab recommends at least 2 vCPU's and 4GB of RAM.
-1. In the settings page you can choose things like the datacenter where your GitLab
- server will be hosted, the number of CPUs and amount of RAM, the disk size
- and type, etc. Read GitLab's [requirements documentation][req] for more
- details on what to choose depending on your needs.
+ ![Launch on Compute Engine](img/vm_details.png)
- ![Deploy settings](img/new_gitlab_deployment_settings.png)
+1. Click **Change** under Boot disk to select the size, type, and desired operating system. GitLab supports a [variety of linux operating systems][req], including Ubuntu and Debian. Click **Select** when finished.
-1. As a last step, hit **Deploy** when ready. The process will finish in a few
- seconds.
+ ![Deploy in progress](img/boot_disk.png)
- ![Deploy in progress](img/gcp_gitlab_being_deployed.png)
+1. As a last step allow HTTP and HTTPS traffic, then click **Create**. The process will finish in a few seconds.
+## Installing GitLab
-## Visiting GitLab for the first time
+After a few seconds, the instance will be created and available to log in. The next step is to install GitLab onto the instance.
-After a few seconds, GitLab will be successfully deployed and you should be
-able to see the IP address that Google assigned to the VM, as well as the
-credentials to the GitLab admin account.
+![Deploy settings](img/vm_created.png)
-![Deploy settings](img/gitlab_deployed_page.png)
+1. Make a note of the IP address of the instance, as you will need that in a later step.
+1. Click on the SSH button to connect to the instance.
+1. A new window will appear, with you logged into the instance.
-1. Click on the IP under **Site address** to visit GitLab.
-1. Accept the self-signed certificate that Google automatically deployed in
- order to securely reach GitLab's login page.
-1. Use the username and password that are present in the Google console page
- to login into GitLab and click **Sign in**.
+ ![GitLab first sign in](img/ssh_terminal.png)
- ![GitLab first sign in](img/gitlab_first_sign_in.png)
+1. Next, follow the instructions for installing GitLab for the operating system you choose, at https://about.gitlab.com/installation/. You can use the IP address from the step above, as the hostname.
-Congratulations! GitLab is now installed and you can access it via your browser,
-but we're not done yet. There are some steps you need to take in order to have
-a fully functional GitLab installation.
+1. Congratulations! GitLab is now installed and you can access it via your browser. To finish installation, open the URL in your browser and provide the initial administrator password. The username for this account is `root`.
+
+ ![GitLab first sign in](img/first_signin.png)
## Next steps
These are the most important next steps to take after you installed GitLab for
the first time.
-### Changing the admin password and email
-
-Google assigned a random password for the GitLab admin account and you should
-change it ASAP:
-
-1. Visit the GitLab admin page through the link in the Google console under
- **Admin URL**.
-1. Find the Administrator user under the **Users** page and hit **Edit**.
-1. Change the email address to a real one and enter a new password.
-
- ![Change GitLab admin password](img/change_admin_passwd_email.png)
-
-1. Hit **Save changes** for the changes to take effect.
-1. After changing the password, you will be signed out from GitLab. Use the
- new credentials to login again.
-
### Assigning a static IP
By default, Google assigns an ephemeral IP to your instance. It is strongly
@@ -112,7 +75,7 @@ here's how you configure GitLab to be aware of the change:
1. SSH into the VM. You can easily use the **SSH** button in the Google console
and a new window will pop up.
- ![SSH button](img/ssh_via_button.png)
+ ![SSH button](img/vm_created.png)
In the future you might want to set up [connecting with an SSH key][ssh]
instead.
@@ -161,7 +124,6 @@ Kerberos, etc. Here are some documents you might be interested in reading:
- [GitLab Pages configuration](https://docs.gitlab.com/ce/administration/pages/index.html)
- [GitLab Container Registry configuration](https://docs.gitlab.com/ce/administration/container_registry.html)
-[console]: https://console.cloud.google.com/launcher "GCP launcher console"
[freetrial]: https://console.cloud.google.com/freetrial "GCP free trial"
[ip]: https://cloud.google.com/compute/docs/configure-instance-ip-addresses#promote_ephemeral_ip "Configuring an Instance's IP Addresses"
[gcp]: https://cloud.google.com/ "Google Cloud Platform"
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 50a8e0d5ec5..bbe25c2d911 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -5,20 +5,23 @@
Connect your project to Google Kubernetes Engine (GKE) or an existing Kubernetes
cluster in a few steps.
-With a cluster associated to your project, you can use Review Apps, deploy your
-applications, run your pipelines, and much more, in an easy way.
+## Overview
+
+With a Kubernetes cluster associated to your project, you can use
+[Review Apps](../../../ci/review_apps/index.md), deploy your applications, run
+your pipelines, and much more, in an easy way.
There are two options when adding a new cluster to your project; either associate
your account with Google Kubernetes Engine (GKE) so that you can [create new
clusters](#adding-and-creating-a-new-gke-cluster-via-gitlab) from within GitLab,
or provide the credentials to an [existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster).
-## Prerequisites
+## Adding and creating a new GKE cluster via GitLab
-In order to be able to manage your Kubernetes cluster through GitLab, the
-following prerequisites must be met.
+NOTE: **Note:**
+You need Master [permissions] and above to access the Kubernetes page.
-**For a cluster hosted on GKE:**
+Before proceeding, make sure the following requirements are met:
- The [Google authentication integration](../../../integration/google.md) must
be enabled in GitLab at the instance level. If that's not the case, ask your
@@ -28,30 +31,16 @@ following prerequisites must be met.
account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
must be set up and that you have to have permissions to access it.
- You must have Master [permissions] in order to be able to access the
- **Cluster** page.
+ **Kubernetes** page.
- You must have [Cloud Billing API](https://cloud.google.com/billing/) enabled
- You must have [Resource Manager
API](https://cloud.google.com/resource-manager/)
-**For an existing Kubernetes cluster:**
-
-- Since the cluster is already created, there are no prerequisites.
-
----
-
-If all of the above requirements are met, you can proceed to add a new Kubernetes
-cluster.
-
-## Adding and creating a new GKE cluster via GitLab
-
-NOTE: **Note:**
-You need Master [permissions] and above to access the Clusters page.
-
-Before proceeding, make sure all [prerequisites](#prerequisites) are met.
-To add a new cluster hosted on GKE to your project:
+If all of the above requirements are met, you can proceed to create and add a
+new Kubernetes cluster that will be hosted on GKE to your project:
-1. Navigate to your project's **CI/CD > Clusters** page.
-1. Click on **Add cluster**.
+1. Navigate to your project's **CI/CD > Kubernetes** page.
+1. Click on **Add Kubernetes cluster**.
1. Click on **Create with GKE**.
1. Connect your Google account if you haven't done already by clicking the
**Sign in with Google** button.
@@ -66,7 +55,7 @@ To add a new cluster hosted on GKE to your project:
- **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
of the Virtual Machine instance that the cluster will be based on.
- **Environment scope** - The [associated environment](#setting-the-environment-scope) to this cluster.
-1. Finally, click the **Create cluster** button.
+1. Finally, click the **Create Kubernetes cluster** button.
After a few moments, your cluster should be created. If something goes wrong,
you will be notified.
@@ -77,14 +66,14 @@ enable the Cluster integration.
## Adding an existing Kubernetes cluster
NOTE: **Note:**
-You need Master [permissions] and above to access the Clusters page.
+You need Master [permissions] and above to access the Kubernetes page.
To add an existing Kubernetes cluster to your project:
-1. Navigate to your project's **CI/CD > Clusters** page.
-1. Click on **Add cluster**.
-1. Click on **Add an existing cluster** and fill in the details:
- - **Cluster name** (required) - The name you wish to give the cluster.
+1. Navigate to your project's **CI/CD > Kubernetes** page.
+1. Click on **Add Kuberntes cluster**.
+1. Click on **Add an existing Kubernetes cluster** and fill in the details:
+ - **Kubernetes cluster name** (required) - The name you wish to give the cluster.
- **Environment scope** (required)- The
[associated environment](#setting-the-environment-scope) to this cluster.
- **API URL** (required) -
@@ -112,15 +101,13 @@ To add an existing Kubernetes cluster to your project:
- If you or someone created a secret specifically for the project, usually
with limited permissions, the secret's namespace and project namespace may
be the same.
-1. Finally, click the **Create cluster** button.
-
-The Kubernetes service takes the following parameters:
+1. Finally, click the **Create Kuberntes cluster** button.
After a few moments, your cluster should be created. If something goes wrong,
you will be notified.
You can now proceed to install some pre-defined applications and then
-enable the Cluster integration.
+enable the Kubernetes cluster integration.
## Installing applications
@@ -139,7 +126,7 @@ added directly to your configured cluster. Those applications are needed for
NOTE: **Note:**
You need a load balancer installed in your cluster in order to obtain the
external IP address with the following procedure. It can be deployed using the
-**Ingress** application described in the previous section.
+[**Ingress** application](#installing-appplications).
In order to publish your web application, you first need to find the external IP
address associated to your load balancer.
@@ -153,7 +140,8 @@ the `gcloud` command in a local terminal or using the **Cloud Shell**.
If the cluster is not on GKE, follow the specific instructions for your
Kubernetes provider to configure `kubectl` with the right credentials.
-If you installed the Ingress using the **Applications** section, run the following command:
+If you installed the Ingress [via the **Applications**](#installing-applications),
+run the following command:
```bash
kubectl get svc --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
@@ -171,9 +159,14 @@ your deployed applications.
## Setting the environment scope
-When adding more than one clusters, you need to differentiate them with an
-environment scope. The environment scope associates clusters and
-[environments](../../../ci/environments.md) in an 1:1 relationship similar to how the
+NOTE: **Note:**
+This is only available for [GitLab Premium][ee] where you can add more than
+one Kubernetes cluster.
+
+When adding more than one Kubernetes clusters to your project, you need to
+differentiate them with an environment scope. The environment scope associates
+clusters and [environments](../../../ci/environments.md) in an 1:1 relationship
+similar to how the
[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-secret-variables)
work.
@@ -183,7 +176,7 @@ cluster in a project, and a validation error will occur if otherwise.
---
-For example, let's say the following clusters exist in a project:
+For example, let's say the following Kubernetes clusters exist in a project:
| Cluster | Environment scope |
| ---------- | ------------------- |
@@ -231,8 +224,7 @@ With GitLab Premium, you can associate more than one Kubernetes clusters to your
project. That way you can have different clusters for different environments,
like dev, staging, production, etc.
-To add another cluster, follow the same steps as described in [adding a
-Kubernetes cluster](#adding-a-kubernetes-cluster) and make sure to
+Simply add another cluster, like you did the first time, and make sure to
[set an environment scope](#setting-the-environment-scope) that will
differentiate the new cluster with the rest.
@@ -240,45 +232,42 @@ differentiate the new cluster with the rest.
The Kubernetes cluster integration exposes the following
[deployment variables](../../../ci/variables/README.md#deployment-variables) in the
-GitLab CI/CD build environment:
-
-- `KUBE_URL` - Equal to the API URL.
-- `KUBE_TOKEN` - The Kubernetes token.
-- `KUBE_NAMESPACE` - The Kubernetes namespace is auto-generated if not specified.
- The default value is `<project_name>-<project_id>`. You can overwrite it to
- use different one if needed, otherwise the `KUBE_NAMESPACE` variable will
- receive the default value.
-- `KUBE_CA_PEM_FILE` - Only present if a custom CA bundle was specified. Path
- to a file containing PEM data.
-- `KUBE_CA_PEM` (deprecated) - Only if a custom CA bundle was specified. Raw PEM data.
-- `KUBECONFIG` - Path to a file containing `kubeconfig` for this deployment.
- CA bundle would be embedded if specified.
-
-## Enabling or disabling the Cluster integration
+GitLab CI/CD build environment.
+
+| Variable | Description |
+| -------- | ----------- |
+| `KUBE_URL` | Equal to the API URL. |
+| `KUBE_TOKEN` | The Kubernetes token. |
+| `KUBE_NAMESPACE` | The Kubernetes namespace is auto-generated if not specified. The default value is `<project_name>-<project_id>`. You can overwrite it to use different one if needed, otherwise the `KUBE_NAMESPACE` variable will receive the default value. |
+| `KUBE_CA_PEM_FILE` | Only present if a custom CA bundle was specified. Path to a file containing PEM data. |
+| `KUBE_CA_PEM` | (**deprecated**) Only if a custom CA bundle was specified. Raw PEM data. |
+| `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. |
+
+## Enabling or disabling the Kubernetes cluster integration
After you have successfully added your cluster information, you can enable the
-Cluster integration:
+Kubernetes cluster integration:
1. Click the "Enabled/Disabled" switch
1. Hit **Save** for the changes to take effect
You can now start using your Kubernetes cluster for your deployments.
-To disable the Cluster integration, follow the same procedure.
+To disable the Kubernetes cluster integration, follow the same procedure.
-## Removing the Cluster integration
+## Removing the Kubernetes cluster integration
NOTE: **Note:**
-You need Master [permissions] and above to remove a cluster integration.
+You need Master [permissions] and above to remove a Kubernetes cluster integration.
NOTE: **Note:**
When you remove a cluster, you only remove its relation to GitLab, not the
cluster itself. To remove the cluster, you can do so by visiting the GKE
dashboard or using `kubectl`.
-To remove the Cluster integration from your project, simply click on the
+To remove the Kubernetes cluster integration from your project, simply click on the
**Remove integration** button. You will then be able to follow the procedure
-and [add a cluster](#adding-a-cluster) again.
+and add a Kubernetes cluster again.
## What you can get with the Kubernetes integration
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index b8f865679a2..dedf102fc37 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -22,6 +22,7 @@
> in the import side is required to map the users, based on email or username.
> Otherwise, a supplementary comment is left to mention the original author and
> the MRs, notes or issues will be owned by the importer.
+> - Control project Import/Export with the [API](../../../api/project_import_export.md).
Existing projects running on any GitLab instance or GitLab.com can be exported
with all their related data and be moved into a new GitLab instance.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e953f3d2eca..754549f72f0 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -138,6 +138,7 @@ module API
mount ::API::PagesDomains
mount ::API::Pipelines
mount ::API::PipelineSchedules
+ mount ::API::ProjectImport
mount ::API::ProjectHooks
mount ::API::Projects
mount ::API::ProjectMilestones
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 03abc1b95c5..45c737c6c29 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -91,6 +91,13 @@ module API
expose :created_at
end
+ class ProjectImportStatus < ProjectIdentity
+ expose :import_status
+
+ # TODO: Use `expose_nil` once we upgrade the grape-entity gem
+ expose :import_error, if: lambda { |status, _ops| status.import_error }
+ end
+
class BasicProjectDetails < ProjectIdentity
include ::API::ProjectsRelationBuilder
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
new file mode 100644
index 00000000000..c32e2f26ae3
--- /dev/null
+++ b/lib/api/project_import.rb
@@ -0,0 +1,65 @@
+module API
+ class ProjectImport < Grape::API
+ include PaginationParams
+
+ helpers do
+ def import_params
+ declared_params(include_missing: false)
+ end
+
+ def file_is_valid?
+ import_params[:file] && import_params[:file]['tempfile'].respond_to?(:read)
+ end
+
+ def validate_file!
+ render_api_error!('The file is invalid', 400) unless file_is_valid?
+ end
+ end
+
+ before do
+ forbidden! unless Gitlab::CurrentSettings.import_sources.include?('gitlab_project')
+ end
+
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+ params do
+ requires :path, type: String, desc: 'The new project path and name'
+ requires :file, type: File, desc: 'The project export file to be imported'
+ optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace."
+ end
+ desc 'Create a new project import' do
+ detail 'This feature was introduced in GitLab 10.6.'
+ success Entities::ProjectImportStatus
+ end
+ post 'import' do
+ validate_file!
+
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42437')
+
+ namespace = if import_params[:namespace]
+ find_namespace!(import_params[:namespace])
+ else
+ current_user.namespace
+ end
+
+ project_params = import_params.merge(namespace_id: namespace.id,
+ file: import_params[:file]['tempfile'])
+ project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
+
+ render_api_error!(project.errors.full_messages&.first, 400) unless project.saved?
+
+ present project, with: Entities::ProjectImportStatus
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ desc 'Get a project export status' do
+ detail 'This feature was introduced in GitLab 10.6.'
+ success Entities::ProjectImportStatus
+ end
+ get ':id/import' do
+ present user_project, with: Entities::ProjectImportStatus
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb
index f3bd587c28b..e008fd428b0 100644
--- a/lib/banzai/filter/html_entity_filter.rb
+++ b/lib/banzai/filter/html_entity_filter.rb
@@ -5,7 +5,7 @@ module Banzai
# Text filter that escapes these HTML entities: & " < >
class HtmlEntityFilter < HTML::Pipeline::TextFilter
def call
- ERB::Util.html_escape_once(text)
+ ERB::Util.html_escape(text)
end
end
end
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index ee7f4be6b9f..62c41801d75 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -8,7 +8,8 @@ module Gitlab
module Asciidoc
DEFAULT_ADOC_ATTRS = [
'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
- 'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font'
+ 'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font',
+ 'outfilesuffix=.adoc'
].freeze
# Public: Converts the provided Asciidoc markup into HTML.
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 95d94b3cc68..98a168b43bb 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -45,6 +45,7 @@ module Gitlab
if user
private_token ||= user.personal_access_tokens.active.pluck(:token).first
+ raise 'Your user must have a personal_access_token' unless private_token
end
headers['Private-Token'] = private_token if private_token
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 8a80b88da5d..fccde8b7eba 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -20,5 +20,9 @@ describe EventsHelper do
it 'handles nil values' do
expect(helper.event_commit_title(nil)).to eq('')
end
+
+ it 'does not escape HTML entities' do
+ expect(helper.event_commit_title("foo & bar")).to eq("foo & bar")
+ end
end
end
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index 5b9cdceee71..ee457a9c48c 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -1,8 +1,10 @@
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import AjaxFormVariableList from '~/ci_variable_list/ajax_variable_list';
const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-project/variables';
+const HIDE_CLASS = 'hide';
describe('AjaxFormVariableList', () => {
preloadFixtures('projects/ci_cd_settings.html.raw');
@@ -45,16 +47,16 @@ describe('AjaxFormVariableList', () => {
const loadingIcon = saveButton.querySelector('.js-secret-variables-save-loading-icon');
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => {
- expect(loadingIcon.classList.contains('hide')).toEqual(false);
+ expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(false);
return [200, {}];
});
- expect(loadingIcon.classList.contains('hide')).toEqual(true);
+ expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked()
.then(() => {
- expect(loadingIcon.classList.contains('hide')).toEqual(true);
+ expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true);
})
.then(done)
.catch(done.fail);
@@ -78,11 +80,11 @@ describe('AjaxFormVariableList', () => {
it('hides any previous error box', (done) => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200);
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked()
.then(() => {
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
})
.then(done)
.catch(done.fail);
@@ -103,17 +105,39 @@ describe('AjaxFormVariableList', () => {
.catch(done.fail);
});
+ it('hides secret values', (done) => {
+ mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {});
+
+ const row = container.querySelector('.js-row:first-child');
+ const valueInput = row.querySelector('.js-ci-variable-input-value');
+ const valuePlaceholder = row.querySelector('.js-secret-value-placeholder');
+
+ valueInput.value = 'bar';
+ $(valueInput).trigger('input');
+
+ expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(true);
+ expect(valueInput.classList.contains(HIDE_CLASS)).toBe(false);
+
+ ajaxVariableList.onSaveClicked()
+ .then(() => {
+ expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(false);
+ expect(valueInput.classList.contains(HIDE_CLASS)).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
it('shows error box with validation errors', (done) => {
const validationError = 'some validation error';
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(400, [
validationError,
]);
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked()
.then(() => {
- expect(errorBox.classList.contains('hide')).toEqual(false);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(false);
expect(errorBox.textContent.trim().replace(/\n+\s+/m, ' ')).toEqual(`Validation failed ${validationError}`);
})
.then(done)
@@ -123,11 +147,11 @@ describe('AjaxFormVariableList', () => {
it('shows flash message when request fails', (done) => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(500);
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked()
.then(() => {
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
})
.then(done)
.catch(done.fail);
@@ -170,9 +194,9 @@ describe('AjaxFormVariableList', () => {
const valueInput = row.querySelector('.js-ci-variable-input-value');
keyInput.value = 'foo';
- keyInput.dispatchEvent(new Event('input'));
+ $(keyInput).trigger('input');
valueInput.value = 'bar';
- valueInput.dispatchEvent(new Event('input'));
+ $(valueInput).trigger('input');
expect(idInput.value).toEqual('');
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 8acb346901f..cac785fd3c6 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -1,6 +1,8 @@
import VariableList from '~/ci_variable_list/ci_variable_list';
import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
+const HIDE_CLASS = 'hide';
+
describe('VariableList', () => {
preloadFixtures('pipeline_schedules/edit.html.raw');
preloadFixtures('pipeline_schedules/edit_with_variables.html.raw');
@@ -92,14 +94,14 @@ describe('VariableList', () => {
const $inputValue = $row.find('.js-ci-variable-input-value');
const $placeholder = $row.find('.js-secret-value-placeholder');
- expect($placeholder.hasClass('hide')).toBe(false);
- expect($inputValue.hasClass('hide')).toBe(true);
+ expect($placeholder.hasClass(HIDE_CLASS)).toBe(false);
+ expect($inputValue.hasClass(HIDE_CLASS)).toBe(true);
// Reveal values
$wrapper.find('.js-secret-value-reveal-button').click();
- expect($placeholder.hasClass('hide')).toBe(true);
- expect($inputValue.hasClass('hide')).toBe(false);
+ expect($placeholder.hasClass(HIDE_CLASS)).toBe(true);
+ expect($inputValue.hasClass(HIDE_CLASS)).toBe(false);
});
});
});
@@ -179,4 +181,35 @@ describe('VariableList', () => {
expect($wrapper.find('.js-ci-variable-input-key:not([disabled])').length).toBe(3);
});
});
+
+ describe('hideValues', () => {
+ beforeEach(() => {
+ loadFixtures('projects/ci_cd_settings.html.raw');
+ $wrapper = $('.js-ci-variable-list-section');
+
+ variableList = new VariableList({
+ container: $wrapper,
+ formField: 'variables',
+ });
+ variableList.init();
+ });
+
+ it('should hide value input and show placeholder stars', () => {
+ const $row = $wrapper.find('.js-row');
+ const $inputValue = $row.find('.js-ci-variable-input-value');
+ const $placeholder = $row.find('.js-secret-value-placeholder');
+
+ $row.find('.js-ci-variable-input-value')
+ .val('foo')
+ .trigger('input');
+
+ expect($placeholder.hasClass(HIDE_CLASS)).toBe(true);
+ expect($inputValue.hasClass(HIDE_CLASS)).toBe(false);
+
+ variableList.hideValues();
+
+ expect($placeholder.hasClass(HIDE_CLASS)).toBe(false);
+ expect($inputValue.hasClass(HIDE_CLASS)).toBe(true);
+ });
+ });
});
diff --git a/spec/javascripts/importer_status_spec.js b/spec/javascripts/importer_status_spec.js
index bb49c576e91..71a2cd51f63 100644
--- a/spec/javascripts/importer_status_spec.js
+++ b/spec/javascripts/importer_status_spec.js
@@ -3,9 +3,18 @@ import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
describe('Importer Status', () => {
+ let instance;
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
describe('addToImport', () => {
- let instance;
- let mock;
const importUrl = '/import_url';
beforeEach(() => {
@@ -21,11 +30,6 @@ describe('Importer Status', () => {
spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {});
spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {});
instance = new ImporterStatus('', importUrl);
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
});
it('sets table row to active after post request', (done) => {
@@ -44,4 +48,60 @@ describe('Importer Status', () => {
.catch(done.fail);
});
});
+
+ describe('autoUpdate', () => {
+ const jobsUrl = '/jobs_url';
+
+ beforeEach(() => {
+ const div = document.createElement('div');
+ div.innerHTML = `
+ <div id="project_1">
+ <div class="job-status">
+ </div>
+ </div>
+ `;
+
+ document.body.appendChild(div);
+
+ spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {});
+ spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {});
+ instance = new ImporterStatus(jobsUrl);
+ });
+
+ function setupMock(importStatus) {
+ mock.onGet(jobsUrl).reply(200, [{
+ id: 1,
+ import_status: importStatus,
+ }]);
+ }
+
+ function expectJobStatus(done, status) {
+ instance.autoUpdate()
+ .then(() => {
+ expect(document.querySelector('#project_1').innerText.trim()).toEqual(status);
+ done();
+ })
+ .catch(done.fail);
+ }
+
+ it('sets the job status to done', (done) => {
+ setupMock('finished');
+ expectJobStatus(done, 'done');
+ });
+
+ it('sets the job status to scheduled', (done) => {
+ setupMock('scheduled');
+ expectJobStatus(done, 'scheduled');
+ });
+
+ it('sets the job status to started', (done) => {
+ setupMock('started');
+ expectJobStatus(done, 'started');
+ });
+
+ it('sets the job status to custom status', (done) => {
+ setupMock('custom status');
+ expectJobStatus(done, 'custom status');
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/gl_modal_spec.js b/spec/javascripts/vue_shared/components/gl_modal_spec.js
new file mode 100644
index 00000000000..d6148cb785b
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/gl_modal_spec.js
@@ -0,0 +1,192 @@
+import Vue from 'vue';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+const modalComponent = Vue.extend(GlModal);
+
+describe('GlModal', () => {
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('props', () => {
+ describe('with id', () => {
+ const props = {
+ id: 'my-modal',
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, props);
+ });
+
+ it('assigns the id to the modal', () => {
+ expect(vm.$el.id).toBe(props.id);
+ });
+ });
+
+ describe('without id', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, { });
+ });
+
+ it('does not add an id attribute to the modal', () => {
+ expect(vm.$el.hasAttribute('id')).toBe(false);
+ });
+ });
+
+ describe('with headerTitleText', () => {
+ const props = {
+ headerTitleText: 'my title text',
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, props);
+ });
+
+ it('sets the modal title', () => {
+ const modalTitle = vm.$el.querySelector('.modal-title');
+ expect(modalTitle.innerHTML.trim()).toBe(props.headerTitleText);
+ });
+ });
+
+ describe('with footerPrimaryButtonVariant', () => {
+ const props = {
+ footerPrimaryButtonVariant: 'danger',
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, props);
+ });
+
+ it('sets the primary button class', () => {
+ const primaryButton = vm.$el.querySelector('.modal-footer button:last-of-type');
+ expect(primaryButton).toHaveClass(`btn-${props.footerPrimaryButtonVariant}`);
+ });
+ });
+
+ describe('with footerPrimaryButtonText', () => {
+ const props = {
+ footerPrimaryButtonText: 'my button text',
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, props);
+ });
+
+ it('sets the primary button text', () => {
+ const primaryButton = vm.$el.querySelector('.modal-footer button:last-of-type');
+ expect(primaryButton.innerHTML.trim()).toBe(props.footerPrimaryButtonText);
+ });
+ });
+ });
+
+ it('works with data-toggle="modal"', (done) => {
+ setFixtures(`
+ <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button>
+ <div id="modal-container"></div>
+ `);
+
+ const modalContainer = document.getElementById('modal-container');
+ const modalButton = document.getElementById('modal-button');
+ vm = mountComponent(modalComponent, {
+ id: 'my-modal',
+ }, modalContainer);
+ $(vm.$el).on('shown.bs.modal', () => done());
+
+ modalButton.click();
+ });
+
+ describe('methods', () => {
+ const dummyEvent = 'not really an event';
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, { });
+ spyOn(vm, '$emit');
+ });
+
+ describe('emitCancel', () => {
+ it('emits a cancel event', () => {
+ vm.emitCancel(dummyEvent);
+
+ expect(vm.$emit).toHaveBeenCalledWith('cancel', dummyEvent);
+ });
+ });
+
+ describe('emitSubmit', () => {
+ it('emits a submit event', () => {
+ vm.emitSubmit(dummyEvent);
+
+ expect(vm.$emit).toHaveBeenCalledWith('submit', dummyEvent);
+ });
+ });
+ });
+
+ describe('slots', () => {
+ const slotContent = 'this should go into the slot';
+ const modalWithSlot = (slotName) => {
+ let template;
+ if (slotName) {
+ template = `
+ <gl-modal>
+ <template slot="${slotName}">${slotContent}</template>
+ </gl-modal>
+ `;
+ } else {
+ template = `<gl-modal>${slotContent}</gl-modal>`;
+ }
+
+ return Vue.extend({
+ components: {
+ GlModal,
+ },
+ template,
+ });
+ };
+
+ describe('default slot', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalWithSlot());
+ });
+
+ it('sets the modal body', () => {
+ const modalBody = vm.$el.querySelector('.modal-body');
+ expect(modalBody.innerHTML).toBe(slotContent);
+ });
+ });
+
+ describe('header slot', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalWithSlot('header'));
+ });
+
+ it('sets the modal header', () => {
+ const modalHeader = vm.$el.querySelector('.modal-header');
+ expect(modalHeader.innerHTML).toBe(slotContent);
+ });
+ });
+
+ describe('title slot', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalWithSlot('title'));
+ });
+
+ it('sets the modal title', () => {
+ const modalTitle = vm.$el.querySelector('.modal-title');
+ expect(modalTitle.innerHTML).toBe(slotContent);
+ });
+ });
+
+ describe('footer slot', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalWithSlot('footer'));
+ });
+
+ it('sets the modal footer', () => {
+ const modalFooter = vm.$el.querySelector('.modal-footer');
+ expect(modalFooter.innerHTML).toBe(slotContent);
+ });
+ });
+ });
+});
diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb
index 91e18d876d5..1d98fc0d5db 100644
--- a/spec/lib/banzai/filter/html_entity_filter_spec.rb
+++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb
@@ -3,17 +3,12 @@ require 'spec_helper'
describe Banzai::Filter::HtmlEntityFilter do
include FilterSpecHelper
- let(:unescaped) { 'foo <strike attr="foo">&&&</strike>' }
- let(:escaped) { 'foo &lt;strike attr=&quot;foo&quot;&gt;&amp;&amp;&amp;&lt;/strike&gt;' }
+ let(:unescaped) { 'foo <strike attr="foo">&&amp;&</strike>' }
+ let(:escaped) { 'foo &lt;strike attr=&quot;foo&quot;&gt;&amp;&amp;amp;&amp;&lt;/strike&gt;' }
it 'converts common entities to their HTML-escaped equivalents' do
output = filter(unescaped)
expect(output).to eq(escaped)
end
-
- it 'does not double-escape' do
- escaped = ERB::Util.html_escape("Merge branch 'blabla' into 'master'")
- expect(filter(escaped)).to eq(escaped)
- end
end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index f668f78c2b8..2a0e19ae796 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -95,6 +95,14 @@ module Gitlab
expect(render(input, context)).to include('<p><code data-math-style="inline" class="code math js-render-math">2+2</code> is 4</p>')
end
end
+
+ context 'outfilesuffix' do
+ it 'defaults to adoc' do
+ output = render("Inter-document reference <<README.adoc#>>", context)
+
+ expect(output).to include("a href=\"README.adoc\"")
+ end
+ end
end
def render(*args)
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 4a43dbb2371..f02b1cf55fb 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -53,6 +53,15 @@ describe Gitlab::Profiler do
described_class.profile('/', user: user)
end
+ context 'when providing a user without a personal access token' do
+ it 'raises an error' do
+ user = double(:user)
+ allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck).and_return([])
+
+ expect { described_class.profile('/', user: user) }.to raise_error('Your user must have a personal_access_token')
+ end
+ end
+
it 'uses the private_token for auth if both it and user are set' do
user = double(:user)
user_token = 'user'
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
new file mode 100644
index 00000000000..eabf9095c13
--- /dev/null
+++ b/spec/requests/api/project_import_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+describe API::ProjectImport do
+ let(:export_path) { "#{Dir.tmpdir}/project_export_spec" }
+ let(:user) { create(:user) }
+ let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
+ let(:namespace) { create(:group) }
+ before do
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+
+ namespace.add_owner(user)
+ end
+
+ after do
+ FileUtils.rm_rf(export_path, secure: true)
+ end
+
+ describe 'POST /projects/import' do
+ it 'schedules an import using a namespace' do
+ stub_import(namespace)
+
+ post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.id
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'schedules an import using the namespace path' do
+ stub_import(namespace)
+
+ post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.full_path
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'schedules an import at the user namespace level' do
+ stub_import(user.namespace)
+
+ post api('/projects/import', user), path: 'test-import2', file: fixture_file_upload(file)
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'schedules an import at the user namespace level' do
+ expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect(Gitlab::ImportExport::ProjectCreator).not_to receive(:new)
+
+ post api('/projects/import', user), namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Namespace Not Found')
+ end
+
+ it 'does not schedule an import if the user has no permission to the namespace' do
+ expect_any_instance_of(Project).not_to receive(:import_schedule)
+
+ post(api('/projects/import', create(:user)),
+ path: 'test-import3',
+ file: fixture_file_upload(file),
+ namespace: namespace.full_path)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Namespace Not Found')
+ end
+
+ it 'does not schedule an import if the user uploads no valid file' do
+ expect_any_instance_of(Project).not_to receive(:import_schedule)
+
+ post api('/projects/import', user), path: 'test-import3', file: './random/test'
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('file is invalid')
+ end
+
+ def stub_import(namespace)
+ expect_any_instance_of(Project).to receive(:import_schedule)
+ expect(Gitlab::ImportExport::ProjectCreator).to receive(:new).with(namespace.id, any_args).and_call_original
+ end
+ end
+
+ describe 'GET /projects/:id/import' do
+ it 'returns the import status' do
+ project = create(:project, import_status: 'started')
+ project.add_master(user)
+
+ get api("/projects/#{project.id}/import", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include('import_status' => 'started')
+ end
+
+ it 'returns the import status and the error if failed' do
+ project = create(:project, import_status: 'failed', import_error: 'error')
+ project.add_master(user)
+
+ get api("/projects/#{project.id}/import", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include('import_status' => 'failed',
+ 'import_error' => 'error')
+ end
+ end
+end
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 094d6791505..0ac8885405e 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -321,7 +321,15 @@ production:
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable"
SAST_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run --volume "$PWD:/code" \
+ # Deprecation notice for CONFIDENCE_LEVEL variable
+ if [ -z "$SAST_CONFIDENCE_LEVEL" -a "$CONFIDENCE_LEVEL" ]; then
+ SAST_CONFIDENCE_LEVEL="$CONFIDENCE_LEVEL"
+ echo "WARNING: CONFIDENCE_LEVEL is deprecated and MUST be replaced with SAST_CONFIDENCE_LEVEL"
+ fi
+
+ docker run --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}" \
+ --env SAST_DISABLE_REMOTE_CHECKS="${SAST_DISABLE_REMOTE_CHECKS:-false}" \
+ --volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
"registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
;;