Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
67fa8362ae
commit
2b6716fbb2
|
@ -53,7 +53,6 @@ eslint-report.html
|
|||
/db/data.yml
|
||||
/doc/code/*
|
||||
/dump.rdb
|
||||
/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_*.txt
|
||||
/jsconfig.json
|
||||
/lefthook-local.yml
|
||||
/log/*.log*
|
||||
|
|
|
@ -13,6 +13,8 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
|||
import IssueCardTimeInfo from 'ee_else_ce/issues/list/components/issue_card_time_info.vue';
|
||||
import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
|
||||
import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql';
|
||||
import getIssuesWithoutCrmQuery from 'ee_else_ce/issues/list/queries/get_issues_without_crm.query.graphql';
|
||||
import getIssuesCountsWithoutCrmQuery from 'ee_else_ce/issues/list/queries/get_issues_counts_without_crm.query.graphql';
|
||||
import createFlash, { FLASH_TYPES } from '~/flash';
|
||||
import { TYPE_USER } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
|
@ -154,7 +156,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
issues: {
|
||||
query: getIssuesQuery,
|
||||
query() {
|
||||
return this.hasCrmParameter ? getIssuesQuery : getIssuesWithoutCrmQuery;
|
||||
},
|
||||
variables() {
|
||||
return this.queryVariables;
|
||||
},
|
||||
|
@ -178,7 +182,9 @@ export default {
|
|||
debounce: 200,
|
||||
},
|
||||
issuesCounts: {
|
||||
query: getIssuesCountsQuery,
|
||||
query() {
|
||||
return this.hasCrmParameter ? getIssuesCountsQuery : getIssuesCountsWithoutCrmQuery;
|
||||
},
|
||||
variables() {
|
||||
return this.queryVariables;
|
||||
},
|
||||
|
@ -390,6 +396,12 @@ export default {
|
|||
...this.urlFilterParams,
|
||||
};
|
||||
},
|
||||
hasCrmParameter() {
|
||||
return (
|
||||
window.location.search.includes('crm_contact_id=') ||
|
||||
window.location.search.includes('crm_organization_id=')
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(newValue, oldValue) {
|
||||
|
|
|
@ -132,6 +132,8 @@ export const TOKEN_TYPE_CONFIDENTIAL = 'confidential';
|
|||
export const TOKEN_TYPE_ITERATION = 'iteration';
|
||||
export const TOKEN_TYPE_EPIC = 'epic_id';
|
||||
export const TOKEN_TYPE_WEIGHT = 'weight';
|
||||
export const TOKEN_TYPE_CONTACT = 'crm_contact';
|
||||
export const TOKEN_TYPE_ORGANIZATION = 'crm_organization';
|
||||
|
||||
export const filters = {
|
||||
[TOKEN_TYPE_AUTHOR]: {
|
||||
|
@ -294,4 +296,24 @@ export const filters = {
|
|||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_CONTACT]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'crmContactId',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'crm_contact_id',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_ORGANIZATION]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'crmOrganizationId',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'crm_organization_id',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -20,6 +20,8 @@ query getIssues(
|
|||
$releaseTag: [String!]
|
||||
$releaseTagWildcardId: ReleaseTagWildcardId
|
||||
$types: [IssueType!]
|
||||
$crmContactId: String
|
||||
$crmOrganizationId: String
|
||||
$not: NegatedIssueFilterInput
|
||||
$beforeCursor: String
|
||||
$afterCursor: String
|
||||
|
@ -43,6 +45,8 @@ query getIssues(
|
|||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
types: $types
|
||||
crmContactId: $crmContactId
|
||||
crmOrganizationId: $crmOrganizationId
|
||||
not: $not
|
||||
before: $beforeCursor
|
||||
after: $afterCursor
|
||||
|
@ -76,6 +80,8 @@ query getIssues(
|
|||
releaseTag: $releaseTag
|
||||
releaseTagWildcardId: $releaseTagWildcardId
|
||||
types: $types
|
||||
crmContactId: $crmContactId
|
||||
crmOrganizationId: $crmOrganizationId
|
||||
not: $not
|
||||
before: $beforeCursor
|
||||
after: $afterCursor
|
||||
|
|
|
@ -14,6 +14,8 @@ query getIssuesCount(
|
|||
$releaseTag: [String!]
|
||||
$releaseTagWildcardId: ReleaseTagWildcardId
|
||||
$types: [IssueType!]
|
||||
$crmContactId: String
|
||||
$crmOrganizationId: String
|
||||
$not: NegatedIssueFilterInput
|
||||
) {
|
||||
group(fullPath: $fullPath) @skip(if: $isProject) {
|
||||
|
@ -32,6 +34,8 @@ query getIssuesCount(
|
|||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
types: $types
|
||||
crmContactId: $crmContactId
|
||||
crmOrganizationId: $crmOrganizationId
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
|
@ -50,6 +54,8 @@ query getIssuesCount(
|
|||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
types: $types
|
||||
crmContactId: $crmContactId
|
||||
crmOrganizationId: $crmOrganizationId
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
|
@ -68,6 +74,8 @@ query getIssuesCount(
|
|||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
types: $types
|
||||
crmContactId: $crmContactId
|
||||
crmOrganizationId: $crmOrganizationId
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
|
@ -90,6 +98,8 @@ query getIssuesCount(
|
|||
releaseTag: $releaseTag
|
||||
releaseTagWildcardId: $releaseTagWildcardId
|
||||
types: $types
|
||||
crmContactId: $crmContactId
|
||||
crmOrganizationId: $crmOrganizationId
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
|
@ -109,6 +119,8 @@ query getIssuesCount(
|
|||
releaseTag: $releaseTag
|
||||
releaseTagWildcardId: $releaseTagWildcardId
|
||||
types: $types
|
||||
crmContactId: $crmContactId
|
||||
crmOrganizationId: $crmOrganizationId
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
|
@ -128,6 +140,8 @@ query getIssuesCount(
|
|||
releaseTag: $releaseTag
|
||||
releaseTagWildcardId: $releaseTagWildcardId
|
||||
types: $types
|
||||
crmContactId: $crmContactId
|
||||
crmOrganizationId: $crmOrganizationId
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
query getIssuesCountWithoutCrm(
|
||||
$isProject: Boolean = false
|
||||
$fullPath: ID!
|
||||
$iid: String
|
||||
$search: String
|
||||
$assigneeId: String
|
||||
$assigneeUsernames: [String!]
|
||||
$authorUsername: String
|
||||
$confidential: Boolean
|
||||
$labelName: [String]
|
||||
$milestoneTitle: [String]
|
||||
$milestoneWildcardId: MilestoneWildcardId
|
||||
$myReactionEmoji: String
|
||||
$releaseTag: [String!]
|
||||
$releaseTagWildcardId: ReleaseTagWildcardId
|
||||
$types: [IssueType!]
|
||||
$not: NegatedIssueFilterInput
|
||||
) {
|
||||
group(fullPath: $fullPath) @skip(if: $isProject) {
|
||||
id
|
||||
openedIssues: issues(
|
||||
includeSubgroups: true
|
||||
state: opened
|
||||
iid: $iid
|
||||
search: $search
|
||||
assigneeId: $assigneeId
|
||||
assigneeUsernames: $assigneeUsernames
|
||||
authorUsername: $authorUsername
|
||||
confidential: $confidential
|
||||
labelName: $labelName
|
||||
milestoneTitle: $milestoneTitle
|
||||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
types: $types
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
}
|
||||
closedIssues: issues(
|
||||
includeSubgroups: true
|
||||
state: closed
|
||||
iid: $iid
|
||||
search: $search
|
||||
assigneeId: $assigneeId
|
||||
assigneeUsernames: $assigneeUsernames
|
||||
authorUsername: $authorUsername
|
||||
confidential: $confidential
|
||||
labelName: $labelName
|
||||
milestoneTitle: $milestoneTitle
|
||||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
types: $types
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
}
|
||||
allIssues: issues(
|
||||
includeSubgroups: true
|
||||
state: all
|
||||
iid: $iid
|
||||
search: $search
|
||||
assigneeId: $assigneeId
|
||||
assigneeUsernames: $assigneeUsernames
|
||||
authorUsername: $authorUsername
|
||||
confidential: $confidential
|
||||
labelName: $labelName
|
||||
milestoneTitle: $milestoneTitle
|
||||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
types: $types
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
}
|
||||
}
|
||||
project(fullPath: $fullPath) @include(if: $isProject) {
|
||||
id
|
||||
openedIssues: issues(
|
||||
state: opened
|
||||
iid: $iid
|
||||
search: $search
|
||||
assigneeId: $assigneeId
|
||||
assigneeUsernames: $assigneeUsernames
|
||||
authorUsername: $authorUsername
|
||||
confidential: $confidential
|
||||
labelName: $labelName
|
||||
milestoneTitle: $milestoneTitle
|
||||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
releaseTag: $releaseTag
|
||||
releaseTagWildcardId: $releaseTagWildcardId
|
||||
types: $types
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
}
|
||||
closedIssues: issues(
|
||||
state: closed
|
||||
iid: $iid
|
||||
search: $search
|
||||
assigneeId: $assigneeId
|
||||
assigneeUsernames: $assigneeUsernames
|
||||
authorUsername: $authorUsername
|
||||
confidential: $confidential
|
||||
labelName: $labelName
|
||||
milestoneTitle: $milestoneTitle
|
||||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
releaseTag: $releaseTag
|
||||
releaseTagWildcardId: $releaseTagWildcardId
|
||||
types: $types
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
}
|
||||
allIssues: issues(
|
||||
state: all
|
||||
iid: $iid
|
||||
search: $search
|
||||
assigneeId: $assigneeId
|
||||
assigneeUsernames: $assigneeUsernames
|
||||
authorUsername: $authorUsername
|
||||
confidential: $confidential
|
||||
labelName: $labelName
|
||||
milestoneTitle: $milestoneTitle
|
||||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
releaseTag: $releaseTag
|
||||
releaseTagWildcardId: $releaseTagWildcardId
|
||||
types: $types
|
||||
not: $not
|
||||
) {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
|
||||
#import "./issue.fragment.graphql"
|
||||
|
||||
query getIssuesWithoutCrm(
|
||||
$isProject: Boolean = false
|
||||
$isSignedIn: Boolean = false
|
||||
$fullPath: ID!
|
||||
$iid: String
|
||||
$search: String
|
||||
$sort: IssueSort
|
||||
$state: IssuableState
|
||||
$assigneeId: String
|
||||
$assigneeUsernames: [String!]
|
||||
$authorUsername: String
|
||||
$confidential: Boolean
|
||||
$labelName: [String]
|
||||
$milestoneTitle: [String]
|
||||
$milestoneWildcardId: MilestoneWildcardId
|
||||
$myReactionEmoji: String
|
||||
$releaseTag: [String!]
|
||||
$releaseTagWildcardId: ReleaseTagWildcardId
|
||||
$types: [IssueType!]
|
||||
$not: NegatedIssueFilterInput
|
||||
$beforeCursor: String
|
||||
$afterCursor: String
|
||||
$firstPageSize: Int
|
||||
$lastPageSize: Int
|
||||
) {
|
||||
group(fullPath: $fullPath) @skip(if: $isProject) {
|
||||
id
|
||||
issues(
|
||||
includeSubgroups: true
|
||||
iid: $iid
|
||||
search: $search
|
||||
sort: $sort
|
||||
state: $state
|
||||
assigneeId: $assigneeId
|
||||
assigneeUsernames: $assigneeUsernames
|
||||
authorUsername: $authorUsername
|
||||
confidential: $confidential
|
||||
labelName: $labelName
|
||||
milestoneTitle: $milestoneTitle
|
||||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
types: $types
|
||||
not: $not
|
||||
before: $beforeCursor
|
||||
after: $afterCursor
|
||||
first: $firstPageSize
|
||||
last: $lastPageSize
|
||||
) {
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
nodes {
|
||||
...IssueFragment
|
||||
reference(full: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
project(fullPath: $fullPath) @include(if: $isProject) {
|
||||
id
|
||||
issues(
|
||||
iid: $iid
|
||||
search: $search
|
||||
sort: $sort
|
||||
state: $state
|
||||
assigneeId: $assigneeId
|
||||
assigneeUsernames: $assigneeUsernames
|
||||
authorUsername: $authorUsername
|
||||
confidential: $confidential
|
||||
labelName: $labelName
|
||||
milestoneTitle: $milestoneTitle
|
||||
milestoneWildcardId: $milestoneWildcardId
|
||||
myReactionEmoji: $myReactionEmoji
|
||||
releaseTag: $releaseTag
|
||||
releaseTagWildcardId: $releaseTagWildcardId
|
||||
types: $types
|
||||
not: $not
|
||||
before: $beforeCursor
|
||||
after: $afterCursor
|
||||
first: $firstPageSize
|
||||
last: $lastPageSize
|
||||
) {
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
nodes {
|
||||
...IssueFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import { GlIcon, GlPopover, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { timeTilRun } from '../../utils';
|
||||
import {
|
||||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
PARTIAL_CLEANUP_CONTINUE_MESSAGE,
|
||||
CLEANUP_STATUS_SCHEDULED,
|
||||
CLEANUP_STATUS_ONGOING,
|
||||
CLEANUP_STATUS_UNFINISHED,
|
||||
|
@ -15,9 +17,9 @@ export default {
|
|||
name: 'CleanupStatus',
|
||||
components: {
|
||||
GlIcon,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
GlPopover,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
|
@ -29,12 +31,17 @@ export default {
|
|||
);
|
||||
},
|
||||
},
|
||||
expirationPolicyNextRunAt: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => '',
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
CLEANUP_STATUS_SCHEDULED,
|
||||
CLEANUP_STATUS_ONGOING,
|
||||
CLEANUP_STATUS_UNFINISHED,
|
||||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
PARTIAL_CLEANUP_CONTINUE_MESSAGE,
|
||||
},
|
||||
computed: {
|
||||
showStatus() {
|
||||
|
@ -46,26 +53,57 @@ export default {
|
|||
statusText() {
|
||||
return this.$options.i18n[`CLEANUP_STATUS_${this.status}`];
|
||||
},
|
||||
expireIconClass() {
|
||||
return this.failedDelete ? 'gl-text-orange-500' : '';
|
||||
calculatedTimeTilNextRun() {
|
||||
return timeTilRun(this.expirationPolicyNextRunAt?.next_run);
|
||||
},
|
||||
},
|
||||
statusPopoverOptions: {
|
||||
triggers: 'hover',
|
||||
placement: 'top',
|
||||
},
|
||||
cleanupPolicyHelpPage: helpPagePath(
|
||||
'user/packages/container_registry/reduce_container_registry_storage.html',
|
||||
{ anchor: 'how-the-cleanup-policy-works' },
|
||||
),
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="showStatus" class="gl-display-inline-flex gl-align-items-center">
|
||||
<gl-icon name="expire" data-testid="main-icon" :class="expireIconClass" />
|
||||
<div
|
||||
v-if="showStatus"
|
||||
id="status-popover-container"
|
||||
class="gl-display-inline-flex gl-align-items-center"
|
||||
>
|
||||
<div class="gl-display-inline-flex gl-align-items-center">
|
||||
<gl-icon name="expire" data-testid="main-icon" />
|
||||
</div>
|
||||
<span class="gl-mx-2">
|
||||
{{ statusText }}
|
||||
</span>
|
||||
<gl-icon
|
||||
v-if="failedDelete"
|
||||
v-gl-tooltip="{ title: $options.i18n.CLEANUP_TIMED_OUT_ERROR_MESSAGE }"
|
||||
id="status-info"
|
||||
:size="14"
|
||||
class="gl-text-black-normal"
|
||||
class="gl-text-gray-500"
|
||||
data-testid="extra-info"
|
||||
name="information"
|
||||
name="information-o"
|
||||
/>
|
||||
<gl-popover
|
||||
target="status-info"
|
||||
container="status-popover-container"
|
||||
v-bind="$options.statusPopoverOptions"
|
||||
>
|
||||
<template #title>
|
||||
{{ $options.i18n.CLEANUP_STATUS_UNFINISHED }}
|
||||
</template>
|
||||
<gl-sprintf :message="$options.i18n.PARTIAL_CLEANUP_CONTINUE_MESSAGE">
|
||||
<template #time>{{ calculatedTimeTilNextRun }}</template
|
||||
><template #link="{ content }"
|
||||
><gl-link :href="$options.cleanupPolicyHelpPage" class="gl-font-sm" target="_blank">{{
|
||||
content
|
||||
}}</gl-link></template
|
||||
>
|
||||
</gl-sprintf>
|
||||
</gl-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -22,6 +22,11 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
expirationPolicy: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showPagination() {
|
||||
|
@ -38,6 +43,7 @@ export default {
|
|||
:key="index"
|
||||
:item="listItem"
|
||||
:metadata-loading="metadataLoading"
|
||||
:expiration-policy="expirationPolicy"
|
||||
@delete="$emit('delete', $event)"
|
||||
/>
|
||||
<div class="gl-display-flex gl-justify-content-center">
|
||||
|
|
|
@ -6,12 +6,10 @@ import { n__ } from '~/locale';
|
|||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
import {
|
||||
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
|
||||
LIST_DELETE_BUTTON_DISABLED,
|
||||
LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
|
||||
REMOVE_REPOSITORY_LABEL,
|
||||
ROW_SCHEDULED_FOR_DELETION,
|
||||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
IMAGE_DELETE_SCHEDULED_STATUS,
|
||||
IMAGE_FAILED_DELETED_STATUS,
|
||||
IMAGE_MIGRATING_STATE,
|
||||
|
@ -45,6 +43,11 @@ export default {
|
|||
default: false,
|
||||
required: false,
|
||||
},
|
||||
expirationPolicy: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
REMOVE_REPOSITORY_LABEL,
|
||||
|
@ -73,15 +76,6 @@ export default {
|
|||
this.item.tagsCount,
|
||||
);
|
||||
},
|
||||
warningIconText() {
|
||||
if (this.failedDelete) {
|
||||
return ASYNC_DELETE_IMAGE_ERROR_MESSAGE;
|
||||
}
|
||||
if (this.item.expirationPolicyStartedAt) {
|
||||
return CLEANUP_TIMED_OUT_ERROR_MESSAGE;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
imageName() {
|
||||
return this.item.name ? this.item.path : `${this.item.path}/ ${ROOT_IMAGE_TEXT}`;
|
||||
},
|
||||
|
@ -140,6 +134,7 @@ export default {
|
|||
v-if="item.expirationPolicyCleanupStatus"
|
||||
class="ml-2"
|
||||
:status="item.expirationPolicyCleanupStatus"
|
||||
:expiration-policy-next-run-at="expirationPolicy.next_run_at"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ export const CLEANUP_DISABLED_TOOLTIP = s__(
|
|||
|
||||
export const CLEANUP_STATUS_SCHEDULED = s__('ContainerRegistry|Cleanup will run soon');
|
||||
export const CLEANUP_STATUS_ONGOING = s__('ContainerRegistry|Cleanup is ongoing');
|
||||
export const CLEANUP_STATUS_UNFINISHED = s__('ContainerRegistry|Cleanup timed out');
|
||||
export const CLEANUP_STATUS_UNFINISHED = s__('ContainerRegistry|Partial cleanup complete');
|
||||
|
||||
export const DETAILS_DELETE_IMAGE_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while scheduling the image for deletion.',
|
||||
|
|
|
@ -10,7 +10,7 @@ export const DELETE_ALERT_TITLE = s__('ContainerRegistry|Some tags were not dele
|
|||
export const DELETE_ALERT_LINK_TEXT = s__(
|
||||
'ContainerRegistry|The cleanup policy timed out before it could delete all tags. An administrator can %{adminLinkStart}manually run cleanup now%{adminLinkEnd} or you can wait for the cleanup policy to automatically run again. %{docLinkStart}More information%{docLinkEnd}',
|
||||
);
|
||||
export const CLEANUP_TIMED_OUT_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Cleanup timed out before it could delete all tags',
|
||||
export const PARTIAL_CLEANUP_CONTINUE_MESSAGE = s__(
|
||||
'ContainerRegistry|The cleanup will continue within %{time}. %{linkStart}Learn more%{linkEnd}',
|
||||
);
|
||||
export const SET_UP_CLEANUP = s__('ContainerRegistry|Set up cleanup');
|
||||
|
|
|
@ -336,6 +336,7 @@ export default {
|
|||
:images="images"
|
||||
:metadata-loading="$apollo.queries.additionalDetails.loading"
|
||||
:page-info="pageInfo"
|
||||
:expiration-policy="config.expirationPolicy"
|
||||
@delete="deleteImage"
|
||||
@prev-page="fetchPreviousPage"
|
||||
@next-page="fetchNextPage"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
|
||||
|
||||
export const timeTilRun = (time) => {
|
||||
if (!time) return '';
|
||||
|
||||
const difference = calculateRemainingMilliseconds(time);
|
||||
return approximateDuration(difference / 1000);
|
||||
};
|
|
@ -159,7 +159,7 @@ export default {
|
|||
</span>
|
||||
</template>
|
||||
|
||||
<template #right-action>
|
||||
<template v-if="packageEntity.canDestroy" #right-action>
|
||||
<gl-dropdown
|
||||
data-testid="delete-dropdown"
|
||||
icon="ellipsis_v"
|
||||
|
|
|
@ -5,6 +5,7 @@ fragment PackageData on Package {
|
|||
packageType
|
||||
createdAt
|
||||
status
|
||||
canDestroy
|
||||
tags {
|
||||
nodes {
|
||||
id
|
||||
|
|
|
@ -126,6 +126,10 @@ class DeployToken < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def impersonated?
|
||||
false
|
||||
end
|
||||
|
||||
def expires_at
|
||||
expires_at = read_attribute(:expires_at)
|
||||
expires_at != Forever.date ? expires_at : nil
|
||||
|
|
|
@ -6,7 +6,7 @@ module ContainerRegistry
|
|||
include ApplicationWorker
|
||||
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include ExclusiveLeaseGuard
|
||||
include Gitlab::ExclusiveLeaseHelpers
|
||||
|
||||
DEFAULT_LEASE_TIMEOUT = 30.minutes.to_i.freeze
|
||||
|
||||
|
@ -18,16 +18,34 @@ module ContainerRegistry
|
|||
|
||||
def perform
|
||||
re_enqueue = false
|
||||
try_obtain_lease do
|
||||
break unless runnable?
|
||||
|
||||
re_enqueue = handle_aborted_migration || handle_next_migration
|
||||
took_lease = with_a_lease_on_repository do
|
||||
if runnable?
|
||||
re_enqueue = handle_aborted_migration || handle_next_migration
|
||||
end
|
||||
end
|
||||
|
||||
log_extra_metadata_on_done(:lease_already_taken, true) unless took_lease
|
||||
|
||||
re_enqueue_if_capacity if re_enqueue
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_a_lease_on_repository
|
||||
repository = next_aborted_repository || next_repository
|
||||
|
||||
if repository
|
||||
in_lock(lease_key_for(repository), retries: 0, ttl: DEFAULT_LEASE_TIMEOUT) { yield }
|
||||
else
|
||||
log_extra_metadata_on_done(:no_container_repository_found, true)
|
||||
end
|
||||
|
||||
true
|
||||
rescue FailedToObtainLockError
|
||||
false
|
||||
end
|
||||
|
||||
def handle_aborted_migration
|
||||
return unless next_aborted_repository
|
||||
|
||||
|
@ -160,14 +178,8 @@ module ContainerRegistry
|
|||
log_extra_metadata_on_done(:container_repository_migration_state, repository.migration_state)
|
||||
end
|
||||
|
||||
# used by ExclusiveLeaseGuard
|
||||
def lease_key
|
||||
'container_registry:migration:enqueuer_worker'
|
||||
end
|
||||
|
||||
# used by ExclusiveLeaseGuard
|
||||
def lease_timeout
|
||||
DEFAULT_LEASE_TIMEOUT
|
||||
def lease_key_for(repository)
|
||||
"container_registry:migration:enqueuer_worker:for:#{repository.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: new_vulnerability_form
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
milestone: '14.9'
|
||||
type: development
|
||||
group: group::threat insights
|
||||
default_enabled: true
|
|
@ -220,7 +220,7 @@ production: &base
|
|||
#
|
||||
# log_path: log/mail_room_json.log
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
expunge_deleted: false
|
||||
|
||||
# For Microsoft Graph support
|
||||
|
|
|
@ -3,6 +3,6 @@ table_name: verification_codes
|
|||
classes: []
|
||||
feature_categories:
|
||||
- jihu
|
||||
description: TODO
|
||||
description: Used by the JiHu edition for user verification
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71139
|
||||
milestone: '14.4'
|
||||
|
|
|
@ -302,7 +302,7 @@ gitlab_rails['incoming_email_mailbox_name'] = "inbox"
|
|||
# The IDLE command timeout.
|
||||
gitlab_rails['incoming_email_idle_timeout'] = 60
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
gitlab_rails['incoming_email_expunge_deleted'] = true
|
||||
```
|
||||
|
||||
|
@ -340,7 +340,7 @@ incoming_email:
|
|||
# The IDLE command timeout.
|
||||
idle_timeout: 60
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
expunge_deleted: true
|
||||
```
|
||||
|
||||
|
@ -384,7 +384,7 @@ gitlab_rails['incoming_email_mailbox_name'] = "inbox"
|
|||
# The IDLE command timeout.
|
||||
gitlab_rails['incoming_email_idle_timeout'] = 60
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
gitlab_rails['incoming_email_expunge_deleted'] = true
|
||||
```
|
||||
|
||||
|
@ -422,7 +422,7 @@ incoming_email:
|
|||
# The IDLE command timeout.
|
||||
idle_timeout: 60
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
expunge_deleted: true
|
||||
```
|
||||
|
||||
|
@ -464,7 +464,7 @@ gitlab_rails['incoming_email_port'] = 993
|
|||
# Whether the IMAP server uses SSL
|
||||
gitlab_rails['incoming_email_ssl'] = true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
gitlab_rails['incoming_email_expunge_deleted'] = true
|
||||
```
|
||||
|
||||
|
@ -495,7 +495,7 @@ incoming_email:
|
|||
# Whether the IMAP server uses SSL
|
||||
ssl: true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
expunge_deleted: true
|
||||
```
|
||||
|
||||
|
@ -528,7 +528,7 @@ gitlab_rails['incoming_email_port'] = 993
|
|||
# Whether the IMAP server uses SSL
|
||||
gitlab_rails['incoming_email_ssl'] = true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
gitlab_rails['incoming_email_expunge_deleted'] = true
|
||||
```
|
||||
|
||||
|
@ -555,7 +555,7 @@ incoming_email:
|
|||
# Whether the IMAP server uses SSL
|
||||
ssl: true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
expunge_deleted: true
|
||||
```
|
||||
|
||||
|
@ -612,7 +612,7 @@ gitlab_rails['incoming_email_port'] = 993
|
|||
# Whether the IMAP server uses SSL
|
||||
gitlab_rails['incoming_email_ssl'] = true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
gitlab_rails['incoming_email_expunge_deleted'] = true
|
||||
```
|
||||
|
||||
|
@ -642,7 +642,7 @@ incoming_email:
|
|||
# Whether the IMAP server uses SSL
|
||||
ssl: true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
expunge_deleted: true
|
||||
```
|
||||
|
||||
|
@ -673,7 +673,7 @@ gitlab_rails['incoming_email_port'] = 993
|
|||
# Whether the IMAP server uses SSL
|
||||
gitlab_rails['incoming_email_ssl'] = true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
gitlab_rails['incoming_email_expunge_deleted'] = true
|
||||
```
|
||||
|
||||
|
@ -703,7 +703,7 @@ incoming_email:
|
|||
# Whether the IMAP server uses SSL
|
||||
ssl: true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
expunge_deleted: true
|
||||
```
|
||||
|
||||
|
@ -733,7 +733,7 @@ gitlab_rails['incoming_email_port'] = 993
|
|||
# Whether the IMAP server uses SSL
|
||||
gitlab_rails['incoming_email_ssl'] = true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
gitlab_rails['incoming_email_expunge_deleted'] = true
|
||||
```
|
||||
|
||||
|
@ -758,7 +758,7 @@ incoming_email:
|
|||
# Whether the IMAP server uses SSL
|
||||
ssl: true
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
expunge_deleted: true
|
||||
```
|
||||
|
||||
|
|
|
@ -25,9 +25,6 @@ review for the pipeline, focusing on the additional access. You can use the [sof
|
|||
as a starting point, and for more information about supply chain attacks, see
|
||||
[How a DevOps Platform helps protect against supply chain attacks](https://about.gitlab.com/blog/2021/04/28/devops-platform-supply-chain-attacks/).
|
||||
|
||||
WARNING:
|
||||
The `CI_JOB_JWT_V2` variable is under development [(alpha)](../../policy/alpha-beta-support.md#alpha-features) and is not yet suitable for production use.
|
||||
|
||||
## Use cases
|
||||
|
||||
- Removes the need to store secrets in your GitLab group or project. Temporary credentials can be retrieved from your cloud provider through OIDC.
|
||||
|
|
|
@ -22,14 +22,14 @@ This tutorial assumes you are familiar with GitLab CI/CD and Vault.
|
|||
To follow along, you must have:
|
||||
|
||||
- An account on GitLab.
|
||||
- Access to a running Vault server (at least v1.2.0) to configure authentication and to create roles and policies. For HashiCorp Vaults, this can be the Open Source or Enterprise version.
|
||||
- Access to a running Vault server (at least v1.2.0) to configure authentication and to create roles and policies. You can use Open Source or Enterprise version of HashiCorp Vault.
|
||||
|
||||
NOTE:
|
||||
You must replace the `vault.example.com` URL below with the URL of your Vault server, and `gitlab.example.com` with the URL of your GitLab instance.
|
||||
|
||||
## How it works
|
||||
|
||||
Each job has JSON Web Token (JWT) provided as CI/CD variable named `CI_JOB_JWT`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication) method.
|
||||
Each job has JSON Web Token (JWT) provided as CI/CD variable named `CI_JOB_JWT` or `CI_JOB_JWT_V2`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication) method.
|
||||
|
||||
The following fields are included in the JWT:
|
||||
|
||||
|
@ -40,7 +40,8 @@ The following fields are included in the JWT:
|
|||
| `iat` | Always | Issued at |
|
||||
| `nbf` | Always | Not valid before |
|
||||
| `exp` | Always | Expires at |
|
||||
| `sub` | Always | Subject (job ID) |
|
||||
| `sub` | Always | `project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}` |
|
||||
| `aud` | Always | Issuer, the domain of your GitLab instance |
|
||||
| `namespace_id` | Always | Use this to scope to group or user level namespace by ID |
|
||||
| `namespace_path` | Always | Use this to scope to group or user level namespace by path |
|
||||
| `project_id` | Always | Use this to scope to project by ID |
|
||||
|
@ -62,11 +63,12 @@ Example JWT payload:
|
|||
```json
|
||||
{
|
||||
"jti": "c82eeb0c-5c6f-4a33-abf5-4c474b92b558",
|
||||
"iss": "gitlab.example.com",
|
||||
"iss": "https://gitlab.example.com",
|
||||
"aud": "https://gitlab.example.com",
|
||||
"iat": 1585710286,
|
||||
"nbf": 1585798372,
|
||||
"exp": 1585713886,
|
||||
"sub": "job_1212",
|
||||
"sub": "project_path:mygroup/myproject:ref_type:branch:ref:main",
|
||||
"namespace_id": "1",
|
||||
"namespace_path": "mygroup",
|
||||
"project_id": "22",
|
||||
|
@ -91,6 +93,8 @@ You can use this JWT and your instance's JWKS endpoint (`https://gitlab.example.
|
|||
|
||||
When configuring roles in Vault, you can use [bound_claims](https://www.vaultproject.io/docs/auth/jwt#bound-claims) to match against the JWT's claims and restrict which secrets each CI job has access to.
|
||||
|
||||
You must use `bound_audiences` to match against the JWT's audience claim.
|
||||
|
||||
To communicate with Vault, you can use either its CLI client or perform API requests (using `curl` or another client).
|
||||
|
||||
## Example
|
||||
|
@ -154,7 +158,8 @@ $ vault write auth/jwt/role/myproject-staging - <<EOF
|
|||
"project_id": "22",
|
||||
"ref": "master",
|
||||
"ref_type": "branch"
|
||||
}
|
||||
},
|
||||
"bound_audiences": "https://gitlab.example.com"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
@ -174,7 +179,8 @@ $ vault write auth/jwt/role/myproject-production - <<EOF
|
|||
"ref_protected": "true",
|
||||
"ref_type": "branch",
|
||||
"ref": "auto-deploy-*"
|
||||
}
|
||||
},
|
||||
"bound_audiences": "https://gitlab.example.com"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
@ -211,7 +217,7 @@ Role example to support the templated policy above, mapping the claim field `pro
|
|||
}
|
||||
```
|
||||
|
||||
For the full list of options, see Vault's [Create Role documentation](https://www.vaultproject.io/api/auth/jwt#create-role).
|
||||
For the full list of options, see the Vault [Create Role documentation](https://www.vaultproject.io/api/auth/jwt#create-role).
|
||||
|
||||
WARNING:
|
||||
Always restrict your roles to project or namespace by using one of the provided claims (for example, `project_id` or `namespace_id`). Otherwise any JWT generated by this instance may be allowed to authenticate using this role.
|
||||
|
@ -221,12 +227,12 @@ Now, configure the JWT Authentication method:
|
|||
```shell
|
||||
$ vault write auth/jwt/config \
|
||||
jwks_url="https://gitlab.example.com/-/jwks" \
|
||||
bound_issuer="gitlab.example.com"
|
||||
bound_issuer="https://gitlab.example.com"
|
||||
```
|
||||
|
||||
[bound_issuer](https://www.vaultproject.io/api/auth/jwt#inlinecode-bound_issuer) specifies that only a JWT with the issuer (that is, the `iss` claim) set to `gitlab.example.com` can use this method to authenticate, and that the JWKS endpoint (`https://gitlab.example.com/-/jwks`) should be used to validate the token.
|
||||
[`bound_issuer`](https://www.vaultproject.io/api/auth/jwt#inlinecode-bound_issuer) specifies that only a JWT with the issuer (that is, the `iss` claim) set to `https://gitlab.example.com` can use this method to authenticate, and that the JWKS endpoint (`https://gitlab.example.com/-/jwks`) should be used to validate the token.
|
||||
|
||||
For the full list of available configuration options, see Vault's [API documentation](https://www.vaultproject.io/api/auth/jwt#configure).
|
||||
For the full list of available configuration options, see the Vault [API documentation](https://www.vaultproject.io/api/auth/jwt#configure).
|
||||
|
||||
The following job, when run for the default branch, is able to read secrets under `secret/myproject/staging/`, but not the secrets under `secret/myproject/production/`:
|
||||
|
||||
|
@ -241,7 +247,7 @@ read_secrets:
|
|||
- export VAULT_ADDR=http://vault.example.com:8200
|
||||
# Authenticate and get token. Token expiry time and other properties can be configured
|
||||
# when configuring JWT Auth - https://www.vaultproject.io/api/auth/jwt#parameters-1
|
||||
- export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-staging jwt=$CI_JOB_JWT)"
|
||||
- export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-staging jwt=$CI_JOB_JWT_V2)"
|
||||
# Now use the VAULT_TOKEN to read the secret and store it in an environment variable
|
||||
- export PASSWORD="$(vault kv get -field=password secret/myproject/staging/db)"
|
||||
# Use the secret
|
||||
|
@ -269,7 +275,7 @@ read_secrets:
|
|||
- export VAULT_ADDR=http://vault.example.com:8200
|
||||
# Authenticate and get token. Token expiry time and other properties can be configured
|
||||
# when configuring JWT Auth - https://www.vaultproject.io/api/auth/jwt#parameters-1
|
||||
- export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-production jwt=$CI_JOB_JWT)"
|
||||
- export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-production jwt=$CI_JOB_JWT_V2)"
|
||||
# Now use the VAULT_TOKEN to read the secret and store it in environment variable
|
||||
- export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)"
|
||||
# Use the secret
|
||||
|
@ -280,7 +286,7 @@ read_secrets:
|
|||
|
||||
### Limit token access to Vault secrets
|
||||
|
||||
You can control `CI_JOB_JWT` access to Vault secrets by using Vault protections
|
||||
You can control `CI_JOB_JWT_V2` access to Vault secrets by using Vault protections
|
||||
and GitLab features. For example, restrict the token by:
|
||||
|
||||
- Using Vault [bound_claims](https://www.vaultproject.io/docs/auth/jwt#bound-claims)
|
||||
|
@ -293,3 +299,14 @@ and GitLab features. For example, restrict the token by:
|
|||
that are restricted to a subset of project users.
|
||||
- Scoping the JWT to [GitLab projected tags](../../../user/project/protected_tags.md),
|
||||
that are restricted to a subset of project users.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `Code: 400. Errors: audience claim found in JWT but no audiences bound to the role`
|
||||
|
||||
This issue occurs because you are using a JWT V2 token and your role on Vault is missing a value for `bound_audiences`.
|
||||
|
||||
### `Code: 400. Errors: error validating claims: validation failed, invalid issuer claim (iss)`
|
||||
|
||||
The issuer in Vault must match the issuer defined in a JWT V2 token.
|
||||
In Vault, ensure you're using `https` in your issuer.
|
||||
|
|
|
@ -25,8 +25,7 @@ as the first supported secrets engine.
|
|||
|
||||
GitLab authenticates using Vault's
|
||||
[JSON Web Token (JWT) authentication method](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication), using
|
||||
the [JSON Web Token](https://gitlab.com/gitlab-org/gitlab/-/issues/207125) (`CI_JOB_JWT`)
|
||||
introduced in GitLab 12.10.
|
||||
the [JSON Web Token](https://gitlab.com/gitlab-org/gitlab/-/issues/207125) (`CI_JOB_JWT_V2`).
|
||||
|
||||
You must [configure your Vault server](#configure-your-vault-server) before you
|
||||
can use [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job).
|
||||
|
@ -34,7 +33,7 @@ can use [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job).
|
|||
The flow for using GitLab with HashiCorp Vault
|
||||
is summarized by this diagram:
|
||||
|
||||
![Flow between GitLab and HashiCorp](../img/gitlab_vault_workflow_v13_4.png "How GitLab CI_JOB_JWT works with HashiCorp Vault")
|
||||
![Flow between GitLab and HashiCorp](../img/gitlab_vault_workflow_v13_4.png "How GitLab CI_JOB_JWT_V2 works with HashiCorp Vault")
|
||||
|
||||
1. Configure your vault and secrets.
|
||||
1. Generate your JWT and provide it to your CI job.
|
||||
|
@ -64,7 +63,7 @@ To configure your Vault server:
|
|||
|
||||
$ vault write auth/jwt/config \
|
||||
jwks_url="https://gitlab.example.com/-/jwks" \
|
||||
bound_issuer="gitlab.example.com"
|
||||
bound_issuer="https://gitlab.example.com"
|
||||
```
|
||||
|
||||
1. Configure policies on your Vault server to grant or forbid access to certain
|
||||
|
@ -167,7 +166,8 @@ $ vault write auth/jwt/role/myproject-production - <<EOF
|
|||
"ref_protected": "true",
|
||||
"ref_type": "tag",
|
||||
"ref": "auto-deploy-*"
|
||||
}
|
||||
},
|
||||
"bound_audiences": "https://gitlab.example.com"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
@ -177,7 +177,7 @@ Always restrict your roles to a project or namespace by using one of the provide
|
|||
claims like `project_id` or `namespace_id`. Without these restrictions, any JWT
|
||||
generated by this GitLab instance may be allowed to authenticate using this role.
|
||||
|
||||
For a full list of `CI_JOB_JWT` claims, read the
|
||||
For a full list of `CI_JOB_JWT_V2` claims, read the
|
||||
[How it works](../examples/authenticating-with-hashicorp-vault/index.md#how-it-works) section of the
|
||||
[Authenticating and Reading Secrets With HashiCorp Vault](../examples/authenticating-with-hashicorp-vault/index.md) tutorial.
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ There are also a number of [variables you can use to configure runner behavior](
|
|||
| `CI_JOB_IMAGE` | 12.9 | 12.9 | The name of the Docker image running the job. |
|
||||
| `CI_JOB_JWT` | 12.10 | all | A RS256 JSON web token to authenticate with third party systems that support JWT authentication, for example [HashiCorp's Vault](../secrets/index.md). |
|
||||
| `CI_JOB_JWT_V1` | 14.6 | all | The same value as `CI_JOB_JWT`. |
|
||||
| `CI_JOB_JWT_V2` | 14.6 | all | [**alpha:**](../../policy/alpha-beta-support.md#alpha-features) A newly formatted RS256 JSON web token to increase compatibility. Similar to `CI_JOB_JWT`, except the issuer (`iss`) claim is changed from `gitlab.com` to `https://gitlab.com`, `sub` has changed from `job_id` to a string that contains the project path, and an `aud` claim is added. Format is subject to change. Be aware, the `aud` field is a constant value. Trusting JWTs in multiple relying parties can lead to [one RP sending a JWT to another one and acting maliciously as a job](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72555#note_769112331). |
|
||||
| `CI_JOB_JWT_V2` | 14.6 | all | A newly formatted RS256 JSON web token to increase compatibility. Similar to `CI_JOB_JWT`, except the issuer (`iss`) claim is changed from `gitlab.com` to `https://gitlab.com`, `sub` has changed from `job_id` to a string that contains the project path, and an `aud` claim is added. Format is subject to change. Be aware, the `aud` field is a constant value. Trusting JWTs in multiple relying parties can lead to [one RP sending a JWT to another one and acting maliciously as a job](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72555#note_769112331). |
|
||||
| `CI_JOB_MANUAL` | 8.12 | all | `true` if a job was started manually. |
|
||||
| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job. |
|
||||
| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the job's stage. |
|
||||
|
|
|
@ -86,7 +86,7 @@ See the [Rails guides](https://guides.rubyonrails.org/action_mailer_basics.html#
|
|||
# The IDLE command timeout.
|
||||
idle_timeout: 60
|
||||
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
|
||||
# Whether to expunge (permanently remove) messages from the mailbox when they are marked as deleted after delivery
|
||||
expunge_deleted: false
|
||||
```
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ serve as input to automated conformance tests. It is
|
|||
[explained in the CommonMark specification](https://spec.commonmark.org/0.30/#about-this-document):
|
||||
|
||||
> This document attempts to specify Markdown syntax unambiguously. It contains many
|
||||
> examples with side-by-side Markdown and HTML. These are intended to double as conformance tests.
|
||||
> examples with side-by-side Markdown and HTML. These examples are intended to double as conformance tests.
|
||||
|
||||
The HTML-rendered versions of the specifications:
|
||||
|
||||
|
@ -508,21 +508,76 @@ They are either downloaded, as in the case of the
|
|||
GFM `spec.txt` file, or manually
|
||||
updated, as in the case of all GFM files.
|
||||
|
||||
- `glfm_specification/input/github_flavored_markdown/gfm_spec_v_0.29.txt` -
|
||||
official latest [GFM spec.txt](https://github.com/github/cmark-gfm/blob/master/test/spec.txt),
|
||||
automatically downloaded and updated by `update-specification.rb` script.
|
||||
- `glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt` -
|
||||
Manually updated text of intro section for generated GLFM `spec.txt`.
|
||||
- Replaces GFM version of introductory
|
||||
section in `spec.txt`.
|
||||
- `glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt` -
|
||||
Manually updated canonical Markdown+HTML examples for GLFM extensions.
|
||||
- Standard backtick-delimited `spec.txt` examples format with Markdown + canonical HTML.
|
||||
- Inserted as a new section before the appendix of generated `spec.txt`.
|
||||
- `glfm_specification/input/gitlab_flavored_markdown/glfm_example_status.yml` -
|
||||
Manually updated status of automatic generation of files based on Markdown
|
||||
examples.
|
||||
- Allows example snapshot generation, Markdown conformance tests, or
|
||||
##### GitHub Flavored Markdown specification
|
||||
|
||||
[`glfm_specification/input/github_flavored_markdown/gfm_spec_v_0.29.txt`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/github_flavored_markdown/gfm_spec_v_0.29.txt)
|
||||
is the official latest [GFM `spec.txt`](https://github.com/github/cmark-gfm/blob/master/test/spec.txt).
|
||||
|
||||
- It is automatically downloaded and updated by `update-specification.rb` script.
|
||||
- When it is downloaded, the version number is added to the filename.
|
||||
|
||||
##### `glfm_intro.txt`
|
||||
|
||||
[`glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt)
|
||||
is the GitLab-specific version of the prose in the introduction section of the GLFM specification.
|
||||
|
||||
- It is manually updated.
|
||||
- The `update-specification.rb` script inserts it into the generated GLFM `spec.txt` to replace
|
||||
the GitHub-specific GFM version of the introductory section.
|
||||
|
||||
##### `glfm_canonical_examples.txt`
|
||||
|
||||
[`glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt)
|
||||
is the manually updated canonical Markdown+HTML examples for GLFM extensions.
|
||||
|
||||
- It contains examples in the [standard backtick-delimited `spec.txt` format](#various-markdown-specifications),
|
||||
each of which contain a Markdown example and the corresponding canonical HTML.
|
||||
- The `update-specification.rb` script inserts it as new sections before the appendix
|
||||
of generated `spec.txt`.
|
||||
- It should consist of `H1` header sections, with a examples nested exactly 2 levels deep within `H2`
|
||||
header sections.
|
||||
|
||||
`glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt` sample entries:
|
||||
|
||||
NOTE:
|
||||
All lines in this example are prefixed with a `|` character. This prefix helps avoid false
|
||||
errors when this file is checked by `markdownlint`, and possible errors in other Markdown editors.
|
||||
The actual file should not have these prefixed `|` characters.
|
||||
|
||||
```plaintext
|
||||
|# First GitLab-Specific Section with Examples
|
||||
|
|
||||
|## Strong but with two asterisks
|
||||
|
|
||||
|```````````````````````````````` example
|
||||
|**bold**
|
||||
|.
|
||||
|<p><strong>bold</strong></p>
|
||||
|````````````````````````````````
|
||||
|
|
||||
|# Second GitLab-Specific Section with Examples
|
||||
|
|
||||
|## Strong but with HTML
|
||||
|
|
||||
|```````````````````````````````` example
|
||||
|<strong>
|
||||
|bold
|
||||
|</strong>
|
||||
|.
|
||||
|<p><strong>
|
||||
|bold
|
||||
|</strong></p>
|
||||
|````````````````````````````````
|
||||
```
|
||||
|
||||
##### `glfm_example_status.yml`
|
||||
|
||||
[`glfm_specification/input/gitlab_flavored_markdown/glfm_example_status.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/gitlab_flavored_markdown/glfm_example_status.yml)
|
||||
controls the behavior of the [scripts](#scripts) and [tests](#types-of-markdown-tests-driven-by-the-glfm-specification).
|
||||
|
||||
- It is manually updated.
|
||||
- It controls the status of automatic generation of files based on Markdown examples.
|
||||
- It allows example snapshot generation, Markdown conformance tests, or
|
||||
Markdown snapshot tests to be skipped for individual examples. For example, if
|
||||
they are unimplemented, broken, or cannot be tested for some reason.
|
||||
|
||||
|
@ -545,28 +600,39 @@ updated, as in the case of all GFM files.
|
|||
The `glfm_specification/output` directory contains the CommonMark standard format
|
||||
`spec.txt` file which represents the canonical GLFM specification which is generated
|
||||
by the `update-specification.rb` script. It also contains the rendered `spec.html`
|
||||
and `spec.pdf` which are generated from with the `spec.txt` as input.
|
||||
which is generated based on the `spec.txt` as input.
|
||||
|
||||
- `glfm_specification/output/spec.txt` - A Markdown file, in the standard format
|
||||
with prose and Markdown + canonical HTML examples, generated (or updated) by the
|
||||
`update-specification.rb` script.
|
||||
- `glfm_specification/output/spec.html` - An HTML file, rendered based on `spec.txt`,
|
||||
also generated (or updated) by the `update-specification.rb` script at the same time as
|
||||
`spec.txt`. It corresponds to the HTML-rendered versions of the
|
||||
"GitHub Flavored Markdown" (<abbr title="GitHub Flavored Markdown">GFM</abbr>)
|
||||
[specification](https://github.github.com/gfm/)
|
||||
and the [CommonMark specification](https://spec.commonmark.org/0.30/).
|
||||
|
||||
These output `spec.**` files, which represent the official, canonical GLFM specification
|
||||
These output `spec.*` files, which represent the official, canonical GLFM specification,
|
||||
are colocated under the same parent folder `glfm_specification` with the other
|
||||
`input` specification files. They're located here both for convenience, and because they are all
|
||||
a mix of manually edited and generated files. In GFM,
|
||||
`spec.txt` is [located in the test dir](https://github.com/github/cmark-gfm/blob/master/test/spec.txt),
|
||||
and in CommonMark it's located
|
||||
[in the project root](https://github.com/github/cmark-gfm/blob/master/test/spec.txt).
|
||||
No precedent exists for a standard location. In the future, we may decide to
|
||||
a mix of manually edited and generated files.
|
||||
|
||||
In GFM, `spec.txt` is [located in the test dir](https://github.com/github/cmark-gfm/blob/master/test/spec.txt),
|
||||
and in CommonMark it's located [in the project root](https://github.com/github/cmark-gfm/blob/master/test/spec.txt). No precedent exists for a standard location. In the future, we may decide to
|
||||
move or copy a hosted version of the rendered HTML `spec.html` version to another location or site.
|
||||
|
||||
##### spec.txt
|
||||
|
||||
[`glfm_specification/output/spec.txt`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/output/spec.txt)
|
||||
is a Markdown specification file, in the standard format
|
||||
with prose and Markdown + canonical HTML examples. It is generated or updated by the
|
||||
`update-specification.rb` script.
|
||||
|
||||
It also serves as input for other scripts such as `update-example-snapshots.rb`
|
||||
and `run-spec-tests.sh`.
|
||||
|
||||
##### spec.html
|
||||
|
||||
[`glfm_specification/output/spec.html`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/output/spec.html)
|
||||
is an HTML file, rendered based on `spec.txt`. It is
|
||||
also generated (or updated) by the `update-specification.rb` script at the same time as
|
||||
`spec.txt`.
|
||||
|
||||
It corresponds to the HTML-rendered versions of the
|
||||
"GitHub Flavored Markdown" (<abbr title="GitHub Flavored Markdown">GFM</abbr>)
|
||||
[specification](https://github.github.com/gfm/)
|
||||
and the [CommonMark specification](https://spec.commonmark.org/0.30/).
|
||||
|
||||
### Example snapshot files
|
||||
|
||||
The `example_snapshots` directory contains files which are generated by the
|
||||
|
@ -578,12 +644,13 @@ After the entire GLFM implementation is complete for both backend (Ruby) and
|
|||
frontend (JavaScript), all of these YAML files can be automatically generated.
|
||||
However, while the implementations are still in progress, the `skip_update_example_snapshots`
|
||||
key in `glfm_specification/input/gitlab_flavored_markdown/glfm_example_status.yml`
|
||||
can be used to disable automatic generation of some examples, and they can instead
|
||||
can be used to disable automatic generation of some examples. They can instead
|
||||
be manually edited as necessary to help drive the implementations.
|
||||
|
||||
#### `spec/fixtures/glfm/example_snapshots/examples_index.yml`
|
||||
|
||||
`spec/fixtures/glfm/example_snapshots/examples_index.yml` is the main list of all
|
||||
[`spec/fixtures/glfm/example_snapshots/examples_index.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/fixtures/glfm/example_snapshots/examples_index.yml)
|
||||
is the main list of all
|
||||
CommonMark, GFM, and GLFM example names, each with a unique canonical name.
|
||||
|
||||
- It is generated from the hierarchical sections and examples in the
|
||||
|
@ -594,10 +661,15 @@ CommonMark, GFM, and GLFM example names, each with a unique canonical name.
|
|||
the additional Section 7 in the GLFM `spec.txt`.
|
||||
- It also contains extra metadata about each example, such as:
|
||||
1. `spec_txt_example_position` - The position of the example in the generated GLFM `spec.txt` file.
|
||||
- This value is the index order of each individual Markdown + HTML5 example in the file. It is _not_
|
||||
the line number in the file.
|
||||
- This value can be used to locate the example in the rendered `spec.html` file, because the standard
|
||||
CommonMark tooling includes the index number for each example in the rendered HTML file.
|
||||
For example: [https://spec.commonmark.org/0.30/#example-42](https://spec.commonmark.org/0.30/#example-42)
|
||||
1. `source_specification` - Which specification the example originally came from:
|
||||
`commonmark`, `github`, or `gitlab`.
|
||||
- The naming convention for example entry names is based on nested header section
|
||||
names and example index within the header.
|
||||
names and example index in the header.
|
||||
- This naming convention should result in fairly stable names and example positions.
|
||||
The CommonMark / GLFM specification rarely changes, and most GLFM
|
||||
examples where multiple examples exist for the same Section 7 subsection are
|
||||
|
@ -625,7 +697,7 @@ CommonMark, GFM, and GLFM example names, each with a unique canonical name.
|
|||
|
||||
#### `spec/fixtures/glfm/example_snapshots/markdown.yml`
|
||||
|
||||
`spec/fixtures/glfm/example_snapshots/markdown.yml` contains the original Markdown
|
||||
[`spec/fixtures/glfm/example_snapshots/markdown.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/fixtures/glfm/example_snapshots/markdown.yml) contains the original Markdown
|
||||
for each entry in `spec/fixtures/glfm/example_snapshots/examples_index.yml`
|
||||
|
||||
- For CommonMark and GFM Markdown,
|
||||
|
@ -644,8 +716,8 @@ for each entry in `spec/fixtures/glfm/example_snapshots/examples_index.yml`
|
|||
|
||||
#### `spec/fixtures/glfm/example_snapshots/html.yml`
|
||||
|
||||
`spec/fixtures/glfm/example_snapshots/html.yml` contains the HTML for each entry in
|
||||
`spec/fixtures/glfm/example_snapshots/examples_index.yml`
|
||||
[`spec/fixtures/glfm/example_snapshots/html.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/fixtures/glfm/example_snapshots/html.yml)
|
||||
contains the HTML for each entry in `spec/fixtures/glfm/example_snapshots/examples_index.yml`
|
||||
|
||||
Three types of entries exist, with different HTML for each:
|
||||
|
||||
|
@ -688,8 +760,8 @@ depending on how the implementations evolve.
|
|||
|
||||
#### `spec/fixtures/glfm/example_snapshots/prosemirror_json.yml`
|
||||
|
||||
`spec/fixtures/glfm/example_snapshots/prosemirror_json.yml` contains the ProseMirror
|
||||
JSON for each entry in `spec/fixtures/glfm/example_snapshots/examples_index.yml`
|
||||
[`spec/fixtures/glfm/example_snapshots/prosemirror_json.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/fixtures/glfm/example_snapshots/prosemirror_json.yml)
|
||||
contains the ProseMirror JSON for each entry in `spec/fixtures/glfm/example_snapshots/examples_index.yml`
|
||||
|
||||
- It is generated (or updated) from the frontend code via the `update-example-snapshots.rb`
|
||||
script, but can be manually updated for examples with incomplete implementations.
|
||||
|
|
|
@ -7,10 +7,6 @@ comments: false
|
|||
|
||||
# Upgrading Community Edition and Enterprise Edition from source **(FREE SELF)**
|
||||
|
||||
NOTE:
|
||||
Users wishing to upgrade to 12.0.0 must take some extra steps. See the
|
||||
version specific upgrade instructions for 12.0.0 for more details.
|
||||
|
||||
Make sure you view this update guide from the branch (version) of GitLab you
|
||||
would like to install (for example, `11.8`). You can select the required version of documentation in the dropdown at the top right corner of GitLab documentation page.
|
||||
|
||||
|
@ -421,6 +417,39 @@ Example:
|
|||
Additional instructions here.
|
||||
-->
|
||||
|
||||
### 15.0.0
|
||||
|
||||
Support for more than one database has been added to GitLab. [As part of this](https://gitlab.com/gitlab-org/gitlab/-/issues/338182),
|
||||
`config/database.yml` needs to include a database name in the database configuration.
|
||||
The `main: database` must be first. If an invalid or deprecated syntax is used, an error is generated
|
||||
during application start:
|
||||
|
||||
```plaintext
|
||||
ERROR: This installation of GitLab uses unsupported 'config/database.yml'.
|
||||
The main: database needs to be defined as a first configuration item instead of primary. (RuntimeError)
|
||||
```
|
||||
|
||||
Previously, the `config/database.yml` file looked like the following:
|
||||
|
||||
```yaml
|
||||
production:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: gitlabhq_production
|
||||
...
|
||||
```
|
||||
|
||||
Starting with GitLab 15.0, it needs to define a `main` database first:
|
||||
|
||||
```yaml
|
||||
production:
|
||||
main:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: gitlabhq_production
|
||||
...
|
||||
```
|
||||
|
||||
### 14.5.0
|
||||
|
||||
As part of [enabling real-time issue assignees](https://gitlab.com/gitlab-org/gitlab/-/issues/330117), Action Cable is now enabled by default, and requires `config/cable.yml` to be present.
|
||||
|
|
|
@ -230,6 +230,7 @@ To undo this action, select a different status from the same menu.
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/301003) in GitLab 14.9. Disabled by default.
|
||||
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/353796) in GitLab 14.10.
|
||||
> - [Feature flag `new_vulnerability_form`](https://gitlab.com/gitlab-org/gitlab/-/issues/359049) removed in GitLab 15.0.
|
||||
|
||||
To add a new vulnerability finding from your project level Vulnerability Report page:
|
||||
|
||||
|
|
|
@ -14,11 +14,10 @@ To connect your Kubernetes cluster with GitLab, you can use:
|
|||
|
||||
The certificate-based integration is
|
||||
[**deprecated**](https://about.gitlab.com/blog/2021/11/15/deprecating-the-cert-based-kubernetes-integration/)
|
||||
in GitLab 14.5. The removal dates are:
|
||||
in GitLab 14.5. The sunsetting plans are described:
|
||||
|
||||
- [GitLab.com customers](../../../update/deprecations.md#saas-certificate-based-integration-with-kubernetes): GitLab 15.0.
|
||||
- [Self-managed customers](../../../update/deprecations.md#self-managed-certificate-based-integration-with-kubernetes):
|
||||
Placed behind a disabled feature flag in GitLab 15.0, and removed entirely in GitLab 15.6.
|
||||
- for [GitLab.com customers](../../../update/deprecations.md#saas-certificate-based-integration-with-kubernetes).
|
||||
- for [Self-managed customers](../../../update/deprecations.md#self-managed-certificate-based-integration-with-kubernetes).
|
||||
|
||||
If you are using the certificate-based integration, you should move to another workflow as soon as possible.
|
||||
|
||||
|
|
|
@ -196,7 +196,9 @@ coverage-jdk11:
|
|||
needs: ["test-jdk11"]
|
||||
artifacts:
|
||||
reports:
|
||||
cobertura: target/site/cobertura.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: target/site/cobertura.xml
|
||||
```
|
||||
|
||||
#### Gradle example
|
||||
|
@ -232,7 +234,9 @@ coverage-jdk11:
|
|||
needs: ["test-jdk11"]
|
||||
artifacts:
|
||||
reports:
|
||||
cobertura: build/cobertura.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: build/cobertura.xml
|
||||
```
|
||||
|
||||
### Python example
|
||||
|
@ -254,7 +258,9 @@ run tests:
|
|||
coverage: '/TOTAL.*\s([.\d]+)%/'
|
||||
artifacts:
|
||||
reports:
|
||||
cobertura: coverage.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
```
|
||||
|
||||
### PHP example
|
||||
|
@ -284,7 +290,9 @@ run tests:
|
|||
- php ./vendor/bin/phpunit --coverage-text --coverage-cobertura=coverage.cobertura.xml
|
||||
artifacts:
|
||||
reports:
|
||||
cobertura: coverage.cobertura.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.cobertura.xml
|
||||
```
|
||||
|
||||
[Codeception](https://codeception.com/), through PHPUnit, also supports generating Cobertura report with
|
||||
|
@ -319,7 +327,9 @@ run tests:
|
|||
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
|
||||
expire_in: 2 days
|
||||
reports:
|
||||
cobertura: build/coverage.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: build/coverage.xml
|
||||
```
|
||||
|
||||
### Go example
|
||||
|
@ -346,7 +356,9 @@ run tests:
|
|||
- go run github.com/boumenot/gocover-cobertura < coverage.txt > coverage.xml
|
||||
artifacts:
|
||||
reports:
|
||||
cobertura: coverage.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
```
|
||||
|
||||
### Ruby example
|
||||
|
@ -373,5 +385,7 @@ run tests:
|
|||
- bundle exec rspec
|
||||
artifacts:
|
||||
reports:
|
||||
cobertura: coverage/coverage.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage/coverage.xml
|
||||
```
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
PLACEHOLDER FILE. Actual contents will be added by https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84347
|
|
@ -0,0 +1 @@
|
|||
PLACEHOLDER FILE. Actual contents will be added in a future MR.
|
|
@ -9543,12 +9543,6 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Cleanup ran but some tags were not removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Cleanup timed out"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Cleanup timed out before it could delete all tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Cleanup will run %{time}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9651,6 +9645,9 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Note: Any policy update will result in a change to the scheduled run date and time"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Partial cleanup complete"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Please try different search criteria"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9749,6 +9746,9 @@ msgstr ""
|
|||
msgid "ContainerRegistry|The cleanup policy timed out before it could delete all tags. An administrator can %{adminLinkStart}manually run cleanup now%{adminLinkEnd} or you can wait for the cleanup policy to automatically run again. %{docLinkStart}More information%{docLinkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|The cleanup will continue within %{time}. %{linkStart}Learn more%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|The filter returned no results"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
PLACEHOLDER FILE. Actual contents will be added by https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84347
|
|
@ -0,0 +1 @@
|
|||
PLACEHOLDER FILE. Actual contents will be added by https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84347
|
|
@ -0,0 +1 @@
|
|||
PLACEHOLDER FILE. Actual contents will be added by https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84347
|
|
@ -0,0 +1 @@
|
|||
PLACEHOLDER FILE. Actual contents will be added by https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84347
|
|
@ -8,6 +8,8 @@ import VueApollo from 'vue-apollo';
|
|||
import VueRouter from 'vue-router';
|
||||
import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
|
||||
import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql';
|
||||
import getIssuesWithoutCrmQuery from 'ee_else_ce/issues/list/queries/get_issues_without_crm.query.graphql';
|
||||
import getIssuesCountsWithoutCrmQuery from 'ee_else_ce/issues/list/queries/get_issues_counts_without_crm.query.graphql';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
|
@ -118,6 +120,8 @@ describe('CE IssuesListApp component', () => {
|
|||
const requestHandlers = [
|
||||
[getIssuesQuery, issuesQueryResponse],
|
||||
[getIssuesCountsQuery, issuesCountsQueryResponse],
|
||||
[getIssuesWithoutCrmQuery, issuesQueryResponse],
|
||||
[getIssuesCountsWithoutCrmQuery, issuesCountsQueryResponse],
|
||||
[setSortPreferenceMutation, sortPreferenceMutationResponse],
|
||||
];
|
||||
|
||||
|
|
|
@ -146,6 +146,8 @@ export const locationSearch = [
|
|||
'not[epic_id]=34',
|
||||
'weight=1',
|
||||
'not[weight]=3',
|
||||
'crm_contact_id=123',
|
||||
'crm_organization_id=456',
|
||||
].join('&');
|
||||
|
||||
export const locationSearchWithSpecialValues = [
|
||||
|
@ -194,6 +196,8 @@ export const filteredTokens = [
|
|||
{ type: 'epic_id', value: { data: '34', operator: OPERATOR_IS_NOT } },
|
||||
{ type: 'weight', value: { data: '1', operator: OPERATOR_IS } },
|
||||
{ type: 'weight', value: { data: '3', operator: OPERATOR_IS_NOT } },
|
||||
{ type: 'crm_contact', value: { data: '123', operator: OPERATOR_IS } },
|
||||
{ type: 'crm_organization', value: { data: '456', operator: OPERATOR_IS } },
|
||||
{ type: 'filtered-search-term', value: { data: 'find' } },
|
||||
{ type: 'filtered-search-term', value: { data: 'issues' } },
|
||||
];
|
||||
|
@ -222,6 +226,8 @@ export const apiParams = {
|
|||
iterationId: ['4', '12'],
|
||||
epicId: '12',
|
||||
weight: '1',
|
||||
crmContactId: '123',
|
||||
crmOrganizationId: '456',
|
||||
not: {
|
||||
authorUsername: 'marge',
|
||||
assigneeUsernames: ['patty', 'selma'],
|
||||
|
@ -270,6 +276,8 @@ export const urlParams = {
|
|||
'not[epic_id]': '34',
|
||||
weight: '1',
|
||||
'not[weight]': '3',
|
||||
crm_contact_id: '123',
|
||||
crm_organization_id: '456',
|
||||
};
|
||||
|
||||
export const urlParamsWithSpecialValues = {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { GlLink, GlPopover, GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import CleanupStatus from '~/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue';
|
||||
import {
|
||||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
CLEANUP_STATUS_SCHEDULED,
|
||||
CLEANUP_STATUS_ONGOING,
|
||||
CLEANUP_STATUS_UNFINISHED,
|
||||
|
@ -17,12 +17,20 @@ describe('cleanup_status', () => {
|
|||
|
||||
const findMainIcon = () => wrapper.findByTestId('main-icon');
|
||||
const findExtraInfoIcon = () => wrapper.findByTestId('extra-info');
|
||||
const findPopover = () => wrapper.findComponent(GlPopover);
|
||||
|
||||
const cleanupPolicyHelpPage = helpPagePath(
|
||||
'user/packages/container_registry/reduce_container_registry_storage.html',
|
||||
{ anchor: 'how-the-cleanup-policy-works' },
|
||||
);
|
||||
|
||||
const mountComponent = (propsData = { status: SCHEDULED_STATUS }) => {
|
||||
wrapper = shallowMountExtended(CleanupStatus, {
|
||||
propsData,
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
stubs: {
|
||||
GlLink,
|
||||
GlPopover,
|
||||
GlSprintf,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -43,7 +51,7 @@ describe('cleanup_status', () => {
|
|||
mountComponent({ status });
|
||||
|
||||
expect(findMainIcon().exists()).toBe(visible);
|
||||
expect(wrapper.text()).toBe(text);
|
||||
expect(wrapper.text()).toContain(text);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -53,12 +61,6 @@ describe('cleanup_status', () => {
|
|||
|
||||
expect(findMainIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it(`has the orange class when the status is ${UNFINISHED_STATUS}`, () => {
|
||||
mountComponent({ status: UNFINISHED_STATUS });
|
||||
|
||||
expect(findMainIcon().classes('gl-text-orange-500')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extra info icon', () => {
|
||||
|
@ -76,12 +78,12 @@ describe('cleanup_status', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it(`has a tooltip`, () => {
|
||||
it(`has a popover with a learn more link`, () => {
|
||||
mountComponent({ status: UNFINISHED_STATUS });
|
||||
|
||||
const tooltip = getBinding(findExtraInfoIcon().element, 'gl-tooltip');
|
||||
|
||||
expect(tooltip.value.title).toBe(CLEANUP_TIMED_OUT_ERROR_MESSAGE);
|
||||
expect(findPopover().exists()).toBe(true);
|
||||
expect(findPopover().findComponent(GlLink).exists()).toBe(true);
|
||||
expect(findPopover().findComponent(GlLink).attributes('href')).toBe(cleanupPolicyHelpPage);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { timeTilRun } from '~/packages_and_registries/container_registry/explorer/utils';
|
||||
|
||||
describe('Container registry utilities', () => {
|
||||
describe('timeTilRun', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
|
||||
});
|
||||
|
||||
it('should return a human readable time', () => {
|
||||
const result = timeTilRun('2063-04-08T01:44:03Z');
|
||||
|
||||
expect(result).toBe('4 days');
|
||||
});
|
||||
|
||||
it('should return an empty string with null times', () => {
|
||||
const result = timeTilRun(null);
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -28,6 +28,7 @@ describe('packages_list_row', () => {
|
|||
|
||||
const packageWithoutTags = { ...packageData(), project: packageProject() };
|
||||
const packageWithTags = { ...packageWithoutTags, tags: { nodes: packageTags() } };
|
||||
const packageCannotDestroy = { ...packageData(), canDestroy: false };
|
||||
|
||||
const findPackageTags = () => wrapper.find(PackageTags);
|
||||
const findPackagePath = () => wrapper.find(PackagePath);
|
||||
|
@ -101,6 +102,12 @@ describe('packages_list_row', () => {
|
|||
});
|
||||
|
||||
describe('delete button', () => {
|
||||
it('does not exist when package cannot be destroyed', () => {
|
||||
mountComponent({ packageEntity: packageCannotDestroy });
|
||||
|
||||
expect(findDeleteDropdown().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('exists and has the correct props', () => {
|
||||
mountComponent({ packageEntity: packageWithoutTags });
|
||||
|
||||
|
|
|
@ -469,4 +469,10 @@ RSpec.describe DeployToken do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.impersonated?' do
|
||||
it 'returns false' do
|
||||
expect(subject.impersonated?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -236,12 +236,22 @@ RSpec.describe ContainerRegistry::Migration::EnqueuerWorker, :aggregate_failures
|
|||
end
|
||||
|
||||
context 'when no repository qualifies' do
|
||||
include_examples 'an idempotent worker' do
|
||||
before do
|
||||
allow(ContainerRepository).to receive(:ready_for_import).and_return(ContainerRepository.none)
|
||||
end
|
||||
before do
|
||||
allow(ContainerRepository).to receive(:ready_for_import).and_return(ContainerRepository.none)
|
||||
end
|
||||
|
||||
it_behaves_like 'no action'
|
||||
context 'idempotency' do
|
||||
include_examples 'an idempotent worker' do
|
||||
it_behaves_like 'no action'
|
||||
end
|
||||
end
|
||||
|
||||
context 'logging' do
|
||||
it 'logs no_container_repository_found' do
|
||||
expect_log_extra_metadata(no_container_repository_found: true)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -307,7 +317,7 @@ RSpec.describe ContainerRegistry::Migration::EnqueuerWorker, :aggregate_failures
|
|||
end
|
||||
|
||||
context 'with the exclusive lease taken' do
|
||||
let(:lease_key) { worker.send(:lease_key) }
|
||||
let(:lease_key) { "container_registry:migration:enqueuer_worker:for:#{container_repository.id}" }
|
||||
|
||||
before do
|
||||
stub_exclusive_lease_taken(lease_key, timeout: 30.minutes)
|
||||
|
@ -316,6 +326,23 @@ RSpec.describe ContainerRegistry::Migration::EnqueuerWorker, :aggregate_failures
|
|||
it 'does not perform' do
|
||||
expect(worker).not_to receive(:runnable?)
|
||||
expect(worker).not_to receive(:re_enqueue_if_capacity)
|
||||
expect_log_extra_metadata(lease_already_taken: true)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'with exclusive lease taken for a different repository' do
|
||||
let(:lease_key) { "container_registry:migration:enqueuer_worker:for:test" }
|
||||
|
||||
before do
|
||||
Gitlab::ExclusiveLease.new(lease_key, timeout: 30.minutes).try_obtain
|
||||
end
|
||||
|
||||
it 'does perform' do
|
||||
expect(worker).to receive(:runnable?).and_return(true)
|
||||
expect(worker).to receive(:handle_aborted_migration).and_return(container_repository)
|
||||
expect(worker).to receive(:re_enqueue_if_capacity)
|
||||
|
||||
subject
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue