Commit 5833ba11 authored by Filipa Lacerda's avatar Filipa Lacerda

[ci skip] Merge branch 'master' into 5105_split_dependency_scanning_from_sast

* master: (76 commits)
  Add step by step guides for GitLab CI/CD for GitHub and Bitbucket
  Update Epic documentation to include labels
  Resolve "Loss of input text on comments after preview"
  Update HA Consul troubleshooting doc-docs
  Add CHANGELOG entry
  Prevent auto-retry AccessDenied error from stopping transition to failed
  Make `ci_cd_projects` globally available for .com
  Update merge request maintainer access docs
  Execute FDW SystemChecks only in Geo Secondary node
  Fix broken if
  Enhance the planned failover docs
  Consistent naming: selective replication -> selective sync
  Clarify Geo object storage interaction
  Clarify Geo limitations
  Breaks security report issues into individual components
  Fix end-to-end specs for rebasing a merge request
  Fix undefined method `log_transfer_error'
  Resolve the conflict in services_helper.rb
  Update cloud native charts docs
  move render_gfm into behaviors directory
  ...
parents 0c9fee8d 586c6506
......@@ -116,7 +116,7 @@ gem 'fog-rackspace', '~> 0.1.1'
gem 'fog-aliyun', '~> 0.2.0'
# for Google storage
gem 'google-api-client', '~> 0.19'
gem 'google-api-client', '~> 0.19.8'
# for aws storage
gem 'unf', '~> 0.1.4'
......@@ -288,7 +288,6 @@ gem 'batch-loader', '~> 1.2.1'
# Perf bar
gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2'
gem 'peek-host', '~> 1.0.0'
gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-performance_bar', '~> 1.3.0'
gem 'peek-pg', '~> 1.3.0', group: :postgres
......
......@@ -623,8 +623,6 @@ GEM
railties (>= 4.0.0)
peek-gc (0.0.2)
peek
peek-host (1.0.0)
peek
peek-mysql2 (1.1.0)
atomic (>= 1.0.0)
mysql2
......@@ -688,7 +686,7 @@ GEM
httpclient (>= 2.4)
multi_json (>= 1.3.6)
rack (>= 1.1)
rack-protection (1.5.3)
rack-protection (2.0.1)
rack
rack-proxy (0.6.0)
rack
......@@ -1103,7 +1101,7 @@ DEPENDENCIES
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
google-api-client (~> 0.19)
google-api-client (~> 0.19.8)
google-protobuf (= 3.5.1)
gpgme
grape (~> 1.0)
......@@ -1162,7 +1160,6 @@ DEPENDENCIES
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
peek-host (~> 1.0.0)
peek-mysql2 (~> 1.1.0)
peek-performance_bar (~> 1.3.0)
peek-pg (~> 1.3.0)
......
import './autosize';
import './bind_in_out';
import initCopyAsGFM from './copy_as_gfm';
import './markdown/render_gfm';
import initCopyAsGFM from './markdown/copy_as_gfm';
import initCopyToClipboard from './copy_to_clipboard';
import './details_behavior';
import installGlEmojiElement from './gl_emoji';
......
......@@ -2,8 +2,8 @@
import $ from 'jquery';
import _ from 'underscore';
import { insertText, getSelectedFragment, nodeMatchesSelector } from '../lib/utils/common_utils';
import { placeholderImage } from '../lazy_loader';
import { insertText, getSelectedFragment, nodeMatchesSelector } from '~/lib/utils/common_utils';
import { placeholderImage } from '~/lazy_loader';
const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
......
import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight';
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
import syntaxHighlight from './syntax_highlight';
// Render Gitlab flavoured Markdown
//
......
import $ from 'jquery';
import { __ } from './locale';
import flash from './flash';
import { __ } from '~/locale';
import flash from '~/flash';
// Renders math using KaTeX in any element with the
// `js-render-math` class
......
import flash from '~/flash';
// Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class.
//
......@@ -12,8 +14,6 @@
// </pre>
//
import Flash from './flash';
export default function renderMermaid($els) {
if (!$els.length) return;
......@@ -52,6 +52,6 @@ export default function renderMermaid($els) {
});
});
}).catch((err) => {
Flash(`Can't load mermaid module: ${err}`);
flash(`Can't load mermaid module: ${err}`);
});
}
......@@ -53,8 +53,12 @@ function initPageShortcuts(page) {
function initGFMInput() {
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
const gfm = new GfmAutoComplete(
gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources,
);
const enableGFM = convertPermissionToBoolean(
el.dataset.supportsAutocomplete,
);
gfm.setup($(el), {
emojis: true,
members: enableGFM,
......@@ -67,9 +71,9 @@ function initGFMInput() {
}
function initPerformanceBar() {
if (document.querySelector('#peek')) {
if (document.querySelector('#js-peek')) {
import('./performance_bar')
.then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
.then(m => new m.default({ container: '#js-peek' })) // eslint-disable-line new-cap
.catch(() => Flash('Error loading performance bar module'));
}
}
......
......@@ -2,7 +2,7 @@ import $ from 'jquery';
import autosize from 'autosize';
import GfmAutoComplete from './gfm_auto_complete';
import dropzoneInput from './dropzone_input';
import textUtils from './lib/utils/text_markdown';
import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
export default class GLForm {
constructor(form, enableGFM = false) {
......@@ -47,7 +47,7 @@ export default class GLForm {
}
// form and textarea event listeners
this.addEventListeners();
textUtils.init(this.form);
addMarkdownListeners(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
this.form.show();
......@@ -86,7 +86,7 @@ export default class GLForm {
clearEventListeners() {
this.textarea.off('focus');
this.textarea.off('blur');
textUtils.removeListeners(this.form);
removeMarkdownListeners(this.form);
}
addEventListeners() {
......
/* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
import $ from 'jquery';
import { insertText } from '~/lib/utils/common_utils';
const textUtils = {};
textUtils.selectedText = function(text, textarea) {
function selectedText(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd);
};
}
textUtils.lineBefore = function(text, textarea) {
function lineBefore(text, textarea) {
var split;
split = text.substring(0, textarea.selectionStart).trim().split('\n');
return split[split.length - 1];
};
}
textUtils.lineAfter = function(text, textarea) {
function lineAfter(text, textarea) {
return text.substring(textarea.selectionEnd).trim().split('\n')[0];
};
}
textUtils.blockTagText = function(text, textArea, blockTag, selected) {
var lineAfter, lineBefore;
lineBefore = this.lineBefore(text, textArea);
lineAfter = this.lineAfter(text, textArea);
if (lineBefore === blockTag && lineAfter === blockTag) {
function blockTagText(text, textArea, blockTag, selected) {
const before = lineBefore(text, textArea);
const after = lineAfter(text, textArea);
if (before === blockTag && after === blockTag) {
// To remove the block tag we have to select the line before & after
if (blockTag != null) {
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
......@@ -32,10 +29,30 @@ textUtils.blockTagText = function(text, textArea, blockTag, selected) {
} else {
return blockTag + "\n" + selected + "\n" + blockTag;
}
};
}
textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
function moveCursor(textArea, tag, wrapped, removedLastNewLine) {
var pos;
if (!textArea.setSelectionRange) {
return;
}
if (textArea.selectionStart === textArea.selectionEnd) {
if (wrapped) {
pos = textArea.selectionStart - tag.length;
} else {
pos = textArea.selectionStart;
}
if (removedLastNewLine) {
pos -= 1;
}
return textArea.setSelectionRange(pos, pos);
}
}
export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap) {
var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
removedLastNewLine = false;
removedFirstNewLine = false;
currentLineEmpty = false;
......@@ -67,9 +84,9 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
if (blockTag != null && blockTag !== '') {
insertText = this.blockTagText(text, textArea, blockTag, selected);
textToInsert = blockTagText(text, textArea, blockTag, selected);
} else {
insertText = selectedSplit.map(function(val) {
textToInsert = selectedSplit.map(function(val) {
if (val.indexOf(tag) === 0) {
return "" + (val.replace(tag, ''));
} else {
......@@ -78,78 +95,42 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
}).join('\n');
}
} else {
insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' ');
}
if (removedFirstNewLine) {
insertText = '\n' + insertText;
textToInsert = '\n' + textToInsert;
}
if (removedLastNewLine) {
insertText += '\n';
textToInsert += '\n';
}
if (document.queryCommandSupported('insertText')) {
inserted = document.execCommand('insertText', false, insertText);
}
if (!inserted) {
try {
document.execCommand("ms-beginUndoUnit");
} catch (error) {}
textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
try {
document.execCommand("ms-endUndoUnit");
} catch (error) {}
}
return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
};
insertText(textArea, textToInsert);
return moveCursor(textArea, tag, wrap, removedLastNewLine);
}
textUtils.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
var pos;
if (!textArea.setSelectionRange) {
return;
}
if (textArea.selectionStart === textArea.selectionEnd) {
if (wrapped) {
pos = textArea.selectionStart - tag.length;
} else {
pos = textArea.selectionStart;
}
if (removedLastNewLine) {
pos -= 1;
}
return textArea.setSelectionRange(pos, pos);
}
};
textUtils.updateText = function(textArea, tag, blockTag, wrap) {
function updateText(textArea, tag, blockTag, wrap) {
var $textArea, selected, text;
$textArea = $(textArea);
textArea = $textArea.get(0);
text = $textArea.val();
selected = this.selectedText(text, textArea);
selected = selectedText(text, textArea);
$textArea.focus();
return this.insertText(textArea, text, tag, blockTag, selected, wrap);
};
return insertMarkdownText(textArea, text, tag, blockTag, selected, wrap);
}
textUtils.init = function(form) {
var self;
self = this;
function replaceRange(s, start, end, substitute) {
return s.substring(0, start) + substitute + s.substring(end);
}
export function addMarkdownListeners(form) {
return $('.js-md', form).off('click').on('click', function() {
var $this;
$this = $(this);
return self.updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
const $this = $(this);
return updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
});
};
}
textUtils.removeListeners = function(form) {
export function removeMarkdownListeners(form) {
return $('.js-md', form).off('click');
};
textUtils.replaceRange = function(s, start, end, substitute) {
return s.substring(0, start) + substitute + s.substring(end);
};
export default textUtils;
}
......@@ -32,7 +32,6 @@ import LazyLoader from './lazy_loader';
import initLogoAnimation from './logo';
import './milestone_select';
import './projects_dropdown';
import './render_gfm';
import initBreadcrumbs from './breadcrumb';
// EE-only scripts
......
......@@ -73,6 +73,10 @@
type: String,
required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
......@@ -188,6 +192,7 @@
:clusters-path="clustersPath"
:empty-getting-started-svg-path="emptyGettingStartedSvgPath"
:empty-loading-svg-path="emptyLoadingSvgPath"
:empty-no-data-svg-path="emptyNoDataSvgPath"
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
/>
</template>
......@@ -27,6 +27,10 @@
type: String,
required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
......@@ -54,7 +58,7 @@
buttonPath: this.documentationPath,
},
noData: {
svgUrl: this.emptyUnableToConnectSvgPath,
svgUrl: this.emptyNoDataSvgPath,
title: 'No data found',
description: `You are connected to the Prometheus server, but there is currently
no data to display.`,
......
import $ from 'jquery';
import 'vendor/peek';
import 'vendor/peek.performance_bar';
import { getParameterValues } from './lib/utils/url_utility';
export default class PerformanceBar {
constructor(opts) {
if (!PerformanceBar.singleton) {
this.init(opts);
PerformanceBar.singleton = this;
}
return PerformanceBar.singleton;
}
init(opts) {
const $container = $(opts.container);
this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile');
this.$lineProfileModal = $('#modal-peek-line-profile');
this.initEventListeners();
this.showModalOnLoad();
}
initEventListeners() {
this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e));
$(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile);
}
showModalOnLoad() {
// When a lineprofiler query-string param is present, we show the line
// profiler modal upon page load
if (/lineprofiler/.test(window.location.search)) {
PerformanceBar.toggleModal(this.$lineProfileModal);
}
}
handleLineProfileLink(e) {
const lineProfilerParameter = getParameterValues('lineprofiler');
const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
const shouldToggleModal = lineProfilerParameter.length > 0 &&
lineProfilerParameterRegex.test(e.currentTarget.href);
if (shouldToggleModal) {
e.preventDefault();
PerformanceBar.toggleModal(this.$lineProfileModal);
}
}
static toggleModal($modal) {
if ($modal.length) {
$modal.modal('toggle');
}
}
static toggleLineProfileFile(e) {
$(e.currentTarget).parents('.peek-rblineprof-file').find('.data').toggle();
}
}
<script>
import GlModal from '~/vue_shared/components/gl_modal.vue';
export default {
components: {
GlModal,
},
props: {
currentRequest: {
type: Object,
required: true,
},
metric: {
type: String,
required: true,
},
header: {
type: String,
required: true,
},
details: {
type: String,
required: true,
},
keys: {
type: Array,
required: true,
},
},
};
</script>
<template>
<div
:id="`peek-view-${metric}`"
class="view"
>
<button
:data-target="`#modal-peek-${metric}-details`"
class="btn-blank btn-link bold"
type="button"
data-toggle="modal"
>
<span
v-if="currentRequest.details"
class="bold"
>
{{ currentRequest.details[metric].duration }}
/
{{ currentRequest.details[metric].calls }}
</span>
</button>
<gl-modal
v-if="currentRequest.details"
:id="`modal-peek-${metric}-details`"
:header-title-text="header"
class="performance-bar-modal"
>
<table class="table">
<tr
v-for="(item, index) in currentRequest.details[metric][details]"
:key="index"
>
<td><strong>{{ item.duration }}ms</strong></td>
<td
v-for="key in keys"
:key="key"
>
{{ item[key] }}
</td>
</tr>
</table>
<div slot="footer">
</div>
</gl-modal>
{{ metric }}
</div>
</template>
<script>
import $ from 'jquery';
import PerformanceBarService from '../services/performance_bar_service';
import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue';
import simpleMetric from './simple_metric.vue';
import upstreamPerformanceBar from './upstream_performance_bar.vue';
import Flash from '../../flash';
export default {
components: {
detailedMetric,
requestSelector,
simpleMetric,
upstreamPerformanceBar,
},
props: {
store: {
type: Object,
required: true,
},
env: {
type: String,
required: true,
},
requestId: {
type: String,
required: true,
},
peekUrl: {
type: String,
required: true,
},
profileUrl: {
type: String,
required: true,
},
},
detailedMetrics: [
{ metric: 'pg', header: 'SQL queries', details: 'queries', keys: ['sql'] },
{
metric: 'gitaly',
header: 'Gitaly calls',
details: 'details',
keys: ['feature', 'request'],
},
],
simpleMetrics: ['redis', 'sidekiq'],
data() {
return { currentRequestId: '' };
},
computed: {
requests() {
return this.store.requestsWithDetails();
},
currentRequest: {
get() {
return this.store.findRequest(this.currentRequestId);
},
set(requestId) {
this.currentRequestId = requestId;
},
},
initialRequest() {
return this.currentRequestId === this.requestId;
},
lineProfileModal() {
return $('#modal-peek-line-profile');
},
},
mounted() {
this.interceptor = PerformanceBarService.registerInterceptor(
this.peekUrl,
this.loadRequestDetails,
);
this.loadRequestDetails(this.requestId, window.location.href);
this.currentRequest = this.requestId;
if (this.lineProfileModal.length) {
this.lineProfileModal.modal('toggle');
}
},
beforeDestroy() {
PerformanceBarService.removeInterceptor(this.interceptor);
},
methods: {
loadRequestDetails(requestId, requestUrl) {
if (!this.store.canTrackRequest(requestUrl)) {
return;
}
this.store.addRequest(requestId, requestUrl);
PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId)
.then(res => {
this.store.addRequestDetails(requestId, res.data.data);
})
.catch(() =>
Flash(`Error getting performance bar results for ${requestId}`),
);
},
changeCurrentRequest(newRequestId) {
this.currentRequest = newRequestId;
},
},
};
</script>
<template>
<div
id="js-peek"
:class="env"
>
<request-selector
v-if="currentRequest"
:current-request="currentRequest"
:requests="requests"
@change-current-request="changeCurrentRequest"
/>
<div
id="peek-view-host"
class="view prepend-left-5"
>
<span
v-if="currentRequest && currentRequest.details"
class="current-host"
>
{{ currentRequest.details.host.hostname }}
</span>
</div>
<div
v-if="currentRequest"
class="wrapper"
>
<upstream-performance-bar
v-if="initialRequest && currentRequest.details"
/>
<detailed-metric
v-for="metric in $options.detailedMetrics"
:key="metric.metric"
:current-request="currentRequest"
:metric="metric.metric"
:header="metric.header"
:details="metric.details"
:keys="metric.keys"
/>
<div
v-if="initialRequest"
id="peek-view-rblineprof"
class="view"
>
<button
v-if="lineProfileModal.length"
class="btn-link btn-blank"
data-toggle="modal"
data-target="#modal-peek-line-profile"
>
profile
</button>
<a
v-else
:href="profileUrl"
>
profile
</a>
</div>
<simple-metric
v-for="metric in $options.simpleMetrics"
:current-request="currentRequest"
:key="metric"
:metric="metric"
/>
<div
id="peek-view-gc"
class="view"
>
<span
v-if="currentRequest.details"
class="bold"
>
<span title="Invoke Time">{{ currentRequest.details.gc.gc_time }}</span>ms
/
<span title="Invoke Count">{{ currentRequest.details.gc.invokes }}</span>
gc
</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
currentRequest: {
type: Object,
required: true,
},
requests: {
type: Array,
required: true,
},
},
data() {
return {
currentRequestId: this.currentRequest.id,
};
},
watch: {
currentRequestId(newRequestId) {
this.$emit('change-current-request', newRequestId);
},
},
methods: {
truncatedUrl(requestUrl) {
const components = requestUrl.replace(/\/$/, '').split('/');
let truncated = components[components.length - 1];
if (truncated.match(/^\d+$/)) {
truncated = `${components[components.length - 2]}/${truncated}`;
}
return truncated;
},
},
};
</script>
<template>
<div
id="peek-request-selector"
class="append-right-5 pull-right"
>
<select v-model="currentRequestId">
<option
v-for="request in requests"
:key="request.id"
:value="request.id"
>
{{ truncatedUrl(request.url) }}
</option>
</select>
</div>
</template>
<script>
export default {
props: {
currentRequest: {
type: Object,
required: true,
},
metric: {
type: String,
required: true,
},
},
};
</script>
<template>
<div
:id="`peek-view-${metric}`"
class="view"
>
<span
v-if="currentRequest.details"
class="bold"
>
{{ currentRequest.details[metric].duration }}
/
{{ currentRequest.details[metric].calls }}
</span>
{{ metric }}
</div>
</template>
<script>
export default {
mounted() {
const upstreamPerformanceBar = document
.getElementById('peek-view-performance-bar')
.cloneNode(true);
this.$refs.wrapper.appendChild(upstreamPerformanceBar);
},
};
</script>
<template>
<div
id="peek-view-performance-bar-vue"
class="view"
ref="wrapper"
></div>
</template>
import 'vendor/peek.performance_bar';
import Vue from 'vue';
import performanceBarApp from './components/performance_bar_app.vue';
import PerformanceBarStore from './stores/performance_bar_store';
export default () =>
new Vue({
el: '#js-peek',
components: {
performanceBarApp,
},
data() {
const performanceBarData = document.querySelector(this.$options.el)
.dataset;
const store = new PerformanceBarStore();
return {
store,
env: performanceBarData.env,
requestId: performanceBarData.requestId,
peekUrl: performanceBarData.peekUrl,
profileUrl: performanceBarData.profileUrl,
};
},
render(createElement) {
return createElement('performance-bar-app', {
props: {
store: this.store,
env: this.env,
requestId: this.requestId,
peekUrl: this.peekUrl,
profileUrl: this.profileUrl,
},
});
},
});
import axios from '../../lib/utils/axios_utils';
export default class PerformanceBarService {
static fetchRequestDetails(peekUrl, requestId) {
return axios.get(peekUrl, { params: { request_id: requestId } });
}
static registerInterceptor(peekUrl, callback) {
return axios.interceptors.response.use(response => {
const requestId = response.headers['x-request-id'];
const requestUrl = response.config.url;
if (requestUrl !== peekUrl && requestId) {
callback(requestId, requestUrl);
}
return response;
});
}
static removeInterceptor(interceptor) {
axios.interceptors.response.eject(interceptor);
}
}
export default class PerformanceBarStore {
constructor() {
this.requests = [];
}
addRequest(requestId, requestUrl, requestDetails) {
if (!this.findRequest(requestId)) {
this.requests.push({
id: requestId,
url: requestUrl,
details: requestDetails,
});
}
return this.requests;
}
findRequest(requestId) {
return this.requests.find(request => request.id === requestId);
}
addRequestDetails(requestId, requestDetails) {
const request = this.findRequest(requestId);
request.details = requestDetails;
return request;
}
requestsWithDetails() {
return this.requests.filter(request => request.details);
}
canTrackRequest(requestUrl) {
return (
this.requests.filter(request => request.url === requestUrl).length < 2
);
}
}
......@@ -3,7 +3,7 @@ import Mousetrap from 'mousetrap';
import _ from 'underscore';
import Sidebar from './right_sidebar';
import Shortcuts from './shortcuts';
import { CopyAsGFM } from './behaviors/copy_as_gfm';
import { CopyAsGFM } from './behaviors/markdown/copy_as_gfm';
export default class ShortcutsIssuable extends Shortcuts {
constructor(isMergeRequest) {
......
.navbar-gitlab {
&.navbar-gitlab {
padding: 0 16px;
z-index: 1000;
margin-bottom: 0;
min-height: $header-height;
border: 0;
border-bottom: 1px solid $border-color;
position: fixed;
top: 0;
left: 0;
right: 0;
border-radius: 0;
.logo-text {
line-height: initial;
svg {
width: 55px;
height: 14px;
margin: 0;
fill: $white-light;
}
}
.container-fluid {
padding: 0;
.user-counter {
svg {
margin-right: 3px;
}
}
.navbar-toggle {
right: -10px;
border-radius: 0;
min-width: 45px;
padding: 0;
margin-right: -7px;
font-size: 14px;
text-align: center;
color: currentColor;
&:hover,
&:focus,
&.active {
color: currentColor;
background-color: transparent;
}
.more-icon,
.close-icon {
fill: $white-light;
margin: auto;
}
}
padding: 0 16px;
z-index: 1000;
margin-bottom: 0;
min-height: $header-height;
border: 0;
border-bottom: 1px solid $border-color;
position: fixed;
top: 0;
left: 0;
right: 0;
border-radius: 0;
.logo-text {
line-height: initial;
svg {
width: 55px;
height: 14px;
margin: 0;
fill: $white-light;
}
}
......@@ -184,6 +148,38 @@
}
.container-fluid {
padding: 0;
.user-counter {
svg {
margin-right: 3px;
}
}
.navbar-toggle {
right: -10px;
border-radius: 0;
min-width: 45px;
padding: 0;
margin-right: -7px;
font-size: 14px;
text-align: center;
color: currentColor;
&:hover,
&:focus,
&.active {
color: currentColor;
background-color: transparent;
}
.more-icon,
.close-icon {
fill: $white-light;
margin: auto;
}
}
.navbar-nav {
@media (max-width: $screen-xs-max) {
display: -webkit-flex;
......
......@@ -140,12 +140,6 @@ ul.notes {
@include bulleted-list;
word-wrap: break-word;
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
}
}
table {
@include markdown-table;
}
......
......@@ -68,7 +68,6 @@
.ide-new-btn {
display: none;
margin-top: -4px;
margin-bottom: -4px;
margin-right: -8px;
}
......@@ -84,7 +83,6 @@
fill: $gl-text-color-secondary;
}
}
}
a {
......@@ -290,7 +288,7 @@
.margin-view-overlays .insert-sign,
.margin-view-overlays .delete-sign {
opacity: .4;
opacity: 0.4;
}
}
}
......@@ -548,7 +546,6 @@
height: 10px;
margin-left: 3px;
}
}
.multi-file-commit-list-path {
......@@ -626,7 +623,7 @@
top: 0;
width: 100px;
height: 1px;
background-color: rgba($red-500, .5);
background-color: rgba($red-500, 0.5);
}
}
}
......@@ -720,12 +717,13 @@
}
.ide-view {
height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height});
height: calc(
100vh - #{$header-height + $performance-bar-height + $flash-height}
);
}
}
}
.dragHandle {
position: absolute;
top: 0;
......
@import "framework/variables";
@import "peek/views/performance_bar";
@import "peek/views/rblineprof";
@import 'framework/variables';
@import 'peek/views/performance_bar';
@import 'peek/views/rblineprof';
#peek {
#js-peek {
position: fixed;
left: 0;
top: 0;
......@@ -21,14 +21,26 @@
&.production {
background-color: $perf-bar-production;
select {
background: $perf-bar-production;
}
}
&.staging {
background-color: $perf-bar-staging;
select {
background: $perf-bar-staging;
}
}
&.development {
background-color: $perf-bar-development;
select {
background: $perf-bar-development;
}
}
.wrapper {
......@@ -42,11 +54,12 @@
background: $perf-bar-bucket-bg;
display: inline-block;
padding: 4px 6px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
font-family: Consolas, 'Liberation Mono', Courier, monospace;
line-height: 1;
color: $perf-bar-bucket-color;
border-radius: 3px;
box-shadow: 0 1px 0 $perf-bar-bucket-box-shadow-from, inset 0 1px 2px $perf-bar-bucket-box-shadow-to;
box-shadow: 0 1px 0 $perf-bar-bucket-box-shadow-from,
inset 0 1px 2px $perf-bar-bucket-box-shadow-to;
.hidden {
display: none;
......@@ -94,6 +107,10 @@
max-width: 10000px !important;
}
}
.performance-bar-modal .modal-footer {
display: none;
}
}
#modal-peek-pg-queries-content {
......
......@@ -16,6 +16,7 @@ class Admin::ProjectsFinder
items = by_archived(items)
items = by_personal(items)
items = by_name(items)
items = items.includes(namespace: [:owner])
sort(items).page(params[:page])
end
......
......@@ -304,7 +304,7 @@ module ApplicationHelper
def linkedin_url(user)
name = user.linkedin
if name =~ %r{\Ahttps?:\/\/(www\.)?linkedin\.com\/in\/}
if name =~ %r{\Ahttps?://(www\.)?linkedin\.com/in/}
name
else
"https://www.linkedin.com/in/#{name}"
......@@ -313,10 +313,10 @@ module ApplicationHelper
def twitter_url(user)
name = user.twitter
if name =~ %r{\Ahttps?:\/\/(www\.)?twitter\.com\/}
if name =~ %r{\Ahttps?://(www\.)?twitter\.com/}
name
else
"https://www.twitter.com/#{name}"
"https://twitter.com/#{name}"
end
end
......
module ServicesHelper
prepend EE::ServicesHelper
def service_event_description(event)
case event
when "push", "push_events"
"Event will be triggered by a push to the repository"
when "tag_push", "tag_push_events"
"Event will be triggered when a new tag is pushed to the repository"
when "note", "note_events"
"Event will be triggered when someone adds a comment"
when "issue", "issue_events"
"Event will be triggered when an issue is created/updated/closed"
when "confidential_issue", "confidential_issue_events"
"Event will be triggered when a confidential issue is created/updated/closed"
when "merge_request", "merge_request_events"
"Event will be triggered when a merge request is created/updated/merged"
when "pipeline", "pipeline_events"
"Event will be triggered when a pipeline status changes"
when "wiki_page", "wiki_page_events"
"Event will be triggered when a wiki page is created/updated"
when "commit", "commit_events"
"Event will be triggered when a commit is created/updated"
end
end
def service_event_field_name(event)
event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
"#{event}_events"
......
......@@ -143,7 +143,11 @@ module Ci
next if build.retries_max.zero?
if build.retries_count < build.retries_max
Ci::Build.retry(build, build.user)
begin
Ci::Build.retry(build, build.user)
rescue Gitlab::Access::AccessDeniedError => ex
Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}"
end
end
end
......@@ -331,8 +335,7 @@ module Ci
end
def erase_old_trace!
write_attribute(:trace, nil)
save
update_column(:trace, nil)
end
def needs_touch?
......
......@@ -35,7 +35,8 @@ class NotificationRecipient
# check this last because it's expensive
# nobody should receive notifications if they've specifically unsubscribed
return false if unsubscribed?
# except if they were mentioned.
return false if @type != :mention && unsubscribed?
true
end
......
......@@ -14,9 +14,8 @@ class JiraService < IssueTrackerService
alias_method :project_url, :url
# This is confusing, but JiraService does not really support these events.
# The values here are required to display correct options in the service
# configuration screen.
# When these are false GitLab does not create cross reference
# comments on JIRA except when an issue gets transitioned.
def self.supported_events
%w(commit merge_request)
end
......@@ -318,4 +317,13 @@ class JiraService < IssueTrackerService
url_changed?
end
def self.event_description(event)
case event
when "merge_request", "merge_request_events"
"JIRA comments will be created when an issue gets referenced in a merge request."
when "commit", "commit_events"
"JIRA comments will be created when an issue gets referenced in a commit."
end
end
end
......@@ -309,6 +309,29 @@ class Service < ActiveRecord::Base
end
end
def self.event_description(event)
case event
when "push", "push_events"
"Event will be triggered by a push to the repository"
when "tag_push", "tag_push_events"
"Event will be triggered when a new tag is pushed to the repository"
when "note", "note_events"
"Event will be triggered when someone adds a comment"
when "issue", "issue_events"
"Event will be triggered when an issue is created/updated/closed"
when "confidential_issue", "confidential_issue_events"
"Event will be triggered when a confidential issue is created/updated/closed"
when "merge_request", "merge_request_events"
"Event will be triggered when a merge request is created/updated/merged"
when "pipeline", "pipeline_events"
"Event will be triggered when a pipeline status changes"
when "wiki_page", "wiki_page_events"
"Event will be triggered when a wiki page is created/updated"
when "commit", "commit_events"
"Event will be triggered when a commit is created/updated"
end
end
def valid_recipients?
activated? && !importing?
end
......
- return unless peek_enabled?
#js-peek{ data: { env: Peek.env,
request_id: Peek.request_id,
peek_url: peek_routes.results_url,
profile_url: url_for(params.merge(lineprofiler: 'true')) },
class: Peek.env }
#peek-view-performance-bar
= render_server_response_time
%span#serverstats
%ul.performance-bar
- local_assigns.fetch(:view)
%button.btn-blank.btn-link.bold{ type: 'button', data: { toggle: 'modal', target: '#modal-peek-gitaly-details' } }
%span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }...
#modal-peek-gitaly-details.modal{ tabindex: -1, role: 'dialog' }
.modal-dialog.modal-full
.modal-content
.modal-header
%button.close{ type: 'button', data: { dismiss: 'modal' }, 'aria-label' => 'Close' }
%span{ 'aria-hidden' => 'true' }
&times;
%h4
Gitaly requests
.modal-body{ data: { defer_to: "#{view.defer_key}-details" } }...
gitaly
%span.current-host
= truncate(view.hostname)
- local_assigns.fetch(:view)
= render 'peek/views/sql', view: view
mysql
- local_assigns.fetch(:view)
= render 'peek/views/sql', view: view
pg
Profile:
= link_to 'all', url_for(lineprofiler: 'true'), class: 'js-toggle-modal-peek-line-profile'
\/
= link_to 'app & lib', url_for(lineprofiler: 'app'), class: 'js-toggle-modal-peek-line-profile'
\/
= link_to 'views', url_for(lineprofiler: 'views'), class: 'js-toggle-modal-peek-line-profile'
%button.btn-blank.btn-link.bold{ type: 'button', data: { toggle: 'modal', target: '#modal-peek-pg-queries' } }
%span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }...
#modal-peek-pg-queries.modal{ tabindex: -1 }
.modal-dialog.modal-full
.modal-content
.modal-header
%button.close{ type: 'button', data: { dismiss: 'modal' }, 'aria-label' => 'Close' }
%span{ 'aria-hidden' => 'true' }
&times;
%h4
SQL queries
.modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }...
......@@ -14,6 +14,7 @@
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
"empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
"empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
"empty-no-data-svg-path": image_path('illustrations/monitoring/no_data.svg'),
"empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect.svg'),
"metrics-endpoint": additional_metrics_project_environment_path(@project, @environment, format: :json),
"deployment-endpoint": project_environment_deployments_path(@project, @environment, format: :json),
......
......@@ -33,7 +33,7 @@
= form.text_field field[:name], class: "form-control", placeholder: field[:placeholder]
%p.light
= service_event_description(event)
= @service.class.event_description(event)
- @service.global_fields.each do |field|
- type = field[:type]
......
......@@ -62,7 +62,7 @@
= f.hidden_field :access_level
.member-form-control.dropdown.append-right-5
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
disabled: member.can_override?,
disabled: member.can_override? && !member.override?,
data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } }
%span.dropdown-toggle-text
= member.human_access
......@@ -82,7 +82,7 @@
can_override: member.can_override?
.prepend-left-5.clearable-input.member-form-control
= f.text_field :expires_at,
disabled: member.can_override?,
disabled: member.can_override? && !member.override?,
class: 'form-control js-access-expiration-date js-member-update-control',
placeholder: 'Expiration date',
id: "member_expires_at_#{member.id}",
......
---
title: Fix Firefox stealing formatting characters on issue notes
merge_request:
author:
type: fixed
---
title: Send @mention notifications even if a user has explicitly unsubscribed from
item
merge_request:
author:
type: added
---
title: Add documentation for runner IP address (#44232)
merge_request: 17837
author:
type: other
---
title: Fix search results stripping last endline when parsing the results
merge_request: 17777
author: Jasper Maes
type: fixed
---
title: Add documentation for displayed K8s Ingress IP address (#44330)
merge_request: 17836
author:
type: other
---
title: Clean up selectors in framework/header.scss
merge_request: 17822
author: Takuya Noguchi
type: other
---
title: Unify format for nested non-task lists
merge_request: 17823
author: Takuya Noguchi
type: fixed
---
title: Update rack-protection to 2.0.1
merge_request: 17835
author: Takuya Noguchi
type: security
---
title: Allow viewing timings for AJAX requests in the performance bar
merge_request:
author:
type: changed
---
title: Prevent auto-retry AccessDenied error from stopping transition to failed
merge_request: 17862
author:
type: fixed
---
title: Improve JIRA event descriptions
merge_request:
author:
type: other
---
title: Make /-/ delimiter optional for search endpoints
merge_request:
author:
type: changed
# rubocop:disable Lint/RescueException
# This patch fixes https://github.com/rails/rails/issues/26024
# TODO: Remove it when it's no longer necessary
module ActiveRecord
module Locking
module Optimistic
# We overwrite this method because we don't want to have default value
# for newly created records
def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
super
end
# Remove this entire initializer when we are at rails 5.0.
# This file fixes the bug (see below) which has been fixed in the upstream.
unless Gitlab.rails5?
# This patch fixes https://github.com/rails/rails/issues/26024
# TODO: Remove it when it's no longer necessary
module ActiveRecord
module Locking
module Optimistic
# We overwrite this method because we don't want to have default value
# for newly created records
def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
super
end
def _update_record(attribute_names = self.attribute_names) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
def _update_record(attribute_names = self.attribute_names) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
lock_col = self.class.locking_column
lock_col = self.class.locking_column
previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend
previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend
# This line is added as a patch
previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0
# This line is added as a patch
previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0
increment_lock
increment_lock
attribute_names += [lock_col]
attribute_names.uniq!
attribute_names += [lock_col]
attribute_names.uniq!
begin
relation = self.class.unscoped
begin
relation = self.class.unscoped
affected_rows = relation.where(
self.class.primary_key => id,
lock_col => previous_lock_value
).update_all(
attributes_for_update(attribute_names).map do |name|
[name, _read_attribute(name)]
end.to_h
)
affected_rows = relation.where(
self.class.primary_key => id,
lock_col => previous_lock_value
).update_all(
attributes_for_update(attribute_names).map do |name|
[name, _read_attribute(name)]
end.to_h
)
unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update")
end
unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update")
end
affected_rows
affected_rows
# If something went wrong, revert the version.
rescue Exception
send(lock_col + '=', previous_lock_value) # rubocop:disable GitlabSecurity/PublicSend
raise
# If something went wrong, revert the version.
rescue Exception
send(lock_col + '=', previous_lock_value) # rubocop:disable GitlabSecurity/PublicSend
raise
end
end
end
# This is patched because we need it to query `lock_version IS NULL`
# rather than `lock_version = 0` whenever lock_version is NULL.
def relation_for_destroy
return super unless locking_enabled?
# This is patched because we need it to query `lock_version IS NULL`
# rather than `lock_version = 0` whenever lock_version is NULL.
def relation_for_destroy
return super unless locking_enabled?
column_name = self.class.locking_column
super.where(self.class.arel_table[column_name].eq(self[column_name]))
column_name = self.class.locking_column
super.where(self.class.arel_table[column_name].eq(self[column_name]))
end
end
end
# This is patched because we want `lock_version` default to `NULL`
# rather than `0`
class LockingType < SimpleDelegator
def type_cast_from_database(value)
super
# This is patched because we want `lock_version` default to `NULL`
# rather than `0`
class LockingType < SimpleDelegator
def type_cast_from_database(value)
super
end
end
end
end
......
......@@ -59,7 +59,7 @@ Rails.application.routes.draw do
get 'readiness' => 'health#readiness'
post 'storage_check' => 'health#storage_check'
resources :metrics, only: [:index]
mount Peek::Railtie => '/peek'
mount Peek::Railtie => '/peek', as: 'peek_routes'
# Boards resources shared between group and projects
resources :boards, only: [] do
......
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180309160427) do
ActiveRecord::Schema.define(version: 20180314100728) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -184,6 +184,7 @@ ActiveRecord::Schema.define(version: 20180309160427) do
t.string "external_authorization_service_url"
t.string "external_authorization_service_default_label"
t.boolean "pages_domain_verification_enabled", default: true, null: false
t.float "external_authorization_service_timeout", default: 0.5, null: false
end
create_table "approvals", force: :cascade do |t|
......
......@@ -143,7 +143,7 @@ keys must be manually replicated to the secondary node.
'This is a primary node'.
1. Optionally, choose which namespaces should be replicated by the
secondary node. Leave blank to replicate all. Read more in
[selective replication](#selective-replication).
[selective synchronization](#selective-synchronization).
1. Click the **Add node** button.
1. SSH into your GitLab **secondary** server and restart the services:
......
......@@ -96,7 +96,7 @@ Read [Manually replicate primary SSH host keys][configuration-replicate-ssh]
'This is a primary node'.
1. Optionally, choose which namespaces should be replicated by the
secondary node. Leave blank to replicate all. Read more in
[selective replication](#selective-replication).
[selective synchronization](#selective-synchronization).
1. Click the **Add node** button.
1. SSH into your GitLab **secondary** server and restart the services:
......
......@@ -203,13 +203,38 @@ extra limitations may be in place.
- You cannot push code to secondary nodes, see [gitlab-org/gitlab-ee#3912] for details.
- The primary node has to be online for OAuth login to happen (existing sessions and Git are not affected)
- It works for repos, wikis, issues, merge requests, file attachments, artifacts and job logs but it does not work for,
GitLab Pages, and Docker images of the Container Registry (by default, but you can configure it separately,
see [replicate the Container Registry][docker-registry] for details).
- The installation takes multiple manual steps that together can take about an hour depending on circumstances; we are
working on improving this experience, see [gitlab-org/omnibus-gitlab#2978] for details.
- Real-time updates of issues/merge requests (e.g. via long polling) doesn't work on the secondary
- Broadcast messages set on the primary won't be seen on the secondary without a cache flush (e.g. gitlab-rake cache:clear)
- [Selective synchronization](configuration.md#selective-synchronization)
applies only to files and repositories. Other datasets are replicated to the
secondary in full, making it inappropriate for use as an access control
mechanism.
### Limitations on replication
Only the following items are replicated to the secondary. Any data not on this
list will not be available on the secondary, and failing over without manually
replicating it will cause the data to be **lost**:
- All database content (e.g., snippets, epics, issues, merge requests, groups, project metadata, etc)
- Project repositories
- Project wiki repositories
- User uploads (e.g. attachments to issues, merge requests and epics, avatars, etc)
- CI job artifacts and traces
### Examples of unreplicated data
Take special note that these GitLab features are both commonly used, and **not**
replicated by Geo at present. If you wish to use them on the secondary, or to
execute a failover successfully, you will need to replicate their data using
some other means.
- [Elasticsearch integration](../../../integration/elasticsearch.md)
- [Container Registry](../../container_registry.md) ([Object Storage][docker-registry] can mitigate this)
- [GitLab Pages](../../pages/index.md)
- [Mattermost integration](https://docs.gitlab.com/omnibus/gitlab-mattermost/)
## Frequently Asked Questions
......
......@@ -18,7 +18,12 @@ For LFS, follow the documentation to
For CI job artifacts, there is similar documentation to configure
[jobs artifact object storage](../../job_artifacts.md#using-object-storage)
Complete these steps on all nodes, primary **and** secondary.
For user uploads, there is similar documentation to configure [upload object storage](../../uploads.md#using-object-storage)
You should enable and configure object storage on both **primary** and **secondary**
nodes. Migrating existing data to object storage should be performed on the
**primary** node only; secondaries will automatically notice that the migrated
files are now in object storage.
## Replication
......
......@@ -60,6 +60,8 @@ To fix this:
```
1. Run `gitlab-ctl reconfigure`
If you still see the errors, you may have to [erase the consul database and reinitialize](#recreate-from-scratch) on the affected node.
### Consul agents do not start - Multiple private IPs
In the case that a node has multiple private IPs the agent be confused as to which of the private addresses to advertise, and then immediately exit on start.
......
......@@ -13,12 +13,16 @@ It allows you to see (from left to right):
![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png)
- time taken and number of [Gitaly] calls, click through for details of these calls
![Gitaly profiling using the Performance Bar](img/performance_bar_gitaly_calls.png)
- profile of the code used to generate the page, line by line for either _all_, _app & lib_ , or _views_. In the profile view, the numbers in the left panel represent wall time, cpu time, and number of calls (based on [rblineprof](https://github.com/tmm1/rblineprof)).
- profile of the code used to generate the page, line by line. In the profile view, the numbers in the left panel represent wall time, cpu time, and number of calls (based on [rblineprof](https://github.com/tmm1/rblineprof)).
![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
- time taken and number of calls to Redis
- time taken and number of background jobs created by Sidekiq
- time taken and number of Ruby GC calls
On the far right is a request selector that allows you to view the same metrics
(excluding the page timing and line profiler) for any requests made while the
page was open. Only the first two requests per unique URL are captured.
## Enable the Performance Bar via the Admin panel
GitLab Performance Bar is disabled by default. To enable it for a given group,
......
......@@ -10,7 +10,7 @@ Epics are available only in Ultimate. If epics feature is not available a `403`
Gets all issues that are assigned to an epic and the authenticated user has access to.
```
GET /groups/:id/-/epics/:epic_iid/issues
GET /groups/:id/epics/:epic_iid/issues
```
| Attribute | Type | Required | Description |
......@@ -19,7 +19,7 @@ GET /groups/:id/-/epics/:epic_iid/issues
| `epic_iid` | integer/string | yes | The internal ID of the epic. |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5/issues/
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics/5/issues/
```
Example response:
......@@ -106,7 +106,7 @@ Example response:
Creates an epic - issue association. If the issue in question belongs to another epic it is unassigned from that epic.
```
POST /groups/:id/-/epics/:epic_iid/issues/:issue_id
POST /groups/:id/epics/:epic_iid/issues/:issue_id
```
| Attribute | Type | Required | Description |
......@@ -116,7 +116,7 @@ POST /groups/:id/-/epics/:epic_iid/issues/:issue_id
| `issue_id` | integer/string | yes | The ID of the issue. |
```bash
curl --header POST "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5/issues/55
curl --header POST "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics/5/issues/55
```
Example response:
......@@ -212,7 +212,7 @@ Example response:
Removes an epic - issue association.
```
DELETE /groups/:id/-/epics/:epic_iid/issues/:epic_issue_id
DELETE /groups/:id/epics/:epic_iid/issues/:epic_issue_id
```
| Attribute | Type | Required | Description |
......@@ -222,7 +222,7 @@ DELETE /groups/:id/-/epics/:epic_iid/issues/:epic_issue_id
| `epic_issue_id` | integer/string | yes | The ID of the issue - epic association. |
```bash
curl --header DELETE "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5/issues/11
curl --header DELETE "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics/5/issues/11
```
Example response:
......@@ -318,7 +318,7 @@ Example response:
Updates an epic - issue association.
```
PUT /groups/:id/-/epics/:epic_iid/issues/:epic_issue_id
PUT /groups/:id/epics/:epic_iid/issues/:epic_issue_id
```
| Attribute | Type | Required | Description |
......@@ -330,7 +330,7 @@ PUT /groups/:id/-/epics/:epic_iid/issues/:epic_issue_id
| `move_after_id` | integer/string | no | The ID of the issue - epic association that should be placed after the link in the question. |
```bash
curl --header PUT "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5/issues/11?move_before_id=20
curl --header PUT "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics/5/issues/11?move_before_id=20
```
Example response:
......
......@@ -15,9 +15,9 @@ The [epic issues API](epic_issues.md) allows you to interact with issues associa
Gets all epics of the requested group and its subgroups.
```
GET /groups/:id/-/epics
GET /groups/:id/-/epics?author_id=5
GET /groups/:id/-/epics?labels=bug,reproduced
GET /groups/:id/epics
GET /groups/:id/epics?author_id=5
GET /groups/:id/epics?labels=bug,reproduced
```
| Attribute | Type | Required | Description |
......@@ -30,7 +30,7 @@ GET /groups/:id/-/epics?labels=bug,reproduced
| `search` | string | no | Search epics against their `title` and `description` |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics
```
Example response:
......@@ -65,7 +65,7 @@ Example response:
Gets a single epic
```
GET /groups/:id/-/epics/:epic_iid
GET /groups/:id/epics/:epic_iid
```
| Attribute | Type | Required | Description |
......@@ -74,7 +74,7 @@ GET /groups/:id/-/epics/:epic_iid
| `epic_iid` | integer/string | yes | The internal ID of the epic. |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics/5
```
Example response:
......@@ -106,7 +106,7 @@ Example response:
Creates a new epic
```
POST /groups/:id/-/epics
POST /groups/:id/epics
```
| Attribute | Type | Required | Description |
......@@ -119,7 +119,7 @@ POST /groups/:id/-/epics
| `end_date` | string. | no | The end date of the epic |
```bash
curl --header POST "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics?title=Epic&description=Epic%20description
curl --header POST "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics?title=Epic&description=Epic%20description
```
Example response:
......@@ -152,7 +152,7 @@ Example response:
Updates an epic
```
PUT /groups/:id/-/epics/:epic_iid
PUT /groups/:id/epics/:epic_iid
```
| Attribute | Type | Required | Description |
......@@ -166,7 +166,7 @@ PUT /groups/:id/-/epics/:epic_iid
| `end_date` | string. | no | The end date of an epic |
```bash
curl --header PUT "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5?title=New%20Title
curl --header PUT "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics/5?title=New%20Title
```
Example response:
......@@ -199,7 +199,7 @@ Example response:
Deletes an epic
```
DELETE /groups/:id/-/epics/:epic_iid
DELETE /groups/:id/epics/:epic_iid
```
| Attribute | Type | Required | Description |
......@@ -208,5 +208,5 @@ DELETE /groups/:id/-/epics/:epic_iid
| `epic_iid` | integer/string | yes | The internal ID of the epic. |
```bash
curl --header DELETE "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5?title=New%20Title
curl --header DELETE "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics/5?title=New%20Title
```
......@@ -374,7 +374,7 @@ Search within the specified group.
If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
```
GET /groups/:id/-/search
GET /groups/:id/search
```
| Attribute | Type | Required | Description |
......@@ -392,7 +392,7 @@ The response depends on the requested scope.
### Scope: projects
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=projects&search=flight
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=projects&search=flight
```
Example response:
......@@ -423,7 +423,7 @@ Example response:
### Scope: issues
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=issues&search=file
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=issues&search=file
```
Example response:
......@@ -488,7 +488,7 @@ Example response:
### Scope: merge_requests
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=merge_requests&search=file
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=merge_requests&search=file
```
Example response:
......@@ -565,7 +565,7 @@ Example response:
### Scope: milestones
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=milestones&search=release
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=milestones&search=release
```
Example response:
......@@ -592,7 +592,7 @@ Example response:
This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/6/-/search?scope=wiki_blobs&search=bye
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/6/search?scope=wiki_blobs&search=bye
```
Example response:
......@@ -617,7 +617,7 @@ Example response:
This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/6/-/search?scope=commits&search=bye
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/6/search?scope=commits&search=bye
```
Example response:
......@@ -650,7 +650,7 @@ Example response:
This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/6/-/search?scope=blobs&search=installation
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/6/search?scope=blobs&search=installation
```
Example response:
......@@ -677,7 +677,7 @@ Search within the specified project.
If a user is not a member of a project and the project is private, a `GET` request on that project will result to a `404` status code.
```
GET /projects/:id/-/search
GET /projects/:id/search
```
| Attribute | Type | Required | Description |
......@@ -694,7 +694,7 @@ The response depends on the requested scope.
### Scope: issues
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/-/search?scope=issues&search=file
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/search?scope=issues&search=file
```
Example response:
......@@ -759,7 +759,7 @@ Example response:
### Scope: merge_requests
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=merge_requests&search=file
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=merge_requests&search=file
```
Example response:
......@@ -836,7 +836,7 @@ Example response:
### Scope: milestones
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/-/search?scope=milestones&search=release
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/search?scope=milestones&search=release
```
Example response:
......@@ -861,7 +861,7 @@ Example response:
### Scope: notes
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=notes&search=maxime
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=notes&search=maxime
```
Example response:
......@@ -893,7 +893,7 @@ Example response:
### Scope: wiki_blobs
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=wiki_blobs&search=bye
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=wiki_blobs&search=bye
```
Example response:
......@@ -916,7 +916,7 @@ Example response:
### Scope: commits
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=commits&search=bye
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=commits&search=bye
```
Example response:
......@@ -947,7 +947,7 @@ Example response:
### Scope: blobs
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=blobs&search=installation
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation
```
Example response:
......
......@@ -173,6 +173,7 @@ PUT /application/settings
| `external_authorization_service_enabled` | boolean | no | Enable using an external authorization service for accessing projects |
| `external_authorization_service_url` | string | no | URL to which authorization requests will be directed |
| `external_authorization_service_default_label` | string | no | The default classification label to use when requesting authorization and no classification label has been specified on the project |
| `external_authorization_service_timeout` | float | no | The timeout to enforce when performing requests to the external authorization service |
```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal
......@@ -213,6 +214,7 @@ Example response:
"polling_interval_multiplier": 1.0,
"external_authorization_service_enabled": true,
"external_authorization_service_url": "https://authorize.me",
"external_authorization_service_default_label": "default"
"external_authorization_service_default_label": "default",
"external_authorization_service_timeout": 0.5
}
```
......@@ -22,6 +22,7 @@ The first steps towards your GitLab CI/CD journey.
- [Pipelines and jobs](pipelines.md): configure your GitLab CI/CD pipelines to build, test, and deploy your application.
- Runners: The [GitLab Runner](https://docs.gitlab.com/runner/) is responsible by running the jobs in your CI/CD pipeline. On GitLab.com, Shared Runners are enabled by default, so
you don't need to set up anything to start to use them with GitLab CI/CD.
- [Using GitLab CI/CD with GitHub and other external repositories](ci_cd_for_external_repos/index.md)
### Introduction to GitLab CI/CD
......
# Using GitLab CI/CD with a Bitbucket Cloud repository
GitLab CI/CD can be used with Bitbucket Cloud by creating a
[CI/CD project](../../user/project/ci_cd_for_external_repo.md) and connecting
your Git repository via URL.
1. In GitLab create a **CI/CD for external repo**, select **Repo by URL** and
create the project.
![Create project](img/external_repository.png)
GitLab will import the repository and enable [Pull Mirroring][pull-mirroring].
1. In GitLab create a
[Personal Access Token](../../user/profile/personal_access_tokens.md)
with `api` scope. This will be used to authenticate requests from the web
hook that will be created in Bitbucket to notify GitLab of new commits.
1. In Bitbucket from **Settings > Webhooks** create a new web hook to notify
GitLab of new commits.
The web hook URL should be set to the GitLab API to trigger pull mirroring,
using the Personal Access Token we just generated for authentication.
```
https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
```
The web hook Trigger should be set to 'Repository Push'.
![Bitbucket Cloud webhook](img/bitbucket_webhook.png)
After saving, test the web hook by pushing a change to your Bitbucket
repository.
1. In Bitbucket create an **App Password** from **Bitbucket Settings > App
Passwords** to authenticate the build status script setting commit build
statuses in Bitbucket. Repository write permissions are required.
![Bitbucket Cloud webhook](img/bitbucket_app_password.png)
1. In GitLab from **Settings > CI/CD > Secret variables** add variables to allow
communication with Bitbucket via the Bitbucket API.
`BITBUCKET_ACCESS_TOKEN`: the Bitbucket app password created above
`BITBUCKET_USERNAME`: the username of the Bitbucket account
`BITBUCKET_NAMESPACE`: set this if your GitLab and Bitbucket namespaces differ
`BITBUCKET_REPOSITORY`: set this if your GitLab and Bitbucket project names differ
1. In Bitbucket add a script to push the pipeline status to Bitbucket.
> Note: changes made in GitLab will be overwritten by any changes made
upstream in Bitbucket.
Create a file `build_status` and insert the script below and run
`chmod +x build_status` in your terminal to make the script executable.
```bash
#!/usr/bin/env bash
# Push GitLab CI/CD build status to Bitbucket Cloud
if [ -z "$BITBUCKET_ACCESS_TOKEN" ]; then
echo "ERROR: BITBUCKET_ACCESS_TOKEN is not set"
exit 1
fi
if [ -z "$BITBUCKET_USERNAME" ]; then
echo "ERROR: BITBUCKET_USERNAME is not set"
exit 1
fi
if [ -z "$BITBUCKET_NAMESPACE" ]; then
echo "Setting BITBUCKET_NAMESPACE to $CI_PROJECT_NAMESPACE"
BITBUCKET_NAMESPACE=$CI_PROJECT_NAMESPACE
fi
if [ -z "$BITBUCKET_REPOSITORY" ]; then
echo "Setting BITBUCKET_REPOSITORY to $CI_PROJECT_NAME"
BITBUCKET_REPOSITORY=$CI_PROJECT_NAME
fi
BITBUCKET_API_ROOT="https://api.bitbucket.org/2.0"
BITBUCKET_STATUS_API="$BITBUCKET_API_ROOT/repositories/$BITBUCKET_NAMESPACE/$BITBUCKET_REPOSITORY/commit/$CI_COMMIT_SHA/statuses/build"
BITBUCKET_KEY="ci/gitlab-ci/$CI_JOB_NAME"
case "$BUILD_STATUS" in
running)
BITBUCKET_STATE="INPROGRESS"
BITBUCKET_DESCRIPTION="The build is running!"
;;
passed)
BITBUCKET_STATE="SUCCESSFUL"
BITBUCKET_DESCRIPTION="The build passed!"
;;
failed)
BITBUCKET_STATE="FAILED"
BITBUCKET_DESCRIPTION="The build failed."
;;
esac
echo "Pushing status to $BITBUCKET_STATUS_API..."
curl --request POST $BITBUCKET_STATUS_API \
--user $BITBUCKET_USERNAME:$BITBUCKET_ACCESS_TOKEN \
--header "Content-Type:application/json" \
--silent \
--data "{ \"state\": \"$BITBUCKET_STATE\", \"key\": \"$BITBUCKET_KEY\", \"description\":
\"$BITBUCKET_DESCRIPTION\",\"url\": \"$CI_PROJECT_URL/-/jobs/$CI_JOB_ID\" }"
```
1. Still in Bitbucket, create a `.gitlab-ci.yml` file to use the script to push
pipeline success and failures to Bitbucket.
```
stages:
- test
- ci_status
unit-tests
script:
- echo "Success. Add your tests!"
success:
stage: ci_status
before_script:
- ""
after_script:
- ""
script:
- BUILD_STATUS=passed BUILD_KEY=push ./build_status
when: on_success
failure:
stage: ci_status
before_script:
- ""
after_script:
- ""
script:
- BUILD_STATUS=failed BUILD_KEY=push ./build_status
when: on_failure
```
GitLab is now configured to mirror changes from Bitbucket, run CI/CD pipelines
configured in `.gitlab-ci.yml` and push the status to Bitbucket.
[pull-mirroring]: ../../workflow/repository_mirroring.md#pulling-from-a-remote-repository
# Using GitLab CI/CD with a GitHub repository
GitLab CI/CD can be used with **GitHub.com** and **GitHub Enterprise** by
creating a [CI/CD project][cicd-projects] to connect your GitHub repository to
GitLab.
> Note: to use **GitHub Enterprise** with **GitLab.com** you should use the
> manual method.
## Connect with GitHub integration
If the [GitHub integration][github-integration] has been enabled by your GitLab
administrator:
1. In GitLab create a **CI/CD for external repo** project and select
**GitHub**.
![Create project](img/github_omniauth.png)
1. Once authenticated, you will be redirected to a list of your repositories to
connect. Click **Connect** to select the repository.
![Create project](img/github_repo_list.png)
1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD][ci-quickstart].
GitLab will import the project, enable [Pull Mirroring][pull-mirroring], enable
[GitHub project integration][github-project-integration], and create a web hook
on GitHub to notify GitLab of new commits.
## Connect with Personal Access Token
> Note: Personal Access Tokens can only be used to connect GitHub.com
repositories to GitLab.
If you are not using the [GitHub integration][github-integration], you can
still perform a one-off authorization with GitHub to grant GitLab access your
repositories:
1. Open https://github.com/settings/tokens/new to create a **Personal Access
Token**. This token with be used to access your repository and push commit
statuses to GitHub.
The `repo` and `admin:repo_hook` should be enable to allow GitLab access to
your project, update commit statuses, and create a web hook to notify
GitLab of new commits.
1. In GitLab create a **CI/CD for external repo** project and select
**GitHub**.
![Create project](img/github_omniauth.png)
1. Paste the token into the **Personal access token** field and click **List
Repositories**. Click **Connect** to select the repository.
1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD][ci-quickstart].
GitLab will import the project, enable [Pull Mirroring][pull-mirroring], enable
[GitHub project integration][github-project-integration], and create a web hook
on GitHub to notify GitLab of new commits.
## Connect manually
If the [GitHub integration][github-integration] is not enabled, or is enabled
for a different GitHub instance, you GitLab CI/CD can be manually enabled for
your repository.
1. In GitHub open https://github.com/settings/tokens/new create a **Personal
Access Token.** GitLab will use this token to access your repository and
push commit statuses.
Enter a **Token description** and update the scope to allow:
`repo` so that GitLab can access your project and update commit statuses
1. In GitLab create a **CI/CD project** using the Git URL option and the HTTPS
URL for your GitHub repository. If your project if private, use the personal
access token you just created for authentication.
GitLab will automatically configure polling-based pull mirroring.
1. Still in GitLab, enable the [GitHub project integration][github-project-integration]
from **Settings > Integrations.**
Check the **Active** checkbox to enable the integration, paste your
personal access token and HTTPS repository URL into the form, and **Save.**
1. Still in GitLab create a **Personal Access Token** with `API` scope to
authenticate the GitHub web hook notifying GitLab of new commits.
1. In GitHub from **Settings > Webhooks** create a web hook to notify GitLab of
new commits.
The web hook URL should be set to the GitLab API to
[trigger pull mirroring][pull-mirroring-trigger],
using the GitLab personal access token we just created.
```
https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
```
![Create web hook](img/github_push_webhook.png)
1. In GitHub add a `.gitlab-ci.yml` to configure GitLab CI/CD.
[ci-quickstart]: ../quick_start/README.md
[cicd-projects]: ../../user/project/ci_cd_for_external_repo.md
[github-integration]: ../../integration/github.md
[github-project-integration]: ../../user/project/integrations/github.md
[pull-mirroring]: ../../workflow/repository_mirroring.md#pulling-from-a-remote-repository
[pull-mirroring-trigger]: ../../api/projects.md#start-the-pull-mirroring-process-for-a-project
# GitLab CI/CD for external repositories
>[Introduced][ee-4642] in [GitLab Premium][eep] 10.6.
GitLab CI/CD can be used with GitHub or any other Git server.
Instead of moving your entire project to GitLab, you can connect your
external repository to get the benefits of GitLab CI/CD.
- [GitHub](github_integration.md)
- [Bitbucket Cloud](bitbucket_integration.md)
Connecting an external repository will set up [repository mirroring][mirroring]
and create a lightweight project where issues, merge requests, container
registry, wiki, and snippets disabled. These features
[can be re-enabled later][settings].
1. From your GitLab dashboard click **New project**
1. Switch to the **CI/CD for external repo** tab
1. Choose **GitHub** or **Repo by URL**
1. The next steps are similar to the [import flow](../../user/project/import/index.md)
![CI/CD for external repository project creation](img/ci_cd_for_external_repo.png)
[ee-4642]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4642
[eep]: https://about.gitlab.com/products/
[mirroring]: ../../workflow/repository_mirroring.md
[settings]: ../../user/project/settings/index.md#sharing-and-permissions
......@@ -280,3 +280,36 @@ We're always looking for contributions that can mitigate these
[register]: http://docs.gitlab.com/runner/register/
[protected branches]: ../../user/project/protected_branches.md
[protected tags]: ../../user/project/protected_tags.md
## Determining the IP address of a Runner
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17286) in GitLab 10.6.
It may be useful to know the IP address of a Runner so you can troubleshoot
issues with that Runner. GitLab stores and displays the IP address by viewing
the source of the HTTP requests it makes to GitLab when polling for jobs. The
IP address is always kept up to date so if the Runner IP changes it will be
automatically updated in GitLab.
The IP address for shared Runners and specific Runners can be found in
different places.
### Shared Runners
To view the IP address of a shared Runner you must have admin access to
the GitLab instance. To determine this:
1. Visit **Admin area ➔ Overview ➔ Runners**
1. Look for the Runner in the table and you should see a column for "IP Address"
![shared Runner IP address](img/shared_runner_ip_address.png)
### Specific Runners
You can find the IP address of a Runner for a specific project by:
1. Visit your project's **Settings ➔ CI/CD**
1. Find the Runner and click on it's ID which links you to the details page
1. On the details page you should see a row for "IP Address"
![specific Runner IP address](img/specific_runner_ip_address.png)
......@@ -10,7 +10,7 @@ should be deployed, upgraded, and configured.
## Chart Overview
* **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart).
* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components.
* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in alpha. Will support large deployments with horizontal scaling of individual GitLab components.
* Other Charts
* [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner.
* [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box.
......@@ -35,7 +35,7 @@ By offering individual containers and charts, we will be able to provide a numbe
* Potential for rolling updates and canaries within a service,
* and plenty more.
This is a large project and will be worked on over the span of multiple releases. For the most up-to-date status and release information, please see our [tracking issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420). We are planning to launch this chart in beta by the end of 2017.
Presently this chart is available in alpha for testing, and not recommended for production use.
Learn more about the [cloud native GitLab chart here ](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) and [here [Video]](https://youtu.be/Z6jWR8Z8dv8).
......
......@@ -38,6 +38,9 @@ admin area under the settings page:
The available required properties are:
- **Service URL**: The URL to make authorization requests to
- **External authorization request timeout**: The timeout after which an
authorization request is aborted. When a request times out, access is denied
to the user.
- **Default classification label**: The classification label to use when
requesting authorization if no specific label is defined on the project
......
......@@ -68,6 +68,7 @@ that of Issues and Merge requests) based on following parameters:
- Title or description
- Author name / username
- Labels
![epics search](img/epics_search.png)
......
## CI/CD for external repositories
>[Introduced][ee-4642] in [GitLab Premium][eep] 10.6.
Instead of importing the repo directly to GitLab, you can connect your
external repository to get GitLab CI/CD benefits.
This will set up [repository mirroring](../../workflow/repository_mirroring.md)
and create a stripped-down version of a project that has issues, merge requests,
container registry, wiki, and snippets disabled but
[can be re-enabled later on](settings/index.md#sharing-and-permissions).
1. From your GitLab dashboard click **New project**
1. Switch to the **CI/CD for external repo** tab
1. Choose **GitHub** or **Repo by URL**
1. The next steps are similar to the [import flow](import/index.md)
![CI/CD for external repository project creation](img/ci_cd_for_external_repo.png)
[ee-4642]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4642
[eep]: https://about.gitlab.com/products/
This document was moved to [another location](../../ci/ci_cd_for_external_repos/index.md).
......@@ -117,7 +117,7 @@ are trusted, so **only trusted users should be allowed to control your clusters*
The default cluster configuration grants access to a wide set of
functionalities needed to successfully build and deploy a containerized
application. Bare in mind that the same credentials are used for all the
application. Bear in mind that the same credentials are used for all the
applications running on the cluster.
When GitLab creates the cluster, it enables and uses the legacy
......@@ -167,6 +167,17 @@ external IP address with the following procedure. It can be deployed using the
In order to publish your web application, you first need to find the external IP
address associated to your load balancer.
### Let GitLab fetch the IP address
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6.
If you installed the Ingress [via the **Applications**](#installing-applications),
you should see the Ingress IP address on this same page within a few minutes.
If you don't see this, GitLab might not be able to determine the IP address of
your ingress application in which case you should manually determine it.
### Manually determining the IP address
If the cluster is on GKE, click on the **Google Kubernetes Engine** link in the
**Advanced settings**, or go directly to the
[Google Kubernetes Engine dashboard](https://console.cloud.google.com/kubernetes/)
......@@ -193,6 +204,24 @@ The output is the external IP address of your cluster. This information can then
be used to set up DNS entries and forwarding rules that allow external access to
your deployed applications.
### Using a static IP
By default, an ephemeral external IP address is associated to the cluster's load
balancer. If you associate the ephemeral IP with your DNS and the IP changes,
your apps will not be able to be reached, and you'd have to change the DNS
record again. In order to avoid that, you should change it into a static
reserved IP.
[Read how to promote an ephemeral external IP address in GKE.](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address#promote_ephemeral_ip)
### Pointing your DNS at the cluster IP
Once you've set up the static IP, you should associate it to a [wildcard DNS
record](https://en.wikipedia.org/wiki/Wildcard_DNS_record), in order to be able
to reach your apps. This heavily depends on your domain provider, but in case
you aren't sure, just create an A record with a wildcard host like
`*.example.com.`.
## Setting the environment scope
NOTE: **Note:**
......@@ -279,6 +308,14 @@ GitLab CI/CD build environment.
| `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. |
## Monitoring your Kubernetes cluster
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate][ee] 10.6.
When [Prometheus is deployed](#installing-applications), GitLab will automatically monitor the cluster's health. At the top of the cluster settings page, CPU and Memory utilization is displayed, along with the total amount available. If the cluster runs out of memory, pods will become to be shutdown or fail to start.
![Cluster Monitoring](img/k8s_cluster_monitoring.png)
## Enabling or disabling the Kubernetes cluster integration
After you have successfully added your cluster information, you can enable the
......
doc/user/project/img/labels_list.png

203 KB | W: | H:

doc/user/project/img/labels_list.png

270 KB | W: | H:

doc/user/project/img/labels_list.png
doc/user/project/img/labels_list.png
doc/user/project/img/labels_list.png
doc/user/project/img/labels_list.png
  • 2-up
  • Swipe
  • Onion skin
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment