Add latest changes from gitlab-org/gitlab@master
|
|
@ -30,7 +30,6 @@ Gitlab/ServiceResponse:
|
|||
- 'app/services/snippets/base_service.rb'
|
||||
- 'app/services/snippets/bulk_destroy_service.rb'
|
||||
- 'app/services/snippets/destroy_service.rb'
|
||||
- 'app/services/snippets/repository_validation_service.rb'
|
||||
- 'app/services/timelogs/base_service.rb'
|
||||
- 'app/services/work_items/create_and_link_service.rb'
|
||||
- 'app/services/work_items/create_from_task_service.rb'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
<script>
|
||||
import {
|
||||
GlLoadingIcon,
|
||||
GlFormInputGroup,
|
||||
GlInputGroupText,
|
||||
GlFormInput,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { cloneDeep, uniqueId } from 'lodash';
|
||||
import { createAlert } from '~/alert';
|
||||
import { reportToSentry } from '~/ci/utils';
|
||||
import { JOB_GRAPHQL_ERRORS } from '~/ci/constants';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPENAME_COMMIT_STATUS } from '~/graphql_shared/constants';
|
||||
import GetJob from '../graphql/queries/get_job.query.graphql';
|
||||
|
||||
export default {
|
||||
name: 'JobVariablesForm',
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
GlFormInputGroup,
|
||||
GlInputGroupText,
|
||||
GlFormInput,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
clearBtnSharedClasses: ['gl-flex-grow-0 gl-basis-0 !gl-m-0 !gl-ml-3'],
|
||||
variableSettings: helpPagePath('ci/variables/_index', { anchor: 'for-a-project' }),
|
||||
inputTypes: {
|
||||
key: 'key',
|
||||
value: 'value',
|
||||
},
|
||||
inject: ['projectPath'],
|
||||
apollo: {
|
||||
variables: {
|
||||
query: GetJob,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.projectPath,
|
||||
id: convertToGraphQLId(TYPENAME_COMMIT_STATUS, this.jobId),
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
// variables list always contains one empty variable
|
||||
// skip refetch if form already has non-empty variables
|
||||
return this.variables.length > 1;
|
||||
},
|
||||
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
|
||||
update(data) {
|
||||
const jobVariables = cloneDeep(data?.project?.job?.manualVariables?.nodes);
|
||||
return [...jobVariables.reverse(), ...this.variables];
|
||||
},
|
||||
error(error) {
|
||||
createAlert({ message: JOB_GRAPHQL_ERRORS.jobQueryErrorText });
|
||||
reportToSentry(this.$options.name, error);
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
jobId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
variables: [
|
||||
{
|
||||
id: uniqueId(),
|
||||
key: '',
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
variables: {
|
||||
handler(newValue) {
|
||||
this.$emit('update-variables', newValue);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
inputRef(type, id) {
|
||||
return `${this.$options.inputTypes[type]}-${id}`;
|
||||
},
|
||||
addEmptyVariable() {
|
||||
const lastVar = this.variables[this.variables.length - 1];
|
||||
|
||||
if (lastVar.key === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.variables.push({
|
||||
id: uniqueId(),
|
||||
key: '',
|
||||
value: '',
|
||||
});
|
||||
},
|
||||
canRemove(index) {
|
||||
return index < this.variables.length - 1;
|
||||
},
|
||||
deleteVariable(id) {
|
||||
this.variables.splice(
|
||||
this.variables.findIndex((el) => el.id === id),
|
||||
1,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-loading-icon v-if="$apollo.queries.variables.loading" class="gl-mt-5" size="lg" />
|
||||
<div v-else class="gl-mx-auto gl-mt-5">
|
||||
<label>{{ s__('CiVariables|Variables') }}</label>
|
||||
|
||||
<div
|
||||
v-for="(variable, index) in variables"
|
||||
:key="variable.id"
|
||||
class="gl-mb-5 gl-flex gl-items-center"
|
||||
data-testid="ci-variable-row"
|
||||
>
|
||||
<gl-form-input-group class="gl-mr-4 gl-grow">
|
||||
<template #prepend>
|
||||
<gl-input-group-text>
|
||||
{{ s__('CiVariables|Key') }}
|
||||
</gl-input-group-text>
|
||||
</template>
|
||||
<gl-form-input
|
||||
:ref="inputRef('key', variable.id)"
|
||||
v-model="variable.key"
|
||||
:placeholder="s__('CiVariables|Input variable key')"
|
||||
data-testid="ci-variable-key"
|
||||
@change="addEmptyVariable"
|
||||
/>
|
||||
</gl-form-input-group>
|
||||
|
||||
<gl-form-input-group class="gl-grow-2">
|
||||
<template #prepend>
|
||||
<gl-input-group-text>
|
||||
{{ s__('CiVariables|Value') }}
|
||||
</gl-input-group-text>
|
||||
</template>
|
||||
<gl-form-input
|
||||
:ref="inputRef('value', variable.id)"
|
||||
v-model="variable.value"
|
||||
:placeholder="s__('CiVariables|Input variable value')"
|
||||
data-testid="ci-variable-value"
|
||||
/>
|
||||
</gl-form-input-group>
|
||||
|
||||
<gl-button
|
||||
v-if="canRemove(index)"
|
||||
v-gl-tooltip
|
||||
:aria-label="s__('CiVariables|Remove inputs')"
|
||||
:title="s__('CiVariables|Remove inputs')"
|
||||
:class="$options.clearBtnSharedClasses"
|
||||
category="tertiary"
|
||||
icon="remove"
|
||||
data-testid="delete-variable-btn"
|
||||
@click="deleteVariable(variable.id)"
|
||||
/>
|
||||
<!-- Placeholder button to keep the layout fixed -->
|
||||
<gl-button
|
||||
v-else
|
||||
class="gl-pointer-events-none gl-opacity-0"
|
||||
:class="$options.clearBtnSharedClasses"
|
||||
data-testid="delete-variable-btn-placeholder"
|
||||
category="tertiary"
|
||||
icon="remove"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="gl-mt-5 gl-text-center">
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
'CiVariables|Specify variable values to be used in this run. The variables specified in the configuration file and %{linkStart}CI/CD settings%{linkEnd} are used by default.',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="$options.variableSettings" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<div class="gl-mt-3 gl-text-center">
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
'CiVariables|Variables specified here are %{boldStart}expanded%{boldEnd} and not %{boldStart}masked.%{boldEnd}',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #bold="{ content }">
|
||||
<strong>
|
||||
{{ content }}
|
||||
</strong>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,28 +1,16 @@
|
|||
<script>
|
||||
import {
|
||||
GlFormInputGroup,
|
||||
GlInputGroupText,
|
||||
GlFormInput,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
GlSprintf,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { cloneDeep, uniqueId } from 'lodash';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { createAlert } from '~/alert';
|
||||
import { TYPENAME_CI_BUILD, TYPENAME_COMMIT_STATUS } from '~/graphql_shared/constants';
|
||||
import { TYPENAME_CI_BUILD } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { JOB_GRAPHQL_ERRORS } from '~/ci/constants';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import { s__ } from '~/locale';
|
||||
import { reportToSentry } from '~/ci/utils';
|
||||
import { confirmJobConfirmationMessage } from '~/ci/pipeline_details/graph/utils';
|
||||
import GetJob from '../graphql/queries/get_job.query.graphql';
|
||||
import playJobWithVariablesMutation from '../graphql/mutations/job_play_with_variables.mutation.graphql';
|
||||
import retryJobWithVariablesMutation from '../graphql/mutations/job_retry_with_variables.mutation.graphql';
|
||||
import JobVariablesForm from './job_variables_form.vue';
|
||||
|
||||
// This component is a port of ~/ci/job_details/components/legacy_manual_variables_form.vue
|
||||
// It is meant to fetch/update the job information via GraphQL instead of REST API.
|
||||
|
|
@ -30,42 +18,8 @@ import retryJobWithVariablesMutation from '../graphql/mutations/job_retry_with_v
|
|||
export default {
|
||||
name: 'ManualVariablesForm',
|
||||
components: {
|
||||
GlFormInputGroup,
|
||||
GlInputGroupText,
|
||||
GlFormInput,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
GlSprintf,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: ['projectPath'],
|
||||
apollo: {
|
||||
variables: {
|
||||
query: GetJob,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.projectPath,
|
||||
id: convertToGraphQLId(TYPENAME_COMMIT_STATUS, this.jobId),
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
// variables list always contains one empty variable
|
||||
// skip refetch if form already has non-empty variables
|
||||
return this.variables.length > 1;
|
||||
},
|
||||
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
|
||||
update(data) {
|
||||
const jobVariables = cloneDeep(data?.project?.job?.manualVariables?.nodes);
|
||||
return [...jobVariables.reverse(), ...this.variables];
|
||||
},
|
||||
error(error) {
|
||||
createAlert({ message: JOB_GRAPHQL_ERRORS.jobQueryErrorText });
|
||||
reportToSentry(this.$options.name, error);
|
||||
},
|
||||
},
|
||||
JobVariablesForm,
|
||||
},
|
||||
props: {
|
||||
isRetryable: {
|
||||
|
|
@ -86,40 +40,16 @@ export default {
|
|||
default: null,
|
||||
},
|
||||
},
|
||||
clearBtnSharedClasses: ['gl-flex-grow-0 gl-basis-0 !gl-m-0 !gl-ml-3'],
|
||||
inputTypes: {
|
||||
key: 'key',
|
||||
value: 'value',
|
||||
data() {
|
||||
return {
|
||||
runBtnDisabled: false,
|
||||
preparedVariables: [],
|
||||
};
|
||||
},
|
||||
i18n: {
|
||||
cancel: s__('CiVariables|Cancel'),
|
||||
removeInputs: s__('CiVariables|Remove inputs'),
|
||||
formHelpText: s__(
|
||||
'CiVariables|Specify variable values to be used in this run. The variables specified in the configuration file and %{linkStart}CI/CD settings%{linkEnd} are used by default.',
|
||||
),
|
||||
overrideNoteText: s__(
|
||||
'CiVariables|Variables specified here are %{boldStart}expanded%{boldEnd} and not %{boldStart}masked.%{boldEnd}',
|
||||
),
|
||||
header: s__('CiVariables|Variables'),
|
||||
keyLabel: s__('CiVariables|Key'),
|
||||
keyPlaceholder: s__('CiVariables|Input variable key'),
|
||||
runAgainButtonText: s__('CiVariables|Run job again'),
|
||||
runButtonText: s__('CiVariables|Run job'),
|
||||
valueLabel: s__('CiVariables|Value'),
|
||||
valuePlaceholder: s__('CiVariables|Input variable value'),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
job: {},
|
||||
variables: [
|
||||
{
|
||||
id: uniqueId(),
|
||||
key: '',
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
runBtnDisabled: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
mutationVariables() {
|
||||
|
|
@ -128,21 +58,18 @@ export default {
|
|||
variables: this.preparedVariables,
|
||||
};
|
||||
},
|
||||
preparedVariables() {
|
||||
return this.variables
|
||||
.filter((variable) => variable.key !== '')
|
||||
.map(({ key, value }) => ({ key, value }));
|
||||
},
|
||||
runBtnText() {
|
||||
return this.isRetryable
|
||||
? this.$options.i18n.runAgainButtonText
|
||||
: this.$options.i18n.runButtonText;
|
||||
},
|
||||
variableSettings() {
|
||||
return helpPagePath('ci/variables/_index', { anchor: 'for-a-project' });
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onVariablesUpdate(variables) {
|
||||
this.preparedVariables = variables
|
||||
.filter((variable) => variable.key !== '')
|
||||
.map(({ key, value }) => ({ key, value }));
|
||||
},
|
||||
async playJob() {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate({
|
||||
|
|
@ -175,31 +102,6 @@ export default {
|
|||
reportToSentry(this.$options.name, error);
|
||||
}
|
||||
},
|
||||
addEmptyVariable() {
|
||||
const lastVar = this.variables[this.variables.length - 1];
|
||||
|
||||
if (lastVar.key === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.variables.push({
|
||||
id: uniqueId(),
|
||||
key: '',
|
||||
value: '',
|
||||
});
|
||||
},
|
||||
canRemove(index) {
|
||||
return index < this.variables.length - 1;
|
||||
},
|
||||
deleteVariable(id) {
|
||||
this.variables.splice(
|
||||
this.variables.findIndex((el) => el.id === id),
|
||||
1,
|
||||
);
|
||||
},
|
||||
inputRef(type, id) {
|
||||
return `${this.$options.inputTypes[type]}-${id}`;
|
||||
},
|
||||
navigateToJob(path) {
|
||||
visitUrl(path);
|
||||
},
|
||||
|
|
@ -227,103 +129,25 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-loading-icon v-if="$apollo.queries.variables.loading" class="gl-mt-9" size="lg" />
|
||||
<div v-else class="row gl-justify-center">
|
||||
<div class="col-10">
|
||||
<label>{{ $options.i18n.header }}</label>
|
||||
<div>
|
||||
<job-variables-form :job-id="jobId" @update-variables="onVariablesUpdate" />
|
||||
|
||||
<div
|
||||
v-for="(variable, index) in variables"
|
||||
:key="variable.id"
|
||||
class="gl-mb-5 gl-flex gl-items-center"
|
||||
data-testid="ci-variable-row"
|
||||
<div class="gl-mt-5 gl-flex gl-justify-center gl-gap-x-2">
|
||||
<gl-button
|
||||
v-if="isRetryable"
|
||||
data-testid="cancel-btn"
|
||||
@click="$emit('hideManualVariablesForm')"
|
||||
>{{ $options.i18n.cancel }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
variant="confirm"
|
||||
category="primary"
|
||||
:disabled="runBtnDisabled"
|
||||
data-testid="run-manual-job-btn"
|
||||
@click="runJob"
|
||||
>
|
||||
<gl-form-input-group class="gl-mr-4 gl-grow">
|
||||
<template #prepend>
|
||||
<gl-input-group-text>
|
||||
{{ $options.i18n.keyLabel }}
|
||||
</gl-input-group-text>
|
||||
</template>
|
||||
<gl-form-input
|
||||
:ref="inputRef('key', variable.id)"
|
||||
v-model="variable.key"
|
||||
:placeholder="$options.i18n.keyPlaceholder"
|
||||
data-testid="ci-variable-key"
|
||||
@change="addEmptyVariable"
|
||||
/>
|
||||
</gl-form-input-group>
|
||||
|
||||
<gl-form-input-group class="gl-grow-2">
|
||||
<template #prepend>
|
||||
<gl-input-group-text>
|
||||
{{ $options.i18n.valueLabel }}
|
||||
</gl-input-group-text>
|
||||
</template>
|
||||
<gl-form-input
|
||||
:ref="inputRef('value', variable.id)"
|
||||
v-model="variable.value"
|
||||
:placeholder="$options.i18n.valuePlaceholder"
|
||||
data-testid="ci-variable-value"
|
||||
/>
|
||||
</gl-form-input-group>
|
||||
|
||||
<gl-button
|
||||
v-if="canRemove(index)"
|
||||
v-gl-tooltip
|
||||
:aria-label="$options.i18n.removeInputs"
|
||||
:title="$options.i18n.removeInputs"
|
||||
:class="$options.clearBtnSharedClasses"
|
||||
category="tertiary"
|
||||
icon="remove"
|
||||
data-testid="delete-variable-btn"
|
||||
@click="deleteVariable(variable.id)"
|
||||
/>
|
||||
<!-- Placeholder button to keep the layout fixed -->
|
||||
<gl-button
|
||||
v-else
|
||||
class="gl-pointer-events-none gl-opacity-0"
|
||||
:class="$options.clearBtnSharedClasses"
|
||||
data-testid="delete-variable-btn-placeholder"
|
||||
category="tertiary"
|
||||
icon="remove"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="gl-mt-5 gl-text-center">
|
||||
<gl-sprintf :message="$options.i18n.formHelpText">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="variableSettings" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<div class="gl-mt-3 gl-text-center">
|
||||
<gl-sprintf :message="$options.i18n.overrideNoteText">
|
||||
<template #bold="{ content }">
|
||||
<strong>
|
||||
{{ content }}
|
||||
</strong>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<div class="gl-mt-5 gl-flex gl-justify-center">
|
||||
<gl-button
|
||||
v-if="isRetryable"
|
||||
data-testid="cancel-btn"
|
||||
@click="$emit('hideManualVariablesForm')"
|
||||
>{{ $options.i18n.cancel }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
variant="confirm"
|
||||
category="primary"
|
||||
:disabled="runBtnDisabled"
|
||||
data-testid="run-manual-job-btn"
|
||||
@click="runJob"
|
||||
>
|
||||
{{ runBtnText }}
|
||||
</gl-button>
|
||||
</div>
|
||||
{{ runBtnText }}
|
||||
</gl-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ module SearchHelper
|
|||
return []
|
||||
end
|
||||
|
||||
search_using_search_service(current_user, 'users', term, limit).map do |user|
|
||||
search_using_search_service(current_user, 'users', term, limit, { autocomplete: true }).map do |user|
|
||||
{
|
||||
category: "Users",
|
||||
id: user.id,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ module Search
|
|||
{
|
||||
state: params[:state],
|
||||
confidential: params[:confidential],
|
||||
include_archived: params[:include_archived]
|
||||
include_archived: params[:include_archived],
|
||||
autocomplete: params[:autocomplete]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Snippets
|
||||
class RepositoryValidationService
|
||||
INVALID_REPOSITORY = :invalid_snippet_repository
|
||||
|
||||
attr_reader :current_user, :snippet, :repository
|
||||
|
||||
RepositoryValidationError = Class.new(StandardError)
|
||||
|
|
@ -25,7 +27,7 @@ module Snippets
|
|||
|
||||
ServiceResponse.success(message: 'Valid snippet repository.')
|
||||
rescue RepositoryValidationError => e
|
||||
ServiceResponse.error(message: "Error: #{e.message}", http_status: 400)
|
||||
ServiceResponse.error(message: "Error: #{e.message}", reason: INVALID_REPOSITORY)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddUsableStorageBytesToZoektNodes < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.10'
|
||||
|
||||
def change
|
||||
add_column :zoekt_nodes, :usable_storage_bytes, :bigint, null: false, default: 0, if_not_exists: true
|
||||
add_column :zoekt_nodes, :usable_storage_bytes_locked_until, :timestamptz, if_not_exists: true
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
7ee4d9d63499f305e7dbe6107de736b578f898d323d295a7372dc34c29da4df4
|
||||
|
|
@ -24553,6 +24553,8 @@ CREATE TABLE zoekt_nodes (
|
|||
search_base_url text NOT NULL,
|
||||
metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
indexed_bytes bigint DEFAULT 0 NOT NULL,
|
||||
usable_storage_bytes bigint DEFAULT 0 NOT NULL,
|
||||
usable_storage_bytes_locked_until timestamp with time zone,
|
||||
CONSTRAINT check_32f39efba3 CHECK ((char_length(search_base_url) <= 1024)),
|
||||
CONSTRAINT check_38c354a3c2 CHECK ((char_length(index_base_url) <= 1024))
|
||||
);
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ To disable the banner:
|
|||
|
||||
By default, a banner shows in merge requests in projects with the [Jenkins integration enabled](../../integration/jenkins.md) to prompt migration to GitLab CI/CD.
|
||||
|
||||

|
||||

|
||||
|
||||
To disable the banner:
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
|
@ -22330,7 +22330,7 @@ Represents the Geo replication and verification state of a ci_secure_file.
|
|||
| <a id="cisecurefileregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the CiSecureFileRegistry do not match on the primary and secondary. |
|
||||
| <a id="cisecurefileregistrycisecurefileid"></a>`ciSecureFileId` | [`ID!`](#id) | ID of the Ci Secure File. |
|
||||
| <a id="cisecurefileregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the CiSecureFileRegistry was created. |
|
||||
| <a id="cisecurefileregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="cisecurefileregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="cisecurefileregistryid"></a>`id` | [`ID!`](#id) | ID of the CiSecureFileRegistry. |
|
||||
| <a id="cisecurefileregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the CiSecureFileRegistry. |
|
||||
| <a id="cisecurefileregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the CiSecureFileRegistry. |
|
||||
|
|
@ -23161,7 +23161,7 @@ Represents the Geo replication and verification state of an Container Repository
|
|||
| <a id="containerrepositoryregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the ContainerRepositoryRegistry do not match on the primary and secondary. |
|
||||
| <a id="containerrepositoryregistrycontainerrepositoryid"></a>`containerRepositoryId` | [`ID!`](#id) | ID of the ContainerRepository. |
|
||||
| <a id="containerrepositoryregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the ContainerRepositoryRegistry was created. |
|
||||
| <a id="containerrepositoryregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="containerrepositoryregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="containerrepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the ContainerRepositoryRegistry. |
|
||||
| <a id="containerrepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the ContainerRepositoryRegistry. |
|
||||
| <a id="containerrepositoryregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the ContainerRepositoryRegistry. |
|
||||
|
|
@ -24267,7 +24267,7 @@ Represents the Geo replication and verification state of a dependency_proxy_blob
|
|||
| <a id="dependencyproxyblobregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the DependencyProxyBlobRegistry do not match on the primary and secondary. |
|
||||
| <a id="dependencyproxyblobregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the DependencyProxyBlobRegistry was created. |
|
||||
| <a id="dependencyproxyblobregistrydependencyproxyblobid"></a>`dependencyProxyBlobId` | [`ID!`](#id) | ID of the Dependency Proxy Blob. |
|
||||
| <a id="dependencyproxyblobregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="dependencyproxyblobregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="dependencyproxyblobregistryid"></a>`id` | [`ID!`](#id) | ID of the DependencyProxyBlobRegistry. |
|
||||
| <a id="dependencyproxyblobregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the DependencyProxyBlobRegistry. |
|
||||
| <a id="dependencyproxyblobregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the DependencyProxyBlobRegistry. |
|
||||
|
|
@ -24326,7 +24326,7 @@ Represents the Geo replication and verification state of a dependency_proxy_mani
|
|||
| <a id="dependencyproxymanifestregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the DependencyProxyManifestRegistry do not match on the primary and secondary. |
|
||||
| <a id="dependencyproxymanifestregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the DependencyProxyManifestRegistry was created. |
|
||||
| <a id="dependencyproxymanifestregistrydependencyproxymanifestid"></a>`dependencyProxyManifestId` | [`ID!`](#id) | ID of the Dependency Proxy Manifest. |
|
||||
| <a id="dependencyproxymanifestregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="dependencyproxymanifestregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="dependencyproxymanifestregistryid"></a>`id` | [`ID!`](#id) | ID of the DependencyProxyManifestRegistry. |
|
||||
| <a id="dependencyproxymanifestregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the DependencyProxyManifestRegistry. |
|
||||
| <a id="dependencyproxymanifestregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the DependencyProxyManifestRegistry. |
|
||||
|
|
@ -24695,7 +24695,7 @@ Represents the Geo replication and verification state of a Design Management Rep
|
|||
| <a id="designmanagementrepositoryregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the DesignManagementRepositoryRegistry do not match on the primary and secondary. |
|
||||
| <a id="designmanagementrepositoryregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the DesignManagementRepositoryRegistry was created. |
|
||||
| <a id="designmanagementrepositoryregistrydesignmanagementrepositoryid"></a>`designManagementRepositoryId` | [`ID!`](#id) | ID of the Design Management Repository. |
|
||||
| <a id="designmanagementrepositoryregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="designmanagementrepositoryregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="designmanagementrepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the DesignManagementRepositoryRegistry. |
|
||||
| <a id="designmanagementrepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the DesignManagementRepositoryRegistry. |
|
||||
| <a id="designmanagementrepositoryregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the DesignManagementRepositoryRegistry. |
|
||||
|
|
@ -28350,7 +28350,7 @@ Represents the Geo sync and verification state of a group wiki repository.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="groupwikirepositoryregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the GroupWikiRepositoryRegistry do not match on the primary and secondary. |
|
||||
| <a id="groupwikirepositoryregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the GroupWikiRepositoryRegistry was created. |
|
||||
| <a id="groupwikirepositoryregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="groupwikirepositoryregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="groupwikirepositoryregistrygroupwikirepositoryid"></a>`groupWikiRepositoryId` | [`ID!`](#id) | ID of the Group Wiki Repository. |
|
||||
| <a id="groupwikirepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the GroupWikiRepositoryRegistry. |
|
||||
| <a id="groupwikirepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the GroupWikiRepositoryRegistry. |
|
||||
|
|
@ -29068,7 +29068,7 @@ Represents the Geo replication and verification state of a job_artifact.
|
|||
| <a id="jobartifactregistryartifactid"></a>`artifactId` | [`ID!`](#id) | ID of the Job Artifact. |
|
||||
| <a id="jobartifactregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the JobArtifactRegistry do not match on the primary and secondary. |
|
||||
| <a id="jobartifactregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the JobArtifactRegistry was created. |
|
||||
| <a id="jobartifactregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="jobartifactregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="jobartifactregistryid"></a>`id` | [`ID!`](#id) | ID of the JobArtifactRegistry. |
|
||||
| <a id="jobartifactregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the JobArtifactRegistry. |
|
||||
| <a id="jobartifactregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the JobArtifactRegistry. |
|
||||
|
|
@ -29172,7 +29172,7 @@ Represents the Geo sync and verification state of an LFS object.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="lfsobjectregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the LfsObjectRegistry do not match on the primary and secondary. |
|
||||
| <a id="lfsobjectregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the LfsObjectRegistry was created. |
|
||||
| <a id="lfsobjectregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="lfsobjectregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="lfsobjectregistryid"></a>`id` | [`ID!`](#id) | ID of the LfsObjectRegistry. |
|
||||
| <a id="lfsobjectregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the LfsObjectRegistry. |
|
||||
| <a id="lfsobjectregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the LfsObjectRegistry. |
|
||||
|
|
@ -30422,7 +30422,7 @@ Represents the Geo sync and verification state of a Merge Request diff.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestdiffregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the MergeRequestDiffRegistry do not match on the primary and secondary. |
|
||||
| <a id="mergerequestdiffregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the MergeRequestDiffRegistry was created. |
|
||||
| <a id="mergerequestdiffregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="mergerequestdiffregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="mergerequestdiffregistryid"></a>`id` | [`ID!`](#id) | ID of the MergeRequestDiffRegistry. |
|
||||
| <a id="mergerequestdiffregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the MergeRequestDiffRegistry. |
|
||||
| <a id="mergerequestdiffregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the MergeRequestDiffRegistry. |
|
||||
|
|
@ -32503,7 +32503,7 @@ Represents the Geo sync and verification state of a package file.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="packagefileregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the PackageFileRegistry do not match on the primary and secondary. |
|
||||
| <a id="packagefileregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the PackageFileRegistry was created. |
|
||||
| <a id="packagefileregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="packagefileregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="packagefileregistryid"></a>`id` | [`ID!`](#id) | ID of the PackageFileRegistry. |
|
||||
| <a id="packagefileregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the PackageFileRegistry. |
|
||||
| <a id="packagefileregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the PackageFileRegistry. |
|
||||
|
|
@ -32704,7 +32704,7 @@ Represents the Geo replication and verification state of a pages_deployment.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="pagesdeploymentregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the PagesDeploymentRegistry do not match on the primary and secondary. |
|
||||
| <a id="pagesdeploymentregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the PagesDeploymentRegistry was created. |
|
||||
| <a id="pagesdeploymentregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="pagesdeploymentregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="pagesdeploymentregistryid"></a>`id` | [`ID!`](#id) | ID of the PagesDeploymentRegistry. |
|
||||
| <a id="pagesdeploymentregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the PagesDeploymentRegistry. |
|
||||
| <a id="pagesdeploymentregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the PagesDeploymentRegistry. |
|
||||
|
|
@ -33041,7 +33041,7 @@ Represents the Geo sync and verification state of a pipeline artifact.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="pipelineartifactregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the PipelineArtifactRegistry do not match on the primary and secondary. |
|
||||
| <a id="pipelineartifactregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the PipelineArtifactRegistry was created. |
|
||||
| <a id="pipelineartifactregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="pipelineartifactregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="pipelineartifactregistryid"></a>`id` | [`ID!`](#id) | ID of the PipelineArtifactRegistry. |
|
||||
| <a id="pipelineartifactregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the PipelineArtifactRegistry. |
|
||||
| <a id="pipelineartifactregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the PipelineArtifactRegistry. |
|
||||
|
|
@ -35818,7 +35818,7 @@ Represents the Geo replication and verification state of a project repository.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="projectrepositoryregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the ProjectRepositoryRegistry do not match on the primary and secondary. |
|
||||
| <a id="projectrepositoryregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the ProjectRepositoryRegistry was created. |
|
||||
| <a id="projectrepositoryregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="projectrepositoryregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="projectrepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the ProjectRepositoryRegistry. |
|
||||
| <a id="projectrepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the ProjectRepositoryRegistry. |
|
||||
| <a id="projectrepositoryregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the ProjectRepositoryRegistry. |
|
||||
|
|
@ -36106,7 +36106,7 @@ Represents the Geo replication and verification state of a project_wiki_reposito
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="projectwikirepositoryregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the ProjectWikiRepositoryRegistry do not match on the primary and secondary. |
|
||||
| <a id="projectwikirepositoryregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the ProjectWikiRepositoryRegistry was created. |
|
||||
| <a id="projectwikirepositoryregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="projectwikirepositoryregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="projectwikirepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the ProjectWikiRepositoryRegistry. |
|
||||
| <a id="projectwikirepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the ProjectWikiRepositoryRegistry. |
|
||||
| <a id="projectwikirepositoryregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the ProjectWikiRepositoryRegistry. |
|
||||
|
|
@ -37318,7 +37318,7 @@ Represents the Geo sync and verification state of a snippet repository.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="snippetrepositoryregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the SnippetRepositoryRegistry do not match on the primary and secondary. |
|
||||
| <a id="snippetrepositoryregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the SnippetRepositoryRegistry was created. |
|
||||
| <a id="snippetrepositoryregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="snippetrepositoryregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="snippetrepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the SnippetRepositoryRegistry. |
|
||||
| <a id="snippetrepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the SnippetRepositoryRegistry. |
|
||||
| <a id="snippetrepositoryregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the SnippetRepositoryRegistry. |
|
||||
|
|
@ -37649,7 +37649,7 @@ Represents the Geo sync and verification state of a terraform state version.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="terraformstateversionregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the TerraformStateVersionRegistry do not match on the primary and secondary. |
|
||||
| <a id="terraformstateversionregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the TerraformStateVersionRegistry was created. |
|
||||
| <a id="terraformstateversionregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="terraformstateversionregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="terraformstateversionregistryid"></a>`id` | [`ID!`](#id) | ID of the TerraformStateVersionRegistry. |
|
||||
| <a id="terraformstateversionregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the TerraformStateVersionRegistry. |
|
||||
| <a id="terraformstateversionregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the TerraformStateVersionRegistry. |
|
||||
|
|
@ -37971,7 +37971,7 @@ Represents the Geo replication and verification state of an upload.
|
|||
| <a id="uploadregistrychecksummismatch"></a>`checksumMismatch` | [`Boolean`](#boolean) | Indicate if the checksums of the UploadRegistry do not match on the primary and secondary. |
|
||||
| <a id="uploadregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the UploadRegistry was created. |
|
||||
| <a id="uploadregistryfileid"></a>`fileId` | [`ID!`](#id) | ID of the Upload. |
|
||||
| <a id="uploadregistryforcetoredownload"></a>`forceToRedownload` | [`Boolean`](#boolean) | Indicate if a forced redownload is to be performed. |
|
||||
| <a id="uploadregistryforcetoredownload"></a>`forceToRedownload` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated** in GitLab 17.10. Removed from registry tables in the database in favor of the newer reusable framework. |
|
||||
| <a id="uploadregistryid"></a>`id` | [`ID!`](#id) | ID of the UploadRegistry. |
|
||||
| <a id="uploadregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the UploadRegistry. |
|
||||
| <a id="uploadregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the UploadRegistry. |
|
||||
|
|
|
|||
|
|
@ -380,6 +380,10 @@ cannot push to the repository in your project.
|
|||
You can also control this setting with the [`ci_push_repository_for_job_token_allowed`](../../api/projects.md#edit-a-project)
|
||||
parameter in the `projects` REST API endpoint.
|
||||
|
||||
## Fine-grained permissions for job tokens
|
||||
|
||||
Fine-grained permissions for job tokens are an [experiment](../../policy/development_stages_support.md#experiment). For information on this feature and the available resources, see [Fine-grained permissions for CI/CD job tokens](fine_grained_permissions.md). Feedback is welcome on this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/519575).
|
||||
|
||||
## Use a job token
|
||||
|
||||
### To `git clone` a private project's repository
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Software Supply Chain Security
|
||||
group: Pipeline Security
|
||||
group: Authorization
|
||||
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
|
||||
title: Fine-grained permissions for CI/CD job tokens
|
||||
---
|
||||
|
|
@ -23,10 +23,43 @@ Status: Experiment
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
{{< history >}}
|
||||
|
||||
- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/15234) in GitLab 17.10. This feature is an [experiment](../../policy/development_stages_support.md#experiment).
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
{{< alert type="flag" >}}
|
||||
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
You can use fine-grained permissions to explicitly allow access to a limited set of API endpoints.
|
||||
These permissions are applied to the CI/CD job tokens in a specified project.
|
||||
This feature is an [experiment](../../policy/development_stages_support.md#experiment).
|
||||
|
||||
## Enable fine-grained permissions
|
||||
|
||||
### On GitLab Self-Managed
|
||||
|
||||
1. Start the GitLab Rails console. For information, see [Enable and disable GitLab features deployed behind feature flags](../../administration/feature_flags.md#enable-or-disable-the-feature)
|
||||
1. Turn on the [feature flag](../../administration/feature_flags.md):
|
||||
|
||||
```ruby
|
||||
# You must include a specific project ID with this command.
|
||||
Feature.enable(:add_policies_to_ci_job_token, <project_id>)
|
||||
```
|
||||
|
||||
### On GitLab.com
|
||||
|
||||
Add a comment on this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/519575) with your project ID.
|
||||
|
||||
## Available API endpoints
|
||||
|
||||
The following endpoints are available for CI/CD job tokens.
|
||||
You can use fine-grained permissions to explicitly allow access to a limited set of the following API endpoints.
|
||||
|
||||
`None` means fine-grained permissions cannot control access to this endpoint.
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
|
@ -758,7 +758,7 @@ To determine the IP address of an instance runner:
|
|||
1. Select **CI/CD > Runners**.
|
||||
1. Find the runner in the table and view the **IP Address** column.
|
||||
|
||||

|
||||

|
||||
|
||||
### Determine the IP address of a project runner
|
||||
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ For each selected vulnerability:
|
|||
- A badge is added to its severity, indicating that the severity has been overridden.
|
||||
- Manual severity adjustments are recorded in the vulnerability's **history**.
|
||||
|
||||

|
||||

|
||||
|
||||
## Sort vulnerabilities by date detected
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 332 KiB |
|
|
@ -32,19 +32,19 @@ short-lived (results from hyperparameter tuning triggered by a merge request),
|
|||
but usually hold model runs that have a similar set of parameters measured
|
||||
by the same metrics.
|
||||
|
||||

|
||||

|
||||
|
||||
## Model run
|
||||
|
||||
A model run is a variation of the training of a machine learning model, that can be eventually promoted to a version
|
||||
of the model.
|
||||
|
||||

|
||||

|
||||
|
||||
The goal of a data scientist is to find the model run whose parameter values lead to the best model
|
||||
performance, as indicated by the given metrics.
|
||||
|
||||

|
||||

|
||||
|
||||
Some example parameters:
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ Trial artifacts are saved as packages. After an artifact is logged for a run, al
|
|||
|
||||
You can associate runs to the CI job that created them, allowing quick links to the merge request, pipeline, and user that triggered the pipeline:
|
||||
|
||||

|
||||

|
||||
|
||||
## View logged metrics
|
||||
|
||||
|
|
@ -89,4 +89,4 @@ To view logged metrics:
|
|||
1. Select the experiment you want to view.
|
||||
1. Select the **Performance** tab.
|
||||
|
||||

|
||||

|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 209 KiB |
|
|
@ -0,0 +1,250 @@
|
|||
import { GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import { createAlert } from '~/alert';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { JOB_GRAPHQL_ERRORS } from '~/ci/constants';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import JobVariablesForm from '~/ci/job_details/components/job_variables_form.vue';
|
||||
import getJobQuery from '~/ci/job_details/graphql/queries/get_job.query.graphql';
|
||||
import { mockFullPath, mockId, mockJobResponse, mockJobWithVariablesResponse } from '../mock_data';
|
||||
|
||||
jest.mock('~/alert');
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const defaultProvide = {
|
||||
projectPath: mockFullPath,
|
||||
};
|
||||
const defaultProps = {
|
||||
jobId: mockId,
|
||||
};
|
||||
|
||||
describe('Job Variables Form', () => {
|
||||
let wrapper;
|
||||
let mockApollo;
|
||||
|
||||
const getJobQueryResponseHandlerWithVariables = jest.fn().mockResolvedValue(mockJobResponse);
|
||||
|
||||
const defaultHandlers = {
|
||||
getJobQueryResponseHandlerWithVariables,
|
||||
};
|
||||
|
||||
const createComponent = ({ handlers = defaultHandlers } = {}) => {
|
||||
mockApollo = createMockApollo([
|
||||
[getJobQuery, handlers.getJobQueryResponseHandlerWithVariables],
|
||||
]);
|
||||
|
||||
const options = {
|
||||
apolloProvider: mockApollo,
|
||||
};
|
||||
|
||||
wrapper = mountExtended(JobVariablesForm, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
},
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
};
|
||||
|
||||
const findHelpText = () => wrapper.findComponent(GlSprintf);
|
||||
const findHelpLink = () => wrapper.findComponent(GlLink);
|
||||
const findDeleteVarBtn = () => wrapper.findByTestId('delete-variable-btn');
|
||||
const findAllDeleteVarBtns = () => wrapper.findAllByTestId('delete-variable-btn');
|
||||
const findDeleteVarBtnPlaceholder = () => wrapper.findByTestId('delete-variable-btn-placeholder');
|
||||
const findCiVariableKey = () => wrapper.findByTestId('ci-variable-key');
|
||||
const findAllCiVariableKeys = () => wrapper.findAllByTestId('ci-variable-key');
|
||||
const findCiVariableValue = () => wrapper.findByTestId('ci-variable-value');
|
||||
const findAllVariables = () => wrapper.findAllByTestId('ci-variable-row');
|
||||
|
||||
const setCiVariableKey = () => {
|
||||
findCiVariableKey().setValue('new key');
|
||||
findCiVariableKey().vm.$emit('change');
|
||||
nextTick();
|
||||
};
|
||||
|
||||
const setCiVariableKeyByPosition = (position, value) => {
|
||||
findAllCiVariableKeys().at(position).setValue(value);
|
||||
findAllCiVariableKeys().at(position).vm.$emit('change');
|
||||
nextTick();
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
createAlert.mockClear();
|
||||
});
|
||||
|
||||
describe('when page renders', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('renders help text with provided link', () => {
|
||||
expect(findHelpText().exists()).toBe(true);
|
||||
expect(findHelpLink().attributes('href')).toBe('/help/ci/variables/_index#for-a-project');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when query is unsuccessful', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest.fn().mockRejectedValue({}),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('shows an alert with error', () => {
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: JOB_GRAPHQL_ERRORS.jobQueryErrorText,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when job has variables', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockJobWithVariablesResponse),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sets job variables', () => {
|
||||
const queryKey = mockJobWithVariablesResponse.data.project.job.manualVariables.nodes[0].key;
|
||||
const queryValue =
|
||||
mockJobWithVariablesResponse.data.project.job.manualVariables.nodes[0].value;
|
||||
|
||||
expect(findCiVariableKey().element.value).toBe(queryKey);
|
||||
expect(findCiVariableValue().element.value).toBe(queryValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updating variables in UI', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest.fn().mockResolvedValue(mockJobResponse),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a new variable when user enters a new key value', async () => {
|
||||
expect(findAllVariables()).toHaveLength(1);
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findAllVariables()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('does not create extra empty variables', async () => {
|
||||
expect(findAllVariables()).toHaveLength(1);
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findAllVariables()).toHaveLength(2);
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findAllVariables()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('removes the correct variable row', async () => {
|
||||
const variableKeyNameOne = 'key-one';
|
||||
const variableKeyNameThree = 'key-three';
|
||||
|
||||
await setCiVariableKeyByPosition(0, variableKeyNameOne);
|
||||
|
||||
await setCiVariableKeyByPosition(1, 'key-two');
|
||||
|
||||
await setCiVariableKeyByPosition(2, variableKeyNameThree);
|
||||
|
||||
expect(findAllVariables()).toHaveLength(4);
|
||||
|
||||
await findAllDeleteVarBtns().at(1).trigger('click');
|
||||
|
||||
expect(findAllVariables()).toHaveLength(3);
|
||||
|
||||
expect(findAllCiVariableKeys().at(0).element.value).toBe(variableKeyNameOne);
|
||||
expect(findAllCiVariableKeys().at(1).element.value).toBe(variableKeyNameThree);
|
||||
expect(findAllCiVariableKeys().at(2).element.value).toBe('');
|
||||
});
|
||||
|
||||
it('delete variable button should only show when there is more than one variable', async () => {
|
||||
expect(findDeleteVarBtn().exists()).toBe(false);
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findDeleteVarBtn().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('variable delete button placeholder', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest.fn().mockResolvedValue(mockJobResponse),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('delete variable button placeholder should only exist when a user cannot remove', () => {
|
||||
expect(findDeleteVarBtnPlaceholder().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not show the placeholder button', () => {
|
||||
expect(findDeleteVarBtnPlaceholder().classes('gl-opacity-0')).toBe(true);
|
||||
});
|
||||
|
||||
it('placeholder button will not delete the row on click', async () => {
|
||||
expect(findAllCiVariableKeys()).toHaveLength(1);
|
||||
expect(findDeleteVarBtnPlaceholder().exists()).toBe(true);
|
||||
|
||||
await findDeleteVarBtnPlaceholder().trigger('click');
|
||||
|
||||
expect(findAllCiVariableKeys()).toHaveLength(1);
|
||||
expect(findDeleteVarBtnPlaceholder().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('emitting events', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest.fn().mockResolvedValue(mockJobResponse),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('emits update-variables event when data changes', async () => {
|
||||
const newVariable = { key: 'new key', value: 'test-value' };
|
||||
const emptyVariable = { key: '', value: '' };
|
||||
|
||||
const initialEvent = wrapper.emitted('update-variables').at(0)[0];
|
||||
expect(initialEvent).toHaveLength(1);
|
||||
expect(initialEvent).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ ...emptyVariable })]),
|
||||
);
|
||||
|
||||
await setCiVariableKey();
|
||||
await findCiVariableValue().setValue(newVariable.value);
|
||||
|
||||
const lastEvent = wrapper.emitted('update-variables').at(-1)[0];
|
||||
expect(lastEvent).toHaveLength(2);
|
||||
expect(lastEvent).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
...newVariable,
|
||||
}),
|
||||
expect.objectContaining({ ...emptyVariable }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import { createAlert } from '~/alert';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { TYPENAME_CI_BUILD } from '~/graphql_shared/constants';
|
||||
import { JOB_GRAPHQL_ERRORS } from '~/ci/constants';
|
||||
|
|
@ -10,16 +9,14 @@ import { convertToGraphQLId } from '~/graphql_shared/utils';
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import ManualVariablesForm from '~/ci/job_details/components/manual_variables_form.vue';
|
||||
import getJobQuery from '~/ci/job_details/graphql/queries/get_job.query.graphql';
|
||||
import playJobMutation from '~/ci/job_details/graphql/mutations/job_play_with_variables.mutation.graphql';
|
||||
import retryJobMutation from '~/ci/job_details/graphql/mutations/job_retry_with_variables.mutation.graphql';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import JobVariablesForm from '~/ci/job_details/components/job_variables_form.vue';
|
||||
|
||||
import {
|
||||
mockFullPath,
|
||||
mockId,
|
||||
mockJobResponse,
|
||||
mockJobWithVariablesResponse,
|
||||
mockJobPlayMutationData,
|
||||
mockJobRetryMutationData,
|
||||
} from '../mock_data';
|
||||
|
|
@ -42,12 +39,10 @@ describe('Manual Variables Form', () => {
|
|||
let mockApollo;
|
||||
let requestHandlers;
|
||||
|
||||
const getJobQueryResponseHandlerWithVariables = jest.fn().mockResolvedValue(mockJobResponse);
|
||||
const playJobMutationHandler = jest.fn().mockResolvedValue({});
|
||||
const retryJobMutationHandler = jest.fn().mockResolvedValue({});
|
||||
|
||||
const defaultHandlers = {
|
||||
getJobQueryResponseHandlerWithVariables,
|
||||
playJobMutationHandler,
|
||||
retryJobMutationHandler,
|
||||
};
|
||||
|
|
@ -56,7 +51,6 @@ describe('Manual Variables Form', () => {
|
|||
requestHandlers = handlers;
|
||||
|
||||
mockApollo = createMockApollo([
|
||||
[getJobQuery, handlers.getJobQueryResponseHandlerWithVariables],
|
||||
[playJobMutation, handlers.playJobMutationHandler],
|
||||
[retryJobMutation, handlers.retryJobMutationHandler],
|
||||
]);
|
||||
|
|
@ -65,7 +59,7 @@ describe('Manual Variables Form', () => {
|
|||
apolloProvider: mockApollo,
|
||||
};
|
||||
|
||||
wrapper = mountExtended(ManualVariablesForm, {
|
||||
wrapper = shallowMountExtended(ManualVariablesForm, {
|
||||
propsData: {
|
||||
jobId: mockId,
|
||||
jobName: 'job-name',
|
||||
|
|
@ -77,74 +71,33 @@ describe('Manual Variables Form', () => {
|
|||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
};
|
||||
|
||||
const findHelpText = () => wrapper.findComponent(GlSprintf);
|
||||
const findHelpLink = () => wrapper.findComponent(GlLink);
|
||||
const findCancelBtn = () => wrapper.findByTestId('cancel-btn');
|
||||
const findRunBtn = () => wrapper.findByTestId('run-manual-job-btn');
|
||||
const findDeleteVarBtn = () => wrapper.findByTestId('delete-variable-btn');
|
||||
const findAllDeleteVarBtns = () => wrapper.findAllByTestId('delete-variable-btn');
|
||||
const findDeleteVarBtnPlaceholder = () => wrapper.findByTestId('delete-variable-btn-placeholder');
|
||||
const findCiVariableKey = () => wrapper.findByTestId('ci-variable-key');
|
||||
const findAllCiVariableKeys = () => wrapper.findAllByTestId('ci-variable-key');
|
||||
const findCiVariableValue = () => wrapper.findByTestId('ci-variable-value');
|
||||
const findAllVariables = () => wrapper.findAllByTestId('ci-variable-row');
|
||||
|
||||
const setCiVariableKey = () => {
|
||||
findCiVariableKey().setValue('new key');
|
||||
findCiVariableKey().vm.$emit('change');
|
||||
nextTick();
|
||||
};
|
||||
|
||||
const setCiVariableKeyByPosition = (position, value) => {
|
||||
findAllCiVariableKeys().at(position).setValue(value);
|
||||
findAllCiVariableKeys().at(position).vm.$emit('change');
|
||||
nextTick();
|
||||
};
|
||||
const findVariablesForm = () => wrapper.findComponent(JobVariablesForm);
|
||||
|
||||
afterEach(() => {
|
||||
createAlert.mockClear();
|
||||
});
|
||||
|
||||
describe('when page renders', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders help text with provided link', () => {
|
||||
expect(findHelpText().exists()).toBe(true);
|
||||
expect(findHelpLink().attributes('href')).toBe('/help/ci/variables/_index#for-a-project');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when query is unsuccessful', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest.fn().mockRejectedValue({}),
|
||||
},
|
||||
});
|
||||
it('renders job id to variables form', () => {
|
||||
expect(findVariablesForm().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('shows an alert with error', () => {
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: JOB_GRAPHQL_ERRORS.jobQueryErrorText,
|
||||
});
|
||||
it('provides job variables form', () => {
|
||||
expect(findVariablesForm().props('jobId')).toBe(mockId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when job has not been retried', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockJobWithVariablesResponse),
|
||||
},
|
||||
});
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('does not render the cancel button', () => {
|
||||
|
|
@ -153,45 +106,25 @@ describe('Manual Variables Form', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when job has variables', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockJobWithVariablesResponse),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sets manual job variables', () => {
|
||||
const queryKey = mockJobWithVariablesResponse.data.project.job.manualVariables.nodes[0].key;
|
||||
const queryValue =
|
||||
mockJobWithVariablesResponse.data.project.job.manualVariables.nodes[0].value;
|
||||
|
||||
expect(findCiVariableKey().element.value).toBe(queryKey);
|
||||
expect(findCiVariableValue().element.value).toBe(queryValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when play mutation fires', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockJobWithVariablesResponse),
|
||||
playJobMutationHandler: jest.fn().mockResolvedValue(mockJobPlayMutationData),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('passes variables in correct format', async () => {
|
||||
await setCiVariableKey();
|
||||
it('passes variables in correct format', () => {
|
||||
findVariablesForm().vm.$emit('update-variables', [
|
||||
{
|
||||
id: 'gid://gitlab/Ci::JobVariable/6',
|
||||
key: 'new key',
|
||||
value: 'new value',
|
||||
},
|
||||
]);
|
||||
|
||||
await findCiVariableValue().setValue('new value');
|
||||
|
||||
await findRunBtn().vm.$emit('click');
|
||||
findRunBtn().vm.$emit('click');
|
||||
|
||||
expect(requestHandlers.playJobMutationHandler).toHaveBeenCalledTimes(1);
|
||||
expect(requestHandlers.playJobMutationHandler).toHaveBeenCalledWith({
|
||||
|
|
@ -212,15 +145,6 @@ describe('Manual Variables Form', () => {
|
|||
expect(requestHandlers.playJobMutationHandler).toHaveBeenCalledTimes(1);
|
||||
expect(visitUrl).toHaveBeenCalledWith(mockJobPlayMutationData.data.jobPlay.job.webPath);
|
||||
});
|
||||
|
||||
it('does not refetch variables after job is run', async () => {
|
||||
expect(requestHandlers.getJobQueryResponseHandlerWithVariables).toHaveBeenCalledTimes(1);
|
||||
|
||||
findRunBtn().vm.$emit('click');
|
||||
await waitForPromises();
|
||||
|
||||
expect(requestHandlers.getJobQueryResponseHandlerWithVariables).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when play mutation is unsuccessful', () => {
|
||||
|
|
@ -243,13 +167,10 @@ describe('Manual Variables Form', () => {
|
|||
});
|
||||
|
||||
describe('when job is retryable', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: { isRetryable: true },
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockJobWithVariablesResponse),
|
||||
retryJobMutationHandler: jest.fn().mockResolvedValue(mockJobRetryMutationData),
|
||||
},
|
||||
});
|
||||
|
|
@ -267,16 +188,13 @@ describe('Manual Variables Form', () => {
|
|||
});
|
||||
|
||||
describe('with confirmation message', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: {
|
||||
isRetryable: true,
|
||||
confirmationMessage: 'Are you sure?',
|
||||
},
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockJobWithVariablesResponse),
|
||||
retryJobMutationHandler: jest.fn().mockResolvedValue(mockJobRetryMutationData),
|
||||
},
|
||||
});
|
||||
|
|
@ -314,20 +232,11 @@ describe('Manual Variables Form', () => {
|
|||
expect(requestHandlers.retryJobMutationHandler).toHaveBeenCalledTimes(1);
|
||||
expect(visitUrl).toHaveBeenCalledWith(mockJobRetryMutationData.data.jobRetry.job.webPath);
|
||||
});
|
||||
|
||||
it('does not refetch variables after job is rerun', async () => {
|
||||
expect(requestHandlers.getJobQueryResponseHandlerWithVariables).toHaveBeenCalledTimes(1);
|
||||
|
||||
findRunBtn().vm.$emit('click');
|
||||
await waitForPromises();
|
||||
|
||||
expect(requestHandlers.getJobQueryResponseHandlerWithVariables).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when retry mutation is unsuccessful', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: { isRetryable: true },
|
||||
handlers: {
|
||||
retryJobMutationHandler: jest.fn().mockRejectedValue({}),
|
||||
|
|
@ -344,91 +253,4 @@ describe('Manual Variables Form', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updating variables in UI', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest.fn().mockResolvedValue(mockJobResponse),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a new variable when user enters a new key value', async () => {
|
||||
expect(findAllVariables()).toHaveLength(1);
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findAllVariables()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('does not create extra empty variables', async () => {
|
||||
expect(findAllVariables()).toHaveLength(1);
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findAllVariables()).toHaveLength(2);
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findAllVariables()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('removes the correct variable row', async () => {
|
||||
const variableKeyNameOne = 'key-one';
|
||||
const variableKeyNameThree = 'key-three';
|
||||
|
||||
await setCiVariableKeyByPosition(0, variableKeyNameOne);
|
||||
|
||||
await setCiVariableKeyByPosition(1, 'key-two');
|
||||
|
||||
await setCiVariableKeyByPosition(2, variableKeyNameThree);
|
||||
|
||||
expect(findAllVariables()).toHaveLength(4);
|
||||
|
||||
await findAllDeleteVarBtns().at(1).trigger('click');
|
||||
|
||||
expect(findAllVariables()).toHaveLength(3);
|
||||
|
||||
expect(findAllCiVariableKeys().at(0).element.value).toBe(variableKeyNameOne);
|
||||
expect(findAllCiVariableKeys().at(1).element.value).toBe(variableKeyNameThree);
|
||||
expect(findAllCiVariableKeys().at(2).element.value).toBe('');
|
||||
});
|
||||
|
||||
it('delete variable button should only show when there is more than one variable', async () => {
|
||||
expect(findDeleteVarBtn().exists()).toBe(false);
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findDeleteVarBtn().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('variable delete button placeholder', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
handlers: {
|
||||
getJobQueryResponseHandlerWithVariables: jest.fn().mockResolvedValue(mockJobResponse),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('delete variable button placeholder should only exist when a user cannot remove', () => {
|
||||
expect(findDeleteVarBtnPlaceholder().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not show the placeholder button', () => {
|
||||
expect(findDeleteVarBtnPlaceholder().classes('gl-opacity-0')).toBe(true);
|
||||
});
|
||||
|
||||
it('placeholder button will not delete the row on click', async () => {
|
||||
expect(findAllCiVariableKeys()).toHaveLength(1);
|
||||
expect(findDeleteVarBtnPlaceholder().exists()).toBe(true);
|
||||
|
||||
await findDeleteVarBtnPlaceholder().trigger('click');
|
||||
|
||||
expect(findAllCiVariableKeys()).toHaveLength(1);
|
||||
expect(findDeleteVarBtnPlaceholder().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ RSpec.describe Snippets::RepositoryValidationService, feature_category: :source_
|
|||
allow(repository).to receive(:branch_count).and_return(2)
|
||||
|
||||
expect(subject).to be_error
|
||||
expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
|
||||
expect(subject.message).to match(/Repository has more than one branch/)
|
||||
end
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ RSpec.describe Snippets::RepositoryValidationService, feature_category: :source_
|
|||
allow(repository).to receive(:branch_names).and_return(['foo'])
|
||||
|
||||
expect(subject).to be_error
|
||||
expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
|
||||
expect(subject.message).to match(/Repository has an invalid default branch name/)
|
||||
end
|
||||
|
||||
|
|
@ -36,6 +38,7 @@ RSpec.describe Snippets::RepositoryValidationService, feature_category: :source_
|
|||
allow(repository).to receive(:tag_count).and_return(1)
|
||||
|
||||
expect(subject).to be_error
|
||||
expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
|
||||
expect(subject.message).to match(/Repository has tags/)
|
||||
end
|
||||
|
||||
|
|
@ -45,6 +48,7 @@ RSpec.describe Snippets::RepositoryValidationService, feature_category: :source_
|
|||
allow(repository).to receive(:ls_files).and_return(files)
|
||||
|
||||
expect(subject).to be_error
|
||||
expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
|
||||
expect(subject.message).to match(/Repository files count over the limit/)
|
||||
end
|
||||
|
||||
|
|
@ -52,6 +56,7 @@ RSpec.describe Snippets::RepositoryValidationService, feature_category: :source_
|
|||
allow(repository).to receive(:ls_files).and_return([])
|
||||
|
||||
expect(subject).to be_error
|
||||
expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
|
||||
expect(subject.message).to match(/Repository must contain at least 1 file/)
|
||||
end
|
||||
|
||||
|
|
@ -61,6 +66,7 @@ RSpec.describe Snippets::RepositoryValidationService, feature_category: :source_
|
|||
end
|
||||
|
||||
expect(subject).to be_error
|
||||
expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
|
||||
expect(subject.message).to match(/Repository size is above the limit/)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Software Supply Chain Security
|
||||
group: Pipeline Security
|
||||
group: Authorization
|
||||
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
|
||||
title: Fine-grained permissions for CI/CD job tokens
|
||||
---
|
||||
|
|
@ -23,10 +23,43 @@ Status: Experiment
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
{{< history >}}
|
||||
|
||||
- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/15234) in GitLab 17.10. This feature is an [experiment](../../policy/development_stages_support.md#experiment).
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
{{< alert type="flag" >}}
|
||||
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
You can use fine-grained permissions to explicitly allow access to a limited set of API endpoints.
|
||||
These permissions are applied to the CI/CD job tokens in a specified project.
|
||||
This feature is an [experiment](../../policy/development_stages_support.md#experiment).
|
||||
|
||||
## Enable fine-grained permissions
|
||||
|
||||
### On GitLab Self-Managed
|
||||
|
||||
1. Start the GitLab Rails console. For information, see [Enable and disable GitLab features deployed behind feature flags](../../administration/feature_flags.md#enable-or-disable-the-feature)
|
||||
1. Turn on the [feature flag](../../administration/feature_flags.md):
|
||||
|
||||
```ruby
|
||||
# You must include a specific project ID with this command.
|
||||
Feature.enable(:add_policies_to_ci_job_token, <project_id>)
|
||||
```
|
||||
|
||||
### On GitLab.com
|
||||
|
||||
Add a comment on this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/519575) with your project ID.
|
||||
|
||||
## Available API endpoints
|
||||
|
||||
The following endpoints are available for CI/CD job tokens.
|
||||
You can use fine-grained permissions to explicitly allow access to a limited set of the following API endpoints.
|
||||
|
||||
`None` means fine-grained permissions cannot control access to this endpoint.
|
||||
|
||||
|
|
|
|||