Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
23985334ba
commit
52982854c8
|
|
@ -41,7 +41,7 @@ review-docs-cleanup:
|
|||
|
||||
.docs-markdown-lint-image:
|
||||
# When updating the image version here, update it in /scripts/lint-doc.sh too.
|
||||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-markdown:alpine-3.20-vale-3.6.1-markdownlint2-0.13.0-lychee-0.15.1
|
||||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-markdown:alpine-3.20-vale-3.7.1-markdownlint2-0.13.0-lychee-0.15.1
|
||||
|
||||
docs-lint markdown:
|
||||
extends:
|
||||
|
|
@ -52,7 +52,6 @@ docs-lint markdown:
|
|||
stage: lint
|
||||
needs: []
|
||||
script:
|
||||
- apk add libuuid
|
||||
- source ./scripts/utils.sh
|
||||
- yarn_install_script
|
||||
- install_gitlab_gem
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -256,7 +256,7 @@ gem 'asciidoctor-kroki', '~> 0.10.0', require: false # rubocop:todo Gemfile/Miss
|
|||
gem 'rouge', '~> 4.3.0', feature_category: :shared
|
||||
gem 'truncato', '~> 0.7.12' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'nokogiri', '~> 1.16' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'gitlab-glfm-markdown', '~> 0.0.19', feature_category: :team_planning
|
||||
gem 'gitlab-glfm-markdown', '~> 0.0.20', feature_category: :team_planning
|
||||
|
||||
# Calendar rendering
|
||||
gem 'icalendar', '~> 2.10.1', feature_category: :system_access
|
||||
|
|
|
|||
|
|
@ -213,11 +213,11 @@
|
|||
{"name":"gitlab-dangerfiles","version":"4.8.0","platform":"ruby","checksum":"b327d079552ec974a63bf34d749a0308425af6ebf51d01064f1a6ff216a523db"},
|
||||
{"name":"gitlab-experiment","version":"0.9.1","platform":"ruby","checksum":"f230ee742154805a755d5f2539dc44d93cdff08c5bbbb7656018d61f93d01f48"},
|
||||
{"name":"gitlab-fog-azure-rm","version":"2.1.0","platform":"ruby","checksum":"f5becd9e412a8c8f18f8ac061b8bc2562a354fb7a9f63d8cc1b301e6e6aa0bdd"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.19","platform":"aarch64-linux","checksum":"7cc4272cddb60c62745ac01e90368f1f11a5efbe8308a6df466eb3cca00b2c1a"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.19","platform":"arm64-darwin","checksum":"1aef2fe4ec251fdd00fe19c163f6ce94111128edd61623e7123b67bac0c74aed"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.19","platform":"ruby","checksum":"17a83a7ad1a63f2483a3c7fa75ecbc97467ec00123811793587995074f95811a"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.19","platform":"x86_64-darwin","checksum":"370a5706058219acaad5e856f4dafd3b6f76ac45222ed53d6edd0b284dc99dde"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.19","platform":"x86_64-linux","checksum":"d7d6e06e22669bd02c4fa5fbbca091e0f43a263bfd74fedb1fd974a5150d6fec"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.20","platform":"aarch64-linux","checksum":"3ef33ad9374467c309e1ca318f42379ec01cf72ffffeb8f1a7eb3919416db619"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.20","platform":"arm64-darwin","checksum":"2e97dcd7d23b9ada3db3bc5eaa3a39d710ef9df93ad68d438df92e835c64c19e"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.20","platform":"ruby","checksum":"8c5057f980d7c6c5bf78de3673eddf825095433f923cdab21603f715da63acd2"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.20","platform":"x86_64-darwin","checksum":"58396e74002e514ff1ad87b8e97d6e69e735ca4c6cd57f1d233c9b06ff95a045"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.20","platform":"x86_64-linux","checksum":"20138aeaa968450897cbfdc67c351c87fa8d514c1bef667424cbcb10ed7c5cbe"},
|
||||
{"name":"gitlab-kas-grpc","version":"17.4.0.pre.rc1","platform":"ruby","checksum":"37679435a3e71b830b215741e78714984c3392fbc09d660b3102e4a5e6ab92ca"},
|
||||
{"name":"gitlab-labkit","version":"0.36.1","platform":"ruby","checksum":"04fb6941b7e5fc1fdcee8f9971fa2086a4dc442e39e67a74b992403dd580c300"},
|
||||
{"name":"gitlab-license","version":"2.5.0","platform":"ruby","checksum":"4c166c469c2ad17876ca43188a4ccebe3feb0726c4c1770047f8dcef96573f4d"},
|
||||
|
|
|
|||
|
|
@ -724,7 +724,7 @@ GEM
|
|||
mime-types
|
||||
net-http-persistent (~> 4.0)
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
gitlab-glfm-markdown (0.0.19)
|
||||
gitlab-glfm-markdown (0.0.20)
|
||||
rb_sys (= 0.9.94)
|
||||
gitlab-kas-grpc (17.4.0.pre.rc1)
|
||||
grpc (~> 1.0)
|
||||
|
|
@ -2054,7 +2054,7 @@ DEPENDENCIES
|
|||
gitlab-duo-workflow-service-client (~> 0.1)!
|
||||
gitlab-experiment (~> 0.9.1)
|
||||
gitlab-fog-azure-rm (~> 2.1.0)
|
||||
gitlab-glfm-markdown (~> 0.0.19)
|
||||
gitlab-glfm-markdown (~> 0.0.20)
|
||||
gitlab-housekeeper!
|
||||
gitlab-http!
|
||||
gitlab-kas-grpc (~> 17.4.0.pre.rc1)
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export default {
|
|||
@click="toggleOpen"
|
||||
/>
|
||||
{{ title }}
|
||||
<gl-badge v-if="!loading" class="gl-ml-1" variant="neutral" size="sm">{{
|
||||
<gl-badge v-if="!loading || count" class="gl-ml-1" variant="neutral" size="sm">{{
|
||||
count
|
||||
}}</gl-badge>
|
||||
</h5>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export default {
|
|||
return QUERIES[this.query];
|
||||
},
|
||||
update(d) {
|
||||
return d.currentUser?.[this.query] || {};
|
||||
return d.currentUser?.mergeRequests || {};
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,16 @@ export function initMergeRequestDashboard(el) {
|
|||
|
||||
const { lists, switch_dashboard_path: switchDashboardPath } = JSON.parse(el.dataset.initialData);
|
||||
|
||||
const keyArgs = [
|
||||
'state',
|
||||
'reviewState',
|
||||
'reviewStates',
|
||||
'reviewerWildcardId',
|
||||
'mergedAfter',
|
||||
'assignedReviewStates',
|
||||
'reviewerReviewStates',
|
||||
];
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
apolloProvider: new VueApollo({
|
||||
|
|
@ -18,19 +28,15 @@ export function initMergeRequestDashboard(el) {
|
|||
cacheConfig: {
|
||||
typePolicies: {
|
||||
CurrentUser: {
|
||||
merge: true,
|
||||
fields: {
|
||||
reviewRequestedMergeRequests: {
|
||||
keyArgs: ['state', 'reviewState', 'reviewStates', 'mergedAfter'],
|
||||
},
|
||||
assignedMergeRequests: {
|
||||
keyArgs: [
|
||||
'state',
|
||||
'reviewState',
|
||||
'reviewStates',
|
||||
'reviewerWildcardId',
|
||||
'mergedAfter',
|
||||
],
|
||||
keyArgs,
|
||||
},
|
||||
reviewRequestedMergeRequests: {
|
||||
keyArgs,
|
||||
},
|
||||
assigneeOrReviewerMergeRequests: {
|
||||
keyArgs,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -39,10 +45,8 @@ export function initMergeRequestDashboard(el) {
|
|||
nodes: concatPagination(),
|
||||
},
|
||||
},
|
||||
UserMergeRequestInteraction: {
|
||||
merge(a) {
|
||||
return a;
|
||||
},
|
||||
MergeRequestReviewer: {
|
||||
keyFields: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ query requestingReview(
|
|||
) {
|
||||
currentUser {
|
||||
id
|
||||
assignedMergeRequests(
|
||||
mergeRequests: assignedMergeRequests(
|
||||
state: $state
|
||||
reviewState: $reviewState
|
||||
reviewStates: $reviewStates
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ query assigneeOrReviewer(
|
|||
) {
|
||||
currentUser {
|
||||
id
|
||||
assigneeOrReviewerMergeRequests(
|
||||
mergeRequests: assigneeOrReviewerMergeRequests(
|
||||
state: $state
|
||||
assignedReviewStates: $assignedReviewStates
|
||||
reviewerReviewStates: $reviewerReviewStates
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ query reviewRequests(
|
|||
) {
|
||||
currentUser {
|
||||
id
|
||||
reviewRequestedMergeRequests(
|
||||
mergeRequests: reviewRequestedMergeRequests(
|
||||
state: $state
|
||||
reviewState: $reviewState
|
||||
reviewStates: $reviewStates
|
||||
|
|
|
|||
|
|
@ -416,7 +416,7 @@ export default {
|
|||
},
|
||||
},
|
||||
showDuoSettings() {
|
||||
return this.licensedAiFeaturesAvailable && this.glFeatures.aiSettingsVueProject;
|
||||
return this.licensedAiFeaturesAvailable;
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -443,19 +443,6 @@ export default {
|
|||
@edit="openAllowedToMergeDrawer"
|
||||
/>
|
||||
|
||||
<rule-drawer
|
||||
:is-open="isAllowedToMergeDrawerOpen || isAllowedToPushAndMergeDrawerOpen"
|
||||
:roles="accessLevelsDrawerData.roles"
|
||||
:users="accessLevelsDrawerData.users"
|
||||
:groups="accessLevelsDrawerData.groups"
|
||||
:deploy-keys="accessLevelsDrawerData.deployKeys"
|
||||
:is-loading="isRuleUpdating"
|
||||
:group-id="groupId"
|
||||
:title="accessLevelsDrawerTitle"
|
||||
@editRule="onEditAccessLevels"
|
||||
@close="closeAccessLevelsDrawer"
|
||||
/>
|
||||
|
||||
<!-- Allowed to push -->
|
||||
<protection
|
||||
class="gl-mt-5"
|
||||
|
|
@ -474,6 +461,20 @@ export default {
|
|||
@edit="openAllowedToPushAndMergeDrawer"
|
||||
/>
|
||||
|
||||
<rule-drawer
|
||||
:is-open="isAllowedToMergeDrawerOpen || isAllowedToPushAndMergeDrawerOpen"
|
||||
:roles="accessLevelsDrawerData.roles"
|
||||
:users="accessLevelsDrawerData.users"
|
||||
:groups="accessLevelsDrawerData.groups"
|
||||
:deploy-keys="accessLevelsDrawerData.deployKeys"
|
||||
:is-loading="isRuleUpdating"
|
||||
:group-id="groupId"
|
||||
:title="accessLevelsDrawerTitle"
|
||||
:is-push-access-levels="isAllowedToPushAndMergeDrawerOpen"
|
||||
@editRule="onEditAccessLevels"
|
||||
@close="closeAccessLevelsDrawer"
|
||||
/>
|
||||
|
||||
<!-- Force push -->
|
||||
<protection-toggle
|
||||
v-if="hasPushAccessLevelSet"
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ export default {
|
|||
ItemsSelector: () =>
|
||||
import('ee_component/projects/settings/branch_rules/components/view/items_selector.vue'),
|
||||
},
|
||||
inject: {
|
||||
showEnterpriseAccessLevels: { default: false },
|
||||
},
|
||||
props: {
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
|
|
@ -78,6 +81,11 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isPushAccessLevels: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -193,20 +201,23 @@ export default {
|
|||
}}
|
||||
</gl-form-checkbox>
|
||||
|
||||
<template v-if="showEnterpriseAccessLevels">
|
||||
<items-selector
|
||||
:type="$options.USERS_TYPE"
|
||||
:items="formatItemsIds(users)"
|
||||
:users-options="$options.projectUsersOptions"
|
||||
data-testid="users-selector"
|
||||
@change="handleRuleDataUpdate('updatedUsers', $event)"
|
||||
/>
|
||||
<items-selector
|
||||
:type="$options.GROUPS_TYPE"
|
||||
:items="formatItemsIds(groups)"
|
||||
data-testid="groups-selector"
|
||||
@change="handleRuleDataUpdate('updatedGroups', $event)"
|
||||
/>
|
||||
</template>
|
||||
<items-selector
|
||||
:type="$options.USERS_TYPE"
|
||||
:items="formatItemsIds(users)"
|
||||
:users-options="$options.projectUsersOptions"
|
||||
data-testid="users-selector"
|
||||
@change="handleRuleDataUpdate('updatedUsers', $event)"
|
||||
/>
|
||||
<items-selector
|
||||
:type="$options.GROUPS_TYPE"
|
||||
:items="formatItemsIds(groups)"
|
||||
data-testid="groups-selector"
|
||||
@change="handleRuleDataUpdate('updatedGroups', $event)"
|
||||
/>
|
||||
<items-selector
|
||||
v-if="isPushAccessLevels"
|
||||
:type="$options.DEPLOY_KEYS_TYPE"
|
||||
:items="formatItemsIds(deployKeys)"
|
||||
data-testid="deploy-keys-selector"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export default function mountBranchRules(el, store) {
|
|||
showStatusChecks,
|
||||
showApprovers,
|
||||
showCodeOwners,
|
||||
showEnterpriseAccessLevels,
|
||||
canAdminProtectedBranches,
|
||||
} = el.dataset;
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ export default function mountBranchRules(el, store) {
|
|||
showStatusChecks: parseBoolean(showStatusChecks),
|
||||
showApprovers: parseBoolean(showApprovers),
|
||||
showCodeOwners: parseBoolean(showCodeOwners),
|
||||
showEnterpriseAccessLevels: parseBoolean(showEnterpriseAccessLevels),
|
||||
canAdminProtectedBranches: parseBoolean(canAdminProtectedBranches),
|
||||
},
|
||||
render(h) {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ export default {
|
|||
inject: {
|
||||
projectPath: { default: '' },
|
||||
branchRulesPath: { default: '' },
|
||||
showStatusChecks: { default: false },
|
||||
showApprovers: { default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -63,13 +65,15 @@ export default {
|
|||
{ text: this.$options.i18n.branchName, action: () => this.openCreateRuleModal() },
|
||||
];
|
||||
|
||||
[this.$options.i18n.allBranches, this.$options.i18n.allProtectedBranches].forEach(
|
||||
(branch) => {
|
||||
if (!this.hasPredefinedBranchRule(branch)) {
|
||||
items.push(this.createPredefinedBrachRulesItem(branch));
|
||||
}
|
||||
},
|
||||
);
|
||||
if (this.showApprovers || this.showStatusChecks) {
|
||||
[this.$options.i18n.allBranches, this.$options.i18n.allProtectedBranches].forEach(
|
||||
(branch) => {
|
||||
if (!this.hasPredefinedBranchRule(branch)) {
|
||||
items.push(this.createPredefinedBrachRulesItem(branch));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,16 +8,8 @@ export const SEARCH_INPUT_DESCRIPTION = 'label-search-input-description';
|
|||
|
||||
export const SEARCH_RESULTS_DESCRIPTION = 'label-search-results-description';
|
||||
|
||||
const header = __('Labels');
|
||||
export const LABEL_FILTER_HEADER = __('Labels');
|
||||
|
||||
const scopes = {
|
||||
ISSUES: 'issues',
|
||||
};
|
||||
export const LABEL_FILTER_PARAM = 'label_name';
|
||||
|
||||
const filterParam = 'labels';
|
||||
|
||||
export const labelFilterData = {
|
||||
header,
|
||||
scopes,
|
||||
filterParam,
|
||||
};
|
||||
export const LABEL_AGREGATION_NAME = 'labels';
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import { uniq } from 'lodash';
|
||||
import { difference, uniq } from 'lodash';
|
||||
import { rgbFromHex } from '@gitlab/ui/dist/utils/utils';
|
||||
import { slugify } from '~/lib/utils/text_utility';
|
||||
|
||||
|
|
@ -26,7 +26,8 @@ import {
|
|||
SEARCH_BOX_INDEX,
|
||||
SEARCH_RESULTS_DESCRIPTION,
|
||||
SEARCH_INPUT_DESCRIPTION,
|
||||
labelFilterData,
|
||||
LABEL_FILTER_PARAM,
|
||||
LABEL_FILTER_HEADER,
|
||||
} from './data';
|
||||
|
||||
import { trackSelectCheckbox, trackOpenDropdown } from './tracking';
|
||||
|
|
@ -50,6 +51,7 @@ export default {
|
|||
return {
|
||||
currentFocusIndex: SEARCH_BOX_INDEX,
|
||||
isFocused: false,
|
||||
combinedSelectedLabels: [],
|
||||
};
|
||||
},
|
||||
i18n: I18N,
|
||||
|
|
@ -83,7 +85,6 @@ export default {
|
|||
combinedSelectedFilters() {
|
||||
const appliedSelectedLabelKeys = this.appliedSelectedLabels.map((label) => label.key);
|
||||
const { labels = [] } = this.query;
|
||||
|
||||
return uniq([...appliedSelectedLabelKeys, ...labels]);
|
||||
},
|
||||
searchLabels: {
|
||||
|
|
@ -96,16 +97,30 @@ export default {
|
|||
},
|
||||
selectedLabels: {
|
||||
get() {
|
||||
return this.combinedSelectedFilters;
|
||||
return this.combinedSelectedLabels;
|
||||
},
|
||||
set(value) {
|
||||
this.setQuery({ key: this.$options.labelFilterData?.filterParam, value });
|
||||
const labelName = this.getLabelNameById(value);
|
||||
this.setQuery({ key: this.$options.LABEL_FILTER_PARAM, value: labelName });
|
||||
trackSelectCheckbox(value);
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
combinedSelectedFilters(newLabels, oldLabels) {
|
||||
const hasDifference = difference(newLabels, oldLabels).length > 0;
|
||||
if (hasDifference) {
|
||||
this.combinedSelectedLabels = newLabels;
|
||||
}
|
||||
},
|
||||
filteredAppliedSelectedLabels(newCount, oldCount) {
|
||||
if (newCount.length !== oldCount.length) {
|
||||
this.currentFocusIndex = FIRST_DROPDOWN_INDEX;
|
||||
}
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
if (this.urlQuery?.[labelFilterData.filterParam]?.length > 0) {
|
||||
if (this.urlQuery?.[LABEL_FILTER_PARAM]?.length > 0) {
|
||||
await this.fetchAllAggregation();
|
||||
}
|
||||
},
|
||||
|
|
@ -136,24 +151,34 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
const { key } = event.target.closest('.gl-label').dataset;
|
||||
this.closeLabel({ key });
|
||||
const { title } = event.target.closest('.gl-label').dataset;
|
||||
this.closeLabel({ title });
|
||||
},
|
||||
inactiveLabelColor(label) {
|
||||
return `rgba(${rgbFromHex(label.color)}, 0.3)`;
|
||||
},
|
||||
getLabelNameById(labelIds) {
|
||||
const labelNames = labelIds.map((id) => {
|
||||
const label = this.filteredLabels.find((filteredLabel) => {
|
||||
return filteredLabel.key === String(id);
|
||||
});
|
||||
return label?.title;
|
||||
});
|
||||
return labelNames;
|
||||
},
|
||||
},
|
||||
FIRST_DROPDOWN_INDEX,
|
||||
SEARCH_RESULTS_DESCRIPTION,
|
||||
SEARCH_INPUT_DESCRIPTION,
|
||||
labelFilterData,
|
||||
LABEL_FILTER_PARAM,
|
||||
LABEL_FILTER_HEADER,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="label-filter gl-relative gl-pb-0 md:gl-pt-0">
|
||||
<div class="gl-mb-2 gl-text-sm gl-font-bold" data-testid="label-filter-title">
|
||||
{{ $options.labelFilterData.header }}
|
||||
{{ $options.LABEL_FILTER_HEADER }}
|
||||
</div>
|
||||
<div>
|
||||
<gl-label
|
||||
|
|
@ -178,9 +203,9 @@ export default {
|
|||
/>
|
||||
<gl-label
|
||||
v-for="label in appliedSelectedLabels"
|
||||
:key="label.key"
|
||||
:key="label.title"
|
||||
class="gl-mb-2 gl-mr-2 gl-bg-gray-10"
|
||||
:data-key="label.key"
|
||||
:data-title="label.title"
|
||||
:background-color="label.color"
|
||||
:title="label.title"
|
||||
:show-close-button="true"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils';
|
|||
import { visitUrl, setUrlParams, getNormalizedURL, updateHistory } from '~/lib/utils/url_utility';
|
||||
import { logError } from '~/lib/logger';
|
||||
import { __ } from '~/locale';
|
||||
import { labelFilterData } from '~/search/sidebar/components/label_filter/data';
|
||||
import { LABEL_FILTER_PARAM } from '~/search/sidebar/components/label_filter/data';
|
||||
import { SCOPE_BLOB, SEARCH_TYPE_ZOEKT } from '~/search/sidebar/constants';
|
||||
import {
|
||||
GROUPS_LOCAL_STORAGE_KEY,
|
||||
|
|
@ -146,10 +146,9 @@ export const resetQuery = ({ state }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const closeLabel = ({ state, commit }, { key }) => {
|
||||
const labels = state?.query?.labels.filter((labelKey) => labelKey !== key);
|
||||
|
||||
setQuery({ state, commit }, { key: labelFilterData.filterParam, value: labels });
|
||||
export const closeLabel = ({ state, commit }, { title }) => {
|
||||
const labels = state?.query?.[LABEL_FILTER_PARAM].filter((labelName) => labelName !== title);
|
||||
setQuery({ state, commit }, { key: LABEL_FILTER_PARAM, value: labels });
|
||||
};
|
||||
|
||||
export const setLabelFilterSearch = ({ commit }, { value }) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { statusFilterData } from '~/search/sidebar/components/status_filter/data';
|
||||
import { confidentialFilterData } from '~/search/sidebar/components/confidentiality_filter/data';
|
||||
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
|
||||
import { labelFilterData } from '~/search/sidebar/components/label_filter/data';
|
||||
import { LABEL_FILTER_PARAM } from '~/search/sidebar/components/label_filter/data';
|
||||
import { archivedFilterData } from '~/search/sidebar/components/archived_filter/data';
|
||||
import { INCLUDE_FORKED_FILTER_PARAM } from '~/search/sidebar/components/forks_filter/index.vue';
|
||||
import { s__ } from '~/locale';
|
||||
|
|
@ -18,7 +18,7 @@ export const SIDEBAR_PARAMS = [
|
|||
statusFilterData.filterParam,
|
||||
confidentialFilterData.filterParam,
|
||||
languageFilterData.filterParam,
|
||||
labelFilterData.filterParam,
|
||||
LABEL_FILTER_PARAM,
|
||||
archivedFilterData.filterParam,
|
||||
INCLUDE_FORKED_FILTER_PARAM,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { findKey, intersection } from 'lodash';
|
||||
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
|
||||
import { labelFilterData } from '~/search/sidebar/components/label_filter/data';
|
||||
import {
|
||||
LABEL_FILTER_PARAM,
|
||||
LABEL_AGREGATION_NAME,
|
||||
} from '~/search/sidebar/components/label_filter/data';
|
||||
import {
|
||||
formatSearchResultCount,
|
||||
addCountOverLimit,
|
||||
|
|
@ -10,8 +13,8 @@ import {
|
|||
import { PROJECT_DATA, SCOPE_BLOB } from '~/search/sidebar/constants';
|
||||
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY, ICON_MAP } from './constants';
|
||||
|
||||
const queryLabelFilters = (state) => state?.query?.[labelFilterData.filterParam] || [];
|
||||
const urlQueryLabelFilters = (state) => state?.urlQuery?.[labelFilterData.filterParam] || [];
|
||||
const queryLabelFilters = (state) => state?.query?.[LABEL_FILTER_PARAM] || [];
|
||||
const urlQueryLabelFilters = (state) => state?.urlQuery?.[LABEL_FILTER_PARAM] || [];
|
||||
|
||||
const appliedSelectedLabelsKeys = (state) =>
|
||||
intersection(urlQueryLabelFilters(state), queryLabelFilters(state));
|
||||
|
|
@ -19,8 +22,11 @@ const appliedSelectedLabelsKeys = (state) =>
|
|||
const unselectedLabelsKeys = (state) =>
|
||||
urlQueryLabelFilters(state)?.filter((label) => !queryLabelFilters(state)?.includes(label));
|
||||
|
||||
const unappliedNewLabelKeys = (state) =>
|
||||
state?.query?.labels?.filter((label) => !urlQueryLabelFilters(state)?.includes(label));
|
||||
const unappliedNewLabelKeys = (state) => {
|
||||
return state?.query?.[LABEL_FILTER_PARAM]?.filter(
|
||||
(label) => !urlQueryLabelFilters(state)?.includes(label),
|
||||
);
|
||||
};
|
||||
|
||||
export const queryLanguageFilters = (state) => state.query[languageFilterData.filterParam] || [];
|
||||
|
||||
|
|
@ -42,9 +48,8 @@ export const languageAggregationBuckets = (state) => {
|
|||
|
||||
export const labelAggregationBuckets = (state) => {
|
||||
return (
|
||||
state?.aggregations?.data?.find(
|
||||
(aggregation) => aggregation.name === labelFilterData.filterParam,
|
||||
)?.buckets || []
|
||||
state?.aggregations?.data?.find((aggregation) => aggregation.name === LABEL_AGREGATION_NAME)
|
||||
?.buckets || []
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -58,26 +63,26 @@ export const filteredLabels = (state) => {
|
|||
};
|
||||
|
||||
export const filteredAppliedSelectedLabels = (state) =>
|
||||
filteredLabels(state)?.filter((label) => urlQueryLabelFilters(state)?.includes(label.key));
|
||||
filteredLabels(state)?.filter((label) => urlQueryLabelFilters(state)?.includes(label.title));
|
||||
|
||||
export const appliedSelectedLabels = (state) => {
|
||||
return labelAggregationBuckets(state)?.filter((label) =>
|
||||
appliedSelectedLabelsKeys(state)?.includes(label.key),
|
||||
appliedSelectedLabelsKeys(state)?.includes(label.title),
|
||||
);
|
||||
};
|
||||
|
||||
export const filteredUnselectedLabels = (state) =>
|
||||
filteredLabels(state)?.filter((label) => !urlQueryLabelFilters(state)?.includes(label.key));
|
||||
filteredLabels(state)?.filter((label) => !urlQueryLabelFilters(state)?.includes(label.title));
|
||||
|
||||
export const unselectedLabels = (state) =>
|
||||
labelAggregationBuckets(state).filter((label) =>
|
||||
unselectedLabelsKeys(state)?.includes(label.key),
|
||||
unselectedLabelsKeys(state)?.includes(label.title),
|
||||
);
|
||||
|
||||
export const unappliedNewLabels = (state) =>
|
||||
labelAggregationBuckets(state).filter((label) =>
|
||||
unappliedNewLabelKeys(state)?.includes(label.key),
|
||||
);
|
||||
labelAggregationBuckets(state).filter((label) => {
|
||||
return unappliedNewLabelKeys(state)?.includes(label.title);
|
||||
});
|
||||
|
||||
export const currentScope = (state) => findKey(state.navigation, { active: true });
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
|
|||
import { formatNumber } from '~/locale';
|
||||
import { joinPaths, queryToObject, objectToQuery, getBaseURL } from '~/lib/utils/url_utility';
|
||||
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
|
||||
import { LABEL_AGREGATION_NAME } from '~/search/sidebar/components/label_filter/data';
|
||||
import {
|
||||
MAX_FREQUENT_ITEMS,
|
||||
MAX_FREQUENCY,
|
||||
|
|
@ -151,6 +152,12 @@ const sortLanguages = (state, entries) => {
|
|||
return orderBy(entries, [({ key }) => queriedLanguagesSet.has(key), 'count'], ['desc', 'desc']);
|
||||
};
|
||||
|
||||
const getUniqueNamesOnly = (items) => {
|
||||
return items.filter(
|
||||
(item, index, array) => index === array.findIndex((obj) => obj.title === item.title),
|
||||
);
|
||||
};
|
||||
|
||||
export const prepareSearchAggregations = (state, aggregationData) =>
|
||||
aggregationData.map((item) => {
|
||||
if (item?.name === LANGUAGE_AGGREGATION_NAME) {
|
||||
|
|
@ -160,6 +167,13 @@ export const prepareSearchAggregations = (state, aggregationData) =>
|
|||
};
|
||||
}
|
||||
|
||||
if (item?.name === LABEL_AGREGATION_NAME) {
|
||||
return {
|
||||
...item,
|
||||
buckets: getUniqueNamesOnly(item.buckets),
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Vertical line gradient for specific system note icons
|
||||
.system-note-icon-v2 {
|
||||
&.icon-success{
|
||||
--bg-color: var(--green-100, #{$green-100});
|
||||
|
|
@ -68,6 +70,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Remove beginning/end of system note gradient activity line
|
||||
.system-note-v2 {
|
||||
&:first-of-type {
|
||||
.system-note-icon-v2::before {
|
||||
@include activity-line-gradient(transparent, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
.system-note-icon-v2::after {
|
||||
@include activity-line-gradient(transparent, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.system-note-v2:first-child :is(.system-note-icon-v2::before, .system-note-icon-v2::after) {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -653,3 +653,11 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
|
|||
@apply gl-bg-subtle;
|
||||
}
|
||||
}
|
||||
|
||||
// Timeline avatars
|
||||
|
||||
// stylelint-disable-next-line gitlab/no-gl-class
|
||||
.timeline-avatar .gl-avatar-link {
|
||||
@apply gl-bg-default;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
- show_code_owners = @project.licensed_feature_available?(:code_owner_approval_required)
|
||||
- show_status_checks = @project.licensed_feature_available?(:external_status_checks)
|
||||
- show_approvers = @project.licensed_feature_available?(:merge_request_approvers)
|
||||
- show_enterprise_access_levels = @project.licensed_feature_available?(:protected_refs_for_users)
|
||||
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Branch rules'),
|
||||
id: 'branch-rules',
|
||||
|
|
@ -14,4 +15,4 @@
|
|||
= _('Define rules for who can push, merge, and the required approvals for each branch.')
|
||||
= link_to(_('Leave feedback.'), 'https://gitlab.com/gitlab-org/gitlab/-/issues/388149', target: '_blank', rel: 'noopener noreferrer')
|
||||
- c.with_body do
|
||||
#js-branch-rules{ data: { project_path: @project.full_path, branch_rules_path: project_settings_repository_branch_rules_path(@project), show_code_owners: show_code_owners.to_s, show_status_checks: show_status_checks.to_s, show_approvers: show_approvers.to_s } }
|
||||
#js-branch-rules{ data: { project_path: @project.full_path, branch_rules_path: project_settings_repository_branch_rules_path(@project), show_code_owners: show_code_owners.to_s, show_status_checks: show_status_checks.to_s, show_approvers: show_approvers.to_s, show_enterprise_access_levels: show_enterprise_access_levels.to_s } }
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
|
||||
%h4.pt-3.pb-3= _("Validate your GitLab CI configuration")
|
||||
|
||||
#js-ci-lint{ data: { endpoint: project_ci_lint_path(@project), pipeline_simulation_help_page_path: help_page_path('ci/lint', anchor: 'simulate-a-pipeline') , lint_help_page_path: help_page_path('ci/lint', anchor: 'check-cicd-syntax') } }
|
||||
#js-ci-lint{ data: { endpoint: project_ci_lint_path(@project), pipeline_simulation_help_page_path: help_page_path('ci/yaml/lint', anchor: 'simulate-a-pipeline') , lint_help_page_path: help_page_path('ci/yaml/lint', anchor: 'check-cicd-syntax') } }
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ feature_categories:
|
|||
description: Dependency list exported data
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104361
|
||||
milestone: '15.7'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/454947
|
||||
|
|
|
|||
|
|
@ -249,6 +249,16 @@ IP addresses that have been added to your IP allowlist can be viewed on the Conf
|
|||
|
||||
Specify a comma separated list of IP addresses that can access your GitLab Dedicated instance in your [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650). The IP addresses are then added to the IP allowlist for your instance.
|
||||
|
||||
#### Enable OpenID Connect for your IP allowlist
|
||||
|
||||
Using [GitLab as an OpenID Connect identity provider](../../integration/openid_connect_provider.md) requires internet access to the OpenID Connect verification endpoint.
|
||||
|
||||
To enable access to the OpenID Connect endpoint while maintaining your IP allowlist:
|
||||
|
||||
- In a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650), request to allow access to the OpenID Connect endpoint.
|
||||
|
||||
The configuration is applied during the next maintenance window.
|
||||
|
||||
### SAML
|
||||
|
||||
You can [configure SAML single sign-on (SSO)](../../integration/saml.md#configure-saml-support-in-gitlab) for your GitLab Dedicated instance.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ POST /projects/:id/ci/lint
|
|||
| Attribute | Type | Required | Description |
|
||||
|----------------|---------|----------|-------------|
|
||||
| `content` | string | Yes | The CI/CD configuration content. |
|
||||
| `dry_run` | boolean | No | Run [pipeline creation simulation](../ci/lint.md#simulate-a-pipeline), or only do static check. Default: `false`. |
|
||||
| `dry_run` | boolean | No | Run [pipeline creation simulation](../ci/yaml/lint.md#simulate-a-pipeline), or only do static check. Default: `false`. |
|
||||
| `include_jobs` | boolean | No | If the list of jobs that would exist in a static check or pipeline simulation should be included in the response. Default: `false`. |
|
||||
| `ref` | string | No | When `dry_run` is `true`, sets the branch or tag context to use to validate the CI/CD YAML configuration. Defaults to the project's default branch when not set. |
|
||||
|
||||
|
|
|
|||
|
|
@ -73,12 +73,12 @@ latest version of the schema.
|
|||
|
||||
#### Verify syntax with CI Lint tool
|
||||
|
||||
You can use the [CI Lint tool](lint.md) to verify that the syntax of a CI/CD configuration
|
||||
You can use the [CI Lint tool](yaml/lint.md) to verify that the syntax of a CI/CD configuration
|
||||
snippet is correct. Paste in full `.gitlab-ci.yml` files or individual job configurations,
|
||||
to verify the basic syntax.
|
||||
|
||||
When a `.gitlab-ci.yml` file is present in a project, you can also use the CI Lint
|
||||
tool to [simulate the creation of a full pipeline](lint.md#simulate-a-pipeline).
|
||||
tool to [simulate the creation of a full pipeline](yaml/lint.md#simulate-a-pipeline).
|
||||
It does deeper verification of the configuration syntax.
|
||||
|
||||
### Use pipeline names
|
||||
|
|
@ -339,7 +339,7 @@ configuration into more independent [parent-child pipelines](../ci/pipelines/pip
|
|||
|
||||
Pipeline configuration warnings are shown when you:
|
||||
|
||||
- [Validate configuration with the CI Lint tool](lint.md).
|
||||
- [Validate configuration with the CI Lint tool](yaml/lint.md).
|
||||
- [Manually run a pipeline](pipelines/index.md#run-a-pipeline-manually).
|
||||
|
||||
### `Job may allow multiple pipelines to run for a single action` warning
|
||||
|
|
|
|||
|
|
@ -589,7 +589,7 @@ unit_test:
|
|||
#### Deploy to production
|
||||
|
||||
The job `deploy_production` will deploy the app to the production server.
|
||||
To deploy our app with Envoy, we had to set up the `$SSH_PRIVATE_KEY` variable as an [SSH private key](../../ssh_keys/index.md#ssh-keys-when-using-the-docker-executor).
|
||||
To deploy our app with Envoy, we had to set up the `$SSH_PRIVATE_KEY` variable as an [SSH private key](../../jobs/ssh_keys.md#ssh-keys-when-using-the-docker-executor).
|
||||
If the SSH keys have added successfully, we can run Envoy.
|
||||
|
||||
As mentioned before, GitLab supports [Continuous Delivery](https://about.gitlab.com/blog/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-delivery) methods as well.
|
||||
|
|
|
|||
|
|
@ -1,139 +1,11 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Pipeline Execution
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: 'runners/git_submodules.md'
|
||||
remove_date: '2024-12-04'
|
||||
---
|
||||
|
||||
# Using Git submodules with GitLab CI/CD
|
||||
This document was moved to [another location](runners/git_submodules.md).
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
Use [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) to keep
|
||||
a Git repository as a subdirectory of another Git repository. You can clone another
|
||||
repository into your project and keep your commits separate.
|
||||
|
||||
## Configure the `.gitmodules` file
|
||||
|
||||
When you use Git submodules, your project should have a file named `.gitmodules`.
|
||||
You have multiple options to configure it to work in a GitLab CI/CD job.
|
||||
|
||||
### Using absolute URLs
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3198) in GitLab Runner 15.11.
|
||||
|
||||
For example, your generated `.gitmodules` configuration might look like the following if:
|
||||
|
||||
- Your project is located at `https://gitlab.com/secret-group/my-project`.
|
||||
- Your project depends on `https://gitlab.com/group/project`, which you want
|
||||
to include as a submodule.
|
||||
- You check out your sources with an SSH address like `git@gitlab.com:secret-group/my-project.git`.
|
||||
|
||||
```ini
|
||||
[submodule "project"]
|
||||
path = project
|
||||
url = git@gitlab.com:group/project.git
|
||||
```
|
||||
|
||||
In this case, use the [`GIT_SUBMODULE_FORCE_HTTPS`](runners/configure_runners.md#rewrite-submodule-urls-to-https) variable
|
||||
to instruct GitLab Runner to convert the URL to HTTPS before it clones the submodules.
|
||||
|
||||
Alternatively, if you also use HTTPS locally, you can configure an HTTPS URL:
|
||||
|
||||
```ini
|
||||
[submodule "project"]
|
||||
path = project
|
||||
url = https://gitlab.com/group/project.git
|
||||
```
|
||||
|
||||
You do not need to configure additional variables in this case, but you need to use a
|
||||
[personal access token](../user/profile/personal_access_tokens.md) to clone it locally.
|
||||
|
||||
### Using relative URLs
|
||||
|
||||
WARNING:
|
||||
If you use relative URLs, submodules may resolve incorrectly in forking workflows.
|
||||
Use absolute URLs instead if you expect your project to have forks.
|
||||
|
||||
When your submodule is on the same GitLab server, you can also use relative URLs in
|
||||
your `.gitmodules` file:
|
||||
|
||||
```ini
|
||||
[submodule "project"]
|
||||
path = project
|
||||
url = ../../project.git
|
||||
```
|
||||
|
||||
The above configuration instructs Git to automatically deduce the URL to
|
||||
use when cloning sources. You can clone with HTTPS in all your CI/CD jobs, and you
|
||||
can continue to use SSH to clone locally.
|
||||
|
||||
For submodules not located on the same GitLab server, always use the full URL:
|
||||
|
||||
```ini
|
||||
[submodule "project-x"]
|
||||
path = project-x
|
||||
url = https://gitserver.com/group/project-x.git
|
||||
```
|
||||
|
||||
## Use Git submodules in CI/CD jobs
|
||||
|
||||
To make submodules work correctly in CI/CD jobs:
|
||||
|
||||
1. You can set the `GIT_SUBMODULE_STRATEGY` variable to either `normal` or `recursive`
|
||||
to tell the runner to [fetch your submodules before the job](runners/configure_runners.md#git-submodule-strategy):
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
```
|
||||
|
||||
1. For submodules located on the same GitLab server and configured with a Git or SSH URL, make sure
|
||||
you set the [`GIT_SUBMODULE_FORCE_HTTPS`](runners/configure_runners.md#rewrite-submodule-urls-to-https) variable.
|
||||
|
||||
1. Use `GIT_SUBMODULE_DEPTH` to configure the cloning depth of submodules independently of the [`GIT_DEPTH`](runners/configure_runners.md#shallow-cloning) variable:
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
GIT_SUBMODULE_DEPTH: 1
|
||||
```
|
||||
|
||||
1. You can filter or exclude specific submodules to control which submodules are synchronized using
|
||||
[`GIT_SUBMODULE_PATHS`](runners/configure_runners.md#sync-or-exclude-specific-submodules-from-ci-jobs).
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
GIT_SUBMODULE_PATHS: submoduleA submoduleB
|
||||
```
|
||||
|
||||
1. You can provide additional flags to control advanced checkout behavior using
|
||||
[`GIT_SUBMODULE_UPDATE_FLAGS`](runners/configure_runners.md#git-submodule-update-flags).
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
GIT_SUBMODULE_UPDATE_FLAGS: --jobs 4
|
||||
```
|
||||
|
||||
If you use the [`CI_JOB_TOKEN`](jobs/ci_job_token.md) to clone a submodule in a
|
||||
pipeline job, the user executing the job must be assigned to a role that has
|
||||
[permission](../user/permissions.md#cicd) to trigger a pipeline
|
||||
in the upstream submodule project. Additionally, [CI/CD job token access](jobs/ci_job_token.md#control-job-token-access-to-your-project) must be properly configured in the upstream submodule project.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Can't find the `.gitmodules` file
|
||||
|
||||
The `.gitmodules` file might be hard to find because it is usually a hidden file.
|
||||
You can check documentation for your specific OS to learn how to find and display
|
||||
hidden files.
|
||||
|
||||
If there is no `.gitmodules` file, it's possible the submodule settings are in a
|
||||
[`git config`](https://www.atlassian.com/git/tutorials/setting-up-a-repository/git-config) file.
|
||||
|
||||
### `fatal: run_command returned non-zero status` error
|
||||
|
||||
This error can happen in a job when working with submodules and the `GIT_STRATEGY` is set to `fetch`.
|
||||
|
||||
Setting the `GIT_STRATEGY` to `clone` should resolve the issue.
|
||||
<!-- This redirect file can be deleted after <2024-12-04>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,442 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Mobile DevOps
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
**Status:** Experiment
|
||||
|
||||
Use GitLab Mobile DevOps to quickly build, sign, and release native and cross-platform mobile apps
|
||||
for Android and iOS using GitLab CI/CD. Mobile DevOps is an experimental feature developed by
|
||||
[GitLab Incubation Engineering](https://handbook.gitlab.com/handbook/engineering/development/incubation/).
|
||||
|
||||
Mobile DevOps is still in development, but you can:
|
||||
|
||||
- [Request a feature](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=feature_request).
|
||||
- [Report a bug](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=report_bug).
|
||||
- [Share feedback](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=general_feedback).
|
||||
|
||||
## Build environments
|
||||
|
||||
Get started quickly by using [GitLab-hosted runners](../runners/index.md),
|
||||
or set up [self-managed runners](https://docs.gitlab.com/runner/#use-self-managed-runners)
|
||||
for complete control over the build environment.
|
||||
|
||||
### Android build environments
|
||||
|
||||
Set up an Android build environment by selecting an appropriate Docker image
|
||||
and adding it to your `.gitlab-ci.yml` file. [Fabernovel](https://hub.docker.com/r/fabernovel/android/tags)
|
||||
provides a variety of supported Android versions.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
test:
|
||||
image: fabernovel/android:api-33-v1.7.0
|
||||
stage: test
|
||||
script:
|
||||
- fastlane test
|
||||
```
|
||||
|
||||
### iOS build environments
|
||||
|
||||
[GitLab hosted runners on macOS](../runners/hosted_runners/macos.md) are in beta.
|
||||
|
||||
[Choose an image](../runners/hosted_runners/macos.md#supported-macos-images) to run a job on a macOS GitLab-hosted runner and add it to your `.gitlab-ci.yml` file.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
test:
|
||||
image: macos-14-xcode-15
|
||||
stage: test
|
||||
script:
|
||||
- fastlane test
|
||||
tags:
|
||||
- saas-macos-medium-m1
|
||||
```
|
||||
|
||||
## Code signing
|
||||
|
||||
All Android and iOS apps must be securely signed before being distributed through
|
||||
the various app stores. Signing ensures that applications haven't been tampered with
|
||||
before reaching a user's device.
|
||||
|
||||
With [project-level secure files](../secure_files/index.md), you can store the following
|
||||
in GitLab, so that they can be used to securely sign apps in CI/CD builds:
|
||||
|
||||
- Keystores
|
||||
- Provision profiles
|
||||
- Signing certificates
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Project-level secure files demo](https://youtu.be/O7FbJu3H2YM).
|
||||
|
||||
### Code signing Android projects with fastlane & Gradle
|
||||
|
||||
To set up code signing for Android:
|
||||
|
||||
1. Upload your keystore and keystore properties files to project-level secure files.
|
||||
1. Update the Gradle configuration to use those files in the build.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [How to build and release an Android app to Google Play with GitLab](https://youtu.be/u8yC8W2k85U).
|
||||
|
||||
#### Create a keystore
|
||||
|
||||
Run the following command to generate a keystore file if you don't already have one:
|
||||
|
||||
```shell
|
||||
keytool -genkey -v -keystore release-keystore.jks -storepass password -alias release -keypass password -keyalg RSA -keysize 2048 -validity 10000
|
||||
```
|
||||
|
||||
Next, put the keystore configuration in a file called `release-keystore.properties`,
|
||||
which should look similar to this example:
|
||||
|
||||
```plaintext
|
||||
storeFile=.secure_files/release-keystore.jks
|
||||
keyAlias=release
|
||||
keyPassword=password
|
||||
storePassword=password
|
||||
```
|
||||
|
||||
After these files are created:
|
||||
|
||||
- [Upload them as Secure Files](../secure_files/index.md) in the GitLab project
|
||||
so they can be used in CI/CD jobs.
|
||||
- Add both files to your `.gitignore` file so they aren't committed to version control.
|
||||
|
||||
#### Configure Gradle
|
||||
|
||||
The next step is to configure Gradle to use the newly created keystore. In the app's `build.gradle` file:
|
||||
|
||||
1. Immediately after the plugins section, add:
|
||||
|
||||
```gradle
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('.secure_files/release-keystore.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
```
|
||||
|
||||
1. Anywhere in the `android` block, add:
|
||||
|
||||
```gradle
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Add the `signingConfig` to the release build type:
|
||||
|
||||
```gradle
|
||||
signingConfig signingConfigs.release
|
||||
```
|
||||
|
||||
With this configuration in place, you can use fastlane to build & sign the app
|
||||
with the files stored in secure files.
|
||||
|
||||
For example:
|
||||
|
||||
- Sample `fastlane/Fastfile` file:
|
||||
|
||||
```ruby
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Create and sign a new build"
|
||||
lane :build do
|
||||
gradle(tasks: ["clean", "assembleRelease", "bundleRelease"])
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- Sample `.gitlab-ci.yml` file:
|
||||
|
||||
```yaml
|
||||
build:
|
||||
image: fabernovel/android:api-33-v1.7.0
|
||||
stage: build
|
||||
script:
|
||||
- apt update -y && apt install -y curl
|
||||
- curl --silent "https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer" | bash
|
||||
- fastlane build
|
||||
```
|
||||
|
||||
### Code sign iOS projects with fastlane
|
||||
|
||||
To set up code signing for iOS, you must:
|
||||
|
||||
1. Install fastlane locally so you can upload your signing certificates to GitLab.
|
||||
1. Configure the build to use those files.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [How to build and release an iOS app to Test Flight with GitLab](https://youtu.be/Ar8IsBgP1as).
|
||||
|
||||
#### Initialize fastlane
|
||||
|
||||
With fastlane installed, start by running:
|
||||
|
||||
```shell
|
||||
fastlane init
|
||||
```
|
||||
|
||||
This command creates a `fastlane` folder in the project with an `Appfile` and a stubbed-out `fastfile`.
|
||||
During this process, you are prompted for App Store Connect login credentials to generate an app identifier and an App Store app if they don't already exist.
|
||||
|
||||
The next step sets up fastlane match to manage code signing files for the project.
|
||||
Run the following command to generate a `Matchfile` with the configuration:
|
||||
|
||||
```shell
|
||||
fastlane match init
|
||||
```
|
||||
|
||||
This command prompts you to:
|
||||
|
||||
- Choose which storage backend you want to use, you must select `gitlab_secure_files`.
|
||||
- Input your project path, for example `gitlab-org/gitlab`.
|
||||
|
||||
#### Generate and upload certificates
|
||||
|
||||
Run the following command to generate certificates and profiles in the Apple Developer portal
|
||||
and upload those files to GitLab:
|
||||
|
||||
```shell
|
||||
PRIVATE_TOKEN=YOUR-TOKEN bundle exec fastlane match development
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
||||
- `YOUR-TOKEN` must be either a personal or project access token with Maintainer role for the GitLab project.
|
||||
- Replace `development` with the type of build you want to sign, for example `appstore` or `ad-hoc`.
|
||||
|
||||
You can view the files in your project's CI/CD settings as soon as the command completes.
|
||||
|
||||
#### Upload-only
|
||||
|
||||
If you have already created signing certificates and provisioning profiles for your project,
|
||||
you can optionally use `fastlane match import` to load your existing files into GitLab:
|
||||
|
||||
```shell
|
||||
PRIVATE_TOKEN=YOUR-TOKEN bundle exec fastlane match import
|
||||
```
|
||||
|
||||
You are prompted to input the path to your files. After you provide those details,
|
||||
your files are uploaded and visible in your project's CI/CD settings.
|
||||
If prompted for the `git_url` during the import, it is safe to leave it blank and press <kbd>enter</kbd>.
|
||||
|
||||
With this configuration in place, you can use fastlane to build and sign the app with
|
||||
the files stored in secure files.
|
||||
|
||||
For example:
|
||||
|
||||
- Sample `fastlane/Fastfile` file:
|
||||
|
||||
```ruby
|
||||
default_platform(:ios)
|
||||
|
||||
platform :ios do
|
||||
desc "Build and sign the application for development"
|
||||
lane :build do
|
||||
setup_ci
|
||||
|
||||
match(type: 'development', readonly: is_ci)
|
||||
|
||||
build_app(
|
||||
project: "ios demo.xcodeproj",
|
||||
scheme: "ios demo",
|
||||
configuration: "Debug",
|
||||
export_method: "development"
|
||||
)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- Sample `.gitlab-ci.yml` file:
|
||||
|
||||
```yaml
|
||||
build_ios:
|
||||
image: macos-12-xcode-14
|
||||
stage: build
|
||||
script:
|
||||
- fastlane build
|
||||
tags:
|
||||
- saas-macos-medium-m1
|
||||
```
|
||||
|
||||
## Distribution
|
||||
|
||||
Signed builds can be uploaded to the Google Play Store or Apple App Store by using
|
||||
the Mobile DevOps Distribution integrations.
|
||||
|
||||
### Android distribution with Google Play integration and fastlane
|
||||
|
||||
To create an Android distribution with Google Play integration and fastlane, you must:
|
||||
|
||||
1. [Create a Google service account](https://docs.fastlane.tools/actions/supply/#setup)
|
||||
in Google Cloud Platform and grant that account access to the project in Google Play.
|
||||
1. [Enable the Google Play integration](#enable-google-play-integration).
|
||||
1. Add the release step to your pipeline.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Google Play integration demo](https://youtu.be/Fxaj3hna4uk).
|
||||
|
||||
#### Enable Google Play Integration
|
||||
|
||||
Use the [Google Play integration](../../user/project/integrations/google_play.md),
|
||||
to configure your CI/CD pipelines to connect to the [Google Play Console](https://play.google.com/console/developers)
|
||||
to build and release Android apps. To enable the integration:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Google Play**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. In **Package name**, enter the package name of the app. For example, `com.gitlab.app_name`.
|
||||
1. In **Service account key (.JSON)** drag or upload your key file.
|
||||
1. Select **Save changes**.
|
||||
|
||||
With the integration enabled, you can use fastlane to distribute a build to Google Play.
|
||||
|
||||
For example:
|
||||
|
||||
- Sample `fastlane/Fastfile`:
|
||||
|
||||
```ruby
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Submit a new Beta build to the Google Play store"
|
||||
lane :beta do
|
||||
upload_to_play_store(
|
||||
track: 'internal',
|
||||
aab: 'app/build/outputs/bundle/release/app-release.aab',
|
||||
release_status: 'draft'
|
||||
)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- Sample `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
beta:
|
||||
image: fabernovel/android:api-33-v1.7.0
|
||||
stage: beta
|
||||
script:
|
||||
- fastlane beta
|
||||
```
|
||||
|
||||
### iOS distribution Apple Store integration and fastlane
|
||||
|
||||
To create an iOS distribution with the Apple Store integration and fastlane, you must:
|
||||
|
||||
1. Generate an API Key for App Store Connect API. In the Apple App Store Connect portal,
|
||||
[generate a new private key for your project](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api).
|
||||
1. [Enable the Apple App Store Connect integration](#enable-the-apple-app-store-connect-integration).
|
||||
1. Add the release step to your pipeline and fastlane configuration.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Apple App Store Connect integration demo](https://youtu.be/CwzAWVgJeK8).
|
||||
<!-- Video published on 2023-03-17 -->
|
||||
|
||||
#### Enable the Apple App Store Connect integration
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have an Apple ID enrolled in the [Apple Developer Program](https://developer.apple.com/programs/enroll/).
|
||||
- You must [generate a new private key](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) for your project in the Apple App Store Connect portal.
|
||||
|
||||
Use the Apple App Store Connect integration to configure your CI/CD pipelines to connect to [App Store Connect](https://appstoreconnect.apple.com).
|
||||
With this integration, you can build and release apps for iOS, iPadOS, macOS, tvOS, and watchOS.
|
||||
|
||||
To enable the Apple App Store Connect integration in GitLab:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Apple App Store Connect**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Provide the Apple App Store Connect configuration information:
|
||||
- **Issuer ID**: The Apple App Store Connect issuer ID.
|
||||
- **Key ID**: The key ID of the generated private key.
|
||||
- **Private key**: The generated private key. You can download this key only once.
|
||||
- **Protected branches and tags only**: Enable to set variables on protected branches and tags only.
|
||||
1. Select **Save changes**.
|
||||
|
||||
With the integration enabled, you can use fastlane to distribute a build to TestFlight
|
||||
and the Apple App Store.
|
||||
|
||||
For example:
|
||||
|
||||
- Sample `fastlane/Fastfile`:
|
||||
|
||||
```ruby
|
||||
default_platform(:ios)
|
||||
|
||||
platform :ios do
|
||||
desc "Build and sign the application for distribution, upload to TestFlight"
|
||||
lane :beta do
|
||||
setup_ci
|
||||
|
||||
match(type: 'appstore', readonly: is_ci)
|
||||
|
||||
app_store_connect_api_key
|
||||
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number(initial_build_number: 1) + 1,
|
||||
xcodeproj: "ios demo.xcodeproj"
|
||||
)
|
||||
|
||||
build_app(
|
||||
project: "ios demo.xcodeproj",
|
||||
scheme: "ios demo",
|
||||
configuration: "Release",
|
||||
export_method: "app-store"
|
||||
)
|
||||
|
||||
upload_to_testflight
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- Sample `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
beta_ios:
|
||||
image: macos-12-xcode-14
|
||||
stage: beta
|
||||
script:
|
||||
- fastlane beta
|
||||
```
|
||||
|
||||
## Review apps for mobile
|
||||
|
||||
You can use [review apps](../review_apps/index.md) to preview changes directly from a merge request.
|
||||
This feature is possible through an integration with [Appetize.io](https://appetize.io/).
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Review apps for mobile setup instructions](https://youtu.be/X15mI19TXa4).
|
||||
|
||||
To get started, see the [setup instructions](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/readme/-/issues/15).
|
||||
|
||||
## Sample Reference Projects
|
||||
|
||||
See the sample reference projects below for complete build, sign, and release pipeline examples for various platforms. A list of all available projects can be found in [the Mobile DevOps Demo Projects group](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/demo-projects/).
|
||||
|
||||
- [Android Demo](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/demo-projects/android_demo)
|
||||
- [iOS Demo](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/demo-projects/ios-demo)
|
||||
- [Flutter Demo](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/demo-projects/flutter-demo)
|
||||
|
||||
## Mobile DevOps Blog
|
||||
|
||||
Additional reference material can be found in the [DevOps section](https://about.gitlab.com/blog/categories/devops/) of the GitLab blog.
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Pipeline Security
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Using SSH keys with GitLab CI/CD
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
GitLab currently doesn't have built-in support for managing SSH keys in a build
|
||||
environment (where the GitLab Runner runs).
|
||||
|
||||
Use SSH keys when you want to:
|
||||
|
||||
- Check out internal submodules.
|
||||
- Download private packages using your package manager. For example, Bundler.
|
||||
- Deploy your application to your own server or, for example, Heroku.
|
||||
- Execute SSH commands from the build environment to a remote server.
|
||||
- Rsync files from the build environment to a remote server.
|
||||
|
||||
If anything of the above rings a bell, then you most likely need an SSH key.
|
||||
|
||||
The most widely supported method is to inject an SSH key into your build
|
||||
environment by extending your `.gitlab-ci.yml`, and it's a solution that works
|
||||
with any type of [executor](https://docs.gitlab.com/runner/executors/)
|
||||
(like Docker or shell, for example).
|
||||
|
||||
## Create and use an SSH key
|
||||
|
||||
To create and use an SSH key in GitLab CI/CD:
|
||||
|
||||
1. [Create a new SSH key pair](../../user/ssh.md#generate-an-ssh-key-pair) locally with `ssh-keygen`.
|
||||
1. Add the private key as a [file type CI/CD variable](../variables/index.md#for-a-project) to
|
||||
your project. The variable value must end in a newline (`LF` character). To add a newline, press <kbd>Enter</kbd> or <kbd>Return</kbd>
|
||||
at the end of the last line of the SSH key before saving it in the CI/CD settings.
|
||||
1. Run the [`ssh-agent`](https://linux.die.net/man/1/ssh-agent) in the job, which loads
|
||||
the private key.
|
||||
1. Copy the public key to the servers you want to have access to (usually in
|
||||
`~/.ssh/authorized_keys`) or add it as a [deploy key](../../user/project/deploy_keys/index.md)
|
||||
if you are accessing a private GitLab repository.
|
||||
|
||||
In the following example, the `ssh-add -` command does not display the value of
|
||||
`$SSH_PRIVATE_KEY` in the job log, though it could be exposed if you enable
|
||||
[debug logging](../variables/index.md#enable-debug-logging). You might also want to
|
||||
check the [visibility of your pipelines](../pipelines/settings.md#change-which-users-can-view-your-pipelines).
|
||||
|
||||
## SSH keys when using the Docker executor
|
||||
|
||||
When your CI/CD jobs run inside Docker containers (meaning the environment is
|
||||
contained) and you want to deploy your code in a private server, you need a way
|
||||
to access it. In this case, you can use an SSH key pair.
|
||||
|
||||
1. You first must create an SSH key pair. For more information, follow
|
||||
the instructions to [generate an SSH key](../../user/ssh.md#generate-an-ssh-key-pair).
|
||||
**Do not** add a passphrase to the SSH key, or the `before_script` will
|
||||
prompt for it.
|
||||
|
||||
1. Create a new [file type CI/CD variable](../variables/index.md#for-a-project).
|
||||
- In the **Key** field, enter `SSH_PRIVATE_KEY`.
|
||||
- In the **Value** field, paste the content of your _private_ key from the key pair that you created earlier.
|
||||
Make sure the file ends with a newline. To add a newline, press
|
||||
<kbd>Enter</kbd> or <kbd>Return</kbd> at the end of the last line of the SSH key before saving your changes.
|
||||
|
||||
1. Modify your `.gitlab-ci.yml` with a `before_script` action. In the following
|
||||
example, a Debian based image is assumed. Edit to your needs:
|
||||
|
||||
```yaml
|
||||
before_script:
|
||||
##
|
||||
## Install ssh-agent if not already installed, it is required by Docker.
|
||||
## (change apt-get to yum if you use an RPM-based image)
|
||||
##
|
||||
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||
|
||||
##
|
||||
## Run ssh-agent (inside the build environment)
|
||||
##
|
||||
- eval $(ssh-agent -s)
|
||||
|
||||
##
|
||||
## Give the right permissions, otherwise ssh-add will refuse to add files
|
||||
## Add the SSH key stored in SSH_PRIVATE_KEY file type CI/CD variable to the agent store
|
||||
##
|
||||
- chmod 400 "$SSH_PRIVATE_KEY"
|
||||
- ssh-add "$SSH_PRIVATE_KEY"
|
||||
|
||||
##
|
||||
## Create the SSH directory and give it the right permissions
|
||||
##
|
||||
- mkdir -p ~/.ssh
|
||||
- chmod 700 ~/.ssh
|
||||
|
||||
##
|
||||
## Optionally, if you will be using any Git commands, set the user name and
|
||||
## and email.
|
||||
##
|
||||
# - git config --global user.email "user@example.com"
|
||||
# - git config --global user.name "User name"
|
||||
```
|
||||
|
||||
The [`before_script`](../yaml/index.md#before_script) can be set as a default
|
||||
or per-job.
|
||||
|
||||
1. Make sure the private server's [SSH host keys are verified](#verifying-the-ssh-host-keys).
|
||||
|
||||
1. As a final step, add the _public_ key from the one you created in the first
|
||||
step to the services that you want to have an access to from inside the build
|
||||
environment. If you are accessing a private GitLab repository you must add
|
||||
it as a [deploy key](../../user/project/deploy_keys/index.md).
|
||||
|
||||
That's it! You can now have access to private servers or repositories in your
|
||||
build environment.
|
||||
|
||||
## SSH keys when using the Shell executor
|
||||
|
||||
If you are using the Shell executor and not Docker, it is easier to set up an
|
||||
SSH key.
|
||||
|
||||
You can generate the SSH key from the machine that GitLab Runner is installed
|
||||
on, and use that key for all projects that are run on this machine.
|
||||
|
||||
1. First, sign in to the server that runs your jobs.
|
||||
|
||||
1. Then, from the terminal, sign in as the `gitlab-runner` user:
|
||||
|
||||
```shell
|
||||
sudo su - gitlab-runner
|
||||
```
|
||||
|
||||
1. Generate the SSH key pair as described in the instructions to
|
||||
[generate an SSH key](../../user/ssh.md#generate-an-ssh-key-pair).
|
||||
**Do not** add a passphrase to the SSH key, or the `before_script` will
|
||||
prompt for it.
|
||||
|
||||
1. As a final step, add the _public_ key from the one you created earlier to the
|
||||
services that you want to have an access to from inside the build environment.
|
||||
If you are accessing a private GitLab repository you must add it as a
|
||||
[deploy key](../../user/project/deploy_keys/index.md).
|
||||
|
||||
After generating the key, try to sign in to the remote server to accept the
|
||||
fingerprint:
|
||||
|
||||
```shell
|
||||
ssh example.com
|
||||
```
|
||||
|
||||
For accessing repositories on GitLab.com, you would use `git@gitlab.com`.
|
||||
|
||||
## Verifying the SSH host keys
|
||||
|
||||
It is a good practice to check the private server's own public key to make sure
|
||||
you are not being targeted by a man-in-the-middle attack. If anything
|
||||
suspicious happens, you notice it because the job fails (the SSH
|
||||
connection fails when the public keys don't match).
|
||||
|
||||
To find out the host keys of your server, run the `ssh-keyscan` command from a
|
||||
trusted network (ideally, from the private server itself):
|
||||
|
||||
```shell
|
||||
## Use the domain name
|
||||
ssh-keyscan example.com
|
||||
|
||||
## Or use an IP
|
||||
ssh-keyscan 10.0.2.2
|
||||
```
|
||||
|
||||
Create a new [file type CI/CD variable](../variables/index.md#use-file-type-cicd-variables)
|
||||
with `SSH_KNOWN_HOSTS` as "Key", and as a "Value" add the output of `ssh-keyscan`.
|
||||
Make sure the file ends with a newline. To add a newline, press <kbd>Enter</kbd> or <kbd>Return</kbd>
|
||||
at the end of the last line of the SSH key before saving your changes.
|
||||
|
||||
If you must connect to multiple servers, all the server host keys
|
||||
must be collected in the **Value** of the variable, one key per line.
|
||||
|
||||
NOTE:
|
||||
By using a file type CI/CD variable instead of `ssh-keyscan` directly inside
|
||||
`.gitlab-ci.yml`, it has the benefit that you don't have to change `.gitlab-ci.yml`
|
||||
if the host domain name changes for some reason. Also, the values are predefined
|
||||
by you, meaning that if the host keys suddenly change, the CI/CD job doesn't fail,
|
||||
so there's something wrong with the server or the network.
|
||||
|
||||
Now that the `SSH_KNOWN_HOSTS` variable is created, in addition to the
|
||||
[content of `.gitlab-ci.yml`](#ssh-keys-when-using-the-docker-executor)
|
||||
above, you must add:
|
||||
|
||||
```yaml
|
||||
before_script:
|
||||
##
|
||||
## Assuming you created the SSH_KNOWN_HOSTS file type CI/CD variable, uncomment the
|
||||
## following two lines.
|
||||
##
|
||||
- cp "$SSH_KNOWN_HOSTS" ~/.ssh/known_hosts
|
||||
- chmod 644 ~/.ssh/known_hosts
|
||||
|
||||
##
|
||||
## Alternatively, use ssh-keyscan to scan the keys of your private server.
|
||||
## Replace example.com with your private server's domain name. Repeat that
|
||||
## command if you have more than one server to connect to. Include the -t
|
||||
## flag to specify the key type.
|
||||
##
|
||||
# - ssh-keyscan -t rsa,ed25519 example.com >> ~/.ssh/known_hosts
|
||||
# - chmod 644 ~/.ssh/known_hosts
|
||||
|
||||
##
|
||||
## You can optionally disable host key checking. Be aware that by adding that
|
||||
## you are susceptible to man-in-the-middle attacks.
|
||||
## WARNING: Use this only with the Docker executor, if you use it with shell
|
||||
## you will overwrite your user's SSH config.
|
||||
##
|
||||
# - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config'
|
||||
```
|
||||
|
||||
## Use SSH key without a file type CI/CD variable
|
||||
|
||||
If you do not want to use a file type CI/CD variable, the [example SSH Project](https://gitlab.com/gitlab-examples/ssh-private-key/)
|
||||
shows an alternative method. This method uses a regular CI/CD variable instead of
|
||||
the file type variable recommended above.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `Error loading key "/builds/path/SSH_PRIVATE_KEY": error in libcrypto` message
|
||||
|
||||
This message can be returned if there is a formatting error with the SSH key.
|
||||
|
||||
When saving the SSH key as a [file type CI/CD variable](../variables/index.md#use-file-type-cicd-variables),
|
||||
the value must end with a newline (`LF` character). To add a newline, press <kbd>Enter</kbd> or <kbd>Return</kbd>
|
||||
at the end of the `-----END OPENSSH PRIVATE KEY-----` line of the SSH key before saving
|
||||
the variable.
|
||||
|
|
@ -1,57 +1,11 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Pipeline Authoring
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: 'yaml/lint.md'
|
||||
remove_date: '2024-12-04'
|
||||
---
|
||||
|
||||
# Validate GitLab CI/CD configuration
|
||||
This document was moved to [another location](yaml/lint.md).
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
Use the CI Lint tool to check the validity of GitLab CI/CD configuration.
|
||||
You can validate the syntax from a `.gitlab-ci.yml` file or any other sample CI/CD configuration.
|
||||
This tool checks for syntax and logic errors, and can simulate pipeline
|
||||
creation to try to find more complicated configuration problems.
|
||||
|
||||
If you use the [pipeline editor](pipeline_editor/index.md), it verifies configuration
|
||||
syntax automatically.
|
||||
|
||||
If you use VS Code, you can validate your CI/CD configuration with the
|
||||
[GitLab Workflow VS Code extension](../editor_extensions/visual_studio_code/index.md).
|
||||
|
||||
## Check CI/CD syntax
|
||||
|
||||
The CI lint tool checks the syntax of GitLab CI/CD configuration, including
|
||||
configuration added with the [`includes` keyword](yaml/index.md#include).
|
||||
|
||||
To check CI/CD configuration with the CI lint tool:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Build > Pipeline editor**.
|
||||
1. Select the **Validate** tab.
|
||||
1. Select **Lint CI/CD sample**.
|
||||
1. Paste a copy of the CI/CD configuration you want to check into the text box.
|
||||
1. Select **Validate**.
|
||||
|
||||
## Simulate a pipeline
|
||||
|
||||
You can simulate the creation of a GitLab CI/CD pipeline to find more complicated issues,
|
||||
including problems with [`needs`](yaml/index.md#needs) and [`rules`](yaml/index.md#rules)
|
||||
configuration. A simulation runs as a Git `push` event on the default branch.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have [permissions](../user/permissions.md#project-members-permissions)
|
||||
to create pipelines on this branch to validate with a simulation.
|
||||
|
||||
To simulate a pipeline:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Build > Pipeline editor**.
|
||||
1. Select the **Validate** tab.
|
||||
1. Select **Lint CI/CD sample**.
|
||||
1. Paste a copy of the CI/CD configuration you want to check into the text box.
|
||||
1. Select **Simulate pipeline creation for the default branch**.
|
||||
1. Select **Validate**.
|
||||
<!-- This redirect file can be deleted after <2024-12-04>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -1,442 +1,11 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: 'jobs/mobile_devops.md'
|
||||
remove_date: '2024-12-04'
|
||||
---
|
||||
|
||||
# Mobile DevOps
|
||||
This document was moved to [another location](jobs/mobile_devops.md).
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
**Status:** Experiment
|
||||
|
||||
Use GitLab Mobile DevOps to quickly build, sign, and release native and cross-platform mobile apps
|
||||
for Android and iOS using GitLab CI/CD. Mobile DevOps is an experimental feature developed by
|
||||
[GitLab Incubation Engineering](https://handbook.gitlab.com/handbook/engineering/development/incubation/).
|
||||
|
||||
Mobile DevOps is still in development, but you can:
|
||||
|
||||
- [Request a feature](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=feature_request).
|
||||
- [Report a bug](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=report_bug).
|
||||
- [Share feedback](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=general_feedback).
|
||||
|
||||
## Build environments
|
||||
|
||||
Get started quickly by using [GitLab-hosted runners](../ci/runners/index.md),
|
||||
or set up [self-managed runners](https://docs.gitlab.com/runner/#use-self-managed-runners)
|
||||
for complete control over the build environment.
|
||||
|
||||
### Android build environments
|
||||
|
||||
Set up an Android build environment by selecting an appropriate Docker image
|
||||
and adding it to your `.gitlab-ci.yml` file. [Fabernovel](https://hub.docker.com/r/fabernovel/android/tags)
|
||||
provides a variety of supported Android versions.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
test:
|
||||
image: fabernovel/android:api-33-v1.7.0
|
||||
stage: test
|
||||
script:
|
||||
- fastlane test
|
||||
```
|
||||
|
||||
### iOS build environments
|
||||
|
||||
[GitLab hosted runners on macOS](../ci/runners/hosted_runners/macos.md) are in beta.
|
||||
|
||||
[Choose an image](../ci/runners/hosted_runners/macos.md#supported-macos-images) to run a job on a macOS GitLab-hosted runner and add it to your `.gitlab-ci.yml` file.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
test:
|
||||
image: macos-14-xcode-15
|
||||
stage: test
|
||||
script:
|
||||
- fastlane test
|
||||
tags:
|
||||
- saas-macos-medium-m1
|
||||
```
|
||||
|
||||
## Code signing
|
||||
|
||||
All Android and iOS apps must be securely signed before being distributed through
|
||||
the various app stores. Signing ensures that applications haven't been tampered with
|
||||
before reaching a user's device.
|
||||
|
||||
With [project-level secure files](secure_files/index.md), you can store the following
|
||||
in GitLab, so that they can be used to securely sign apps in CI/CD builds:
|
||||
|
||||
- Keystores
|
||||
- Provision profiles
|
||||
- Signing certificates
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Project-level secure files demo](https://youtu.be/O7FbJu3H2YM).
|
||||
|
||||
### Code signing Android projects with fastlane & Gradle
|
||||
|
||||
To set up code signing for Android:
|
||||
|
||||
1. Upload your keystore and keystore properties files to project-level secure files.
|
||||
1. Update the Gradle configuration to use those files in the build.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [How to build and release an Android app to Google Play with GitLab](https://youtu.be/u8yC8W2k85U).
|
||||
|
||||
#### Create a keystore
|
||||
|
||||
Run the following command to generate a keystore file if you don't already have one:
|
||||
|
||||
```shell
|
||||
keytool -genkey -v -keystore release-keystore.jks -storepass password -alias release -keypass password -keyalg RSA -keysize 2048 -validity 10000
|
||||
```
|
||||
|
||||
Next, put the keystore configuration in a file called `release-keystore.properties`,
|
||||
which should look similar to this example:
|
||||
|
||||
```plaintext
|
||||
storeFile=.secure_files/release-keystore.jks
|
||||
keyAlias=release
|
||||
keyPassword=password
|
||||
storePassword=password
|
||||
```
|
||||
|
||||
After these files are created:
|
||||
|
||||
- [Upload them as Secure Files](secure_files/index.md) in the GitLab project
|
||||
so they can be used in CI/CD jobs.
|
||||
- Add both files to your `.gitignore` file so they aren't committed to version control.
|
||||
|
||||
#### Configure Gradle
|
||||
|
||||
The next step is to configure Gradle to use the newly created keystore. In the app's `build.gradle` file:
|
||||
|
||||
1. Immediately after the plugins section, add:
|
||||
|
||||
```gradle
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('.secure_files/release-keystore.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
```
|
||||
|
||||
1. Anywhere in the `android` block, add:
|
||||
|
||||
```gradle
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Add the `signingConfig` to the release build type:
|
||||
|
||||
```gradle
|
||||
signingConfig signingConfigs.release
|
||||
```
|
||||
|
||||
With this configuration in place, you can use fastlane to build & sign the app
|
||||
with the files stored in secure files.
|
||||
|
||||
For example:
|
||||
|
||||
- Sample `fastlane/Fastfile` file:
|
||||
|
||||
```ruby
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Create and sign a new build"
|
||||
lane :build do
|
||||
gradle(tasks: ["clean", "assembleRelease", "bundleRelease"])
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- Sample `.gitlab-ci.yml` file:
|
||||
|
||||
```yaml
|
||||
build:
|
||||
image: fabernovel/android:api-33-v1.7.0
|
||||
stage: build
|
||||
script:
|
||||
- apt update -y && apt install -y curl
|
||||
- curl --silent "https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer" | bash
|
||||
- fastlane build
|
||||
```
|
||||
|
||||
### Code sign iOS projects with fastlane
|
||||
|
||||
To set up code signing for iOS, you must:
|
||||
|
||||
1. Install fastlane locally so you can upload your signing certificates to GitLab.
|
||||
1. Configure the build to use those files.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [How to build and release an iOS app to Test Flight with GitLab](https://youtu.be/Ar8IsBgP1as).
|
||||
|
||||
#### Initialize fastlane
|
||||
|
||||
With fastlane installed, start by running:
|
||||
|
||||
```shell
|
||||
fastlane init
|
||||
```
|
||||
|
||||
This command creates a `fastlane` folder in the project with an `Appfile` and a stubbed-out `fastfile`.
|
||||
During this process, you are prompted for App Store Connect login credentials to generate an app identifier and an App Store app if they don't already exist.
|
||||
|
||||
The next step sets up fastlane match to manage code signing files for the project.
|
||||
Run the following command to generate a `Matchfile` with the configuration:
|
||||
|
||||
```shell
|
||||
fastlane match init
|
||||
```
|
||||
|
||||
This command prompts you to:
|
||||
|
||||
- Choose which storage backend you want to use, you must select `gitlab_secure_files`.
|
||||
- Input your project path, for example `gitlab-org/gitlab`.
|
||||
|
||||
#### Generate and upload certificates
|
||||
|
||||
Run the following command to generate certificates and profiles in the Apple Developer portal
|
||||
and upload those files to GitLab:
|
||||
|
||||
```shell
|
||||
PRIVATE_TOKEN=YOUR-TOKEN bundle exec fastlane match development
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
||||
- `YOUR-TOKEN` must be either a personal or project access token with Maintainer role for the GitLab project.
|
||||
- Replace `development` with the type of build you want to sign, for example `appstore` or `ad-hoc`.
|
||||
|
||||
You can view the files in your project's CI/CD settings as soon as the command completes.
|
||||
|
||||
#### Upload-only
|
||||
|
||||
If you have already created signing certificates and provisioning profiles for your project,
|
||||
you can optionally use `fastlane match import` to load your existing files into GitLab:
|
||||
|
||||
```shell
|
||||
PRIVATE_TOKEN=YOUR-TOKEN bundle exec fastlane match import
|
||||
```
|
||||
|
||||
You are prompted to input the path to your files. After you provide those details,
|
||||
your files are uploaded and visible in your project's CI/CD settings.
|
||||
If prompted for the `git_url` during the import, it is safe to leave it blank and press <kbd>enter</kbd>.
|
||||
|
||||
With this configuration in place, you can use fastlane to build and sign the app with
|
||||
the files stored in secure files.
|
||||
|
||||
For example:
|
||||
|
||||
- Sample `fastlane/Fastfile` file:
|
||||
|
||||
```ruby
|
||||
default_platform(:ios)
|
||||
|
||||
platform :ios do
|
||||
desc "Build and sign the application for development"
|
||||
lane :build do
|
||||
setup_ci
|
||||
|
||||
match(type: 'development', readonly: is_ci)
|
||||
|
||||
build_app(
|
||||
project: "ios demo.xcodeproj",
|
||||
scheme: "ios demo",
|
||||
configuration: "Debug",
|
||||
export_method: "development"
|
||||
)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- Sample `.gitlab-ci.yml` file:
|
||||
|
||||
```yaml
|
||||
build_ios:
|
||||
image: macos-12-xcode-14
|
||||
stage: build
|
||||
script:
|
||||
- fastlane build
|
||||
tags:
|
||||
- saas-macos-medium-m1
|
||||
```
|
||||
|
||||
## Distribution
|
||||
|
||||
Signed builds can be uploaded to the Google Play Store or Apple App Store by using
|
||||
the Mobile DevOps Distribution integrations.
|
||||
|
||||
### Android distribution with Google Play integration and fastlane
|
||||
|
||||
To create an Android distribution with Google Play integration and fastlane, you must:
|
||||
|
||||
1. [Create a Google service account](https://docs.fastlane.tools/actions/supply/#setup)
|
||||
in Google Cloud Platform and grant that account access to the project in Google Play.
|
||||
1. [Enable the Google Play integration](#enable-google-play-integration).
|
||||
1. Add the release step to your pipeline.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Google Play integration demo](https://youtu.be/Fxaj3hna4uk).
|
||||
|
||||
#### Enable Google Play Integration
|
||||
|
||||
Use the [Google Play integration](../user/project/integrations/google_play.md),
|
||||
to configure your CI/CD pipelines to connect to the [Google Play Console](https://play.google.com/console/developers)
|
||||
to build and release Android apps. To enable the integration:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Google Play**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. In **Package name**, enter the package name of the app. For example, `com.gitlab.app_name`.
|
||||
1. In **Service account key (.JSON)** drag or upload your key file.
|
||||
1. Select **Save changes**.
|
||||
|
||||
With the integration enabled, you can use fastlane to distribute a build to Google Play.
|
||||
|
||||
For example:
|
||||
|
||||
- Sample `fastlane/Fastfile`:
|
||||
|
||||
```ruby
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Submit a new Beta build to the Google Play store"
|
||||
lane :beta do
|
||||
upload_to_play_store(
|
||||
track: 'internal',
|
||||
aab: 'app/build/outputs/bundle/release/app-release.aab',
|
||||
release_status: 'draft'
|
||||
)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- Sample `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
beta:
|
||||
image: fabernovel/android:api-33-v1.7.0
|
||||
stage: beta
|
||||
script:
|
||||
- fastlane beta
|
||||
```
|
||||
|
||||
### iOS distribution Apple Store integration and fastlane
|
||||
|
||||
To create an iOS distribution with the Apple Store integration and fastlane, you must:
|
||||
|
||||
1. Generate an API Key for App Store Connect API. In the Apple App Store Connect portal,
|
||||
[generate a new private key for your project](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api).
|
||||
1. [Enable the Apple App Store Connect integration](#enable-the-apple-app-store-connect-integration).
|
||||
1. Add the release step to your pipeline and fastlane configuration.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Apple App Store Connect integration demo](https://youtu.be/CwzAWVgJeK8).
|
||||
<!-- Video published on 2023-03-17 -->
|
||||
|
||||
#### Enable the Apple App Store Connect integration
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have an Apple ID enrolled in the [Apple Developer Program](https://developer.apple.com/programs/enroll/).
|
||||
- You must [generate a new private key](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) for your project in the Apple App Store Connect portal.
|
||||
|
||||
Use the Apple App Store Connect integration to configure your CI/CD pipelines to connect to [App Store Connect](https://appstoreconnect.apple.com).
|
||||
With this integration, you can build and release apps for iOS, iPadOS, macOS, tvOS, and watchOS.
|
||||
|
||||
To enable the Apple App Store Connect integration in GitLab:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Apple App Store Connect**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Provide the Apple App Store Connect configuration information:
|
||||
- **Issuer ID**: The Apple App Store Connect issuer ID.
|
||||
- **Key ID**: The key ID of the generated private key.
|
||||
- **Private key**: The generated private key. You can download this key only once.
|
||||
- **Protected branches and tags only**: Enable to set variables on protected branches and tags only.
|
||||
1. Select **Save changes**.
|
||||
|
||||
With the integration enabled, you can use fastlane to distribute a build to TestFlight
|
||||
and the Apple App Store.
|
||||
|
||||
For example:
|
||||
|
||||
- Sample `fastlane/Fastfile`:
|
||||
|
||||
```ruby
|
||||
default_platform(:ios)
|
||||
|
||||
platform :ios do
|
||||
desc "Build and sign the application for distribution, upload to TestFlight"
|
||||
lane :beta do
|
||||
setup_ci
|
||||
|
||||
match(type: 'appstore', readonly: is_ci)
|
||||
|
||||
app_store_connect_api_key
|
||||
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number(initial_build_number: 1) + 1,
|
||||
xcodeproj: "ios demo.xcodeproj"
|
||||
)
|
||||
|
||||
build_app(
|
||||
project: "ios demo.xcodeproj",
|
||||
scheme: "ios demo",
|
||||
configuration: "Release",
|
||||
export_method: "app-store"
|
||||
)
|
||||
|
||||
upload_to_testflight
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- Sample `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
beta_ios:
|
||||
image: macos-12-xcode-14
|
||||
stage: beta
|
||||
script:
|
||||
- fastlane beta
|
||||
```
|
||||
|
||||
## Review apps for mobile
|
||||
|
||||
You can use [review apps](review_apps/index.md) to preview changes directly from a merge request.
|
||||
This feature is possible through an integration with [Appetize.io](https://appetize.io/).
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Review apps for mobile setup instructions](https://youtu.be/X15mI19TXa4).
|
||||
|
||||
To get started, see the [setup instructions](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/readme/-/issues/15).
|
||||
|
||||
## Sample Reference Projects
|
||||
|
||||
See the sample reference projects below for complete build, sign, and release pipeline examples for various platforms. A list of all available projects can be found in [the Mobile DevOps Demo Projects group](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/demo-projects/).
|
||||
|
||||
- [Android Demo](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/demo-projects/android_demo)
|
||||
- [iOS Demo](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/demo-projects/ios-demo)
|
||||
- [Flutter Demo](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/demo-projects/flutter-demo)
|
||||
|
||||
## Mobile DevOps Blog
|
||||
|
||||
Additional reference material can be found in the [DevOps section](https://about.gitlab.com/blog/categories/devops/) of the GitLab blog.
|
||||
<!-- This redirect file can be deleted after <2024-12-04>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ This tool checks for syntax and logical errors but goes into more detail than th
|
|||
automatic [validation](#validate-ci-configuration) in the editor.
|
||||
|
||||
The results are updated in real-time. Any changes you make to the configuration are
|
||||
reflected in the CI lint. It displays the same results as the existing [CI Lint tool](../lint.md).
|
||||
reflected in the CI lint. It displays the same results as the existing [CI Lint tool](../yaml/lint.md).
|
||||
|
||||

|
||||
|
||||
|
|
@ -63,7 +63,7 @@ reflected in the CI lint. It displays the same results as the existing [CI Lint
|
|||
To look for pipeline syntax and logic issues, you can simulate the creation of a
|
||||
GitLab CI/CD pipeline in the **Validate** tab. A pipeline simulation can help find
|
||||
problems such as incorrect `rules` and `needs` job dependencies, and is similar to
|
||||
simulations in the [CI Lint tool](../lint.md#simulate-a-pipeline).
|
||||
simulations in the [CI Lint tool](../yaml/lint.md#simulate-a-pipeline).
|
||||
|
||||
## View included CI/CD configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Pipeline Execution
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Using Git submodules with GitLab CI/CD
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
Use [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) to keep
|
||||
a Git repository as a subdirectory of another Git repository. You can clone another
|
||||
repository into your project and keep your commits separate.
|
||||
|
||||
## Configure the `.gitmodules` file
|
||||
|
||||
When you use Git submodules, your project should have a file named `.gitmodules`.
|
||||
You have multiple options to configure it to work in a GitLab CI/CD job.
|
||||
|
||||
### Using absolute URLs
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3198) in GitLab Runner 15.11.
|
||||
|
||||
For example, your generated `.gitmodules` configuration might look like the following if:
|
||||
|
||||
- Your project is located at `https://gitlab.com/secret-group/my-project`.
|
||||
- Your project depends on `https://gitlab.com/group/project`, which you want
|
||||
to include as a submodule.
|
||||
- You check out your sources with an SSH address like `git@gitlab.com:secret-group/my-project.git`.
|
||||
|
||||
```ini
|
||||
[submodule "project"]
|
||||
path = project
|
||||
url = git@gitlab.com:group/project.git
|
||||
```
|
||||
|
||||
In this case, use the [`GIT_SUBMODULE_FORCE_HTTPS`](configure_runners.md#rewrite-submodule-urls-to-https) variable
|
||||
to instruct GitLab Runner to convert the URL to HTTPS before it clones the submodules.
|
||||
|
||||
Alternatively, if you also use HTTPS locally, you can configure an HTTPS URL:
|
||||
|
||||
```ini
|
||||
[submodule "project"]
|
||||
path = project
|
||||
url = https://gitlab.com/group/project.git
|
||||
```
|
||||
|
||||
You do not need to configure additional variables in this case, but you need to use a
|
||||
[personal access token](../../user/profile/personal_access_tokens.md) to clone it locally.
|
||||
|
||||
### Using relative URLs
|
||||
|
||||
WARNING:
|
||||
If you use relative URLs, submodules may resolve incorrectly in forking workflows.
|
||||
Use absolute URLs instead if you expect your project to have forks.
|
||||
|
||||
When your submodule is on the same GitLab server, you can also use relative URLs in
|
||||
your `.gitmodules` file:
|
||||
|
||||
```ini
|
||||
[submodule "project"]
|
||||
path = project
|
||||
url = ../../project.git
|
||||
```
|
||||
|
||||
The above configuration instructs Git to automatically deduce the URL to
|
||||
use when cloning sources. You can clone with HTTPS in all your CI/CD jobs, and you
|
||||
can continue to use SSH to clone locally.
|
||||
|
||||
For submodules not located on the same GitLab server, always use the full URL:
|
||||
|
||||
```ini
|
||||
[submodule "project-x"]
|
||||
path = project-x
|
||||
url = https://gitserver.com/group/project-x.git
|
||||
```
|
||||
|
||||
## Use Git submodules in CI/CD jobs
|
||||
|
||||
To make submodules work correctly in CI/CD jobs:
|
||||
|
||||
1. You can set the `GIT_SUBMODULE_STRATEGY` variable to either `normal` or `recursive`
|
||||
to tell the runner to [fetch your submodules before the job](configure_runners.md#git-submodule-strategy):
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
```
|
||||
|
||||
1. For submodules located on the same GitLab server and configured with a Git or SSH URL, make sure
|
||||
you set the [`GIT_SUBMODULE_FORCE_HTTPS`](configure_runners.md#rewrite-submodule-urls-to-https) variable.
|
||||
|
||||
1. Use `GIT_SUBMODULE_DEPTH` to configure the cloning depth of submodules independently of the [`GIT_DEPTH`](configure_runners.md#shallow-cloning) variable:
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
GIT_SUBMODULE_DEPTH: 1
|
||||
```
|
||||
|
||||
1. You can filter or exclude specific submodules to control which submodules are synchronized using
|
||||
[`GIT_SUBMODULE_PATHS`](configure_runners.md#sync-or-exclude-specific-submodules-from-ci-jobs).
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
GIT_SUBMODULE_PATHS: submoduleA submoduleB
|
||||
```
|
||||
|
||||
1. You can provide additional flags to control advanced checkout behavior using
|
||||
[`GIT_SUBMODULE_UPDATE_FLAGS`](configure_runners.md#git-submodule-update-flags).
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
GIT_SUBMODULE_UPDATE_FLAGS: --jobs 4
|
||||
```
|
||||
|
||||
If you use the [`CI_JOB_TOKEN`](../jobs/ci_job_token.md) to clone a submodule in a
|
||||
pipeline job, the user executing the job must be assigned to a role that has
|
||||
[permission](../../user/permissions.md#cicd) to trigger a pipeline
|
||||
in the upstream submodule project. Additionally, [CI/CD job token access](../jobs/ci_job_token.md#control-job-token-access-to-your-project) must be properly configured in the upstream submodule project.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Can't find the `.gitmodules` file
|
||||
|
||||
The `.gitmodules` file might be hard to find because it is usually a hidden file.
|
||||
You can check documentation for your specific OS to learn how to find and display
|
||||
hidden files.
|
||||
|
||||
If there is no `.gitmodules` file, it's possible the submodule settings are in a
|
||||
[`git config`](https://www.atlassian.com/git/tutorials/setting-up-a-repository/git-config) file.
|
||||
|
||||
### `fatal: run_command returned non-zero status` error
|
||||
|
||||
This error can happen in a job when working with submodules and the `GIT_STRATEGY` is set to `fetch`.
|
||||
|
||||
Setting the `GIT_STRATEGY` to `clone` should resolve the issue.
|
||||
|
|
@ -13,7 +13,7 @@ DETAILS:
|
|||
|
||||
Hosted runners on macOS provide an on-demand macOS environment, fully integrated with GitLab [CI/CD](../../../ci/index.md).
|
||||
You can use these runners to build, test, and deploy apps for the Apple ecosystem (macOS, iOS, watchOS, tvOS).
|
||||
Our [Mobile DevOps section](../../../ci/mobile_devops.md#ios-build-environments) provides features, documentation, and guidance on building and deploying mobile applications for iOS.
|
||||
Our [Mobile DevOps section](../../../ci/jobs/mobile_devops.md#ios-build-environments) provides features, documentation, and guidance on building and deploying mobile applications for iOS.
|
||||
|
||||
Hosted runners on macOS are in [beta](../../../policy/experiment-beta-support.md#beta) and available for open source programs and customers in Premium and Ultimate plans.
|
||||
[General availability](../../../policy/experiment-beta-support.md#generally-available-ga) of Hosted runners on macOS is proposed in [epic 8267](https://gitlab.com/groups/gitlab-org/-/epics/8267).
|
||||
|
|
@ -86,7 +86,7 @@ Before you can integrate GitLab with Apple services, install to a device, or dep
|
|||
Included in each runner on macOS VM image is [fastlane](https://fastlane.tools/),
|
||||
an open-source solution aimed at simplifying mobile app deployment.
|
||||
|
||||
For information about how to set up code signing for your application, see the instructions in the [Mobile DevOps documentation](../../../ci/mobile_devops.md#code-sign-ios-projects-with-fastlane).
|
||||
For information about how to set up code signing for your application, see the instructions in the [Mobile DevOps documentation](../../../ci/jobs/mobile_devops.md#code-sign-ios-projects-with-fastlane).
|
||||
|
||||
Related topics:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,231 +1,11 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Pipeline Security
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: '../jobs/ssh_keys.md'
|
||||
remove_date: '2024-12-04'
|
||||
---
|
||||
|
||||
# Using SSH keys with GitLab CI/CD
|
||||
This document was moved to [another location](../jobs/ssh_keys.md).
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
GitLab currently doesn't have built-in support for managing SSH keys in a build
|
||||
environment (where the GitLab Runner runs).
|
||||
|
||||
Use SSH keys when you want to:
|
||||
|
||||
- Check out internal submodules.
|
||||
- Download private packages using your package manager. For example, Bundler.
|
||||
- Deploy your application to your own server or, for example, Heroku.
|
||||
- Execute SSH commands from the build environment to a remote server.
|
||||
- Rsync files from the build environment to a remote server.
|
||||
|
||||
If anything of the above rings a bell, then you most likely need an SSH key.
|
||||
|
||||
The most widely supported method is to inject an SSH key into your build
|
||||
environment by extending your `.gitlab-ci.yml`, and it's a solution that works
|
||||
with any type of [executor](https://docs.gitlab.com/runner/executors/)
|
||||
(like Docker or shell, for example).
|
||||
|
||||
## Create and use an SSH key
|
||||
|
||||
To create and use an SSH key in GitLab CI/CD:
|
||||
|
||||
1. [Create a new SSH key pair](../../user/ssh.md#generate-an-ssh-key-pair) locally with `ssh-keygen`.
|
||||
1. Add the private key as a [file type CI/CD variable](../variables/index.md#for-a-project) to
|
||||
your project. The variable value must end in a newline (`LF` character). To add a newline, press <kbd>Enter</kbd> or <kbd>Return</kbd>
|
||||
at the end of the last line of the SSH key before saving it in the CI/CD settings.
|
||||
1. Run the [`ssh-agent`](https://linux.die.net/man/1/ssh-agent) in the job, which loads
|
||||
the private key.
|
||||
1. Copy the public key to the servers you want to have access to (usually in
|
||||
`~/.ssh/authorized_keys`) or add it as a [deploy key](../../user/project/deploy_keys/index.md)
|
||||
if you are accessing a private GitLab repository.
|
||||
|
||||
In the following example, the `ssh-add -` command does not display the value of
|
||||
`$SSH_PRIVATE_KEY` in the job log, though it could be exposed if you enable
|
||||
[debug logging](../variables/index.md#enable-debug-logging). You might also want to
|
||||
check the [visibility of your pipelines](../pipelines/settings.md#change-which-users-can-view-your-pipelines).
|
||||
|
||||
## SSH keys when using the Docker executor
|
||||
|
||||
When your CI/CD jobs run inside Docker containers (meaning the environment is
|
||||
contained) and you want to deploy your code in a private server, you need a way
|
||||
to access it. In this case, you can use an SSH key pair.
|
||||
|
||||
1. You first must create an SSH key pair. For more information, follow
|
||||
the instructions to [generate an SSH key](../../user/ssh.md#generate-an-ssh-key-pair).
|
||||
**Do not** add a passphrase to the SSH key, or the `before_script` will
|
||||
prompt for it.
|
||||
|
||||
1. Create a new [file type CI/CD variable](../variables/index.md#for-a-project).
|
||||
- In the **Key** field, enter `SSH_PRIVATE_KEY`.
|
||||
- In the **Value** field, paste the content of your _private_ key from the key pair that you created earlier.
|
||||
Make sure the file ends with a newline. To add a newline, press
|
||||
<kbd>Enter</kbd> or <kbd>Return</kbd> at the end of the last line of the SSH key before saving your changes.
|
||||
|
||||
1. Modify your `.gitlab-ci.yml` with a `before_script` action. In the following
|
||||
example, a Debian based image is assumed. Edit to your needs:
|
||||
|
||||
```yaml
|
||||
before_script:
|
||||
##
|
||||
## Install ssh-agent if not already installed, it is required by Docker.
|
||||
## (change apt-get to yum if you use an RPM-based image)
|
||||
##
|
||||
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||
|
||||
##
|
||||
## Run ssh-agent (inside the build environment)
|
||||
##
|
||||
- eval $(ssh-agent -s)
|
||||
|
||||
##
|
||||
## Give the right permissions, otherwise ssh-add will refuse to add files
|
||||
## Add the SSH key stored in SSH_PRIVATE_KEY file type CI/CD variable to the agent store
|
||||
##
|
||||
- chmod 400 "$SSH_PRIVATE_KEY"
|
||||
- ssh-add "$SSH_PRIVATE_KEY"
|
||||
|
||||
##
|
||||
## Create the SSH directory and give it the right permissions
|
||||
##
|
||||
- mkdir -p ~/.ssh
|
||||
- chmod 700 ~/.ssh
|
||||
|
||||
##
|
||||
## Optionally, if you will be using any Git commands, set the user name and
|
||||
## and email.
|
||||
##
|
||||
# - git config --global user.email "user@example.com"
|
||||
# - git config --global user.name "User name"
|
||||
```
|
||||
|
||||
The [`before_script`](../yaml/index.md#before_script) can be set as a default
|
||||
or per-job.
|
||||
|
||||
1. Make sure the private server's [SSH host keys are verified](#verifying-the-ssh-host-keys).
|
||||
|
||||
1. As a final step, add the _public_ key from the one you created in the first
|
||||
step to the services that you want to have an access to from inside the build
|
||||
environment. If you are accessing a private GitLab repository you must add
|
||||
it as a [deploy key](../../user/project/deploy_keys/index.md).
|
||||
|
||||
That's it! You can now have access to private servers or repositories in your
|
||||
build environment.
|
||||
|
||||
## SSH keys when using the Shell executor
|
||||
|
||||
If you are using the Shell executor and not Docker, it is easier to set up an
|
||||
SSH key.
|
||||
|
||||
You can generate the SSH key from the machine that GitLab Runner is installed
|
||||
on, and use that key for all projects that are run on this machine.
|
||||
|
||||
1. First, sign in to the server that runs your jobs.
|
||||
|
||||
1. Then, from the terminal, sign in as the `gitlab-runner` user:
|
||||
|
||||
```shell
|
||||
sudo su - gitlab-runner
|
||||
```
|
||||
|
||||
1. Generate the SSH key pair as described in the instructions to
|
||||
[generate an SSH key](../../user/ssh.md#generate-an-ssh-key-pair).
|
||||
**Do not** add a passphrase to the SSH key, or the `before_script` will
|
||||
prompt for it.
|
||||
|
||||
1. As a final step, add the _public_ key from the one you created earlier to the
|
||||
services that you want to have an access to from inside the build environment.
|
||||
If you are accessing a private GitLab repository you must add it as a
|
||||
[deploy key](../../user/project/deploy_keys/index.md).
|
||||
|
||||
After generating the key, try to sign in to the remote server to accept the
|
||||
fingerprint:
|
||||
|
||||
```shell
|
||||
ssh example.com
|
||||
```
|
||||
|
||||
For accessing repositories on GitLab.com, you would use `git@gitlab.com`.
|
||||
|
||||
## Verifying the SSH host keys
|
||||
|
||||
It is a good practice to check the private server's own public key to make sure
|
||||
you are not being targeted by a man-in-the-middle attack. If anything
|
||||
suspicious happens, you notice it because the job fails (the SSH
|
||||
connection fails when the public keys don't match).
|
||||
|
||||
To find out the host keys of your server, run the `ssh-keyscan` command from a
|
||||
trusted network (ideally, from the private server itself):
|
||||
|
||||
```shell
|
||||
## Use the domain name
|
||||
ssh-keyscan example.com
|
||||
|
||||
## Or use an IP
|
||||
ssh-keyscan 10.0.2.2
|
||||
```
|
||||
|
||||
Create a new [file type CI/CD variable](../variables/index.md#use-file-type-cicd-variables)
|
||||
with `SSH_KNOWN_HOSTS` as "Key", and as a "Value" add the output of `ssh-keyscan`.
|
||||
Make sure the file ends with a newline. To add a newline, press <kbd>Enter</kbd> or <kbd>Return</kbd>
|
||||
at the end of the last line of the SSH key before saving your changes.
|
||||
|
||||
If you must connect to multiple servers, all the server host keys
|
||||
must be collected in the **Value** of the variable, one key per line.
|
||||
|
||||
NOTE:
|
||||
By using a file type CI/CD variable instead of `ssh-keyscan` directly inside
|
||||
`.gitlab-ci.yml`, it has the benefit that you don't have to change `.gitlab-ci.yml`
|
||||
if the host domain name changes for some reason. Also, the values are predefined
|
||||
by you, meaning that if the host keys suddenly change, the CI/CD job doesn't fail,
|
||||
so there's something wrong with the server or the network.
|
||||
|
||||
Now that the `SSH_KNOWN_HOSTS` variable is created, in addition to the
|
||||
[content of `.gitlab-ci.yml`](#ssh-keys-when-using-the-docker-executor)
|
||||
above, you must add:
|
||||
|
||||
```yaml
|
||||
before_script:
|
||||
##
|
||||
## Assuming you created the SSH_KNOWN_HOSTS file type CI/CD variable, uncomment the
|
||||
## following two lines.
|
||||
##
|
||||
- cp "$SSH_KNOWN_HOSTS" ~/.ssh/known_hosts
|
||||
- chmod 644 ~/.ssh/known_hosts
|
||||
|
||||
##
|
||||
## Alternatively, use ssh-keyscan to scan the keys of your private server.
|
||||
## Replace example.com with your private server's domain name. Repeat that
|
||||
## command if you have more than one server to connect to. Include the -t
|
||||
## flag to specify the key type.
|
||||
##
|
||||
# - ssh-keyscan -t rsa,ed25519 example.com >> ~/.ssh/known_hosts
|
||||
# - chmod 644 ~/.ssh/known_hosts
|
||||
|
||||
##
|
||||
## You can optionally disable host key checking. Be aware that by adding that
|
||||
## you are susceptible to man-in-the-middle attacks.
|
||||
## WARNING: Use this only with the Docker executor, if you use it with shell
|
||||
## you will overwrite your user's SSH config.
|
||||
##
|
||||
# - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config'
|
||||
```
|
||||
|
||||
## Use SSH key without a file type CI/CD variable
|
||||
|
||||
If you do not want to use a file type CI/CD variable, the [example SSH Project](https://gitlab.com/gitlab-examples/ssh-private-key/)
|
||||
shows an alternative method. This method uses a regular CI/CD variable instead of
|
||||
the file type variable recommended above.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `Error loading key "/builds/path/SSH_PRIVATE_KEY": error in libcrypto` message
|
||||
|
||||
This message can be returned if there is a formatting error with the SSH key.
|
||||
|
||||
When saving the SSH key as a [file type CI/CD variable](../variables/index.md#use-file-type-cicd-variables),
|
||||
the value must end with a newline (`LF` character). To add a newline, press <kbd>Enter</kbd> or <kbd>Return</kbd>
|
||||
at the end of the `-----END OPENSSH PRIVATE KEY-----` line of the SSH key before saving
|
||||
the variable.
|
||||
<!-- This redirect file can be deleted after <2024-12-04>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Pipeline Authoring
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Validate GitLab CI/CD configuration
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
Use the CI Lint tool to check the validity of GitLab CI/CD configuration.
|
||||
You can validate the syntax from a `.gitlab-ci.yml` file or any other sample CI/CD configuration.
|
||||
This tool checks for syntax and logic errors, and can simulate pipeline
|
||||
creation to try to find more complicated configuration problems.
|
||||
|
||||
If you use the [pipeline editor](../pipeline_editor/index.md), it verifies configuration
|
||||
syntax automatically.
|
||||
|
||||
If you use VS Code, you can validate your CI/CD configuration with the
|
||||
[GitLab Workflow VS Code extension](../../editor_extensions/visual_studio_code/index.md).
|
||||
|
||||
## Check CI/CD syntax
|
||||
|
||||
The CI lint tool checks the syntax of GitLab CI/CD configuration, including
|
||||
configuration added with the [`includes` keyword](index.md#include).
|
||||
|
||||
To check CI/CD configuration with the CI lint tool:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Build > Pipeline editor**.
|
||||
1. Select the **Validate** tab.
|
||||
1. Select **Lint CI/CD sample**.
|
||||
1. Paste a copy of the CI/CD configuration you want to check into the text box.
|
||||
1. Select **Validate**.
|
||||
|
||||
## Simulate a pipeline
|
||||
|
||||
You can simulate the creation of a GitLab CI/CD pipeline to find more complicated issues,
|
||||
including problems with [`needs`](index.md#needs) and [`rules`](index.md#rules)
|
||||
configuration. A simulation runs as a Git `push` event on the default branch.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have [permissions](../../user/permissions.md#project-members-permissions)
|
||||
to create pipelines on this branch to validate with a simulation.
|
||||
|
||||
To simulate a pipeline:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Build > Pipeline editor**.
|
||||
1. Select the **Validate** tab.
|
||||
1. Select **Lint CI/CD sample**.
|
||||
1. Paste a copy of the CI/CD configuration you want to check into the text box.
|
||||
1. Select **Simulate pipeline creation for the default branch**.
|
||||
1. Select **Validate**.
|
||||
|
|
@ -198,6 +198,10 @@ As an alternative to using hosted runners, you can use your own runners for your
|
|||
|
||||
To use self-managed runners, install [GitLab Runner](https://docs.gitlab.com/runner/install/) on infrastructure that you own or manage.
|
||||
|
||||
#### OpenID Connect
|
||||
|
||||
You can use [GitLab as an OpenID Connect identity provider](../../integration/openid_connect_provider.md). If you use an IP allowlist to restrict access to your instance, you can [enable OpenID Connect requests](../../administration/dedicated/configure_instance.md#enable-openid-connect-for-your-ip-allowlist) while maintaining your IP restrictions.
|
||||
|
||||
#### Migration
|
||||
|
||||
To help you migrate your data to GitLab Dedicated, choose from the following options:
|
||||
|
|
|
|||
|
|
@ -14,6 +14,5 @@ Use CI/CD to generate your application.
|
|||
| [**Getting started**](../ci/index.md)<br>Overview of how CI/CD features fit together. | [**CI/CD YAML syntax reference**](../ci/yaml/index.md)<br>Pipeline configuration keywords, syntax, examples, inputs. | [**Runners**](https://docs.gitlab.com/runner/)<br>Installation, configuration, job execution. |
|
||||
| [**Pipelines**](../ci/pipelines/index.md)<br>Configuration, automation, stages, schedules, efficiency. | [**Jobs**](../ci/jobs/index.md)<br>Configuration, rules, caching, artifacts, logs. | [**CI/CD components**](../ci/components/index.md)<br>Reusable, versioned CI/CD components for pipelines. |
|
||||
| [**CI/CD variables**](../ci/variables/index.md)<br>Configuration, usage, security. | [**Pipeline security**](../ci/pipelines/pipeline_security.md)<br>Secrets management, job tokens, secure files, cloud security. | [**Debugging**](../ci/debugging.md)<br>Configuration validation, warnings, errors, troubleshooting. |
|
||||
| [**Auto DevOps**](autodevops/index.md)<br>Automated DevOps, language detection, deployment, customization. | [**Testing**](../ci/testing/index.md)<br>Unit tests, integration tests, test reports, coverage, quality assurance. | [**SSH keys**](../ci/ssh_keys/index.md)<br>Authentication, secure access, deployment, remote execution, key management. |
|
||||
| [**Mobile DevOps**](../ci/mobile_devops.md)<br>Mobile apps, Android, build automation, app distribution. | [**Google cloud integration**](../ci/gitlab_google_cloud_integration/index.md)<br>Cloud services, Kubernetes deployments. | [**External repository integrations**](../ci/ci_cd_for_external_repos/index.md)<br>GitHub, Bitbucket, external sources, mirroring, cross-platform. |
|
||||
| [**Migrate to GitLab CI/CD**](../ci/migration/plan_a_migration.md)<br> Migrate from Jenkins, GitHub Actions, others. | | |
|
||||
| [**Auto DevOps**](autodevops/index.md)<br>Automated DevOps, language detection, deployment, customization. | [**Testing**](../ci/testing/index.md)<br>Unit tests, integration tests, test reports, coverage, quality assurance. | [**Google cloud integration**](../ci/gitlab_google_cloud_integration/index.md)<br>Cloud services, Kubernetes deployments. |
|
||||
| [**Migrate to GitLab CI/CD**](../ci/migration/plan_a_migration.md)<br> Migrate from Jenkins, GitHub Actions, others. | [**External repository integrations**](../ci/ci_cd_for_external_repos/index.md)<br>GitHub, Bitbucket, external sources, mirroring, cross-platform. | |
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@ on the files make them readable to you but not accessible to others.
|
|||
## Configure two-factor authentication (2FA)
|
||||
|
||||
You can set up two-factor authentication (2FA) for
|
||||
[Git over SSH](../security/two_factor_authentication.md#2fa-for-git-over-ssh-operations). We recommend using
|
||||
[Git over SSH](../security/two_factor_authentication.md#2fa-for-git-over-ssh-operations). You should use
|
||||
[ED25519_SK](#ed25519_sk-ssh-keys) or [ECDSA_SK](#ecdsa_sk-ssh-keys) SSH keys.
|
||||
|
||||
## Use EGit on Eclipse
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ default:
|
|||
before_script:
|
||||
- cd $[[inputs.gem_path_prefix]]$[[inputs.gem_name]]
|
||||
- ruby -v # Print out ruby version for debugging
|
||||
- gem update --system
|
||||
- bundle_version=$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1 | sed -e 's/[[:space:]]//')
|
||||
- gem install bundler --version "$bundle_version" --no-document # Bundler is not installed with the image
|
||||
- bundle config # Show bundler configuration
|
||||
|
|
|
|||
|
|
@ -186,4 +186,4 @@ DEPENDENCIES
|
|||
rubocop-rails (<= 2.20)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.7
|
||||
2.5.18
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@
|
|||
module API
|
||||
class Notes < ::API::Base
|
||||
include PaginationParams
|
||||
include APIGuard
|
||||
helpers ::API::Helpers::NotesHelpers
|
||||
|
||||
before { authenticate! }
|
||||
|
||||
allow_access_with_scope :ai_workflows
|
||||
|
||||
urgency :low, [
|
||||
'/projects/:id/merge_requests/:noteable_id/notes',
|
||||
'/projects/:id/merge_requests/:noteable_id/notes/:note_id'
|
||||
|
|
|
|||
|
|
@ -22255,6 +22255,9 @@ msgstr ""
|
|||
msgid "Excluding merge commits. Limited to 6,000 commits."
|
||||
msgstr ""
|
||||
|
||||
msgid "Exclusions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Execution time"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -48267,9 +48270,6 @@ msgstr ""
|
|||
msgid "Secret Detection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secret Detection Configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secret push protection"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -48282,15 +48282,24 @@ msgstr ""
|
|||
msgid "Secret token."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecretDetection|Add exclusion"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecretDetection|Configure Secret Detection"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecretDetection|Feature not available"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecretDetection|No exclusions yet"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecretDetection|Only a project maintainer or owner can toggle this feature."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecretDetection|Secret detection configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecretDetection|Secret push protection is disabled"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -48305,6 +48314,9 @@ msgstr[1] ""
|
|||
msgid "SecretDetection|This feature has been disabled at the instance level. Please reach out to your instance administrator to request activation."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecretDetection|Use secret detection exclusions to specify file paths, raw values, and regex that should be excluded by secret detection in this project. %{linkStart}Learn more.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecretDetection|Warning: Potential secret detected"
|
||||
msgid_plural "SecretDetection|Warning: Potential secrets detected"
|
||||
msgstr[0] ""
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ function run_locally_or_in_container() {
|
|||
local cmd=$1
|
||||
local args=$2
|
||||
local files=$3
|
||||
local registry_url="registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.20-vale-3.6.1-markdownlint2-0.13.0-lychee-0.15.1"
|
||||
local registry_url="registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.20-vale-3.7.1-markdownlint2-0.13.0-lychee-0.15.1"
|
||||
|
||||
if hash "${cmd}" 2>/dev/null
|
||||
then
|
||||
|
|
|
|||
|
|
@ -49,25 +49,6 @@ RSpec.describe 'Projects > Settings > Repository > Branch rules settings', featu
|
|||
end
|
||||
end
|
||||
|
||||
context 'Branch rule details for a predefined rule', :js do
|
||||
let(:role) { :maintainer }
|
||||
|
||||
before do
|
||||
visit_branch_rules_settings
|
||||
wait_for_requests
|
||||
|
||||
click_button 'Add branch rule'
|
||||
click_button 'All branches'
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'does not create a rule if a user leaves it empty' do
|
||||
visit_branch_rules_settings
|
||||
expect(page).not_to have_css '[data-testid="branch-content"]', text: 'All branches'
|
||||
end
|
||||
end
|
||||
|
||||
context 'Branch rule details for custom rule', :js do
|
||||
let(:role) { :maintainer }
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ describe('Merge requests app component', () => {
|
|||
data: {
|
||||
currentUser: {
|
||||
id: 1,
|
||||
assignedMergeRequests: {
|
||||
mergeRequests: {
|
||||
count: 1,
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ describe('Merge requests query component', () => {
|
|||
data: {
|
||||
currentUser: {
|
||||
id: 1,
|
||||
reviewRequestedMergeRequests: {
|
||||
mergeRequests: {
|
||||
count: 0,
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
|
|
@ -40,7 +40,7 @@ describe('Merge requests query component', () => {
|
|||
data: {
|
||||
currentUser: {
|
||||
id: 1,
|
||||
assignedMergeRequests: {
|
||||
mergeRequests: {
|
||||
count: 0,
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
|
|
|
|||
|
|
@ -835,19 +835,10 @@ describe('Settings Panel', () => {
|
|||
});
|
||||
});
|
||||
describe('Duo', () => {
|
||||
describe('when the FF is on', () => {
|
||||
it('shows duo toggle', () => {
|
||||
wrapper = mountComponent({ glFeatures: { aiSettingsVueProject: true } });
|
||||
it('shows duo toggle', () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
expect(findDuoSettings().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
describe('when the FF is off', () => {
|
||||
it('does not show duo toggle', () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
expect(findDuoSettings().exists()).toBe(false);
|
||||
});
|
||||
expect(findDuoSettings().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ describe('Branch rules app', () => {
|
|||
glFeatures = { editBranchRules: true },
|
||||
queryHandler = branchRulesQuerySuccessHandler,
|
||||
mutationHandler = addRuleMutationSuccessHandler,
|
||||
provided = appProvideMock,
|
||||
} = {}) => {
|
||||
fakeApollo = createMockApollo([
|
||||
[branchRulesQuery, queryHandler],
|
||||
|
|
@ -63,7 +64,7 @@ describe('Branch rules app', () => {
|
|||
wrapper = mountExtended(BranchRules, {
|
||||
apolloProvider: fakeApollo,
|
||||
provide: {
|
||||
...appProvideMock,
|
||||
...provided,
|
||||
glFeatures,
|
||||
},
|
||||
stubs: {
|
||||
|
|
@ -120,28 +121,12 @@ describe('Branch rules app', () => {
|
|||
expect(findAddBranchRuleDropdown().props('toggleText')).toBe('Add branch rule');
|
||||
});
|
||||
|
||||
it('renders a dropdown containing predefined branch rules with actions', () => {
|
||||
it('renders a dropdown containing custom rules with actions', () => {
|
||||
expect(findAddBranchRuleDropdown().props('items')).toEqual([
|
||||
{ action: expect.any(Function), text: 'Branch name or pattern' },
|
||||
{ action: expect.any(Function), text: 'All branches' },
|
||||
{ action: expect.any(Function), text: 'All protected branches' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not render predefined branch rules when they are already set', async () => {
|
||||
const { nodes } = predefinedBranchRulesMockResponse.data.project.branchRules;
|
||||
|
||||
await createComponent({
|
||||
queryHandler: jest.fn().mockResolvedValue(predefinedBranchRulesMockResponse),
|
||||
});
|
||||
await findAddBranchRuleDropdown().vm.$emit('shown');
|
||||
await nextTick();
|
||||
|
||||
expect(findAddBranchRuleDropdown().props('items').length).toEqual(
|
||||
addBranchRulesItems.length - nodes.length,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a modal with correct props/attributes', () => {
|
||||
expect(findModal().props()).toMatchObject({
|
||||
title: I18N.createBranchRule,
|
||||
|
|
@ -193,6 +178,48 @@ describe('Branch rules app', () => {
|
|||
label: 'branch_rule_details',
|
||||
});
|
||||
});
|
||||
|
||||
describe('when status check and merge request approvals features are available', () => {
|
||||
describe.each`
|
||||
showStatusChecks | showApprovers
|
||||
${true} | ${false}
|
||||
${false} | ${true}
|
||||
${true} | ${true}
|
||||
`(
|
||||
'when showStatusChecks is $showStatusChecks and showApprovers is $showApprovers',
|
||||
({ showStatusChecks, showApprovers }) => {
|
||||
it(`renders a dropdown containing predefined branch rules with actions`, () => {
|
||||
createComponent({
|
||||
provided: {
|
||||
...appProvideMock,
|
||||
showStatusChecks,
|
||||
showApprovers,
|
||||
},
|
||||
});
|
||||
expect(findAddBranchRuleDropdown().props('items')).toEqual([
|
||||
{ action: expect.any(Function), text: 'Branch name or pattern' },
|
||||
{ action: expect.any(Function), text: 'All branches' },
|
||||
{ action: expect.any(Function), text: 'All protected branches' },
|
||||
]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('does not render predefined branch rules when they are already set', async () => {
|
||||
const { nodes } = predefinedBranchRulesMockResponse.data.project.branchRules;
|
||||
|
||||
await createComponent({
|
||||
queryHandler: jest.fn().mockResolvedValue(predefinedBranchRulesMockResponse),
|
||||
provided: { ...appProvideMock, showStatusChecks: true, showApprovers: true },
|
||||
});
|
||||
await findAddBranchRuleDropdown().vm.$emit('shown');
|
||||
await nextTick();
|
||||
|
||||
expect(findAddBranchRuleDropdown().props('items').length).toEqual(
|
||||
addBranchRulesItems.length - nodes.length,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Add branch rule when editBranchRules FF disabled', () => {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const MOCK_QUERY = {
|
|||
confidential: null,
|
||||
group_id: 1,
|
||||
language: ['C', 'JavaScript'],
|
||||
labels: ['60', '37'],
|
||||
label_name: ['Aftersync', 'Brist'],
|
||||
search: '*',
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import {
|
|||
TRACKING_ACTION_SHOW,
|
||||
} from '~/search/sidebar/components/label_filter/tracking';
|
||||
|
||||
import { labelFilterData } from '~/search/sidebar/components/label_filter/data';
|
||||
import { LABEL_FILTER_PARAM } from '~/search/sidebar/components/label_filter/data';
|
||||
|
||||
import {
|
||||
RECEIVE_AGGREGATIONS_SUCCESS,
|
||||
|
|
@ -62,6 +62,7 @@ describe('GlobalSearchSidebarLabelFilter', () => {
|
|||
state = createState({
|
||||
query: MOCK_QUERY,
|
||||
aggregations: MOCK_LABEL_AGGREGATIONS,
|
||||
navigation: {},
|
||||
...initialState,
|
||||
});
|
||||
|
||||
|
|
@ -343,21 +344,21 @@ describe('GlobalSearchSidebarLabelFilter', () => {
|
|||
|
||||
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||
|
||||
await findCheckboxGroup().vm.$emit('input', 6);
|
||||
await findCheckboxGroup().vm.$emit('input', [6]);
|
||||
await Vue.nextTick();
|
||||
});
|
||||
|
||||
it('trigger event', () => {
|
||||
expect(actionSpies.setQuery).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ key: labelFilterData?.filterParam, value: 6 }),
|
||||
expect.objectContaining({ key: LABEL_FILTER_PARAM, value: ['Cosche'] }),
|
||||
);
|
||||
});
|
||||
|
||||
it('sends tracking information when checkbox is selected', () => {
|
||||
expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_SELECT, TRACKING_LABEL_CHECKBOX, {
|
||||
label: TRACKING_LABEL_FILTER,
|
||||
property: 6,
|
||||
property: [6],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ describe('Global Search Store Actions', () => {
|
|||
it('calls visitUrl and setParams with the state.query', async () => {
|
||||
await testAction(actions.applyQuery, null, state, [], []);
|
||||
expect(urlUtils.visitUrl).toHaveBeenCalledWith(
|
||||
'https://test/?scope=issues&state=all&group_id=1&language%5B%5D=C&language%5B%5D=JavaScript&labels%5B%5D=60&labels%5B%5D=37&search=*',
|
||||
'https://test/?scope=issues&state=all&group_id=1&language%5B%5D=C&language%5B%5D=JavaScript&label_name%5B%5D=Aftersync&label_name%5B%5D=Brist&search=*',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -408,8 +408,8 @@ describe('Global Search Store Actions', () => {
|
|||
const expectedResult = [
|
||||
{
|
||||
payload: {
|
||||
key: 'labels',
|
||||
value: ['37'],
|
||||
key: 'label_name',
|
||||
value: ['Aftersync', 'Brist'],
|
||||
},
|
||||
type: 'SET_QUERY',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ describe('Global Search Store Getters', () => {
|
|||
|
||||
describe('unselectedLabels', () => {
|
||||
it('returns all labels that are not selected', () => {
|
||||
state.query.labels = ['60'];
|
||||
state.query.label_name = ['Brist'];
|
||||
expect(getters.unselectedLabels(state)).toStrictEqual([MOCK_LABEL_SEARCH_RESULT]);
|
||||
});
|
||||
});
|
||||
|
|
@ -151,9 +151,9 @@ describe('Global Search Store Getters', () => {
|
|||
describe('unappliedNewLabels', () => {
|
||||
it('returns all labels that are selected but not applied', () => {
|
||||
// Applied labels
|
||||
state.urlQuery.labels = ['37', '60'];
|
||||
state.urlQuery.label_name = ['Aftersync', 'Brist'];
|
||||
// Applied and selected labels
|
||||
state.query.labels = ['37', '6', '73', '60'];
|
||||
state.query.label_name = ['Aftersync', 'Cosche', 'Accent', 'Brist'];
|
||||
// Selected but unapplied labels
|
||||
// expect(getters.unappliedNewLabels(state)).toStrictEqual(MOCK_FILTERED_UNSELECTED_LABELS);
|
||||
expect(getters.unappliedNewLabels(state).map(({ key }) => key)).toStrictEqual(['6', '73']);
|
||||
|
|
|
|||
|
|
@ -418,5 +418,29 @@ RSpec.describe API::Notes, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated with a token that has the ai_workflows scope' do
|
||||
let(:oauth_token) { create(:oauth_access_token, user: user, scopes: [:ai_workflows]) }
|
||||
|
||||
context 'a post request creates a merge request note' do
|
||||
subject { post api(request_path, oauth_access_token: oauth_token), params: params }
|
||||
|
||||
it 'is successful' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
end
|
||||
end
|
||||
|
||||
context 'a get request returns a list of merge request notes' do
|
||||
subject { get api(request_path, oauth_access_token: oauth_token) }
|
||||
|
||||
it 'is successful' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue