Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-12-18 18:35:10 +00:00
parent f2da46b705
commit 41533891fd
84 changed files with 1283 additions and 252 deletions

View File

@ -6,7 +6,7 @@ include:
inputs:
cng_path: 'charts/components/images'
- project: 'gitlab-org/quality/pipeline-common'
ref: '9.7.1'
ref: '9.8.0'
file: ci/base.gitlab-ci.yml
stages:

View File

@ -55,7 +55,6 @@ Gitlab/AvoidGitlabInstanceChecks:
- 'ee/app/models/ee/namespace.rb'
- 'ee/app/models/ee/plan.rb'
- 'ee/app/models/ee/preloaders/group_policy_preloader.rb'
- 'ee/app/models/ee/preloaders/single_hierarchy_project_group_plans_preloader.rb'
- 'ee/app/models/ee/project.rb'
- 'ee/app/models/ee/project_statistics.rb'
- 'ee/app/models/ee/user.rb'

View File

@ -1141,7 +1141,6 @@ Gitlab/BoundedContexts:
- 'app/models/preloaders/project_root_ancestor_preloader.rb'
- 'app/models/preloaders/projects/notes_preloader.rb'
- 'app/models/preloaders/runner_manager_policy_preloader.rb'
- 'app/models/preloaders/single_hierarchy_project_group_plans_preloader.rb'
- 'app/models/preloaders/user_max_access_level_in_groups_preloader.rb'
- 'app/models/preloaders/user_max_access_level_in_projects_preloader.rb'
- 'app/models/preloaders/users_max_access_level_by_project_preloader.rb'
@ -2823,7 +2822,6 @@ Gitlab/BoundedContexts:
- 'ee/app/models/ee/preloaders/group_policy_preloader.rb'
- 'ee/app/models/ee/preloaders/labels_preloader.rb'
- 'ee/app/models/ee/preloaders/project_policy_preloader.rb'
- 'ee/app/models/ee/preloaders/single_hierarchy_project_group_plans_preloader.rb'
- 'ee/app/models/ee/preloaders/users_max_access_level_by_project_preloader.rb'
- 'ee/app/models/ee/project.rb'
- 'ee/app/models/ee/project_authorization.rb'

View File

@ -189,7 +189,6 @@ Layout/LineLength:
- 'app/models/pages_domain.rb'
- 'app/models/personal_access_token.rb'
- 'app/models/preloaders/environments/deployment_preloader.rb'
- 'app/models/preloaders/single_hierarchy_project_group_plans_preloader.rb'
- 'app/models/preloaders/user_max_access_level_in_groups_preloader.rb'
- 'app/models/project.rb'
- 'app/models/project_feature.rb'

View File

@ -97,7 +97,6 @@ Lint/AssignmentInCondition:
- 'ee/app/graphql/mutations/incident_management/escalation_policy/base.rb'
- 'ee/app/models/ee/ci/build.rb'
- 'ee/app/models/ee/merge_request.rb'
- 'ee/app/models/ee/preloaders/single_hierarchy_project_group_plans_preloader.rb'
- 'ee/app/models/productivity_analytics.rb'
- 'ee/app/presenters/ee/ci/pipeline_presenter.rb'
- 'ee/app/services/app_sec/dast/profiles/create_associations_service.rb'

View File

@ -14,4 +14,3 @@ Performance/FlatMap:
- 'lib/gitlab/diff/file_collection/base.rb'
- 'lib/gitlab/instrumentation/redis_cluster_validator.rb'
- 'lib/gitlab/testing/request_inspector_middleware.rb'
- 'qa/qa/tools/ci/non_empty_suites.rb'

View File

@ -389,9 +389,9 @@ gem 'gitlab-license', '~> 2.6', feature_category: :shared
gem 'rack-attack', '~> 6.7.0' # rubocop:todo Gemfile/MissingFeatureCategory
# Sentry integration
gem 'sentry-ruby', '~> 5.21.0', feature_category: :observability
gem 'sentry-rails', '~> 5.21.0', feature_category: :observability
gem 'sentry-sidekiq', '~> 5.21.0', feature_category: :observability
gem 'sentry-ruby', '~> 5.22.0', feature_category: :observability
gem 'sentry-rails', '~> 5.22.0', feature_category: :observability
gem 'sentry-sidekiq', '~> 5.22.0', feature_category: :observability
# PostgreSQL query parsing
#

View File

@ -669,9 +669,9 @@
{"name":"seed-fu","version":"2.3.7","platform":"ruby","checksum":"f19673443e9af799b730e3d4eca6a89b39e5a36825015dffd00d02ea3365cf74"},
{"name":"selenium-webdriver","version":"4.27.0","platform":"ruby","checksum":"8821f4ad60b935cfcdc5954c0a6642d894e936250aece8bf37a6fcbebe5eb6e0"},
{"name":"semver_dialects","version":"3.4.5","platform":"ruby","checksum":"7382ca351dc4796a8c824447a1ad87dfdea41f73b625cd2a5efabe54d11be63e"},
{"name":"sentry-rails","version":"5.21.0","platform":"ruby","checksum":"b5a943d199aff0d3cb94dbac4eb3e00622dd0c55fd1be0cffd43a7e09f0ad602"},
{"name":"sentry-ruby","version":"5.21.0","platform":"ruby","checksum":"294e0dd59afce7e08ba22a4e880924345c75c3e858dc8ee23553716793f78629"},
{"name":"sentry-sidekiq","version":"5.21.0","platform":"ruby","checksum":"6df54ec79238f69d9d4b7647bcd2a192a4702f3a39edffd63a455203430e90e2"},
{"name":"sentry-rails","version":"5.22.1","platform":"ruby","checksum":"23227608dc0e202de8cf96840a591e52bd7d6967ebaed6eb2da50a7d2a2d3fb7"},
{"name":"sentry-ruby","version":"5.22.1","platform":"ruby","checksum":"ed77bdd76da7a4c6a3de43dc6d19d3c0412b2675b014a2654bc5bafd4d5b3289"},
{"name":"sentry-sidekiq","version":"5.22.1","platform":"ruby","checksum":"bd7a3f915e58e13ea67251d9a458667fc4bee6dfbbd12614c47daa239e822a89"},
{"name":"shellany","version":"0.0.1","platform":"ruby","checksum":"0e127a9132698766d7e752e82cdac8250b6adbd09e6c0a7fbbb6f61964fedee7"},
{"name":"shoulda-matchers","version":"5.1.0","platform":"ruby","checksum":"a01d20589989e9653ab4a28c67d9db2b82bcf0a2496cf01d5e1a95a4aaaf5b07"},
{"name":"sidekiq-cron","version":"1.12.0","platform":"ruby","checksum":"6663080a454088bd88773a0da3ae91e554b8a2e8b06cfc629529a83fd1a3096c"},

View File

@ -1722,14 +1722,14 @@ GEM
pastel (~> 0.8.0)
thor (~> 1.3)
tty-command (~> 0.10.1)
sentry-rails (5.21.0)
sentry-rails (5.22.1)
railties (>= 5.0)
sentry-ruby (~> 5.21.0)
sentry-ruby (5.21.0)
sentry-ruby (~> 5.22.1)
sentry-ruby (5.22.1)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
sentry-sidekiq (5.21.0)
sentry-ruby (~> 5.21.0)
sentry-sidekiq (5.22.1)
sentry-ruby (~> 5.22.1)
sidekiq (>= 3.0)
shellany (0.0.1)
shoulda-matchers (5.1.0)
@ -2290,9 +2290,9 @@ DEPENDENCIES
seed-fu (~> 2.3.7)
selenium-webdriver (~> 4.21, >= 4.21.1)
semver_dialects (~> 3.0)
sentry-rails (~> 5.21.0)
sentry-ruby (~> 5.21.0)
sentry-sidekiq (~> 5.21.0)
sentry-rails (~> 5.22.0)
sentry-ruby (~> 5.22.0)
sentry-sidekiq (~> 5.22.0)
shoulda-matchers (~> 5.1.0)
sidekiq!
sidekiq-cron (~> 1.12.0)

View File

@ -344,7 +344,7 @@
{"name":"io-event","version":"1.6.5","platform":"ruby","checksum":"5da4c044ac5f411563da1a4743d28c8d30d7802e29370db42139a52b807b4ce2"},
{"name":"ipaddr","version":"1.2.5","platform":"ruby","checksum":"4e679c71d6d8ed99f925487082f70f9a958de155591caa0e7f6cef9aa160f17a"},
{"name":"ipaddress","version":"0.8.3","platform":"ruby","checksum":"85640c4f9194c26937afc8c78e3074a8e7c97d5d1210358d1440f01034d006f5"},
{"name":"irb","version":"1.14.2","platform":"ruby","checksum":"243040f6419115beb2404380a62e41861ddb03c0792c5873dec2a69a542723c6"},
{"name":"irb","version":"1.14.3","platform":"ruby","checksum":"c457f1f2f1438ae9ce5c5be3981ae2138dec7fb894c7d73777eeeb0a6c0d0752"},
{"name":"jaeger-client","version":"1.1.0","platform":"ruby","checksum":"cb5e9b9bbee6ee8d6a82d03d947a5b04543d8c0a949c22e484254f18d8a458a8"},
{"name":"jaro_winkler","version":"1.5.6","platform":"java","checksum":"3262aea433861fec3179184e9adc1933cca8bc15665957a143b56816f1a22f74"},
{"name":"jaro_winkler","version":"1.5.6","platform":"ruby","checksum":"007db7805527ada1cc12f2547676181d63b0a504ec4dd7a9a2eb2424521ccd81"},
@ -567,7 +567,7 @@
{"name":"rbs","version":"3.6.1","platform":"ruby","checksum":"ed7273d018556844583d1785ac54194e67eec594d68e317d57fa90ad035532c0"},
{"name":"rbtrace","version":"0.5.1","platform":"ruby","checksum":"e8cba64d462bfb8ba102d7be2ecaacc789247d52ac587d8003549d909cb9c5dc"},
{"name":"rchardet","version":"1.8.0","platform":"ruby","checksum":"693acd5253d5ade81a51940697955f6dd4bb2f0d245bda76a8e23deec70a52c7"},
{"name":"rdoc","version":"6.9.1","platform":"ruby","checksum":"3344bf498a46b701aba70ccdd5cdfa8be37e68493984c1bf8c579f06c3442c9f"},
{"name":"rdoc","version":"6.10.0","platform":"ruby","checksum":"db665021883ac9df3ba29cdf71aece960749888db1bf9615b4a584cfa3fa3eda"},
{"name":"re2","version":"2.7.0","platform":"aarch64-linux","checksum":"778921298b6e8aba26a6230dd298c9b361b92e45024f81fa6aee788060fa307c"},
{"name":"re2","version":"2.7.0","platform":"arm-linux","checksum":"d328b5286d83ae265e13b855da8e348a976f80f91b748045b52073a570577954"},
{"name":"re2","version":"2.7.0","platform":"arm64-darwin","checksum":"7d993f27a1afac4001c539a829e2af211ced62604930c90df32a307cf74cb4a4"},
@ -679,9 +679,9 @@
{"name":"seed-fu","version":"2.3.7","platform":"ruby","checksum":"f19673443e9af799b730e3d4eca6a89b39e5a36825015dffd00d02ea3365cf74"},
{"name":"selenium-webdriver","version":"4.27.0","platform":"ruby","checksum":"8821f4ad60b935cfcdc5954c0a6642d894e936250aece8bf37a6fcbebe5eb6e0"},
{"name":"semver_dialects","version":"3.4.5","platform":"ruby","checksum":"7382ca351dc4796a8c824447a1ad87dfdea41f73b625cd2a5efabe54d11be63e"},
{"name":"sentry-rails","version":"5.21.0","platform":"ruby","checksum":"b5a943d199aff0d3cb94dbac4eb3e00622dd0c55fd1be0cffd43a7e09f0ad602"},
{"name":"sentry-ruby","version":"5.21.0","platform":"ruby","checksum":"294e0dd59afce7e08ba22a4e880924345c75c3e858dc8ee23553716793f78629"},
{"name":"sentry-sidekiq","version":"5.21.0","platform":"ruby","checksum":"6df54ec79238f69d9d4b7647bcd2a192a4702f3a39edffd63a455203430e90e2"},
{"name":"sentry-rails","version":"5.22.1","platform":"ruby","checksum":"23227608dc0e202de8cf96840a591e52bd7d6967ebaed6eb2da50a7d2a2d3fb7"},
{"name":"sentry-ruby","version":"5.22.1","platform":"ruby","checksum":"ed77bdd76da7a4c6a3de43dc6d19d3c0412b2675b014a2654bc5bafd4d5b3289"},
{"name":"sentry-sidekiq","version":"5.22.1","platform":"ruby","checksum":"bd7a3f915e58e13ea67251d9a458667fc4bee6dfbbd12614c47daa239e822a89"},
{"name":"shellany","version":"0.0.1","platform":"ruby","checksum":"0e127a9132698766d7e752e82cdac8250b6adbd09e6c0a7fbbb6f61964fedee7"},
{"name":"shoulda-matchers","version":"5.1.0","platform":"ruby","checksum":"a01d20589989e9653ab4a28c67d9db2b82bcf0a2496cf01d5e1a95a4aaaf5b07"},
{"name":"sidekiq-cron","version":"1.12.0","platform":"ruby","checksum":"6663080a454088bd88773a0da3ae91e554b8a2e8b06cfc629529a83fd1a3096c"},

View File

@ -1038,7 +1038,7 @@ GEM
io-event (1.6.5)
ipaddr (1.2.5)
ipaddress (0.8.3)
irb (1.14.2)
irb (1.14.3)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jaeger-client (1.1.0)
@ -1563,7 +1563,7 @@ GEM
msgpack (>= 0.4.3)
optimist (>= 3.0.0)
rchardet (1.8.0)
rdoc (6.9.1)
rdoc (6.10.0)
psych (>= 4.0.0)
re2 (2.7.0)
mini_portile2 (~> 2.8.5)
@ -1749,14 +1749,14 @@ GEM
pastel (~> 0.8.0)
thor (~> 1.3)
tty-command (~> 0.10.1)
sentry-rails (5.21.0)
sentry-rails (5.22.1)
railties (>= 5.0)
sentry-ruby (~> 5.21.0)
sentry-ruby (5.21.0)
sentry-ruby (~> 5.22.1)
sentry-ruby (5.22.1)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
sentry-sidekiq (5.21.0)
sentry-ruby (~> 5.21.0)
sentry-sidekiq (5.22.1)
sentry-ruby (~> 5.22.1)
sidekiq (>= 3.0)
shellany (0.0.1)
shoulda-matchers (5.1.0)
@ -2318,9 +2318,9 @@ DEPENDENCIES
seed-fu (~> 2.3.7)
selenium-webdriver (~> 4.21, >= 4.21.1)
semver_dialects (~> 3.0)
sentry-rails (~> 5.21.0)
sentry-ruby (~> 5.21.0)
sentry-sidekiq (~> 5.21.0)
sentry-rails (~> 5.22.0)
sentry-ruby (~> 5.22.0)
sentry-sidekiq (~> 5.22.0)
shoulda-matchers (~> 5.1.0)
sidekiq!
sidekiq-cron (~> 1.12.0)

View File

@ -29,7 +29,7 @@ export default {
};
</script>
<template>
<span class="gl-flex gl-max-w-full gl-grow gl-items-center">
<span class="gl-flex gl-items-center">
<ci-icon
:size="iconSize"
:status="status"
@ -37,7 +37,7 @@ export default {
:use-link="false"
class="gl-leading-0"
/>
<span class="gl-inline-block gl-max-w-7/10 gl-truncate gl-pl-3" :title="name">
<span class="gl-line-clamp-2 gl-pl-3 gl-wrap-anywhere" :title="name">
{{ name }}
</span>
</span>

View File

@ -51,10 +51,9 @@ export default {
<template>
<gl-disclosure-dropdown-item :item="item" class="ci-job-component" data-testid="job-item">
<template #list-item>
<div class="-gl-my-2 gl-flex gl-h-6">
<div class="-gl-my-2 gl-flex gl-items-center gl-justify-between">
<job-name-component
v-gl-tooltip.viewport.left
class="-gl-my-2 gl-min-w-0"
:title="tooltipText"
:name="job.name"
:status="status"
@ -62,6 +61,7 @@ export default {
/>
<job-action-button
v-if="hasJobAction"
class="gl-ml-6"
:job-id="job.id"
:job-action="status.action"
:job-name="job.name"

View File

@ -115,6 +115,7 @@ export default {
<gl-disclosure-dropdown
data-testid="pipeline-mini-graph-dropdown"
:aria-label="stageAriaLabel(stage.name)"
fluid-width
@hidden="onHideDropdown"
@shown="onShowDropdown"
>
@ -145,7 +146,7 @@ export default {
</div>
<ul
v-else
class="gl-m-0 gl-overflow-y-auto gl-p-0"
class="gl-m-0 gl-w-34 gl-overflow-y-auto gl-p-0"
data-testid="pipeline-mini-graph-dropdown-menu-list"
@click.stop
>

View File

@ -1,14 +1,19 @@
<script>
import { GlFormGroup, GlButton, GlFormInput } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import addNamespaceMutation from '../graphql/mutations/inbound_add_group_or_project_ci_job_token_scope.mutation.graphql';
import PoliciesSelector from './policies_selector.vue';
export default {
components: { GlFormGroup, GlButton, GlFormInput },
components: { GlFormGroup, GlButton, GlFormInput, PoliciesSelector },
mixins: [glFeatureFlagsMixin()],
inject: ['fullPath'],
data() {
return {
namespacePath: '',
targetPath: '',
defaultPermissions: true,
jobTokenPolicies: [],
errorMessage: '',
isSaving: false,
};
@ -19,10 +24,14 @@ export default {
this.isSaving = true;
this.errorMessage = '';
const response = await this.$apollo.mutate({
mutation: addNamespaceMutation,
variables: { projectPath: this.fullPath, targetPath: this.namespacePath },
});
const variables = { projectPath: this.fullPath, targetPath: this.targetPath };
if (this.glFeatures.addPoliciesToCiJobToken) {
variables.defaultPermissions = this.defaultPermissions;
variables.jobTokenPolicies = this.defaultPermissions ? [] : this.jobTokenPolicies;
}
const response = await this.$apollo.mutate({ mutation: addNamespaceMutation, variables });
const error = response.data.ciJobTokenScopeAddGroupOrProject.errors[0];
if (error) {
@ -57,7 +66,7 @@ export default {
>
<gl-form-input
id="namespace-input"
v-model.trim="namespacePath"
v-model.trim="targetPath"
autofocus
:state="!errorMessage"
:placeholder="fullPath"
@ -66,9 +75,19 @@ export default {
/>
</gl-form-group>
<policies-selector
v-if="glFeatures.addPoliciesToCiJobToken"
:is-default-permissions-selected="defaultPermissions"
:job-token-policies="jobTokenPolicies"
:disabled="isSaving"
class="gl-mb-6"
@update:isDefaultPermissionsSelected="defaultPermissions = $event"
@update:jobTokenPolicies="jobTokenPolicies = $event"
/>
<gl-button
variant="confirm"
:disabled="!namespacePath"
:disabled="!targetPath"
:loading="isSaving"
data-testid="add-button"
@click="saveNamespace"

View File

@ -0,0 +1,116 @@
<script>
import { GlCollapsibleListbox, GlFormRadioGroup, GlFormRadio, GlTableLite } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import {
POLICIES_BY_RESOURCE,
JOB_TOKEN_RESOURCES,
JOB_TOKEN_POLICIES,
POLICY_NONE,
} from '../constants';
export const TABLE_FIELDS = [
{
key: 'resource.text',
label: s__('JobToken|Resource'),
class: '!gl-border-none !gl-py-3 !gl-pl-0 !gl-align-middle gl-w-28',
},
{
key: 'policies',
label: __('Permissions'),
class: '!gl-border-none !gl-py-3',
},
];
export default {
components: { GlCollapsibleListbox, GlFormRadioGroup, GlFormRadio, GlTableLite },
props: {
isDefaultPermissionsSelected: {
type: Boolean,
required: true,
},
jobTokenPolicies: {
type: Array,
required: true,
},
disabled: {
type: Boolean,
required: true,
},
},
computed: {
selected() {
// Create an object where the key is the resource key and the value is the None option.
const selected = Object.keys(JOB_TOKEN_RESOURCES).reduce((acc, resourceKey) => {
acc[resourceKey] = POLICY_NONE.value;
return acc;
}, {});
// Then use the values in jobTokenPolicies to set the selected options.
this.jobTokenPolicies.forEach((policyValue) => {
const policy = JOB_TOKEN_POLICIES[policyValue];
selected[policy.resource.value] = policyValue;
});
return selected;
},
},
methods: {
emitPermissionTypeChange(value) {
this.$emit('update:isDefaultPermissionsSelected', value);
},
emitPoliciesChange(value, item) {
const policies = { ...this.selected, [item.resource.value]: value };
// Remove any '' values from the None policy.
this.$emit('update:jobTokenPolicies', Object.values(policies).filter(Boolean));
},
},
i18n: {
defaultPermissions: s__(
'JobToken|Use the standard permissions model based on user membership and roles.',
),
fineGrainedPermissions: s__(
'JobToken|Apply permissions that grant access to individual resources.',
),
},
TABLE_FIELDS,
POLICIES_BY_RESOURCE,
};
</script>
<template>
<div>
<label>{{ __('Permissions') }}</label>
<gl-form-radio-group
:checked="isDefaultPermissionsSelected"
:disabled="disabled"
class="gl-mb-6"
@change="emitPermissionTypeChange"
>
<gl-form-radio :value="true" data-testid="default-radio">
{{ s__('JobToken|Default permissions') }}
<template #help>{{ $options.i18n.defaultPermissions }}</template>
</gl-form-radio>
<gl-form-radio :value="false" data-testid="fine-grained-radio">
{{ s__('JobToken|Fine-grained permissions') }}
<template #help>{{ $options.i18n.fineGrainedPermissions }}</template>
</gl-form-radio>
</gl-form-radio-group>
<gl-table-lite
v-if="!isDefaultPermissionsSelected"
:fields="$options.TABLE_FIELDS"
:items="$options.POLICIES_BY_RESOURCE"
fixed
>
<template #cell(policies)="{ item, value: policies }">
<gl-collapsible-listbox
:items="policies"
:disabled="disabled"
:selected="selected[item.resource.value]"
block
class="gl-w-20"
@select="emitPoliciesChange($event, item)"
/>
</template>
</gl-table-lite>
</div>
</template>

View File

@ -9,13 +9,13 @@ export default {
{
key: 'fullPath',
label: s__('CICD|Group or project'),
tdClass: 'gl-w-3/4',
tdClass: 'gl-w-full',
},
{
key: 'actions',
label: __('Actions'),
class: 'gl-text-right',
tdClass: '!gl-py-0 !gl-pl-0 gl-w-0 !gl-align-middle',
class: 'gl-text-right !gl-pl-0',
tdClass: '!gl-py-0 !gl-align-middle',
},
],
components: {

View File

@ -0,0 +1,147 @@
import { keyBy } from 'lodash';
import { s__, __ } from '~/locale';
export const RESOURCE_CONTAINERS = { value: 'CONTAINERS', text: s__('JobToken|Containers') };
export const RESOURCE_DEPLOYMENTS = { value: 'DEPLOYMENTS', text: s__('JobToken|Deployments') };
export const RESOURCE_ENVIRONMENTS = { value: 'ENVIRONMENTS', text: s__('JobToken|Environments') };
export const RESOURCE_JOBS = { value: 'JOBS', text: s__('JobToken|Jobs') };
export const RESOURCE_PACKAGES = { value: 'PACKAGES', text: s__('JobToken|Packages') };
export const RESOURCE_RELEASES = { value: 'RELEASES', text: s__('JobToken|Releases') };
export const RESOURCE_SECURE_FILES = { value: 'SECURE_FILES', text: s__('JobToken|Secure files') };
export const RESOURCE_TERRAFORM_STATE = {
value: 'TERRAFORM_STATE',
text: s__('JobToken|Terraform state'),
};
const READ = s__('JobToken|Read');
const READ_AND_WRITE = s__('JobToken|Read and write');
export const POLICY_READ_CONTAINERS = {
value: 'READ_CONTAINERS',
text: READ,
resource: RESOURCE_CONTAINERS,
};
export const POLICY_ADMIN_CONTAINERS = {
value: 'ADMIN_CONTAINERS',
text: READ_AND_WRITE,
resource: RESOURCE_CONTAINERS,
};
export const POLICY_READ_DEPLOYMENTS = {
value: 'READ_DEPLOYMENTS',
text: READ,
resource: RESOURCE_DEPLOYMENTS,
};
export const POLICY_ADMIN_DEPLOYMENTS = {
value: 'ADMIN_DEPLOYMENTS',
text: READ_AND_WRITE,
resource: RESOURCE_DEPLOYMENTS,
};
export const POLICY_READ_ENVIRONMENTS = {
value: 'READ_ENVIRONMENTS',
text: READ,
resource: RESOURCE_ENVIRONMENTS,
};
export const POLICY_ADMIN_ENVIRONMENTS = {
value: 'ADMIN_ENVIRONMENTS',
text: READ_AND_WRITE,
resource: RESOURCE_ENVIRONMENTS,
};
export const POLICY_READ_JOBS = {
value: 'READ_JOBS',
text: READ,
resource: RESOURCE_JOBS,
};
export const POLICY_ADMIN_JOBS = {
value: 'ADMIN_JOBS',
text: READ_AND_WRITE,
resource: RESOURCE_JOBS,
};
export const POLICY_READ_PACKAGES = {
value: 'READ_PACKAGES',
text: READ,
resource: RESOURCE_PACKAGES,
};
export const POLICY_ADMIN_PACKAGES = {
value: 'ADMIN_PACKAGES',
text: READ_AND_WRITE,
resource: RESOURCE_PACKAGES,
};
export const POLICY_READ_RELEASES = {
value: 'READ_RELEASES',
text: READ,
resource: RESOURCE_RELEASES,
};
export const POLICY_ADMIN_RELEASES = {
value: 'ADMIN_RELEASES',
text: READ_AND_WRITE,
resource: RESOURCE_RELEASES,
};
export const POLICY_READ_SECURE_FILES = {
value: 'READ_SECURE_FILES',
text: READ,
resource: RESOURCE_SECURE_FILES,
};
export const POLICY_ADMIN_SECURE_FILES = {
value: 'ADMIN_SECURE_FILES',
text: READ_AND_WRITE,
resource: RESOURCE_SECURE_FILES,
};
export const POLICY_READ_TERRAFORM_STATE = {
value: 'READ_TERRAFORM_STATE',
text: READ,
resource: RESOURCE_TERRAFORM_STATE,
};
export const POLICY_ADMIN_TERRAFORM_STATE = {
value: 'ADMIN_TERRAFORM_STATE',
text: READ_AND_WRITE,
resource: RESOURCE_TERRAFORM_STATE,
};
export const POLICY_NONE = { value: '', text: __('None') };
export const POLICIES_BY_RESOURCE = [
{
resource: RESOURCE_CONTAINERS,
policies: [POLICY_NONE, POLICY_READ_CONTAINERS, POLICY_ADMIN_CONTAINERS],
},
{
resource: RESOURCE_DEPLOYMENTS,
policies: [POLICY_NONE, POLICY_READ_DEPLOYMENTS, POLICY_ADMIN_DEPLOYMENTS],
},
{
resource: RESOURCE_ENVIRONMENTS,
policies: [POLICY_NONE, POLICY_READ_ENVIRONMENTS, POLICY_ADMIN_ENVIRONMENTS],
},
{
resource: RESOURCE_JOBS,
policies: [POLICY_NONE, POLICY_READ_JOBS, POLICY_ADMIN_JOBS],
},
{
resource: RESOURCE_PACKAGES,
policies: [POLICY_NONE, POLICY_READ_PACKAGES, POLICY_ADMIN_PACKAGES],
},
{
resource: RESOURCE_RELEASES,
policies: [POLICY_NONE, POLICY_READ_RELEASES, POLICY_ADMIN_RELEASES],
},
{
resource: RESOURCE_SECURE_FILES,
policies: [POLICY_NONE, POLICY_READ_SECURE_FILES, POLICY_ADMIN_SECURE_FILES],
},
{
resource: RESOURCE_TERRAFORM_STATE,
policies: [POLICY_NONE, POLICY_READ_TERRAFORM_STATE, POLICY_ADMIN_TERRAFORM_STATE],
},
];
// Create an object where the key is the resource value string and the value is the resource object. Used to look up
// a resource by its value string.
export const JOB_TOKEN_RESOURCES = keyBy(
POLICIES_BY_RESOURCE.map(({ resource }) => resource),
({ value }) => value,
);
// Create an object where the key is the policy value string and the value is the policy object. Used to look up a
// policy by its value string.
export const JOB_TOKEN_POLICIES = keyBy(
POLICIES_BY_RESOURCE.flatMap(({ policies }) => policies),
({ value }) => value,
);

View File

@ -1,5 +1,17 @@
mutation inboundAddGroupOrProjectCIJobTokenScope($projectPath: ID!, $targetPath: ID!) {
ciJobTokenScopeAddGroupOrProject(input: { projectPath: $projectPath, targetPath: $targetPath }) {
mutation inboundAddGroupOrProjectCIJobTokenScope(
$projectPath: ID!
$targetPath: ID!
$defaultPermissions: Boolean
$jobTokenPolicies: [CiJobTokenScopePolicies!]
) {
ciJobTokenScopeAddGroupOrProject(
input: {
projectPath: $projectPath
targetPath: $targetPath
defaultPermissions: $defaultPermissions
jobTokenPolicies: $jobTokenPolicies
}
) {
errors
}
}

View File

@ -544,7 +544,7 @@ export default {
type="danger"
dismissible
data-testid="merge-error"
class="mr-widget-section"
class="mr-widget-section gl-rounded-b-none gl-border-b-section"
>
<span v-safe-html="mergeError"></span>
</mr-widget-alert-message>
@ -552,7 +552,7 @@ export default {
v-if="showMergePipelineForkWarning"
type="warning"
:help-path="mr.mergeRequestPipelinesHelpPath"
class="mr-widget-section"
class="mr-widget-section gl-rounded-b-none gl-border-b-section"
data-testid="merge-pipeline-fork-warning"
>
{{

View File

@ -265,6 +265,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
persist_accepted_terms_if_required(@user) if new_user
perform_registration_tasks(@user, oauth['provider']) if new_user
enqueue_after_sign_in_workers(@user)
sign_in_and_redirect_or_verify_identity(@user, auth_user, new_user)
end
else
@ -438,6 +441,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
sign_in_and_redirect(user, event: :authentication)
end
# overridden in specific EE class
def enqueue_after_sign_in_workers(_user)
true
end
def store_idp_two_factor_status(bypass_2fa)
if Feature.enabled?(:by_pass_two_factor_for_current_session)
session[:provider_2FA] = true if bypass_2fa

View File

@ -17,6 +17,7 @@ module Projects
before_action do
push_frontend_feature_flag(:ci_variables_pages, current_user)
push_frontend_feature_flag(:allow_push_repository_for_job_token, @project)
push_frontend_feature_flag(:add_policies_to_ci_job_token, @project)
push_frontend_ability(ability: :admin_project, resource: @project, user: current_user)
end

View File

@ -1,17 +0,0 @@
# frozen_string_literal: true
module Preloaders
class SingleHierarchyProjectGroupPlansPreloader
attr_reader :projects
def initialize(projects_relation)
@projects = projects_relation
end
def execute
# no-op in FOSS
end
end
end
Preloaders::SingleHierarchyProjectGroupPlansPreloader.prepend_mod_with('Preloaders::SingleHierarchyProjectGroupPlansPreloader')

View File

@ -925,6 +925,12 @@ production: &base
#
admin_group: ''
# LDAP group of users who should have Duo Add-on seats assigned in GitLab
#
# Ex. ['DuoGroup1', 'DuoGroup2']
#
duo_add_on_groups: []
# LDAP group of users who should be marked as external users in GitLab
#
# Ex. ['Contractors', 'Interns']

View File

@ -385,6 +385,8 @@
- 1
- - gitlab_subscriptions_add_on_purchases_email_on_duo_bulk_user_assignments
- 1
- - gitlab_subscriptions_add_on_purchases_ldap_add_on_seat_sync
- 1
- - gitlab_subscriptions_add_on_purchases_refresh_user_assignments
- 1
- - gitlab_subscriptions_member_management_apply_pending_member_approvals

View File

@ -0,0 +1,8 @@
---
migration_job_name: BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId
description: Backfills sharding key `protected_branch_unprotect_access_levels.protected_branch_namespace_id` from `protected_branches`.
feature_category: source_code_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175718
milestone: '17.8'
queued_migration_version: 20241213141747
finalized_by: # version of the migration that finalized this BBM

View File

@ -0,0 +1,8 @@
---
migration_job_name: BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId
description: Backfills sharding key `protected_branch_unprotect_access_levels.protected_branch_project_id` from `protected_branches`.
feature_category: source_code_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175718
milestone: '17.8'
queued_migration_version: 20241213141742
finalized_by: # version of the migration that finalized this BBM

View File

@ -26,3 +26,6 @@ desired_sharding_key:
sharding_key: namespace_id
belongs_to: protected_branch
table_size: small
desired_sharding_key_migration_job_name:
- BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId
- BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddProtectedBranchProjectIdToProtectedBranchUnprotectAccessLevels < Gitlab::Database::Migration[2.2]
milestone '17.8'
def change
add_column :protected_branch_unprotect_access_levels, :protected_branch_project_id, :bigint
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddProtectedBranchNamespaceIdToProtectedBranchUnprotectAccessLevels < Gitlab::Database::Migration[2.2]
milestone '17.8'
def change
add_column :protected_branch_unprotect_access_levels, :protected_branch_namespace_id, :bigint
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class IndexProtectedBranchUnprotectAccessLevelsOnProtectedBranchProjectId < Gitlab::Database::Migration[2.2]
milestone '17.8'
disable_ddl_transaction!
INDEX_NAME = 'i_protected_branch_unprotect_access_levels_protected_branch_pro'
def up
add_concurrent_index :protected_branch_unprotect_access_levels, :protected_branch_project_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :protected_branch_unprotect_access_levels, INDEX_NAME
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddProtectedBranchUnprotectAccessLevelsProtectedBranchProjectIdFk < Gitlab::Database::Migration[2.2]
milestone '17.8'
disable_ddl_transaction!
def up
add_concurrent_foreign_key :protected_branch_unprotect_access_levels, :projects,
column: :protected_branch_project_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :protected_branch_unprotect_access_levels, column: :protected_branch_project_id
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddProtectedBranchUnprotectAccessLevelsProtectedBranchProjectIdTrigger < Gitlab::Database::Migration[2.2]
milestone '17.8'
def up
install_sharding_key_assignment_trigger(
table: :protected_branch_unprotect_access_levels,
sharding_key: :protected_branch_project_id,
parent_table: :protected_branches,
parent_sharding_key: :project_id,
foreign_key: :protected_branch_id
)
end
def down
remove_sharding_key_assignment_trigger(
table: :protected_branch_unprotect_access_levels,
sharding_key: :protected_branch_project_id,
parent_table: :protected_branches,
parent_sharding_key: :project_id,
foreign_key: :protected_branch_id
)
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
class QueueBackfillProtectedBranchUnprotectAccessLevelsProjectId < Gitlab::Database::Migration[2.2]
milestone '17.8'
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
MIGRATION = "BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:protected_branch_unprotect_access_levels,
:id,
:protected_branch_project_id,
:protected_branches,
:project_id,
:protected_branch_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(
MIGRATION,
:protected_branch_unprotect_access_levels,
:id,
[
:protected_branch_project_id,
:protected_branches,
:project_id,
:protected_branch_id
]
)
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class IndexProtectedBranchUnprotectAccessLevelsOnProtectedBranchNamespaceId < Gitlab::Database::Migration[2.2]
milestone '17.8'
disable_ddl_transaction!
INDEX_NAME = 'i_protected_branch_unprotect_access_levels_protected_branch_nam'
def up
add_concurrent_index :protected_branch_unprotect_access_levels, :protected_branch_namespace_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :protected_branch_unprotect_access_levels, INDEX_NAME
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceIdFk < Gitlab::Database::Migration[2.2]
milestone '17.8'
disable_ddl_transaction!
def up
add_concurrent_foreign_key :protected_branch_unprotect_access_levels, :namespaces,
column: :protected_branch_namespace_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :protected_branch_unprotect_access_levels, column: :protected_branch_namespace_id
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceIdTrigger < Gitlab::Database::Migration[2.2]
milestone '17.8'
def up
install_sharding_key_assignment_trigger(
table: :protected_branch_unprotect_access_levels,
sharding_key: :protected_branch_namespace_id,
parent_table: :protected_branches,
parent_sharding_key: :namespace_id,
foreign_key: :protected_branch_id
)
end
def down
remove_sharding_key_assignment_trigger(
table: :protected_branch_unprotect_access_levels,
sharding_key: :protected_branch_namespace_id,
parent_table: :protected_branches,
parent_sharding_key: :namespace_id,
foreign_key: :protected_branch_id
)
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
class QueueBackfillProtectedBranchUnprotectAccessLevelsNamespaceId < Gitlab::Database::Migration[2.2]
milestone '17.8'
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
MIGRATION = "BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:protected_branch_unprotect_access_levels,
:id,
:protected_branch_namespace_id,
:protected_branches,
:namespace_id,
:protected_branch_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(
MIGRATION,
:protected_branch_unprotect_access_levels,
:id,
[
:protected_branch_namespace_id,
:protected_branches,
:namespace_id,
:protected_branch_id
]
)
end
end

View File

@ -0,0 +1 @@
a083f15fc20f7f3d6501e5ea5fd11ba7b8dd9c7e6d86216f83e5214fc5fa9f9f

View File

@ -0,0 +1 @@
d10e72b8cf31f8ad24d4f2c3e7ed89533677c73d7367da9f896f9740b3c5a22d

View File

@ -0,0 +1 @@
b005f824cd7cb6542305413209e1c71e70e102eb73a35628ed09e328ee373929

View File

@ -0,0 +1 @@
3b2a59e3b874d57a98d8a029c26ca96fe18f6364b24e9b61b8e831c54a93b2c3

View File

@ -0,0 +1 @@
b06ddae59bae1e4db5229521bf7a17530b50550543a282821b7d4084d8784907

View File

@ -0,0 +1 @@
f600fb78971583654009b0c3312b50559da80c51ef8f4cdb52a4cc5916d6e1e8

View File

@ -0,0 +1 @@
f0b00d1e6904ea0381c25e4470066631ae0e0d64c0e7884a9595cd20b32d7171

View File

@ -0,0 +1 @@
e54c68567a9cd1b9740f1c5436fb2c956a073890e513e2c8630caf3e2ad27c41

View File

@ -0,0 +1 @@
c9388208f640459b3dfb2bc72901375911d897c4bb679784bc750836e290b45b

View File

@ -0,0 +1 @@
9a6b9a01e0d5c8c0cd2f124822c92d9a878f13d24bad3fe58c5c9d5c789cdf58

View File

@ -2512,6 +2512,22 @@ RETURN NEW;
END
$$;
CREATE FUNCTION trigger_96298f7da5d3() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW."protected_branch_project_id" IS NULL THEN
SELECT "project_id"
INTO NEW."protected_branch_project_id"
FROM "protected_branches"
WHERE "protected_branches"."id" = NEW."protected_branch_id";
END IF;
RETURN NEW;
END
$$;
CREATE FUNCTION trigger_9699ea03bb37() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -3200,6 +3216,22 @@ RETURN NEW;
END
$$;
CREATE FUNCTION trigger_ed554313ca66() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW."protected_branch_namespace_id" IS NULL THEN
SELECT "namespace_id"
INTO NEW."protected_branch_namespace_id"
FROM "protected_branches"
WHERE "protected_branches"."id" = NEW."protected_branch_id";
END IF;
RETURN NEW;
END
$$;
CREATE FUNCTION trigger_efb9d354f05a() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -18947,7 +18979,9 @@ CREATE TABLE protected_branch_unprotect_access_levels (
protected_branch_id bigint NOT NULL,
access_level integer DEFAULT 40,
user_id bigint,
group_id bigint
group_id bigint,
protected_branch_project_id bigint,
protected_branch_namespace_id bigint
);
CREATE SEQUENCE protected_branch_unprotect_access_levels_id_seq
@ -28947,6 +28981,10 @@ CREATE UNIQUE INDEX i_pm_package_versions_on_package_id_and_version ON pm_packag
CREATE UNIQUE INDEX i_pm_packages_purl_type_and_name ON pm_packages USING btree (purl_type, name);
CREATE INDEX i_protected_branch_unprotect_access_levels_protected_branch_nam ON protected_branch_unprotect_access_levels USING btree (protected_branch_namespace_id);
CREATE INDEX i_protected_branch_unprotect_access_levels_protected_branch_pro ON protected_branch_unprotect_access_levels USING btree (protected_branch_project_id);
CREATE UNIQUE INDEX i_sbom_occurrences_vulnerabilities_on_occ_id_and_vuln_id ON sbom_occurrences_vulnerabilities USING btree (sbom_occurrence_id, vulnerability_id);
CREATE INDEX i_software_license_policies_on_custom_software_license_id ON software_license_policies USING btree (custom_software_license_id);
@ -36019,6 +36057,8 @@ CREATE TRIGGER trigger_9259aae92378 BEFORE INSERT OR UPDATE ON packages_build_in
CREATE TRIGGER trigger_94514aeadc50 BEFORE INSERT OR UPDATE ON deployment_approvals FOR EACH ROW EXECUTE FUNCTION trigger_94514aeadc50();
CREATE TRIGGER trigger_96298f7da5d3 BEFORE INSERT OR UPDATE ON protected_branch_unprotect_access_levels FOR EACH ROW EXECUTE FUNCTION trigger_96298f7da5d3();
CREATE TRIGGER trigger_9699ea03bb37 BEFORE INSERT OR UPDATE ON related_epic_links FOR EACH ROW EXECUTE FUNCTION trigger_9699ea03bb37();
CREATE TRIGGER trigger_96a76ee9f147 BEFORE INSERT OR UPDATE ON design_management_versions FOR EACH ROW EXECUTE FUNCTION trigger_96a76ee9f147();
@ -36107,6 +36147,8 @@ CREATE TRIGGER trigger_ebab34f83f1d BEFORE INSERT OR UPDATE ON packages_debian_p
CREATE TRIGGER trigger_ec1934755627 BEFORE INSERT OR UPDATE ON alert_management_alert_metric_images FOR EACH ROW EXECUTE FUNCTION trigger_ec1934755627();
CREATE TRIGGER trigger_ed554313ca66 BEFORE INSERT OR UPDATE ON protected_branch_unprotect_access_levels FOR EACH ROW EXECUTE FUNCTION trigger_ed554313ca66();
CREATE TRIGGER trigger_efb9d354f05a BEFORE INSERT OR UPDATE ON incident_management_issuable_escalation_statuses FOR EACH ROW EXECUTE FUNCTION trigger_efb9d354f05a();
CREATE TRIGGER trigger_eff80ead42ac BEFORE INSERT OR UPDATE ON ci_unit_test_failures FOR EACH ROW EXECUTE FUNCTION trigger_eff80ead42ac();
@ -36536,6 +36578,9 @@ ALTER TABLE ONLY approvals
ALTER TABLE ONLY namespaces
ADD CONSTRAINT fk_319256d87a FOREIGN KEY (file_template_project_id) REFERENCES projects(id) ON DELETE SET NULL;
ALTER TABLE ONLY protected_branch_unprotect_access_levels
ADD CONSTRAINT fk_325cad614b FOREIGN KEY (protected_branch_project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY bulk_import_entities
ADD CONSTRAINT fk_32782a175e FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
@ -36755,6 +36800,9 @@ ALTER TABLE ONLY status_check_responses
ALTER TABLE ONLY merge_request_metrics
ADD CONSTRAINT fk_56067dcb44 FOREIGN KEY (target_project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY protected_branch_unprotect_access_levels
ADD CONSTRAINT fk_5632201009 FOREIGN KEY (protected_branch_namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY merge_request_diffs
ADD CONSTRAINT fk_56ac6fc9c0 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;

View File

@ -287,7 +287,7 @@ Allow enough time for downtime while step two is being executed.
Edit your `/etc/gitlab/gitlab.rb` and add the `maintenance` section to the `registry['storage']`
configuration. For example, for a `gcs` backed registry using a `gs://my-company-container-registry`
bucket , the configuration could be:
bucket, the configuration could be:
```ruby
## Object Storage - Container Registry

View File

@ -308,8 +308,8 @@ the specific list of rules.
If you want to force `e2e:test-on-omnibus` to run regardless of your changes, you can add the
`pipeline:run-all-e2e` label to the merge request.
The [`e2e:test-on-gdk`](../testing_guide/end_to_end/index.md#using-the-test-on-gdk-job) child pipeline runs `:blocking`
E2E specs automatically for all `code patterns changes`. See `.qa:rules:e2e-blocking-gdk` [`rules.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rules.gitlab-ci.yml) for specific set of rules.
The [`e2e:test-on-gdk`](../testing_guide/end_to_end/index.md#using-the-test-on-gdk-job) child pipeline runs
E2E specs automatically for all `code patterns changes`. See `.qa:rules:e2e:test-on-gdk` in [`rules.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rules.gitlab-ci.yml) for specific set of rules.
Consult the [End-to-end Testing](../testing_guide/end_to_end/index.md) dedicated page for more information.

View File

@ -41,7 +41,6 @@ This is a partial list of the [RSpec metadata](https://rspec.info/features/3-12/
| `:product_group` | Specifies what product group the test belongs to. See [Product sections, stages, groups, and categories](https://handbook.gitlab.com/handbook/product/categories/) for the comprehensive groups list. |
| `:quarantine` | The test has been [quarantined](https://handbook.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantining-tests), runs in a separate job that only includes quarantined tests, and is allowed to fail. The test is skipped in its regular job so that if it fails it doesn't hold up the pipeline. Note that you can also [quarantine a test only when it runs in a specific context](execution_context_selection.md#quarantine-a-test-for-a-specific-environment). |
| `:relative_url` | The test requires a GitLab instance to be installed under a [relative URL](../../../../install/relative_url.md). |
| `:blocking` | Tag for tests running in `e2e:test-on-gdk` pipeline and not allowed to fail. Only tests that pass consistently should have this tag. |
| `:repository_storage` | The test requires a GitLab instance to be configured to use multiple [repository storage paths](../../../../administration/repository_storage_paths.md). Paired with the `:orchestrated` tag. |
| `:requires_admin` | The test requires an administrator account. Tests with the tag are excluded when run against Canary and Production environments. |
| `:requires_git_protocol_v2` | The test requires that Git protocol version 2 is enabled on the server. It's assumed to be enabled by default but if not the test can be skipped by setting `QA_CAN_TEST_GIT_PROTOCOL_V2` to `false`. |

View File

@ -21,27 +21,7 @@ For any of the following scenarios, the `start-review-app-pipeline` job would be
- for scheduled pipelines
- the MR has the `pipeline:run-review-app` label set
## E2E test runs on review apps
On every pipeline in the `qa` stage (which comes after the `review` stage), the `review-qa-smoke` and `review-qa-blocking` jobs are automatically started.
`qa` stage consists of following jobs:
- `review-qa-smoke`: small and fast subset of tests to validate core functionality of GitLab.
- `review-qa-blocking`: subset of tests that block the merge request. These tests are
considered stable and are not allowed to fail.
- `review-qa-non-blocking`: rest of the e2e tests that can be triggered manually.
`review-qa-*` jobs ensure that end-to-end tests for the changes in the merge request pass in a live environment. This shifts the identification of e2e failures from an environment
on the path to production to the merge request to prevent breaking features on GitLab.com or costly GitLab.com deployment blockers. If needed, `review-qa-*` failures should be
investigated with an SET (software engineer in test) counterpart to help determine the root cause of the error.
After the end-to-end test runs have finished, [Allure reports](https://github.com/allure-framework/allure2) are generated and published by
the `e2e-test-report` job. A comment with links to the reports is added to the merge request.
Errors can be found in the `gitlab-review-apps` Sentry project and [filterable by review app URL](https://sentry.gitlab.net/gitlab/gitlab-review-apps/?query=url%3A%22https%3A%2F%2Fgitlab-review-require-ve-u92nn2.gitlab-review.app%2F%22) or [commit SHA](https://sentry.gitlab.net/gitlab/gitlab-review-apps/releases/6095b501da7/all-events/).
### Bypass failed review app deployment to merge a broken `master` fix
## Bypass failed review app deployment to merge a broken `master` fix
Maintainers can elect to use the [process for merging during broken `master`](https://handbook.gitlab.com/handbook/engineering/workflow/#instructions-for-the-maintainer) if a customer-critical merge request is blocked by pipelines failing due to review app deployment failures.
@ -138,14 +118,12 @@ graph TD
B[review-build-cng];
C["review-deploy<br><br>Helm deploys the review app using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br><br>Cloud Native images are deployed to the `review-apps`<br>Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."];
D[CNG-mirror];
E[review-qa-smoke, review-qa-blocking, review-qa-non-blocking<br><br>gitlab-qa runs the e2e tests against the review app.];
A --> B1
B1 --> B
B -.->|triggers a CNG-mirror pipeline| D
D -.->|depends on the multi-project pipeline| B
B --> C
C --> E
subgraph "1. gitlab-org/gitlab parent pipeline"
A
@ -155,7 +133,6 @@ subgraph "1. gitlab-org/gitlab parent pipeline"
subgraph "2. gitlab-org/gitlab child pipeline"
B
C
E
end
subgraph "CNG-mirror pipeline"
@ -207,13 +184,6 @@ subgraph "CNG-mirror pipeline"
issue with a link to your merge request. The deployment failure can
reveal an actual problem introduced in your merge request (that is, this isn't
necessarily a transient failure)!
- If the `review-qa-smoke` job keeps failing (we already retry them once),
check the job's logs: you could discover an actual problem introduced in
your merge request. You can also download the artifacts to see screenshots of
the page at the time the failures occurred. If you don't find the cause of the
failure or if it seems unrelated to your change, post a message in the
`#test-platform` channel and/or create a ~Quality ~"type::bug" issue with a link to your
merge request.
- The manual `review-stop` can be used to
stop a review app manually, and is also started by GitLab once a merge
request's branch is deleted after being merged.

View File

@ -41,6 +41,8 @@ The following regions are verified for use:
- US West (Oregon)
- Middle East (Bahrain)
For more information about selecting low emission regions, see [Choose Region based on both business requirements and sustainability goals](https://docs.aws.amazon.com/wellarchitected/latest/sustainability-pillar/sus_sus_region_a2.html).
If you're interested in a region not listed here, contact your account representative or [GitLab Support](https://about.gitlab.com/support/) to inquire about availability.
## Data isolation

View File

@ -30,7 +30,7 @@ To improve your workflow across the entire software development lifecycle, try t
- [GitLab Duo Chat](../gitlab_duo_chat/index.md): Write and understand code, get up to speed on the status of projects,
and learn about GitLab by asking your questions in a chat window.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=ZQBAuf-CTAY&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=ZQBAuf-CTAY)
- [Self-Hosted Models](../../administration/self_hosted_models/index.md): Host the language models that power AI features in GitLab.
Code Suggestions and Duo Chat are supported. Use GitLab model vendors or self-host a supported language model.
- [GitLab Duo Workflow](../duo_workflow/index.md): Automate tasks and help increase productivity in your development workflow.
@ -41,8 +41,11 @@ To improve your workflow across the entire software development lifecycle, try t
To improve your workflow while planning work, try these features:
- [Issue Description Generation](../project/issues/managing_issues.md#populate-an-issue-with-issue-description-generation): Generate a more in-depth issue description based on a short summary.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=-BWBQat7p5M)
<!-- Video published on 2024-12-18 -->
- [Discussion Summary](../discussions/index.md#summarize-issue-discussions-with-duo-chat): Summarize lengthy conversations in an issue.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=IcdxLfTIUgc)
<!-- Video published on 2024-03-28 -->
## Authoring code
@ -56,11 +59,11 @@ To improve your workflow while authoring code, try these features:
- [A file](../../user/project/repository/code_explain.md).
- [A merge request](../../user/project/merge_requests/changes.md#explain-code-in-a-merge-request).
- [Test Generation](../gitlab_duo_chat/examples.md#write-tests-in-the-ide): Test your code by generating tests.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU)
- [Refactor Code](../gitlab_duo_chat/examples.md#refactor-code-in-the-ide): Improve or refactor the selected code.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU)
- [Fix Code](../gitlab_duo_chat/examples.md#fix-code-in-the-ide): Fix quality problems, like bugs or typos, in the selected code.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU)
- [GitLab Duo for the CLI](../../editor_extensions/gitlab_cli/index.md#gitlab-duo-for-the-cli): Discover or recall `git` commands.
## Reviewing code
@ -71,7 +74,7 @@ To improve your workflow while reviewing code in merge requests, try these featu
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=CKjkVsfyFd8&list=PLFGfElNsQthZGazU1ZdfDpegu0HflunXW)
- [Code Review](../project/merge_requests/duo_in_merge_requests.md#have-gitlab-duo-review-your-code): Review proposed code changes.
- [Code Review Summary](../project/merge_requests/duo_in_merge_requests.md#summarize-a-code-review): Summarize all the comments in a review.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=Bx6Zajyuy9k&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=Bx6Zajyuy9k)
- [Merge Commit Message Generation](../project/merge_requests/duo_in_merge_requests.md#generate-a-merge-commit-message): Generate commit messages.
## Testing and deploying code

View File

@ -137,6 +137,7 @@ Project items that are migrated to the destination GitLab instance include:
| Snippets | [GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/343438) |
| Settings | [GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/339416) |
| Uploads | [GitLab 14.5](https://gitlab.com/gitlab-org/gitlab/-/issues/339401) |
| Vulnerability report | [GitLab 17.7](https://gitlab.com/gitlab-org/gitlab/-/issues/501466) |
| Wikis | [GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/345923) |
<!-- vale gitlab_base.OutdatedVersions = YES -->

View File

@ -198,6 +198,7 @@ For a quick overview, items that are exported include:
- Some merge request approval rules:
- [Approvals for protected branches](../merge_requests/approvals/rules.md#approvals-for-protected-branches)
- [Eligible approvers](../merge_requests/approvals/rules.md#eligible-approvers)
- Vulnerability report ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/501466) in GitLab 17.7)
#### Project items that are not exported

View File

@ -114,11 +114,10 @@ module API
paginate(projects)
end
def present_projects(params, projects, single_hierarchy: false)
def present_projects(params, projects)
options = {
with: params[:simple] ? Entities::BasicProjectDetails : Entities::Project,
current_user: current_user,
single_hierarchy: single_hierarchy
current_user: current_user
}
projects, options = with_custom_attributes(projects, options)
@ -437,7 +436,7 @@ module API
projects = find_group_projects(params, finder_options)
present_projects(params, projects, single_hierarchy: true)
present_projects(params, projects)
end
desc 'Get a list of shared projects in this group' do

View File

@ -17,7 +17,6 @@ module API
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute if options[:current_user]
preload_member_roles(projects_relation, options[:current_user]) if options[:current_user]
Preloaders::SingleHierarchyProjectGroupPlansPreloader.new(projects_relation).execute if options[:single_hierarchy]
preload_groups(projects_relation) if options[:with] == Entities::Project
projects_relation

View File

@ -148,6 +148,10 @@ module Gitlab
options['admin_group']
end
def duo_add_on_groups
Array(options['duo_add_on_groups'])
end
def active_directory
options['active_directory']
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId < BackfillDesiredShardingKeyJob
operation_name :backfill_protected_branch_unprotect_access_levels_protected_branch_namespace_id
feature_category :source_code_management
end
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId < BackfillDesiredShardingKeyJob
operation_name :backfill_protected_branch_unprotect_access_levels_protected_branch_project_id
feature_category :source_code_management
end
end
end

View File

@ -1,5 +1,5 @@
variables:
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.114.0'
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.115.0'
.dast-auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -1,5 +1,5 @@
variables:
AUTO_DEPLOY_IMAGE_VERSION: 'v2.114.0'
AUTO_DEPLOY_IMAGE_VERSION: 'v2.115.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -1,5 +1,5 @@
variables:
AUTO_DEPLOY_IMAGE_VERSION: 'v2.114.0'
AUTO_DEPLOY_IMAGE_VERSION: 'v2.115.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -20634,10 +20634,13 @@ msgstr ""
msgid "DuoCodeReview|Hey :wave: I'm starting to review your merge request and I will let you know when I'm finished."
msgstr ""
msgid "DuoCodeReview|I encountered some problems while responding to your query. Please try again later."
msgstr ""
msgid "DuoCodeReview|I finished my review and found nothing to comment on. Nice work! :tada:"
msgstr ""
msgid "DuoCodeReview|I have encountered some issues while I was reviewing. Please try again later."
msgid "DuoCodeReview|I have encountered some problems while I was reviewing. Please try again later."
msgstr ""
msgid "DuoEnterpriseDiscover|AI Impact Dashboard measures the ROI of AI"
@ -31535,6 +31538,51 @@ msgstr ""
msgid "JobAssistant|week(s)"
msgstr ""
msgid "JobToken|Apply permissions that grant access to individual resources."
msgstr ""
msgid "JobToken|Containers"
msgstr ""
msgid "JobToken|Default permissions"
msgstr ""
msgid "JobToken|Deployments"
msgstr ""
msgid "JobToken|Environments"
msgstr ""
msgid "JobToken|Fine-grained permissions"
msgstr ""
msgid "JobToken|Jobs"
msgstr ""
msgid "JobToken|Packages"
msgstr ""
msgid "JobToken|Read"
msgstr ""
msgid "JobToken|Read and write"
msgstr ""
msgid "JobToken|Releases"
msgstr ""
msgid "JobToken|Resource"
msgstr ""
msgid "JobToken|Secure files"
msgstr ""
msgid "JobToken|Terraform state"
msgstr ""
msgid "JobToken|Use the standard permissions model based on user membership and roles."
msgstr ""
msgid "Jobs"
msgstr ""

View File

@ -20,7 +20,6 @@ module QA
end
it 'returns a custom server hook error', :skip_live_env,
except: { job: 'review-qa-*' },
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/369053' do
expect { project.create_repository_tag('v1.2.3') }
.to raise_error.with_message(

View File

@ -3,7 +3,7 @@
module QA
RSpec.describe 'Analytics' do
describe 'Service ping default enabled', product_group: :analytics_instrumentation do
context 'when using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do
context 'when using default enabled from gitlab.yml config', :requires_admin do
before do
Flow::Login.sign_in_as_admin

View File

@ -65,13 +65,9 @@ module QA
Resource::Issue.fabricate_via_api_unless_fips! { |issue| issue.project = project }.visit!
end
# The following example is excluded from running in `review-qa-smoke` job
# as it proved to be flaky when running against Review App
# See https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/11568#note_621999351
it(
'comments on an issue with an attachment',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347946',
except: { job: 'review-qa-*' }
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347946'
) do
Page::Project::Issue::Show.perform do |show|
show.comment('See attached image for scale', attachment: file_to_attach)

View File

@ -4,7 +4,6 @@ module QA
RSpec.describe 'Plan',
:gitlab_pages,
:orchestrated,
except: { job: 'review-qa-*' },
quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/383215',
type: :broken

View File

@ -1,87 +0,0 @@
# frozen_string_literal: true
require 'open3'
module QA
module Tools
module Ci
# Run count commands for scenarios and detect which ones have more than 0 examples to run
#
class NonEmptySuites
include Helpers
# @return [Array] scenarios that never run in test-on-omnibus pipeline
IGNORED_SCENARIOS = [
"QA::EE::Scenario::Test::Geo",
"QA::Scenario::Test::Instance::Airgapped"
].freeze
def initialize(qa_tests)
@qa_tests = qa_tests
end
# Run counts and return runnable scenario list
#
# @return [String]
def fetch
logger.info("Checking for runnable suites")
scenarios.each_with_object([]) do |scenario, runnable_scenarios|
logger.info(" fetching runnable specs for '#{scenario}'")
next logger.info(" scenario is in ignore list, skipping") if IGNORED_SCENARIOS.include?(scenario)
out, err, status = run_command(scenario)
unless status.success?
logger.error(" example count failed!\n#{err}")
next
end
count = out.split("\n").last.to_i
logger.info(" found #{count} examples to run")
runnable_scenarios << scenario if count > 0
end.join(",")
end
private
attr_reader :qa_tests
# Get all defined scenarios
#
# @return [Array<String>]
def scenarios
foss_scenarios = scenario_classes(QA::Scenario::Test)
return foss_scenarios unless QA.const_defined?("QA::EE")
foss_scenarios + scenario_classes(QA::EE::Scenario::Test)
end
# Fetch scenario classes recursively
#
# @param [Module] mod
# @return [Array<String>]
def scenario_classes(mod)
mod.constants.map do |const|
c = mod.const_get(const, false)
next c.to_s if c.is_a?(Class)
scenario_classes(c)
end.flatten
end
# Run scenario count command
#
# @param [String] klass
# @return [String]
def run_command(klass)
cmd = ["bundle exec bin/qa"]
cmd << klass
cmd << "--count-examples-only --address http://dummy1.test"
cmd << "-- #{qa_tests}" unless qa_tests.blank?
Open3.capture3(cmd.join(" "))
end
end
end
end
end

View File

@ -0,0 +1,94 @@
# frozen_string_literal: true
module QA
module Tools
module Ci
# Execute rspec dry run to get list of executable specs for each scenario class
#
class RunnableSpecs
include Helpers
# @return [Array] scenarios that never run in test-on-omnibus pipeline
IGNORED_SCENARIOS = [
QA::EE::Scenario::Test::Geo,
QA::Scenario::Test::Instance::Airgapped,
QA::Scenario::Test::Sanity::Selectors
].freeze
def initialize(qa_tests)
@qa_tests = qa_tests
end
# Return list of executable spec files for each scenario class
#
# @return [Hash<Class, Array<String>>]
def fetch
logger.info("Checking for runnable suites")
(scenarios - IGNORED_SCENARIOS).each_with_object({}) do |scenario, runnable_scenarios|
specs = fetch_specs(scenario)
logger.info(" found #{specs.size} spec files to run")
runnable_scenarios[scenario] = specs unless specs.empty?
end
end
private
attr_reader :qa_tests
# Get all defined scenarios
#
# @return [Array<String>]
def scenarios
foss_scenarios = scenario_classes(QA::Scenario::Test)
return foss_scenarios unless QA.const_defined?("QA::EE")
foss_scenarios + scenario_classes(QA::EE::Scenario::Test)
end
# Fetch scenario classes recursively
#
# @param [Module] mod
# @return [Array<Object>]
def scenario_classes(mod)
mod.constants.flat_map do |const|
c = mod.const_get(const, false)
next c if c.is_a?(Class)
scenario_classes(c)
end
end
# Fetch list of executable spec files for scenario class
#
# @param klass [Class]
# @return [Array<String>]
def fetch_specs(klass)
logger.info(" fetching runnable spec files for '#{klass}'")
Tempfile.open("test-metadata.json") do |file|
Process.fork do
err = StringIO.new
tags = klass.focus.presence || Specs::Runner::DEFAULT_SKIPPED_TAGS.map { |tag| "~#{tag}" }
args = ["--dry-run", *tags.flat_map { |tag| ["-t", tag.to_s] }]
qa_tests.blank? ? args.push(*Specs::Runner::DEFAULT_TEST_PATH_ARGS) : args.push(*qa_tests)
# Clear variables that automatically add formatters in spec_helper
%w[CI CI_SERVER COVERBAND_ENABLED].each { |var| ENV.delete(var) }
RSpec.configure { |config| config.add_formatter(QA::Support::JsonFormatter, file.path) }
status = RSpec::Core::Runner.run(args, err, StringIO.new)
logger.error(" subprocess failed! Err output: #{err.string}") if status.nonzero?
Kernel.exit(status)
end
_pid, status = Process.wait2
raise "Failed to fetch executable spec files for #{klass}" unless status.success?
JSON.load_file(file, symbolize_names: true)[:examples]
.map { |example| example[:file_path].gsub("./", "") }
.uniq
end
end
end
end
end
end

View File

@ -1,19 +0,0 @@
# frozen_string_literal: true
RSpec.describe QA::Tools::Ci::NonEmptySuites do
let(:non_empty_suites) { described_class.new(nil) }
let(:status) { instance_double(Process::Status, success?: true) }
before do
allow(Gitlab::QA::TestLogger).to receive(:logger).and_return(Logger.new(StringIO.new))
allow(Open3).to receive(:capture3).and_return(["output\n0", "", status])
allow(Open3).to receive(:capture3)
.with("bundle exec bin/qa QA::Scenario::Test::Instance::All --count-examples-only --address http://dummy1.test")
.and_return(["output\n1", "", status])
end
it "returns runnable test suites" do
expect(non_empty_suites.fetch).to eq("QA::Scenario::Test::Instance::All")
end
end

View File

@ -0,0 +1,67 @@
# frozen_string_literal: true
RSpec.describe QA::Tools::Ci::RunnableSpecs do
let(:runnable_specs) { described_class.new([]).fetch }
let(:config_mock) { instance_double(RSpec::Core::Configuration, add_formatter: nil) }
let(:run_result_file) { instance_double(File, path: "file_path") }
let(:rspec_json) { { examples: [] } }
let(:rspec_exit) { 0 }
before do
allow(ENV).to receive(:delete)
allow(Gitlab::QA::TestLogger).to receive(:logger).and_return(Logger.new(StringIO.new))
allow(Tempfile).to receive(:open).with("test-metadata.json").and_yield(run_result_file)
allow(JSON).to receive(:load_file).with(run_result_file, symbolize_names: true).and_return(rspec_json)
allow(RSpec).to receive(:configure).and_yield(config_mock)
allow(RSpec::Core::Runner).to receive(:run).and_return(rspec_exit)
allow(Process).to receive(:fork).and_yield
allow(Process).to receive(:wait2).and_return(
[1, instance_double(Process::Status, success?: rspec_exit.nonzero? ? false : true)]
)
allow(Kernel).to receive(:exit)
end
it "correctly configures formatter" do
runnable_specs
expect(config_mock).to have_received(:add_formatter)
.with(QA::Support::JsonFormatter, run_result_file.path)
.at_least(:once)
end
context "with rspec process failure" do
let(:rspec_exit) { 1 }
before do
# skip yielding fork block to correctly raise error
allow(Process).to receive(:fork)
end
it "raises error" do
expect { runnable_specs }.to raise_error(RuntimeError, /Failed to fetch executable spec files for .*/)
end
end
context "with rspec returning runnable specs" do
let(:rspec_json) do
{
examples: [
{
file_path: "./qa/specs/features/ee/test_spec.rb"
}
]
}
end
it "returns runnable spec list" do
expect(runnable_specs.all? { |_k, v| v == ["qa/specs/features/ee/test_spec.rb"] }).to be true
end
end
context "with rspec returning no runnable specs" do
it "returns empty spec list" do
expect(runnable_specs.all? { |_k, v| v == [] }).to be true
end
end
end

View File

@ -71,7 +71,7 @@ namespace :ci do
end
# always check all test suites in case a suite is defined but doesn't have any runnable specs
suites = QA::Tools::Ci::NonEmptySuites.new(tests).fetch
suites = QA::Tools::Ci::RunnableSpecs.new(tests).fetch.keys.join(",")
append_to_file(env_file, <<~TXT)
QA_SUITES='#{suites}'
QA_TESTS='#{tests}'

View File

@ -106,7 +106,6 @@ ee/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_containe
ee/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
ee/spec/frontend/vue_shared/components/groups_list/groups_list_item_spec.js
ee/spec/frontend/vue_shared/components/projects_list/projects_list_item_spec.js
ee/spec/frontend/vue_shared/security_reports/components/security_training_promo_spec.js
ee/spec/frontend/vulnerabilities/generic_report/types/list_graphql_spec.js
ee/spec/frontend/vulnerabilities/related_issues_spec.js
spec/frontend/__helpers__/vue_test_utils_helper_spec.js

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_category: :continuous_integration do
RSpec.describe 'Merge request < User sees pipeline mini graph', :js, feature_category: :continuous_integration do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project, head_pipeline: pipeline) }
@ -23,7 +23,7 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_cat
visit project_merge_request_path(project, merge_request, format: format, serializer: serializer)
end
it 'displays a mini pipeline graph' do
it 'displays a pipeline mini graph' do
expect(page).to have_selector('[data-testid="pipeline-mini-graph"]')
end
@ -52,7 +52,7 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_cat
end
end
describe 'build list toggle' do
describe 'stage dropdown toggle' do
let(:toggle) do
find(dropdown_selector)
first(dropdown_selector)
@ -69,7 +69,7 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_cat
end
end
describe 'builds list menu' do
describe 'stage dropdown' do
let(:toggle) do
find(dropdown_selector)
first(dropdown_selector)
@ -96,21 +96,20 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_cat
expect(toggle.find(:xpath, '..')).not_to have_selector('[data-testid="pipeline-mini-graph-dropdown-menu"]')
end
describe 'build list build item' do
let(:build_item) do
find('.ci-job-component')
first('.ci-job-component')
describe 'job list job item' do
let(:job_item) do
first('[data-testid="job-name"]')
end
it 'visits the build page when clicked' do
build_item.click
it 'visits the job page when clicked' do
job_item.click
find('.build-page')
expect(page).to have_current_path(project_job_path(project, build), ignore_query: true)
end
it 'shows tooltip when hovered' do
build_item.hover
job_item.hover
expect(page).to have_selector('.tooltip')
end

View File

@ -7,6 +7,7 @@ import NamespaceForm from '~/token_access/components/namespace_form.vue';
import addNamespaceMutation from '~/token_access/graphql/mutations/inbound_add_group_or_project_ci_job_token_scope.mutation.graphql';
import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import PoliciesSelector from '~/token_access/components/policies_selector.vue';
import { getAddNamespaceHandler } from './mock_data';
Vue.use(VueApollo);
@ -16,10 +17,13 @@ describe('Namespace form component', () => {
const defaultAddMutationHandler = getAddNamespaceHandler();
const createWrapper = ({ addMutationHandler = defaultAddMutationHandler } = {}) => {
const createWrapper = ({
addMutationHandler = defaultAddMutationHandler,
addPoliciesToCiJobToken = true,
} = {}) => {
wrapper = shallowMountExtended(NamespaceForm, {
apolloProvider: createMockApollo([[addNamespaceMutation, addMutationHandler]]),
provide: { fullPath: 'full/path' },
provide: { fullPath: 'full/path', glFeatures: { addPoliciesToCiJobToken } },
stubs: {
GlFormInput: stubComponent(GlFormInput, {
props: ['autofocus', 'disabled', 'state', 'placeholder'],
@ -32,6 +36,7 @@ describe('Namespace form component', () => {
const findFormInput = () => wrapper.findComponent(GlFormInput);
const findAddButton = () => wrapper.findByTestId('add-button');
const findCancelButton = () => wrapper.findByTestId('cancel-button');
const findPoliciesSelector = () => wrapper.findComponent(PoliciesSelector);
describe('on page load', () => {
beforeEach(() => createWrapper());
@ -55,6 +60,30 @@ describe('Namespace form component', () => {
});
});
describe('policies selector', () => {
it('shows policies selector', () => {
expect(findPoliciesSelector().props()).toMatchObject({
isDefaultPermissionsSelected: true,
jobTokenPolicies: [],
disabled: false,
});
});
it('updates defaultPermissions when policies selector emits an update', async () => {
findPoliciesSelector().vm.$emit('update:isDefaultPermissionsSelected', false);
await nextTick();
expect(findPoliciesSelector().props('isDefaultPermissionsSelected')).toBe(false);
});
it('updates jobTokenPolicies when policies selector emits an update', async () => {
findPoliciesSelector().vm.$emit('update:jobTokenPolicies', ['ADMIN_JOB']);
await nextTick();
expect(findPoliciesSelector().props('jobTokenPolicies')).toEqual(['ADMIN_JOB']);
});
});
describe('Add button', () => {
it('shows button', () => {
expect(findAddButton().text()).toBe('Add');
@ -98,6 +127,8 @@ describe('Namespace form component', () => {
expect(defaultAddMutationHandler).toHaveBeenCalledWith({
projectPath: 'full/path',
targetPath: 'gitlab',
defaultPermissions: true,
jobTokenPolicies: [],
});
});
@ -105,6 +136,10 @@ describe('Namespace form component', () => {
expect(findFormInput().props('disabled')).toBe(true);
});
it('disables policies selector', () => {
expect(findPoliciesSelector().props('disabled')).toBe(true);
});
it('disables Add button', () => {
expect(findAddButton().props('loading')).toBe(true);
});
@ -125,6 +160,10 @@ describe('Namespace form component', () => {
expect(findFormInput().props('disabled')).toBe(false);
});
it('enables policies selector', () => {
expect(findPoliciesSelector().props('disabled')).toBe(false);
});
it('enables Add button', () => {
expect(findAddButton().props('loading')).toBe(false);
});
@ -188,4 +227,24 @@ describe('Namespace form component', () => {
});
});
});
describe('when the addPoliciesToCiJobToken feature flag is disabled', () => {
beforeEach(() => createWrapper({ addPoliciesToCiJobToken: false }));
it('does not show permissions selector', () => {
expect(findPoliciesSelector().exists()).toBe(false);
});
describe('when namespace is saved', () => {
it('calls mutation without defaultPermissions or jobTokenPolicies', () => {
findFormInput().vm.$emit('input', 'gitlab');
findAddButton().vm.$emit('click');
expect(defaultAddMutationHandler).toHaveBeenCalledWith({
projectPath: 'full/path',
targetPath: 'gitlab',
});
});
});
});
});

View File

@ -0,0 +1,187 @@
import { GlTableLite, GlFormRadio, GlFormRadioGroup, GlCollapsibleListbox } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import PoliciesSelector, { TABLE_FIELDS } from '~/token_access/components/policies_selector.vue';
import { stubComponent } from 'helpers/stub_component';
import { POLICIES_BY_RESOURCE, RESOURCE_JOBS, RESOURCE_RELEASES } from '~/token_access/constants';
describe('Policies selector component', () => {
let wrapper;
const createWrapper = ({
isDefaultPermissionsSelected = false,
jobTokenPolicies = [],
disabled = false,
stubs = {},
} = {}) => {
wrapper = mountExtended(PoliciesSelector, {
propsData: { isDefaultPermissionsSelected, jobTokenPolicies, disabled },
stubs,
});
return waitForPromises();
};
const findTable = () => wrapper.findComponent(GlTableLite);
const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
const findDefaultRadio = () => wrapper.findByTestId('default-radio');
const findFineGrainedRadio = () => wrapper.findByTestId('fine-grained-radio');
const getResourceIndex = (item) =>
POLICIES_BY_RESOURCE.map(({ resource }) => resource).indexOf(item);
const findNameForResource = (resource) =>
findTable().findAll('tbody tr').at(getResourceIndex(resource)).findAll('td').at(0);
const findPolicyDropdownForResource = (resource) =>
wrapper.findAllComponents(GlCollapsibleListbox).at(getResourceIndex(resource));
describe('permission type radio options', () => {
beforeEach(() =>
createWrapper({
stubs: {
GlFormRadioGroup: stubComponent(GlFormRadioGroup, { props: ['checked'] }),
GlFormRadio: stubComponent(GlFormRadio, {
template: '<div><slot></slot><slot name="help"></slot></div>',
props: ['value'],
}),
},
}),
);
describe('radio group', () => {
it('shows radio group', () => {
expect(findRadioGroup().exists()).toBe(true);
});
it.each`
name | isDefaultPermissionsSelected
${'default'} | ${true}
${'fine-grained'} | ${false}
`(
'selects the $name option when the isDefaultPermissionsSelected prop is $isDefaultPermissionsSelected',
async ({ isDefaultPermissionsSelected }) => {
await wrapper.setProps({ isDefaultPermissionsSelected });
expect(findRadioGroup().props('checked')).toBe(isDefaultPermissionsSelected);
},
);
it.each`
name | isDefaultPermissionsSelected
${'default'} | ${true}
${'fine-grained'} | ${false}
`(
'emits update:isDefaultPermissionsSelected event with $isDefaultPermissionsSelected when $name is selected',
({ isDefaultPermissionsSelected }) => {
findRadioGroup().vm.$emit('change', isDefaultPermissionsSelected);
expect(wrapper.emitted('update:isDefaultPermissionsSelected')[0][0]).toEqual(
isDefaultPermissionsSelected,
);
},
);
});
it.each`
name | value | findRadio | expectedText
${'default'} | ${true} | ${findDefaultRadio} | ${'Default permissions Use the standard permissions model based on user membership and roles.'}
${'fine-grained'} | ${false} | ${findFineGrainedRadio} | ${'Fine-grained permissions Apply permissions that grant access to individual resources.'}
`('shows the $name radio', ({ value, findRadio, expectedText }) => {
expect(findRadio().text()).toMatchInterpolatedText(expectedText);
expect(findRadio().props('value')).toBe(value);
});
});
describe('when Default permissions is selected', () => {
beforeEach(() => createWrapper({ isDefaultPermissionsSelected: true }));
it('does not show policies table', () => {
expect(findTable().exists()).toBe(false);
});
});
describe('when Fine-grained permissions is selected', () => {
beforeEach(() => {
createWrapper({
stubs: { GlTableLite: stubComponent(GlTableLite, { props: ['fields', 'items'] }) },
});
});
it('shows policies table', () => {
expect(findTable().props()).toMatchObject({
items: POLICIES_BY_RESOURCE,
fields: TABLE_FIELDS,
});
});
describe('policies table', () => {
beforeEach(() => createWrapper());
describe.each(POLICIES_BY_RESOURCE)(
'for resource $resource.text',
({ resource, policies }) => {
it('shows the resource name', () => {
expect(findNameForResource(resource).text()).toBe(resource.text);
});
it('shows the resource dropdown', () => {
expect(findPolicyDropdownForResource(resource).props('items')).toEqual(policies);
});
it.each(policies)(`emits $value policy when it is selected`, (policy) => {
const expected = policy.value ? [policy.value] : [];
findPolicyDropdownForResource(resource).vm.$emit('select', policy.value);
expect(wrapper.emitted('update:jobTokenPolicies')[0][0]).toEqual(expected);
});
},
);
describe('when multiple policies are selected across different resources', () => {
const jobTokenPolicies = ['READ_JOBS', 'ADMIN_PACKAGES'];
beforeEach(() => wrapper.setProps({ jobTokenPolicies }));
it('adds the policy when there is no policy set for the resource', () => {
findPolicyDropdownForResource(RESOURCE_RELEASES).vm.$emit('select', 'READ_RELEASES');
expect(wrapper.emitted('update:jobTokenPolicies')[0][0]).toEqual([
...jobTokenPolicies,
'READ_RELEASES',
]);
});
it('updates the policy when there is already a policy for the resource', () => {
findPolicyDropdownForResource(RESOURCE_JOBS).vm.$emit('select', 'ADMIN_JOBS');
expect(wrapper.emitted('update:jobTokenPolicies')[0][0]).toEqual([
'ADMIN_JOBS',
'ADMIN_PACKAGES',
]);
});
});
});
describe('disabled prop', () => {
describe.each([true, false])('when disabled prop is %s', (disabled) => {
beforeEach(() =>
createWrapper({
disabled,
stubs: { GlFormRadioGroup: stubComponent(GlFormRadioGroup, { props: ['disabled'] }) },
}),
);
it(`sets radio group disabled to ${disabled}`, () => {
expect(findRadioGroup().props('disabled')).toBe(disabled);
});
it(`sets all policy dropdowns disabled to ${disabled}`, () => {
wrapper.findAllComponents(GlCollapsibleListbox).wrappers.forEach((dropdown) => {
expect(dropdown.props('disabled')).toBe(disabled);
});
});
});
});
});
});

View File

@ -614,6 +614,22 @@ AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK
end
end
describe '#duo_add_on_groups' do
it 'returns empty array when not set' do
expect(config.duo_add_on_groups).to be_empty
end
context 'when the config is set' do
before do
stub_ldap_config(options: { duo_add_on_groups: %w[duo_group_1 duo_group_2] })
end
it 'returns configured duo_add_on_groups array' do
expect(config.duo_add_on_groups).to match_array(%w[duo_group_1 duo_group_2])
end
end
end
describe 'sign_in_enabled?' do
using RSpec::Parameterized::TableSyntax

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId,
feature_category: :source_code_management,
schema: 20241213141743 do
include_examples 'desired sharding key backfill job' do
let(:batch_table) { :protected_branch_unprotect_access_levels }
let(:backfill_column) { :protected_branch_namespace_id }
let(:backfill_via_table) { :protected_branches }
let(:backfill_via_column) { :namespace_id }
let(:backfill_via_foreign_key) { :protected_branch_id }
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId,
feature_category: :source_code_management,
schema: 20241213141738 do
include_examples 'desired sharding key backfill job' do
let(:batch_table) { :protected_branch_unprotect_access_levels }
let(:backfill_column) { :protected_branch_project_id }
let(:backfill_via_table) { :protected_branches }
let(:backfill_via_column) { :project_id }
let(:backfill_via_foreign_key) { :protected_branch_id }
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillProtectedBranchUnprotectAccessLevelsProjectId, feature_category: :source_code_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :protected_branch_unprotect_access_levels,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
gitlab_schema: :gitlab_main_cell,
job_arguments: [
:protected_branch_project_id,
:protected_branches,
:project_id,
:protected_branch_id
]
)
}
end
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillProtectedBranchUnprotectAccessLevelsNamespaceId, feature_category: :source_code_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :protected_branch_unprotect_access_levels,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
gitlab_schema: :gitlab_main_cell,
job_arguments: [
:protected_branch_namespace_id,
:protected_branches,
:namespace_id,
:protected_branch_id
]
)
}
end
end
end