Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d83c5c5d4e
commit
b9f4411b1b
|
|
@ -4267,7 +4267,6 @@ Gitlab/BoundedContexts:
|
|||
- 'lib/quality/seeders/issues.rb'
|
||||
- 'lib/release_highlights/validator.rb'
|
||||
- 'lib/release_highlights/validator/entry.rb'
|
||||
- 'lib/result.rb'
|
||||
- 'lib/rouge/formatters/html_gitlab.rb'
|
||||
- 'lib/rouge/plugins/common_mark.rb'
|
||||
- 'lib/safe_zip/entry.rb'
|
||||
|
|
|
|||
|
|
@ -479,7 +479,6 @@ Layout/ClassStructure:
|
|||
- 'lib/peek/views/external_http.rb'
|
||||
- 'lib/peek/views/zoekt.rb'
|
||||
- 'lib/release_highlights/validator.rb'
|
||||
- 'lib/result.rb'
|
||||
- 'lib/sbom/package_url.rb'
|
||||
- 'lib/system_check/base_check.rb'
|
||||
- 'lib/uploaded_file.rb'
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ RSpec/FeatureCategory:
|
|||
- 'ee/spec/helpers/analytics/code_review_helper_spec.rb'
|
||||
- 'ee/spec/helpers/application_helper_spec.rb'
|
||||
- 'ee/spec/helpers/boards_helper_spec.rb'
|
||||
- 'ee/spec/helpers/container_registry/container_registry_helper_spec.rb'
|
||||
- 'ee/spec/helpers/credentials_inventory_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/access_tokens_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/admin/identities_helper_spec.rb'
|
||||
|
|
@ -1945,7 +1946,7 @@ RSpec/FeatureCategory:
|
|||
- 'spec/helpers/commits_helper_spec.rb'
|
||||
- 'spec/helpers/components_helper_spec.rb'
|
||||
- 'spec/helpers/container_expiration_policies_helper_spec.rb'
|
||||
- 'spec/helpers/container_registry_helper_spec.rb'
|
||||
- 'spec/helpers/container_registry/container_registry_helper_spec.rb'
|
||||
- 'spec/helpers/cookies_helper_spec.rb'
|
||||
- 'spec/helpers/dashboard_helper_spec.rb'
|
||||
- 'spec/helpers/dev_ops_report_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -2447,7 +2447,6 @@ Style/InlineDisableAnnotation:
|
|||
- 'lib/kramdown/parser/atlassian_document_format.rb'
|
||||
- 'lib/object_storage/pending_direct_upload.rb'
|
||||
- 'lib/quality/seeders/issues.rb'
|
||||
- 'lib/result.rb'
|
||||
- 'lib/safe_zip/extract.rb'
|
||||
- 'lib/sidebars/context.rb'
|
||||
- 'lib/sidebars/menu_item.rb'
|
||||
|
|
@ -2727,7 +2726,6 @@ Style/InlineDisableAnnotation:
|
|||
- 'spec/lib/mattermost/team_spec.rb'
|
||||
- 'spec/lib/object_storage/pending_direct_upload_spec.rb'
|
||||
- 'spec/lib/omni_auth/strategies/jwt_spec.rb'
|
||||
- 'spec/lib/result_spec.rb'
|
||||
- 'spec/mailers/notify_spec.rb'
|
||||
- 'spec/migrations/20230726144458_swap_notes_id_to_bigint_for_self_managed_spec.rb'
|
||||
- 'spec/migrations/20230803125434_add_has_merge_request_on_vulnerability_reads_trigger_spec.rb'
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -510,7 +510,7 @@ group :development, :test do
|
|||
end
|
||||
|
||||
group :development, :test, :danger do
|
||||
gem 'gitlab-dangerfiles', '~> 4.7.0', require: false, feature_category: :tooling
|
||||
gem 'gitlab-dangerfiles', '~> 4.8.0', require: false, feature_category: :tooling
|
||||
end
|
||||
|
||||
group :development, :test, :coverage do
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@
|
|||
{"name":"gitaly","version":"17.0.2","platform":"ruby","checksum":"ecd1cdf097318652e0df7eb661f7c1689f8734b0fa36dade52fb0799fe4fba82"},
|
||||
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
|
||||
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
|
||||
{"name":"gitlab-dangerfiles","version":"4.7.0","platform":"ruby","checksum":"2576876a8dcb7290853fc3aef8048001cfe593b87318dd0016959d42e0e145ca"},
|
||||
{"name":"gitlab-dangerfiles","version":"4.8.0","platform":"ruby","checksum":"b327d079552ec974a63bf34d749a0308425af6ebf51d01064f1a6ff216a523db"},
|
||||
{"name":"gitlab-experiment","version":"0.9.1","platform":"ruby","checksum":"f230ee742154805a755d5f2539dc44d93cdff08c5bbbb7656018d61f93d01f48"},
|
||||
{"name":"gitlab-fog-azure-rm","version":"1.9.1","platform":"ruby","checksum":"026b8e188ac4183c1bf1b1909b0489da0ffad453996a6e744e0eba67dc284f37"},
|
||||
{"name":"gitlab-glfm-markdown","version":"0.0.17","platform":"aarch64-linux","checksum":"81ccfd91c7a1da4b165e700f1a6fbb15cf20ffd283ec8c6e05d5e2078a569717"},
|
||||
|
|
|
|||
|
|
@ -691,7 +691,7 @@ GEM
|
|||
terminal-table (>= 1.5.1)
|
||||
gitlab-chronic (0.10.5)
|
||||
numerizer (~> 0.2)
|
||||
gitlab-dangerfiles (4.7.0)
|
||||
gitlab-dangerfiles (4.8.0)
|
||||
danger (>= 9.3.0)
|
||||
danger-gitlab (>= 8.0.0)
|
||||
rake (~> 13.0)
|
||||
|
|
@ -2003,7 +2003,7 @@ DEPENDENCIES
|
|||
gitaly (~> 17.0.1)
|
||||
gitlab-backup-cli!
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 4.7.0)
|
||||
gitlab-dangerfiles (~> 4.8.0)
|
||||
gitlab-experiment (~> 0.9.1)
|
||||
gitlab-fog-azure-rm (~> 1.9.1)
|
||||
gitlab-glfm-markdown (~> 0.0.17)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<script>
|
||||
import {
|
||||
GlCard,
|
||||
GlTable,
|
||||
GlButton,
|
||||
GlPagination,
|
||||
GlIcon,
|
||||
GlLoadingIcon,
|
||||
GlEmptyState,
|
||||
GlModal,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
|
||||
import { __ } from '~/locale';
|
||||
import Api, { DEFAULT_PER_PAGE } from '~/api';
|
||||
import CrudComponent from '~/vue_shared/components/crud_component.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
|
||||
import { createAlert } from '~/alert';
|
||||
|
|
@ -86,16 +86,18 @@ export default {
|
|||
csrf,
|
||||
DEFAULT_PER_PAGE,
|
||||
components: {
|
||||
GlCard,
|
||||
CrudComponent,
|
||||
GlTable,
|
||||
GlButton,
|
||||
GlPagination,
|
||||
TimeAgoTooltip,
|
||||
GlIcon,
|
||||
GlLoadingIcon,
|
||||
GlEmptyState,
|
||||
GlModal,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: ['editPath', 'deletePath', 'createPath', 'emptyStateSvgPath'],
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -190,19 +192,13 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-card
|
||||
class="gl-new-card"
|
||||
header-class="gl-new-card-header"
|
||||
body-class="gl-new-card-body gl-px-0"
|
||||
<crud-component
|
||||
:title="$options.i18n.pageTitle"
|
||||
:count="totalItems.toString()"
|
||||
icon="key"
|
||||
class="gl-mt-5"
|
||||
>
|
||||
<template #header>
|
||||
<div class="gl-new-card-title-wrapper">
|
||||
<h3 class="gl-new-card-title">{{ $options.i18n.pageTitle }}</h3>
|
||||
<span class="gl-new-card-count">
|
||||
<gl-icon name="key" class="gl-mr-2" />
|
||||
{{ totalItems }}
|
||||
</span>
|
||||
</div>
|
||||
<template #actions>
|
||||
<div class="gl-new-card-actions">
|
||||
<gl-button size="small" :href="createPath" data-testid="new-deploy-key-button">{{
|
||||
$options.i18n.newDeployKeyButtonText
|
||||
|
|
@ -257,21 +253,24 @@ export default {
|
|||
</template>
|
||||
|
||||
<template #cell(actions)="{ item: { id } }">
|
||||
<gl-button
|
||||
icon="pencil"
|
||||
size="small"
|
||||
:aria-label="$options.i18n.edit"
|
||||
:href="editHref(id)"
|
||||
class="gl-mr-2"
|
||||
/>
|
||||
<gl-button
|
||||
variant="danger"
|
||||
category="secondary"
|
||||
icon="remove"
|
||||
size="small"
|
||||
:aria-label="$options.i18n.delete"
|
||||
@click="handleDeleteClick(id)"
|
||||
/>
|
||||
<div class="gl-flex gl-gap-2 -gl-my-3">
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
:title="$options.i18n.edit"
|
||||
category="tertiary"
|
||||
icon="pencil"
|
||||
:aria-label="$options.i18n.edit"
|
||||
:href="editHref(id)"
|
||||
/>
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
:title="$options.i18n.delete"
|
||||
category="tertiary"
|
||||
icon="remove"
|
||||
:aria-label="$options.i18n.delete"
|
||||
@click="handleDeleteClick(id)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</gl-table>
|
||||
<gl-empty-state
|
||||
|
|
@ -307,5 +306,5 @@ export default {
|
|||
</form>
|
||||
{{ $options.i18n.modal.body }}
|
||||
</gl-modal>
|
||||
</gl-card>
|
||||
</crud-component>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -257,7 +257,6 @@ export default {
|
|||
stacked="md"
|
||||
fixed
|
||||
show-empty
|
||||
no-sort-reset
|
||||
no-local-sorting
|
||||
@sort-changed="(val) => $emit('sort-changed', val)"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -238,7 +238,6 @@ export default {
|
|||
stacked="md"
|
||||
table-class="text-secondary"
|
||||
show-empty
|
||||
no-sort-reset
|
||||
:empty-text="$options.i18n.noFilesMessage"
|
||||
>
|
||||
<template #table-busy>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,12 @@ export default {
|
|||
TitleArea,
|
||||
MetadataItem,
|
||||
GlLink,
|
||||
MetadataContainerScanning: () =>
|
||||
import(
|
||||
'ee_component/packages_and_registries/container_registry/explorer/components/list_page/metadata_container_scanning.vue'
|
||||
),
|
||||
},
|
||||
inject: ['config'],
|
||||
props: {
|
||||
expirationPolicy: {
|
||||
type: Object,
|
||||
|
|
@ -115,5 +120,8 @@ export default {
|
|||
$options.i18n.SET_UP_CLEANUP
|
||||
}}</gl-link>
|
||||
</template>
|
||||
<template v-if="!config.isGroupPage" #metadata-container-scanning>
|
||||
<metadata-container-scanning />
|
||||
</template>
|
||||
</title-area>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -9,23 +9,21 @@ export const mergeVariables = (existing, incoming) => {
|
|||
return incoming;
|
||||
};
|
||||
|
||||
export const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(
|
||||
{},
|
||||
{
|
||||
batchMax: 1,
|
||||
cacheConfig: {
|
||||
typePolicies: {
|
||||
ContainerRepositoryDetails: {
|
||||
fields: {
|
||||
tags: {
|
||||
keyArgs: ['id', 'name', 'sort'],
|
||||
merge: mergeVariables,
|
||||
},
|
||||
},
|
||||
export const config = {
|
||||
cacheConfig: {
|
||||
typePolicies: {
|
||||
ContainerRepositoryDetails: {
|
||||
fields: {
|
||||
tags: {
|
||||
keyArgs: ['id', 'name', 'sort'],
|
||||
merge: mergeVariables,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient({}, config),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import PerformancePlugin from '~/performance/vue_performance_plugin';
|
|||
import Translate from '~/vue_shared/translate';
|
||||
import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
|
||||
import { injectVueAppBreadcrumbs } from '~/lib/utils/breadcrumbs';
|
||||
import { apolloProvider } from './graphql/index';
|
||||
import { apolloProvider } from 'ee_else_ce/packages_and_registries/container_registry/explorer/graphql';
|
||||
import RegistryExplorer from './pages/index.vue';
|
||||
import createRouter from './router';
|
||||
|
||||
|
|
@ -41,6 +41,8 @@ export default () => {
|
|||
showUnfinishedTagCleanupCallout,
|
||||
connectionError,
|
||||
invalidPathError,
|
||||
securityConfigurationPath,
|
||||
containerScanningForRegistryDocsPath,
|
||||
...config
|
||||
} = el.dataset;
|
||||
|
||||
|
|
@ -76,6 +78,8 @@ export default () => {
|
|||
connectionError: parseBoolean(connectionError),
|
||||
invalidPathError: parseBoolean(invalidPathError),
|
||||
isMetadataDatabaseEnabled: parseBoolean(isMetadataDatabaseEnabled),
|
||||
securityConfigurationPath,
|
||||
containerScanningForRegistryDocsPath,
|
||||
},
|
||||
/* eslint-disable @gitlab/require-i18n-strings */
|
||||
dockerBuildCommand: `docker build -t ${config.repositoryUrl} .`,
|
||||
|
|
|
|||
|
|
@ -112,7 +112,6 @@ export default {
|
|||
:sort-by="sortBy"
|
||||
sort-desc
|
||||
no-local-sorting
|
||||
no-sort-reset
|
||||
@sort-changed="$emit('sortChanged', $event)"
|
||||
>
|
||||
<template v-for="field in fields" #[getHeaderSlotName(field.key)]>
|
||||
|
|
|
|||
|
|
@ -141,7 +141,9 @@ export default {
|
|||
return url.split(value).join(`{${key}}`);
|
||||
},
|
||||
onItemInput({ index, key, value }) {
|
||||
this.$set(this.items, index, { key, value });
|
||||
const copy = [...this.items];
|
||||
copy[index] = { key, value };
|
||||
this.items = copy;
|
||||
},
|
||||
addItem() {
|
||||
this.items.push({});
|
||||
|
|
|
|||
|
|
@ -17,16 +17,8 @@ module Groups
|
|||
end
|
||||
|
||||
def show
|
||||
@work_item = group.work_items.find_by_iid(show_params[:iid])
|
||||
|
||||
not_found unless Feature.enabled?(:namespace_level_work_items, group)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def show_params
|
||||
params.permit(:iid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
|
|||
|
||||
path = project_runners_path(project)
|
||||
|
||||
if ::Ci::Runners::AssignRunnerService.new(@runner, @project, current_user).execute.success?
|
||||
response = ::Ci::Runners::AssignRunnerService.new(@runner, @project, current_user).execute
|
||||
if response.success?
|
||||
flash[:success] = s_('Runners|Runner assigned to project.')
|
||||
redirect_to path
|
||||
else
|
||||
assign_to_messages = @runner.errors.messages[:assign_to]
|
||||
alert = assign_to_messages&.join(',') || 'Failed adding runner to project'
|
||||
assign_to_messages = [response.message] + (@runner.errors.messages[:assign_to] || [])
|
||||
alert = assign_to_messages.join(', ').presence || 'Failed adding runner to project'
|
||||
|
||||
redirect_to path, alert: alert
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Projects::RunnersController < Projects::ApplicationController
|
||||
before_action :authorize_admin_runner!
|
||||
before_action :authorize_read_runner!
|
||||
before_action :authorize_admin_runner!, except: [:index, :show]
|
||||
before_action :authorize_create_runner!, only: [:new, :register]
|
||||
before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show, :register]
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ module Mutations
|
|||
description: 'Indicates CI/CD job tokens generated in other projects ' \
|
||||
'have restricted access to this project.'
|
||||
|
||||
argument :push_repository_for_job_token_allowed, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: 'Indicates the ability to push to the original project ' \
|
||||
'repository using a job token'
|
||||
|
||||
field :ci_cd_settings,
|
||||
Types::Ci::CiCdSettingType,
|
||||
null: false,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,13 @@ module Types
|
|||
null: true,
|
||||
description: 'Project the CI/CD settings belong to.',
|
||||
authorize: :admin_project
|
||||
field :push_repository_for_job_token_allowed,
|
||||
GraphQL::Types::Boolean,
|
||||
null: true,
|
||||
description: 'Indicates the ability to push to the original project ' \
|
||||
'repository using a job token',
|
||||
method: :push_repository_for_job_token_allowed?,
|
||||
authorize: :admin_project
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ContainerRegistry
|
||||
module ContainerRegistryHelper
|
||||
def container_repository_gid_prefix
|
||||
"gid://#{GlobalID.app}/#{ContainerRepository.name}/"
|
||||
end
|
||||
|
||||
def project_container_registry_template_data(project, connection_error, invalid_path_error)
|
||||
{
|
||||
endpoint: project_container_registry_index_path(project),
|
||||
expiration_policy: project.container_expiration_policy.to_json,
|
||||
help_page_path: help_page_path('user/packages/container_registry/index'),
|
||||
two_factor_auth_help_link: help_page_path('user/profile/account/two_factor_authentication'),
|
||||
personal_access_tokens_help_link: help_page_path('user/profile/personal_access_tokens'),
|
||||
no_containers_image: image_path('illustrations/docker-empty-state.svg'),
|
||||
containers_error_image: image_path('illustrations/docker-error-state.svg'),
|
||||
repository_url: escape_once(project.container_registry_url),
|
||||
registry_host_url_with_port: escape_once(registry_config.host_port),
|
||||
expiration_policy_help_page_path:
|
||||
help_page_path('user/packages/container_registry/reduce_container_registry_storage',
|
||||
anchor: 'cleanup-policy'),
|
||||
garbage_collection_help_page_path: help_page_path('administration/packages/container_registry',
|
||||
anchor: 'container-registry-garbage-collection'),
|
||||
run_cleanup_policies_help_page_path: help_page_path('administration/packages/container_registry',
|
||||
anchor: 'run-the-cleanup-policy-now'),
|
||||
project_path: project.full_path,
|
||||
gid_prefix: container_repository_gid_prefix,
|
||||
is_admin: current_user&.admin.to_s,
|
||||
show_cleanup_policy_link: show_cleanup_policy_link(project).to_s,
|
||||
show_container_registry_settings: show_container_registry_settings(project).to_s,
|
||||
cleanup_policies_settings_path: project_settings_packages_and_registries_path(project),
|
||||
connection_error: (!!connection_error).to_s,
|
||||
invalid_path_error: (!!invalid_path_error).to_s,
|
||||
user_callouts_path: callouts_path,
|
||||
user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
|
||||
is_metadata_database_enabled: ContainerRegistry::GitlabApiClient.supports_gitlab_api?.to_s,
|
||||
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ContainerRegistryHelper
|
||||
def container_repository_gid_prefix
|
||||
"gid://#{GlobalID.app}/#{ContainerRepository.name}/"
|
||||
end
|
||||
end
|
||||
|
|
@ -247,7 +247,11 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
condition(:push_repository_for_job_token_allowed) do
|
||||
@user&.from_ci_job_token? && project.ci_push_repository_for_job_token_allowed? && @user.ci_job_token_scope.self_referential?(project)
|
||||
if ::Feature.enabled?(:allow_push_repository_for_job_token, @subject)
|
||||
@user&.from_ci_job_token? && project.ci_push_repository_for_job_token_allowed? && @user.ci_job_token_scope.self_referential?(project)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
condition(:packages_disabled, scope: :subject) { !@subject.packages_enabled }
|
||||
|
|
@ -622,6 +626,7 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
rule { can?(:admin_build) }.enable :manage_trigger
|
||||
rule { can?(:admin_runner) }.enable :read_runner
|
||||
|
||||
rule { public_project & metrics_dashboard_allowed }.policy do
|
||||
enable :metrics_dashboard
|
||||
|
|
|
|||
|
|
@ -2,35 +2,56 @@
|
|||
|
||||
module Ci
|
||||
module Runners
|
||||
# Service used to assign a runner to a project.
|
||||
# This class can be reused by SetRunnerAssociatedProjectsService in the context of a bulk assignment.
|
||||
class AssignRunnerService
|
||||
# @param [Ci::Runner] runner: the runner to assign to a project
|
||||
# @param [Project] project: the new project to assign the runner to
|
||||
# @param [User] user: the user performing the operation
|
||||
def initialize(runner, project, user)
|
||||
# @param [Boolean] quiet: true if service should avoid side-effects, such as logging
|
||||
# (e.g. when used by another service)
|
||||
def initialize(runner, project, user, quiet: false)
|
||||
@runner = runner
|
||||
@project = project
|
||||
@user = user
|
||||
@quiet = quiet
|
||||
end
|
||||
|
||||
def execute
|
||||
unless @user.present? && @user.can?(:assign_runner, @runner)
|
||||
return ServiceResponse.error(message: 'user not allowed to assign runner', http_status: :forbidden)
|
||||
end
|
||||
|
||||
unless @user.can?(:create_runner, @project)
|
||||
return ServiceResponse.error(message: 'user not allowed to add runners to project', http_status: :forbidden)
|
||||
end
|
||||
response = validate
|
||||
return response if response.error?
|
||||
|
||||
if @runner.assign_to(@project, @user)
|
||||
ServiceResponse.success
|
||||
else
|
||||
ServiceResponse.error(message: 'failed to assign runner')
|
||||
ServiceResponse.error(
|
||||
message: @runner.errors.full_messages_for(:assign_to).presence || _('failed to assign runner to project'),
|
||||
reason: :runner_error)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :runner, :project, :user
|
||||
attr_reader :runner, :project, :user, :quiet
|
||||
|
||||
def validate
|
||||
unless @user.present? && @user.can?(:assign_runner, @runner)
|
||||
return ServiceResponse.error(message: _('user not allowed to assign runner'),
|
||||
reason: :not_authorized_to_assign_runner)
|
||||
end
|
||||
|
||||
unless @user.can?(:create_runner, @project)
|
||||
return ServiceResponse.error(message: _('user is not authorized to add runners to project'),
|
||||
reason: :not_authorized_to_add_runner_in_project)
|
||||
end
|
||||
|
||||
if project.organization_id != runner.owner_project.organization_id
|
||||
return ServiceResponse.error(message: _('runner can only be assigned to projects in the same organization'),
|
||||
reason: :project_not_in_same_organization)
|
||||
end
|
||||
|
||||
ServiceResponse.success
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ module Ci
|
|||
|
||||
def execute
|
||||
unless current_user&.can?(:assign_runner, runner)
|
||||
return ServiceResponse.error(message: 'user not allowed to assign runner', http_status: :forbidden)
|
||||
return ServiceResponse.error(message: _('user not allowed to assign runner'),
|
||||
reason: :not_authorized_to_assign_runner)
|
||||
end
|
||||
|
||||
return ServiceResponse.success if project_ids.nil?
|
||||
|
|
@ -44,12 +45,17 @@ module Ci
|
|||
def associate_new_projects(new_project_ids, current_project_ids)
|
||||
missing_projects = Project.id_in(new_project_ids - current_project_ids)
|
||||
|
||||
unless missing_projects.all? { |project| current_user.can?(:create_runner, project) }
|
||||
return ServiceResponse.error(message: 'user is not authorized to add runners to project')
|
||||
end
|
||||
error_responses = missing_projects.map do |project|
|
||||
Ci::Runners::AssignRunnerService.new(runner, project, current_user, quiet: true)
|
||||
end.map(&:execute).select(&:error?)
|
||||
|
||||
unless missing_projects.all? { |project| runner.assign_to(project, current_user) }
|
||||
return ServiceResponse.error(message: 'failed to assign projects to runner')
|
||||
if error_responses.any?
|
||||
return error_responses.first if error_responses.count == 1
|
||||
|
||||
return ServiceResponse.error(
|
||||
message: error_responses.map(&:message).uniq,
|
||||
reason: :multiple_errors
|
||||
)
|
||||
end
|
||||
|
||||
ServiceResponse.success
|
||||
|
|
@ -65,7 +71,7 @@ module Ci
|
|||
.all?(&:destroyed?)
|
||||
return ServiceResponse.success if all_destroyed
|
||||
|
||||
ServiceResponse.error(message: 'failed to destroy runner project')
|
||||
ServiceResponse.error(message: _('failed to destroy runner project'), reason: :failed_runner_project_destroy)
|
||||
end
|
||||
|
||||
attr_reader :runner, :current_user, :project_ids
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@
|
|||
},
|
||||
"remove_project": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"read_runners": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
- page_title _('Health Check')
|
||||
- no_errors = @errors.blank?
|
||||
|
||||
#div{ data: { event_tracking_load: 'true', event_tracking: 'view_admin_health_check_pageload' } }
|
||||
%h1.page-title.gl-font-size-h-display= page_title
|
||||
= render Pajamas::AlertComponent.new(variant: :tip, dismissible: false, alert_options: { class: 'gl-mb-5' }) do |c|
|
||||
- c.with_body do
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- page_title _('System information')
|
||||
= render ::Layouts::PageHeadingComponent.new(_('System information'))
|
||||
|
||||
.gl-grid.sm:gl-grid-cols-2.md:gl-grid-cols-3.gl-gap-5
|
||||
.gl-grid.sm:gl-grid-cols-2.md:gl-grid-cols-3.gl-gap-5{ data: { event_tracking_load: 'true', event_tracking: 'view_admin_system_info_pageload' } }
|
||||
= render Pajamas::CardComponent.new do |c|
|
||||
- c.with_body do
|
||||
- if @cpus
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
- page_title "#{@work_item.title} (#{@work_item.to_reference})", _("Epics")
|
||||
- page_description @work_item.description_html
|
||||
- page_card_attributes @work_item.card_attributes
|
||||
- add_work_item_show_breadcrumb(@group, @work_item.iid)
|
||||
- page_title "##{request.params['iid']}"
|
||||
- add_work_item_show_breadcrumb(@group, request.params['iid'])
|
||||
- add_page_specific_style 'page_bundles/work_items'
|
||||
- add_page_specific_style 'page_bundles/design_management'
|
||||
- @gfm_form = true
|
||||
|
|
|
|||
|
|
@ -2,27 +2,4 @@
|
|||
- add_page_startup_graphql_call('container_registry/get_container_repositories', { fullPath: @project.full_path, first: 10, name: nil, isGroupPage: false, sort: nil})
|
||||
|
||||
%section
|
||||
#js-container-registry{ data: { endpoint: project_container_registry_index_path(@project),
|
||||
expiration_policy: @project.container_expiration_policy.to_json,
|
||||
"help_page_path" => help_page_path('user/packages/container_registry/index'),
|
||||
"two_factor_auth_help_link" => help_page_path('user/profile/account/two_factor_authentication'),
|
||||
"personal_access_tokens_help_link" => help_page_path('user/profile/personal_access_tokens'),
|
||||
"no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
|
||||
"containers_error_image" => image_path('illustrations/docker-error-state.svg'),
|
||||
"repository_url" => escape_once(@project.container_registry_url),
|
||||
"registry_host_url_with_port" => escape_once(registry_config.host_port),
|
||||
"expiration_policy_help_page_path" => help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy'),
|
||||
"garbage_collection_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'container-registry-garbage-collection'),
|
||||
"run_cleanup_policies_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'run-the-cleanup-policy-now'),
|
||||
"project_path": @project.full_path,
|
||||
"gid_prefix": container_repository_gid_prefix,
|
||||
"is_admin": current_user&.admin.to_s,
|
||||
"show_cleanup_policy_link": show_cleanup_policy_link(@project).to_s,
|
||||
"show_container_registry_settings": show_container_registry_settings(@project).to_s,
|
||||
"cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project),
|
||||
connection_error: (!!@connection_error).to_s,
|
||||
invalid_path_error: (!!@invalid_path_error).to_s,
|
||||
user_callouts_path: callouts_path,
|
||||
user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
|
||||
is_metadata_database_enabled: ContainerRegistry::GitlabApiClient.supports_gitlab_api?.to_s,
|
||||
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } }
|
||||
#js-container-registry{ data: project_container_registry_template_data(@project, @connection_error, @invalid_path_error) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
- return unless can?(current_user, :read_tracing, @project) || can?(current_user, :read_observability_metrics, @project) || can?(current_user, :read_observability_logs, @project)
|
||||
|
||||
.js-observability-settings
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
- breadcrumb_title _('Monitor Settings')
|
||||
- @force_desktop_expanded_sidebar = true
|
||||
|
||||
= render 'projects/settings/operations/observability'
|
||||
= render 'projects/settings/operations/error_tracking'
|
||||
= render 'projects/settings/operations/alert_management'
|
||||
= render 'projects/settings/operations/incidents'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
description: Tracks pageviews for the admin health check page
|
||||
internal_events: true
|
||||
action: view_admin_health_check_pageload
|
||||
identifiers:
|
||||
- user
|
||||
product_group: personal_productivity
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156829
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
description: Tracks pageviews for the admin system information page
|
||||
internal_events: true
|
||||
action: view_admin_system_info_pageload
|
||||
identifiers:
|
||||
- user
|
||||
product_group: personal_productivity
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156825
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: allow_push_repository_for_job_token
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154111
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/468320
|
||||
milestone: "17.2"
|
||||
type: development
|
||||
group: group::pipeline security
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_view_admin_health_check_pageload_monthly
|
||||
description: Monthly count of unique users who visited the admin health check page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156829
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_health_check_pageload
|
||||
unique: user.id
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_view_admin_system_info_pageload_monthly
|
||||
description: Monthly count of unique users who visited the admin system info page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156825
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_system_info_pageload
|
||||
unique: user.id
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.count_total_view_admin_health_check_pageload_monthly
|
||||
description: Monthly count of total users who visited the admin health check page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156829
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_health_check_pageload
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.count_total_view_admin_system_info_pageload_monthly
|
||||
description: Monthly count of total users who visited the admin system info page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156825
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_system_info_pageload
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_view_admin_health_check_pageload_weekly
|
||||
description: Weekly count of unique users who visited the admin health check page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156829
|
||||
time_frame: 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_health_check_pageload
|
||||
unique: user.id
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_view_admin_system_info_pageload_weekly
|
||||
description: Weekly count of unique users who visited the admin system info page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156825
|
||||
time_frame: 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_system_info_pageload
|
||||
unique: user.id
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.count_total_view_admin_health_check_pageload_weekly
|
||||
description: Weekly count of total users who visited the admin health check page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156829
|
||||
time_frame: 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_health_check_pageload
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.count_total_view_admin_system_info_pageload_weekly
|
||||
description: Weekly count of total users who visited the admin system info page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156825
|
||||
time_frame: 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_system_info_pageload
|
||||
|
|
@ -12,10 +12,6 @@ MARKDOWN
|
|||
|
||||
POST_TABLE_MESSAGE = <<MARKDOWN
|
||||
|
||||
**Please check reviewer's status!**
|
||||
- {width=12} Reviewer is available!
|
||||
- {width=12} Reviewer is unavailable!
|
||||
|
||||
Please refer to [documentation page](https://docs.gitlab.com/ee/development/code_review.html#reviewer-roulette)
|
||||
for guidance on how you can benefit from the Reviewer Roulette, or use the
|
||||
[GitLab Review Workload Dashboard](https://gitlab-org.gitlab.io/gitlab-roulette/)
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ We do not yet support multi-arch image, only `linux/amd64`. If you try to run th
|
|||
|
||||
```shell
|
||||
AI_GATEWAY_URL=https://<your_ai_gitlab_domain>
|
||||
CLOUD_CONNECTOR_SELF_SIGN_TOKENS=1
|
||||
```
|
||||
|
||||
1. After you've set up the environment variables, run the image. For example:
|
||||
|
|
|
|||
|
|
@ -7411,6 +7411,7 @@ Input type: `ProjectCiCdSettingsUpdateInput`
|
|||
| <a id="mutationprojectcicdsettingsupdatemergepipelinesenabled"></a>`mergePipelinesEnabled` | [`Boolean`](#boolean) | Indicates if merged results pipelines are enabled for the project. |
|
||||
| <a id="mutationprojectcicdsettingsupdatemergetrainsenabled"></a>`mergeTrainsEnabled` | [`Boolean`](#boolean) | Indicates if merge trains are enabled for the project. |
|
||||
| <a id="mutationprojectcicdsettingsupdatemergetrainsskiptrainallowed"></a>`mergeTrainsSkipTrainAllowed` | [`Boolean`](#boolean) | Indicates whether an option is allowed to merge without refreshing the merge train. Ignored unless the `merge_trains_skip_train` feature flag is also enabled. |
|
||||
| <a id="mutationprojectcicdsettingsupdatepushrepositoryforjobtokenallowed"></a>`pushRepositoryForJobTokenAllowed` | [`Boolean`](#boolean) | Indicates the ability to push to the original project repository using a job token. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
|
@ -29348,6 +29349,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="projectcicdsettingmergetrainsenabled"></a>`mergeTrainsEnabled` | [`Boolean`](#boolean) | Whether merge trains are enabled. |
|
||||
| <a id="projectcicdsettingmergetrainsskiptrainallowed"></a>`mergeTrainsSkipTrainAllowed` | [`Boolean!`](#boolean) | Whether merge immediately is allowed for merge trains. |
|
||||
| <a id="projectcicdsettingproject"></a>`project` | [`Project`](#project) | Project the CI/CD settings belong to. |
|
||||
| <a id="projectcicdsettingpushrepositoryforjobtokenallowed"></a>`pushRepositoryForJobTokenAllowed` | [`Boolean`](#boolean) | Indicates the ability to push to the original project repository using a job token. |
|
||||
|
||||
### `ProjectDataTransfer`
|
||||
|
||||
|
|
@ -34732,6 +34734,7 @@ Member role permission.
|
|||
| <a id="memberrolepermissionread_code"></a>`READ_CODE` | Allows read-only access to the source code in the user interface. Does not allow users to edit or download repository archives, clone or pull repositories, view source code in an IDE, or view merge requests for private projects. You can download individual files because read-only access inherently grants the ability to make a local copy of the file. |
|
||||
| <a id="memberrolepermissionread_crm_contact"></a>`READ_CRM_CONTACT` | Read CRM contact. |
|
||||
| <a id="memberrolepermissionread_dependency"></a>`READ_DEPENDENCY` | Allows read-only access to the dependencies and licenses. |
|
||||
| <a id="memberrolepermissionread_runners"></a>`READ_RUNNERS` | Allows read-only access to group or project runners, including the runner fleet dashboard. |
|
||||
| <a id="memberrolepermissionread_vulnerability"></a>`READ_VULNERABILITY` | Read vulnerability reports and security dashboards. |
|
||||
| <a id="memberrolepermissionremove_group"></a>`REMOVE_GROUP` | Ability to delete or restore a group. This ability does not allow deleting top level groups. Review the Retention period settings to prevent accidental deletion. |
|
||||
| <a id="memberrolepermissionremove_project"></a>`REMOVE_PROJECT` | Allows deletion of projects. |
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ Example response:
|
|||
"manage_project_access_tokens": false,
|
||||
"manage_security_policy_link": false,
|
||||
"read_code": true,
|
||||
"read_runners": false,
|
||||
"read_dependency": false,
|
||||
"read_vulnerability": false,
|
||||
"remove_group": false,
|
||||
|
|
@ -115,6 +116,7 @@ Supported attributes:
|
|||
| `manage_project_access_tokens` | boolean | no | Permission to manage project access tokens. |
|
||||
| `manage_security_policy_link` | boolean | no | Permission to link security policy projects. |
|
||||
| `read_code` | boolean | no | Permission to read project code. |
|
||||
| `read_runners` | boolean | no | Permission to view project runners. |
|
||||
| `read_dependency` | boolean | no | Permission to read project dependencies. |
|
||||
| `read_vulnerability` | boolean | no | Permission to read project vulnerabilities. |
|
||||
| `remove_group` | boolean | no | Permission to delete or restore a group. |
|
||||
|
|
@ -152,6 +154,7 @@ Example response:
|
|||
"manage_project_access_tokens": false,
|
||||
"manage_security_policy_link": false,
|
||||
"read_code": true,
|
||||
"read_runners": false,
|
||||
"read_dependency": false,
|
||||
"read_vulnerability": false,
|
||||
"remove_group": false,
|
||||
|
|
@ -236,6 +239,7 @@ Example response:
|
|||
"manage_project_access_tokens": false,
|
||||
"manage_security_policy_link": false,
|
||||
"read_code": true,
|
||||
"read_runners": false,
|
||||
"read_dependency": false,
|
||||
"read_vulnerability": false,
|
||||
"remove_group": false,
|
||||
|
|
@ -262,6 +266,7 @@ Example response:
|
|||
"manage_project_access_tokens": false,
|
||||
"manage_security_policy_link": false,
|
||||
"read_code": true,
|
||||
"read_runners": false,
|
||||
"read_dependency": true,
|
||||
"read_vulnerability": true,
|
||||
"remove_group": false,
|
||||
|
|
@ -300,6 +305,7 @@ Parameters:
|
|||
| `manage_project_access_tokens` | boolean | no | Permission to manage project access tokens. |
|
||||
| `manage_security_policy_link` | boolean | no | Permission to link security policy projects. |
|
||||
| `read_code` | boolean | no | Permission to read project code. |
|
||||
| `read_runners` | boolean | no | Permission to view project runners. |
|
||||
| `read_dependency` | boolean | no | Permission to read project dependencies. |
|
||||
| `read_vulnerability` | boolean | no | Permission to read project vulnerabilities. |
|
||||
| `remove_group` | boolean | no | Permission to delete or restore a group. |
|
||||
|
|
@ -335,6 +341,7 @@ Example response:
|
|||
"manage_project_access_tokens": false,
|
||||
"manage_security_policy_link": false,
|
||||
"read_code": true,
|
||||
"read_runners": false,
|
||||
"read_dependency": false,
|
||||
"read_vulnerability": false,
|
||||
"remove_group": false,
|
||||
|
|
|
|||
|
|
@ -249,6 +249,8 @@ When the user is authenticated and `simple` is not set this returns something li
|
|||
"ci_job_token_scope_enabled": false,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"ci_pipeline_variables_minimum_override_role": "maintainer",
|
||||
"ci_push_repository_for_job_token_allowed": false,
|
||||
"public_jobs": true,
|
||||
"build_timeout": 3600,
|
||||
"auto_cancel_pending_pipelines": "enabled",
|
||||
|
|
@ -425,6 +427,8 @@ GET /users/:user_id/projects
|
|||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"ci_pipeline_variables_minimum_override_role": "maintainer",
|
||||
"ci_push_repository_for_job_token_allowed": false,
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
|
|
@ -546,6 +550,8 @@ GET /users/:user_id/projects
|
|||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"ci_pipeline_variables_minimum_override_role": "maintainer",
|
||||
"ci_push_repository_for_job_token_allowed": false,
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
|
|
@ -1218,6 +1224,8 @@ GET /projects/:id
|
|||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"ci_pipeline_variables_minimum_override_role": "maintainer",
|
||||
"ci_push_repository_for_job_token_allowed": false,
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [
|
||||
{
|
||||
|
|
@ -1760,6 +1768,8 @@ General project attributes:
|
|||
| `ci_allow_fork_pipelines_to_run_in_parent_project` | boolean | No | Enable or disable [running pipelines in the parent project for merge requests from forks](../ci/pipelines/merge_request_pipelines.md#run-pipelines-in-the-parent-project). _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/325189) in GitLab 15.3.)_ |
|
||||
| `ci_separated_caches` | boolean | No | Set whether or not caches should be [separated](../ci/caching/index.md#cache-key-names) by branch protection status. |
|
||||
| `ci_restrict_pipeline_cancellation_role` | string | No | Set the [role required to cancel a pipeline or job](../ci/pipelines/settings.md#restrict-roles-that-can-cancel-pipelines-or-jobs). One of `developer`, `maintainer`, or `no_one`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429921) in GitLab 16.8. Premium and Ultimate only. |
|
||||
| `ci_pipeline_variables_minimum_override_role` | string | No | When `restrict_user_defined_variables` is enabled, you can specify which role can override variables. One of `owner`, `maintainer`, `developer` or `no_one_allowed`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/440338) in GitLab 17.1. |
|
||||
| `ci_push_repository_for_job_token_allowed` | boolean | No | Enable or disable the ability to push to the project repository using job token. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/389060) in GitLab 17.2. |
|
||||
| `container_expiration_policy_attributes` | hash | No | Update the image cleanup policy for this project. Accepts: `cadence` (string), `keep_n` (integer), `older_than` (string), `name_regex` (string), `name_regex_delete` (string), `name_regex_keep` (string), `enabled` (boolean). |
|
||||
| `container_registry_enabled` | boolean | No | _(Deprecated)_ Enable container registry for this project. Use `container_registry_access_level` instead. |
|
||||
| `default_branch` | string | No | The [default branch](../user/project/repository/branches/default.md) name. |
|
||||
|
|
@ -2379,6 +2389,8 @@ Example response:
|
|||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"ci_pipeline_variables_minimum_override_role": "maintainer",
|
||||
"ci_push_repository_for_job_token_allowed": false,
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
|
|
@ -2511,6 +2523,8 @@ Example response:
|
|||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"ci_pipeline_variables_minimum_override_role": "maintainer",
|
||||
"ci_push_repository_for_job_token_allowed": false,
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
|
|
|
|||
|
|
@ -276,3 +276,21 @@ While troubleshooting CI/CD job token authentication issues, be aware that:
|
|||
- To remove project access.
|
||||
- The CI job token becomes invalid if the job is no longer running, has been erased,
|
||||
or if the project is in the process of being deleted.
|
||||
|
||||
### Push to a project repository using a job token
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/389060) in GitLab 17.2. [with a flag](../../administration/feature_flags.md) named `allow_push_repository_for_job_token`. Disabled by default.
|
||||
|
||||
WARNING:
|
||||
Pushing via job token is still in development and is not yet optimized for performance.
|
||||
If you enable this feature for testing, you must thoroughly test and implement validation measures
|
||||
to prevent infinite loops of "push" pipelines triggering more pipelines.
|
||||
|
||||
By default, pushing to a project repository by authenticating with a job token is disabled.
|
||||
To enable this ability, you can:
|
||||
|
||||
- Feature flag named `allow_push_repository_for_job_token` should be enabled.
|
||||
- Enable the [`pushRepositoryForJobTokenAllowed`](../../api/graphql/reference/index.md#mutationprojectcicdsettingsupdate) GraphQL endpoint.
|
||||
- Enable the [`ci_push_repository_for_job_token_allowed`](../../api/projects.md#edit-project) REST API endpoint.
|
||||
|
||||
You are only permitted to push to the repository of the project where the job is running.
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ If you are currently tracking a metric in `RedisHLL` like this:
|
|||
|
||||
To start using Internal Events Tracking, follow these steps:
|
||||
|
||||
1. If event is not being sent to Snowplow, consider renaming if to meet [our naming convention](quick_start.md#defining-event-and-metrics).
|
||||
1. Create an event definition that describes `git_write_action` ([guide](event_definition_guide.md)).
|
||||
1. Find metric definitions that list `git_write_action` in the events section (`20210216182041_action_monthly_active_users_git_write.yml` and `20210216184045_git_write_action_weekly.yml`).
|
||||
1. Change the `data_source` from `redis_hll` to `internal_events` in the metric definition files.
|
||||
|
|
|
|||
|
|
@ -452,6 +452,55 @@ PersonalAccessToken.project_access_token.where(expires_at: Date.today .. Date.to
|
|||
end
|
||||
```
|
||||
|
||||
#### tokens_with_no_expiry.rb
|
||||
|
||||
This script finds tokens that do not have an expiry date, that is, `expires_at` is set to `NULL`. For users who have not yet upgraded to GitLab version 16.0 or later, the token `expires_at` value will be `NULL` and can be used to identify tokens that will be set with an expiration date.
|
||||
|
||||
```ruby
|
||||
# This script finds tokens which do not have an expires_at value set.
|
||||
|
||||
# Check for expiring personal access tokens
|
||||
PersonalAccessToken.owner_is_human.where(expires_at: nil).find_each do |token|
|
||||
puts "Expires_at is nil for Personal Access Token ID: #{token.id}, User Email: #{token.user.email}, Name: #{token.name}, Scopes: #{token.scopes}, Last used: #{token.last_used_at}"
|
||||
end
|
||||
|
||||
# Check for expiring project and group access tokens
|
||||
PersonalAccessToken.project_access_token.where(expires_at: nil).find_each do |token|
|
||||
token.user.members.each do |member|
|
||||
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
|
||||
|
||||
puts "Expires_at is nil for #{type} access token in #{type} ID #{member.source_id}, Token ID: #{token.id}, Name: #{token.name}, Scopes: #{token.scopes}, Last used: #{token.last_used_at}"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
You can use this script in either the [Rails console](../administration/operations/rails_console.md) or the [Rails Runner](../administration/operations/rails_console.md#using-the-rails-runner):
|
||||
|
||||
::Tabs
|
||||
|
||||
:::TabTitle Rails console session
|
||||
|
||||
1. In your terminal window, connect to your instance.
|
||||
1. Start a Rails console session with `sudo gitlab-rails console`.
|
||||
1. Paste in the entire script.
|
||||
1. Press <kbd>Enter</kbd>.
|
||||
|
||||
:::TabTitle Rails Runner
|
||||
|
||||
1. In your terminal window, connect to your instance.
|
||||
1. Copy this entire script, and save it as a file on your instance:
|
||||
- Name it `tokens_with_no_expiry.rb`.
|
||||
- The file must be accessible to `git:git`.
|
||||
1. Run this command, changing the path to the _full_ path to your `tokens_with_no_expiry.rb` file:
|
||||
|
||||
```shell
|
||||
sudo gitlab-rails runner /path/to/tokens_with_no_expiry.rb
|
||||
```
|
||||
|
||||
For more information, see the [Rails Runner troubleshooting section](../administration/operations/rails_console.md#troubleshooting).
|
||||
|
||||
::EndTabs
|
||||
|
||||
### Extend token lifetime
|
||||
|
||||
Delay the expiration of certain tokens with this script.
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ These requirements are documented in the `Required permission` column in the fol
|
|||
| Name | Required permission | Description | Introduced in | Feature flag | Enabled in |
|
||||
|:-----|:------------|:------------------|:---------|:--------------|:---------|
|
||||
| [`admin_runners`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151825) | | Create, view, edit, and delete group or project Runners. Includes configuring Runner settings. | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/442851) | `custom_ability_admin_runners` | |
|
||||
| [`read_runners`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156798) | | Allows read-only access to group or project runners, including the runner fleet dashboard. | GitLab [17.2](https://gitlab.com/gitlab-org/gitlab/-/issues/468202) | | |
|
||||
|
||||
## Secrets management
|
||||
|
||||
|
|
|
|||
|
|
@ -320,10 +320,11 @@ module API
|
|||
runner = get_runner(params[:runner_id])
|
||||
authenticate_enable_runner!(runner)
|
||||
|
||||
if ::Ci::Runners::AssignRunnerService.new(runner, user_project, current_user).execute.success?
|
||||
result = ::Ci::Runners::AssignRunnerService.new(runner, user_project, current_user).execute
|
||||
if result.success?
|
||||
present runner, with: Entities::Ci::Runner
|
||||
else
|
||||
render_validation_error!(runner)
|
||||
render_api_error_with_reason!(:bad_request, result.message, result.reason)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ module API
|
|||
expose :auto_devops_deploy_strategy, documentation: { type: 'string', example: 'continuous' } do |project, options|
|
||||
project.auto_devops.nil? ? 'continuous' : project.auto_devops.deploy_strategy
|
||||
end
|
||||
expose :ci_push_repository_for_job_token_allowed, documentation: { type: 'boolean' }
|
||||
end
|
||||
|
||||
expose :ci_config_path, documentation: { type: 'string', example: '' }, if: ->(project, options) { Ability.allowed?(options[:current_user], :read_code, project) }
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ module API
|
|||
optional :ci_separated_caches, type: Boolean, desc: 'Enable or disable separated caches based on branch protection.'
|
||||
optional :restrict_user_defined_variables, type: Boolean, desc: 'Restrict use of user-defined variables when triggering a pipeline'
|
||||
optional :ci_pipeline_variables_minimum_override_role, values: %w[no_one_allowed developer maintainer owner], type: String, desc: 'Limit ability to override CI/CD variables when triggering a pipeline to only users with at least the set minimum role'
|
||||
optional :ci_push_repository_for_job_token_allowed, type: Boolean, desc: "Allow pushing to this project's repository by authenticating with a CI/CD job token generated in this project."
|
||||
end
|
||||
|
||||
params :optional_update_params_ee do
|
||||
|
|
@ -210,6 +211,7 @@ module API
|
|||
:model_registry_access_level,
|
||||
:warn_about_potentially_unwanted_characters,
|
||||
:ci_pipeline_variables_minimum_override_role,
|
||||
:ci_push_repository_for_job_token_allowed,
|
||||
|
||||
# TODO: remove in API v5, replaced by *_access_level
|
||||
:issues_enabled,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# A (partial) implementation of the functional Result type, with naming conventions based on the
|
||||
# Rust implementation (https://doc.rust-lang.org/std/result/index.html)
|
||||
#
|
||||
# Modern Ruby 3+ destructuring and pattern matching are supported.
|
||||
#
|
||||
# - See "Railway Oriented Programming and the Result Class" in `ee/lib/remote_development/README.md` for details
|
||||
# and example usage.
|
||||
# - See `spec/lib/gitlab/fp/result_spec.rb` for detailed executable example usage.
|
||||
# - See https://en.wikipedia.org/wiki/Result_type for a general description of the Result pattern.
|
||||
# - See https://fsharpforfunandprofit.com/rop/ for how this can be used with Railway Oriented Programming (ROP)
|
||||
# to improve design and architecture
|
||||
# - See https://doc.rust-lang.org/std/result/ for the Rust implementation.
|
||||
module Gitlab
|
||||
# noinspection RubyClassModuleNamingConvention -- JetBrains is changing this to allow shorter names
|
||||
module Fp
|
||||
class Result
|
||||
# The .ok and .err factory class methods are the only way to create a Result
|
||||
#
|
||||
# "self.ok" corresponds to Ok(T) in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Ok
|
||||
#
|
||||
# @param [Object, #new] ok_value
|
||||
# @return [Result]
|
||||
# noinspection MissingYardParamTag -- RubyMine does not recognize "duck type" Types
|
||||
# (https://rubydoc.info/gems/yard/file/docs/Tags.md#duck-types). This has been
|
||||
# reported to JetBrains - issue link pending
|
||||
def self.ok(ok_value)
|
||||
new(ok_value: ok_value)
|
||||
end
|
||||
|
||||
# "self.err" corresponds to Err(E) in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err
|
||||
#
|
||||
# @param [Object, #new] err_value
|
||||
# @return [Result]
|
||||
# noinspection MissingYardParamTag -- RubyMine does not recognize "duck type" Types
|
||||
# (https://rubydoc.info/gems/yard/file/docs/Tags.md#duck-types). This has been
|
||||
# reported to JetBrains - issue link pending
|
||||
def self.err(err_value)
|
||||
new(err_value: err_value)
|
||||
end
|
||||
|
||||
# @param [Object, nil] ok_value
|
||||
# @param [Object, nil] err_value
|
||||
# @return [Object]
|
||||
def initialize(ok_value: nil, err_value: nil)
|
||||
if (!ok_value.nil? && !err_value.nil?) || (ok_value.nil? && err_value.nil?)
|
||||
raise(ArgumentError, 'Do not directly use private constructor, use Result.ok or Result.err')
|
||||
end
|
||||
|
||||
@ok = err_value.nil?
|
||||
@value = ok? ? ok_value : err_value
|
||||
end
|
||||
private :initialize
|
||||
|
||||
# "#unwrap" corresponds to "unwrap" in Rust.
|
||||
#
|
||||
# @return [Object]
|
||||
# @raise [RuntimeError] if called on an "err" Result
|
||||
def unwrap
|
||||
ok? ? value : raise("Called Result#unwrap on an 'err' Result")
|
||||
end
|
||||
|
||||
# "#unwrap" corresponds to "unwrap" in Rust.
|
||||
#
|
||||
# @return [Object]
|
||||
# @raise [RuntimeError] if called on an "ok" Result
|
||||
def unwrap_err
|
||||
err? ? value : raise("Called Result#unwrap_err on an 'ok' Result")
|
||||
end
|
||||
|
||||
# The `ok?` attribute is true if the Result was constructed with .ok, and false if it was constructed with .err
|
||||
#
|
||||
# "#ok?" corresponds to "is_ok" in Rust.
|
||||
# @return [Boolean]
|
||||
def ok?
|
||||
# We don't make `@ok` an attr_reader, because we don't want to confusingly shadow the class method `.ok`
|
||||
@ok
|
||||
end
|
||||
|
||||
# The `err?` attribute is false if the Result was constructed with .ok, and true if it was constructed with .err
|
||||
# "#err?" corresponds to "is_err" in Rust.
|
||||
#
|
||||
# @return [Boolean]
|
||||
def err?
|
||||
!ok?
|
||||
end
|
||||
|
||||
# `and_then` is a functional way to chain together operations which may succeed or have errors. It is passed
|
||||
# a lambda or class (singleton) method object, and must return a Result object representing "ok"
|
||||
# or "err".
|
||||
#
|
||||
# If the Result object it is called on is "ok", then the passed lambda or singleton method
|
||||
# is called with the value contained in the Result.
|
||||
#
|
||||
# If the Result object it is called on is "err", then it is returned without calling the passed
|
||||
# lambda or method.
|
||||
#
|
||||
# It only supports being passed a lambda, or a class (singleton) method object
|
||||
# which responds to `call` with a single argument (arity of 1). If multiple values are needed,
|
||||
# pass a hash or array. Note that passing `Proc` objects is NOT supported, even though the YARD
|
||||
# annotation contains `Proc` (because the type of a lambda is also `Proc`).
|
||||
#
|
||||
# Passing instance methods to `and_then` is not supported, because the methods in the chain should be
|
||||
# stateless "pure functions", and should not be persisting or referencing any instance state anyway.
|
||||
#
|
||||
# "#and_then" corresponds to "and_then" in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#method.and_then
|
||||
#
|
||||
# @param [Proc, Method] lambda_or_singleton_method
|
||||
# @return [Result]
|
||||
# @raise [TypeError]
|
||||
def and_then(lambda_or_singleton_method)
|
||||
validate_lambda_or_singleton_method(lambda_or_singleton_method)
|
||||
|
||||
# Return/passthough the Result itself if it is an err
|
||||
return self if err?
|
||||
|
||||
# If the Result is ok, call the lambda or singleton method with the contained value
|
||||
result = lambda_or_singleton_method.call(value)
|
||||
|
||||
unless result.is_a?(Result)
|
||||
err_msg = "'Result##{__method__}' expects a lambda or singleton method object which returns a 'Result' " \
|
||||
"type, but instead received '#{lambda_or_singleton_method.inspect}' which returned '#{result.class}'. " \
|
||||
"Check that the previous method calls in the '#and_then' chain are correct."
|
||||
raise(TypeError, err_msg)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# `map` is similar to `and_then`, but it is used for "single track" methods which always succeed,
|
||||
# and have no possibility of returning an error (but they may still raise exceptions,
|
||||
# which is unrelated to the Result handling). The passed lambda or singleton method must return
|
||||
# a value, not a Result.
|
||||
#
|
||||
# If the Result object it is called on is "ok", then the passed lambda or singleton method
|
||||
# is called with the value contained in the Result.
|
||||
#
|
||||
# If the Result object it is called on is "err", then it is returned without calling the passed
|
||||
# lambda or method.
|
||||
#
|
||||
# "#map" corresponds to "map" in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#method.map
|
||||
#
|
||||
# @param [Proc, Method] lambda_or_singleton_method
|
||||
# @return [Result]
|
||||
# @raise [TypeError]
|
||||
def map(lambda_or_singleton_method)
|
||||
validate_lambda_or_singleton_method(lambda_or_singleton_method)
|
||||
|
||||
# Return/passthrough the Result itself if it is an err
|
||||
return self if err?
|
||||
|
||||
# If the Result is ok, call the lambda or singleton method with the contained value
|
||||
mapped_value = lambda_or_singleton_method.call(value)
|
||||
|
||||
if mapped_value.is_a?(Result)
|
||||
err_msg = "'Result##{__method__}' expects a lambda or singleton method object which returns an unwrapped " \
|
||||
"value, not a 'Result', but instead received '#{lambda_or_singleton_method.inspect}' which returned " \
|
||||
"a 'Result'."
|
||||
raise(TypeError, err_msg)
|
||||
end
|
||||
|
||||
# wrap the returned mapped_value in an "ok" Result.
|
||||
Result.ok(mapped_value)
|
||||
end
|
||||
|
||||
# `to_h` supports destructuring of a result object, for example: `result => { ok: }; puts ok`
|
||||
#
|
||||
# @return [Hash]
|
||||
def to_h
|
||||
ok? ? { ok: value } : { err: value }
|
||||
end
|
||||
|
||||
# `deconstruct_keys` supports pattern matching on a Result object with a `case` statement. See specs for examples.
|
||||
#
|
||||
# @param [Array] keys
|
||||
# @return [Hash]
|
||||
# @raise [ArgumentError]
|
||||
def deconstruct_keys(keys)
|
||||
raise(ArgumentError, 'Use either :ok or :err for pattern matching') unless [[:ok], [:err]].include?(keys)
|
||||
|
||||
to_h
|
||||
end
|
||||
|
||||
# @param [Result] other
|
||||
# @return [Boolean]
|
||||
def ==(other)
|
||||
# NOTE: The underlying `@ok` instance variable is a boolean, so we only need to check `ok?`, not `err?` too
|
||||
self.class == other.class && other.ok? == ok? && other.instance_variable_get(:@value) == value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# The `value` attribute will contain either the ok_value or the err_value
|
||||
#
|
||||
# @return [Object]
|
||||
# noinspection RubyMismatchedReturnType
|
||||
def value # rubocop:disable Style/TrivialAccessors -- We are not using attr_reader here, so we can avoid nilability type errors in RubyMine
|
||||
# TODO: We are not using attr_reader here, so we can avoid nilability type errors in RubyMine.
|
||||
# This will be reported to JetBrains and tracked on
|
||||
# https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/,
|
||||
# this comment should then be updated with the issue link on that page.
|
||||
# Note that we don't use `noinspection` to suppress this error because there's several instances where
|
||||
# this error leaks to other classes through the call stack.
|
||||
@value
|
||||
end
|
||||
|
||||
# @param [Proc, Method] lambda_or_singleton_method
|
||||
# @return [void]
|
||||
# @raise [TypeError]
|
||||
def validate_lambda_or_singleton_method(lambda_or_singleton_method)
|
||||
is_lambda = lambda_or_singleton_method.is_a?(Proc) && lambda_or_singleton_method.lambda?
|
||||
is_singleton_method =
|
||||
lambda_or_singleton_method.is_a?(Method) && lambda_or_singleton_method.owner.singleton_class?
|
||||
|
||||
unless is_lambda || is_singleton_method
|
||||
err_msg = "'Result##{__method__}' expects a lambda or singleton method object, " \
|
||||
"but instead received '#{lambda_or_singleton_method.inspect}'."
|
||||
raise(TypeError, err_msg)
|
||||
end
|
||||
|
||||
arity = lambda_or_singleton_method.arity
|
||||
|
||||
return if arity == 1
|
||||
return if arity == -1 && lambda_or_singleton_method.source_location[0].include?('rspec')
|
||||
|
||||
err_msg = "'Result##{__method__}' expects a lambda or singleton method object with a single argument " \
|
||||
"(arity of 1), but instead received '#{lambda_or_singleton_method.inspect}' with an arity of #{arity}."
|
||||
raise(ArgumentError, err_msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -19,7 +19,11 @@ module Gitlab
|
|||
fp_class_singleton_methods = fp_module_or_class.singleton_methods(false)
|
||||
public_singleton_methods = fp_class_singleton_methods - public_singleton_methods_to_ignore
|
||||
|
||||
return public_singleton_methods.first if public_singleton_methods.size == 1
|
||||
# Note: Intentionally using Array#[] instead of Array#first here, because there appears to be a bug
|
||||
# in the type declaration, that doesn't indicate that #first should have `implicitly-returns-nil`
|
||||
# behavior. See https://github.com/ruby/rbs/pull/1226, this probably needs to be fixed for #first too.
|
||||
# Until then, we use #[] to avoid type inspection warnings in RubyMine.
|
||||
return public_singleton_methods[0] if public_singleton_methods.size == 1
|
||||
|
||||
fp_doc_link =
|
||||
"https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/remote_development/README.md#functional-patterns"
|
||||
|
|
|
|||
|
|
@ -304,7 +304,8 @@ module Gitlab
|
|||
_('One or more contacts were successfully added.')
|
||||
end
|
||||
command :add_contacts do |contact_emails|
|
||||
@updates[:add_contacts] = contact_emails.split(' ')
|
||||
@updates[:add_contacts] ||= []
|
||||
@updates[:add_contacts] += contact_emails.split(' ')
|
||||
end
|
||||
|
||||
desc { _('Remove customer relation contacts') }
|
||||
|
|
@ -319,7 +320,8 @@ module Gitlab
|
|||
_('One or more contacts were successfully removed.')
|
||||
end
|
||||
command :remove_contacts do |contact_emails|
|
||||
@updates[:remove_contacts] = contact_emails.split(' ')
|
||||
@updates[:remove_contacts] ||= []
|
||||
@updates[:remove_contacts] += contact_emails.split(' ')
|
||||
end
|
||||
|
||||
desc { _('Add a timeline event to incident') }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module RemoteDevelopment
|
|||
include Messages
|
||||
|
||||
# @param [Hash] context
|
||||
# @return [Result]
|
||||
# @return [Gitlab::Fp::Result]
|
||||
def self.read(context)
|
||||
err_result = nil
|
||||
context[:settings].each_key do |setting_name|
|
||||
|
|
@ -19,8 +19,8 @@ module RemoteDevelopment
|
|||
setting_type = context[:setting_types][setting_name]
|
||||
|
||||
unless current_setting_value.is_a?(setting_type)
|
||||
# err_result will be set to a non-nil Result.err if type check fails
|
||||
err_result = Result.err(SettingsCurrentSettingsReadFailed.new(
|
||||
# err_result will be set to a non-nil Gitlab::Fp::Result.err if type check fails
|
||||
err_result = Gitlab::Fp::Result.err(SettingsCurrentSettingsReadFailed.new(
|
||||
details: "Gitlab::CurrentSettings.#{setting_name} type of '#{current_setting_value.class}' " \
|
||||
"did not match initialized Remote Development Settings type of '#{setting_type}'."
|
||||
))
|
||||
|
|
@ -32,7 +32,7 @@ module RemoteDevelopment
|
|||
|
||||
return err_result if err_result
|
||||
|
||||
Result.ok(context)
|
||||
Gitlab::Fp::Result.ok(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module RemoteDevelopment
|
|||
REQUIRED_ENV_VAR_PREFIX = "GITLAB_REMOTE_DEVELOPMENT"
|
||||
|
||||
# @param [Hash] context
|
||||
# @return [Result]
|
||||
# @return [Gitlab::Fp::Result]
|
||||
def self.read(context)
|
||||
err_result = nil
|
||||
context[:settings].each_key do |setting_name|
|
||||
|
|
@ -27,8 +27,8 @@ module RemoteDevelopment
|
|||
setting_type: context[:setting_types][setting_name]
|
||||
)
|
||||
rescue RuntimeError => e
|
||||
# err_result will be set to a non-nil Result.err if casting fails
|
||||
err_result = Result.err(SettingsEnvironmentVariableReadFailed.new(details: e.message))
|
||||
# err_result will be set to a non-nil Gitlab::Fp::Result.err if casting fails
|
||||
err_result = Gitlab::Fp::Result.err(SettingsEnvironmentVariableReadFailed.new(details: e.message))
|
||||
end
|
||||
|
||||
# ENV var matches an existing setting and is of the correct type, use its value to override the default value
|
||||
|
|
@ -37,7 +37,7 @@ module RemoteDevelopment
|
|||
|
||||
return err_result if err_result
|
||||
|
||||
Result.ok(context)
|
||||
Gitlab::Fp::Result.ok(context)
|
||||
end
|
||||
|
||||
# @param [String] env_var_name
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module RemoteDevelopment
|
|||
include Messages
|
||||
|
||||
# @param [Hash] context
|
||||
# @return [Result]
|
||||
# @return [Gitlab::Fp::Result]
|
||||
def self.validate(context)
|
||||
context => { settings: Hash => settings }
|
||||
settings => { vscode_extensions_gallery_metadata: Hash => extensions_gallery_metadata }
|
||||
|
|
@ -15,9 +15,11 @@ module RemoteDevelopment
|
|||
errors = validate_against_schema(validatable_hash)
|
||||
|
||||
if errors.none?
|
||||
Result.ok(context)
|
||||
Gitlab::Fp::Result.ok(context)
|
||||
else
|
||||
Result.err(SettingsVscodeExtensionsGalleryMetadataValidationFailed.new(details: errors.join(". ")))
|
||||
Gitlab::Fp::Result.err(
|
||||
SettingsVscodeExtensionsGalleryMetadataValidationFailed.new(details: errors.join(". "))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module RemoteDevelopment
|
|||
include Messages
|
||||
|
||||
# @param [Hash] context
|
||||
# @return [Result]
|
||||
# @return [Gitlab::Fp::Result]
|
||||
def self.validate(context)
|
||||
context => { settings: Hash => settings }
|
||||
settings => { vscode_extensions_gallery: Hash => vscode_extensions_gallery }
|
||||
|
|
@ -17,9 +17,9 @@ module RemoteDevelopment
|
|||
errors = validate_against_schema(vscode_extensions_gallery.deep_stringify_keys)
|
||||
|
||||
if errors.none?
|
||||
Result.ok(context)
|
||||
Gitlab::Fp::Result.ok(context)
|
||||
else
|
||||
Result.err(SettingsVscodeExtensionsGalleryValidationFailed.new(details: errors.join(". ")))
|
||||
Gitlab::Fp::Result.err(SettingsVscodeExtensionsGalleryValidationFailed.new(details: errors.join(". ")))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module RemoteDevelopment
|
|||
# @return [Hash]
|
||||
# @raise [UnmatchedResultError]
|
||||
def self.get_settings(context)
|
||||
initial_result = Result.ok(context)
|
||||
initial_result = Gitlab::Fp::Result.ok(context)
|
||||
|
||||
# The order of the chain determines the precedence of settings. I.e., defaults are
|
||||
# overridden by env vars, and any subsequent steps override env vars.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module RemoteDevelopment
|
|||
include Messages
|
||||
|
||||
# @param [Hash] context
|
||||
# @return [Result]
|
||||
# @return [Gitlab::Fp::Result]
|
||||
def self.validate(context)
|
||||
context => {
|
||||
settings: {
|
||||
|
|
@ -16,24 +16,24 @@ module RemoteDevelopment
|
|||
}
|
||||
|
||||
unless partial_reconciliation_interval_seconds > 0
|
||||
return Result.err(SettingsPartialReconciliationIntervalSecondsValidationFailed.new(
|
||||
details: "Partial reconciliation interval must be greater than zero")
|
||||
)
|
||||
return Gitlab::Fp::Result.err(SettingsPartialReconciliationIntervalSecondsValidationFailed.new(
|
||||
details: "Partial reconciliation interval must be greater than zero"
|
||||
))
|
||||
end
|
||||
|
||||
unless full_reconciliation_interval_seconds > 0
|
||||
return Result.err(SettingsFullReconciliationIntervalSecondsValidationFailed.new(
|
||||
details: "Full reconciliation interval must be greater than zero")
|
||||
)
|
||||
details = "Full reconciliation interval must be greater than zero"
|
||||
return Gitlab::Fp::Result.err(SettingsFullReconciliationIntervalSecondsValidationFailed.new(details: details))
|
||||
end
|
||||
|
||||
if full_reconciliation_interval_seconds <= partial_reconciliation_interval_seconds
|
||||
return Result.err(SettingsPartialReconciliationIntervalSecondsValidationFailed.new(
|
||||
details: "Partial reconciliation interval must be less than full reconciliation interval")
|
||||
)
|
||||
details = "Partial reconciliation interval must be less than full reconciliation interval"
|
||||
return Gitlab::Fp::Result.err(
|
||||
SettingsPartialReconciliationIntervalSecondsValidationFailed.new(details: details)
|
||||
)
|
||||
end
|
||||
|
||||
Result.ok(context)
|
||||
Gitlab::Fp::Result.ok(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module RemoteDevelopment
|
||||
class UnmatchedResultError < RuntimeError
|
||||
# @param [Result] result
|
||||
# @param [Gitlab::Fp::Result] result
|
||||
# @return [void]
|
||||
def initialize(result:)
|
||||
msg = "Failed to pattern match #{result.ok? ? "'ok'" : "'err'"} Result " \
|
||||
|
|
|
|||
219
lib/result.rb
219
lib/result.rb
|
|
@ -1,219 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# A (partial) implementation of the functional Result type, with naming conventions based on the
|
||||
# Rust implementation (https://doc.rust-lang.org/std/result/index.html)
|
||||
#
|
||||
# Modern Ruby 3+ destructuring and pattern matching are supported.
|
||||
#
|
||||
# - See "Railway Oriented Programming and the Result Class" in `ee/lib/remote_development/README.md` for details
|
||||
# and example usage.
|
||||
# - See `spec/lib/result_spec.rb` for detailed executable example usage.
|
||||
# - See https://en.wikipedia.org/wiki/Result_type for a general description of the Result pattern.
|
||||
# - See https://fsharpforfunandprofit.com/rop/ for how this can be used with Railway Oriented Programming (ROP)
|
||||
# to improve design and architecture
|
||||
# - See https://doc.rust-lang.org/std/result/ for the Rust implementation.
|
||||
|
||||
# NOTE: This class is intentionally not namespaced to allow for more concise, readable, and explicit usage.
|
||||
# It it a generic reusable implementation of the Result type, and is not specific to any domain
|
||||
# rubocop:disable Gitlab/NamespacedClass -- TODO: We should move this to the GitLab namespace, as it is part of the platform layer for bounded contexts - https://gitlab.com/gitlab-org/gitlab/-/issues/467293
|
||||
# rubocop:disable Gitlab/BoundedContexts -- TODO: We should move this to the GitLab namespace, as it is part of the platform layer for bounded contexts - https://gitlab.com/gitlab-org/gitlab/-/issues/467293
|
||||
class Result
|
||||
# The .ok and .err factory class methods are the only way to create a Result
|
||||
#
|
||||
# "self.ok" corresponds to Ok(T) in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Ok
|
||||
#
|
||||
# @param [Object, #new] ok_value
|
||||
# @return [Result]
|
||||
def self.ok(ok_value)
|
||||
new(ok_value: ok_value)
|
||||
end
|
||||
|
||||
# "self.err" corresponds to Err(E) in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err
|
||||
#
|
||||
# @param [Object, #new] ok_value
|
||||
# @return [Result]
|
||||
def self.err(err_value)
|
||||
new(err_value: err_value)
|
||||
end
|
||||
|
||||
# "#unwrap" corresponds to "unwrap" in Rust.
|
||||
#
|
||||
# @return [Object]
|
||||
# @raise [RuntimeError] if called on an "err" Result
|
||||
def unwrap
|
||||
ok? ? value : raise("Called Result#unwrap on an 'err' Result")
|
||||
end
|
||||
|
||||
# "#unwrap" corresponds to "unwrap" in Rust.
|
||||
#
|
||||
# @return [Object]
|
||||
# @raise [RuntimeError] if called on an "ok" Result
|
||||
def unwrap_err
|
||||
err? ? value : raise("Called Result#unwrap_err on an 'ok' Result")
|
||||
end
|
||||
|
||||
# The `ok?` attribute will be true if the Result was constructed with .ok, and false if it was constructed with .err
|
||||
#
|
||||
# "#ok?" corresponds to "is_ok" in Rust.
|
||||
# @return [Boolean]
|
||||
def ok?
|
||||
# We don't make `@ok` an attr_reader, because we don't want to confusingly shadow the class method `.ok`
|
||||
@ok
|
||||
end
|
||||
|
||||
# The `err?` attribute will be false if the Result was constructed with .ok, and true if it was constructed with .err
|
||||
# "#err?" corresponds to "is_err" in Rust.
|
||||
#
|
||||
# @return [Boolean]
|
||||
def err?
|
||||
!ok?
|
||||
end
|
||||
|
||||
# `and_then` is a functional way to chain together operations which may succeed or have errors. It is passed
|
||||
# a lambda or class (singleton) method object, and must return a Result object representing "ok"
|
||||
# or "err".
|
||||
#
|
||||
# If the Result object it is called on is "ok", then the passed lambda or singleton method
|
||||
# is called with the value contained in the Result.
|
||||
#
|
||||
# If the Result object it is called on is "err", then it is returned without calling the passed
|
||||
# lambda or method.
|
||||
#
|
||||
# It only supports being passed a lambda, or a class (singleton) method object
|
||||
# which responds to `call` with a single argument (arity of 1). If multiple values are needed,
|
||||
# pass a hash or array. Note that passing `Proc` objects is NOT supported, even though the YARD
|
||||
# annotation contains `Proc` (because the type of a lambda is also `Proc`).
|
||||
#
|
||||
# Passing instance methods to `and_then` is not supported, because the methods in the chain should be
|
||||
# stateless "pure functions", and should not be persisting or referencing any instance state anyway.
|
||||
#
|
||||
# "#and_then" corresponds to "and_then" in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#method.and_then
|
||||
#
|
||||
# @param [Proc, Method] lambda_or_singleton_method
|
||||
# @return [Result]
|
||||
# @raise [TypeError]
|
||||
def and_then(lambda_or_singleton_method)
|
||||
validate_lambda_or_singleton_method(lambda_or_singleton_method)
|
||||
|
||||
# Return/passthough the Result itself if it is an err
|
||||
return self if err?
|
||||
|
||||
# If the Result is ok, call the lambda or singleton method with the contained value
|
||||
result = lambda_or_singleton_method.call(value)
|
||||
|
||||
unless result.is_a?(Result)
|
||||
err_msg = "'Result##{__method__}' expects a lambda or singleton method object which returns a 'Result' type " \
|
||||
", but instead received '#{lambda_or_singleton_method.inspect}' which returned '#{result.class}'. " \
|
||||
"Check that the previous method calls in the '#and_then' chain are correct."
|
||||
raise(TypeError, err_msg)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# `map` is similar to `and_then`, but it is used for "single track" methods which always succeed,
|
||||
# and have no possibility of returning an error (but they may still raise exceptions,
|
||||
# which is unrelated to the Result handling). The passed lambda or singleton method must return
|
||||
# a value, not a Result.
|
||||
#
|
||||
# If the Result object it is called on is "ok", then the passed lambda or singleton method
|
||||
# is called with the value contained in the Result.
|
||||
#
|
||||
# If the Result object it is called on is "err", then it is returned without calling the passed
|
||||
# lambda or method.
|
||||
#
|
||||
# "#map" corresponds to "map" in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#method.map
|
||||
#
|
||||
# @param [Proc, Method] lambda_or_singleton_method
|
||||
# @return [Result]
|
||||
# @raise [TypeError]
|
||||
def map(lambda_or_singleton_method)
|
||||
validate_lambda_or_singleton_method(lambda_or_singleton_method)
|
||||
|
||||
# Return/passthrough the Result itself if it is an err
|
||||
return self if err?
|
||||
|
||||
# If the Result is ok, call the lambda or singleton method with the contained value
|
||||
mapped_value = lambda_or_singleton_method.call(value)
|
||||
|
||||
if mapped_value.is_a?(Result)
|
||||
err_msg = "'Result##{__method__}' expects a lambda or singleton method object which returns an unwrapped " \
|
||||
"value, not a 'Result', but instead received '#{lambda_or_singleton_method.inspect}' which returned " \
|
||||
"a 'Result'."
|
||||
raise(TypeError, err_msg)
|
||||
end
|
||||
|
||||
# wrap the returned mapped_value in an "ok" Result.
|
||||
Result.ok(mapped_value)
|
||||
end
|
||||
|
||||
# `to_h` supports destructuring of a result object, for example: `result => { ok: }; puts ok`
|
||||
#
|
||||
# @return [Hash]
|
||||
def to_h
|
||||
ok? ? { ok: value } : { err: value }
|
||||
end
|
||||
|
||||
# `deconstruct_keys` supports pattern matching on a Result object with a `case` statement. See specs for examples.
|
||||
#
|
||||
# @param [Array] keys
|
||||
# @return [Hash]
|
||||
# @raise [ArgumentError]
|
||||
def deconstruct_keys(keys)
|
||||
raise(ArgumentError, 'Use either :ok or :err for pattern matching') unless [[:ok], [:err]].include?(keys)
|
||||
|
||||
to_h
|
||||
end
|
||||
|
||||
# @return [Boolean]
|
||||
def ==(other)
|
||||
# NOTE: The underlying `@ok` instance variable is a boolean, so we only need to check `ok?`, not `err?` too
|
||||
self.class == other.class && other.ok? == ok? && other.instance_variable_get(:@value) == value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# The `value` attribute will contain either the ok_value or the err_value
|
||||
def value # rubocop:disable Style/TrivialAccessors -- We are not using attr_reader here, so we can avoid nilability type errors in RubyMine
|
||||
# TODO: We are not using attr_reader here, so we can avoid nilability type errors in RubyMine. This will be reported
|
||||
# to JetBrains and tracked on
|
||||
# https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/,
|
||||
# this comment should then be updated with the issue link on that page.
|
||||
# Note that we don't use `noinspection` to suppress this error because there's several instances where this error
|
||||
# leaks to other classes through the call stack.
|
||||
@value
|
||||
end
|
||||
|
||||
def initialize(ok_value: nil, err_value: nil) # rubocop:disable Layout/ClassStructure -- We want this constructor to be private
|
||||
if (!ok_value.nil? && !err_value.nil?) || (ok_value.nil? && err_value.nil?)
|
||||
raise(ArgumentError, 'Do not directly use private constructor, use Result.ok or Result.err')
|
||||
end
|
||||
|
||||
@ok = err_value.nil?
|
||||
@value = ok? ? ok_value : err_value
|
||||
end
|
||||
|
||||
# @param [Proc, Method] lambda_or_singleton_method
|
||||
# @return [void]
|
||||
# @raise [TypeError]
|
||||
def validate_lambda_or_singleton_method(lambda_or_singleton_method)
|
||||
is_lambda = lambda_or_singleton_method.is_a?(Proc) && lambda_or_singleton_method.lambda?
|
||||
is_singleton_method = lambda_or_singleton_method.is_a?(Method) && lambda_or_singleton_method.owner.singleton_class?
|
||||
unless is_lambda || is_singleton_method
|
||||
err_msg = "'Result##{__method__}' expects a lambda or singleton method object, " \
|
||||
"but instead received '#{lambda_or_singleton_method.inspect}'."
|
||||
raise(TypeError, err_msg)
|
||||
end
|
||||
|
||||
arity = lambda_or_singleton_method.arity
|
||||
|
||||
return if arity == 1
|
||||
return if arity == -1 && lambda_or_singleton_method.source_location[0].include?('rspec')
|
||||
|
||||
err_msg = "'Result##{__method__}' expects a lambda or singleton method object with a single argument " \
|
||||
"(arity of 1), but instead received '#{lambda_or_singleton_method.inspect}' with an arity of #{arity}."
|
||||
raise(ArgumentError, err_msg)
|
||||
end
|
||||
end
|
||||
# rubocop:enable Gitlab/NamespacedClass
|
||||
# rubocop:enable Gitlab/BoundedContexts
|
||||
|
|
@ -14277,12 +14277,21 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Container Registry"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Container Scanning for Registry: Off"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Container Scanning for Registry: On"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Container protection rule deleted."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Container protection rule updated."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Continuous container scanning runs in the registry when any image or database is updated."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Copy build command"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36018,6 +36027,9 @@ msgstr ""
|
|||
msgid "Observability|Enable"
|
||||
msgstr ""
|
||||
|
||||
msgid "Observability|Enable tracing, metrics, or logs on your project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Observability|Error: Failed to enable GitLab Observability. Please retry later."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36066,6 +36078,15 @@ msgstr ""
|
|||
msgid "Observability|To widen your search, change or remove filters above"
|
||||
msgstr ""
|
||||
|
||||
msgid "Observability|Tracing, Metrics & Logs"
|
||||
msgstr ""
|
||||
|
||||
msgid "Observability|View our %{docsLink} for further instructions on how to use these features."
|
||||
msgstr ""
|
||||
|
||||
msgid "Observability|documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oct"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -59539,6 +59560,9 @@ msgstr ""
|
|||
msgid "What is a compute quota?"
|
||||
msgstr ""
|
||||
|
||||
msgid "What is continuous container scanning?"
|
||||
msgstr ""
|
||||
|
||||
msgid "What is listed here?"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -62748,6 +62772,12 @@ msgstr ""
|
|||
msgid "failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "failed to assign runner to project"
|
||||
msgstr ""
|
||||
|
||||
msgid "failed to destroy runner project"
|
||||
msgstr ""
|
||||
|
||||
msgid "failed to dismiss associated finding(id=%{finding_id}): %{message}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -63854,6 +63884,9 @@ msgid_plural "rules"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "runner can only be assigned to projects in the same organization"
|
||||
msgstr ""
|
||||
|
||||
msgid "running"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -64095,9 +64128,15 @@ msgstr[1] ""
|
|||
msgid "user avatar"
|
||||
msgstr ""
|
||||
|
||||
msgid "user is not authorized to add runners to project"
|
||||
msgstr ""
|
||||
|
||||
msgid "user namespace cannot be the parent of another namespace"
|
||||
msgstr ""
|
||||
|
||||
msgid "user not allowed to assign runner"
|
||||
msgstr ""
|
||||
|
||||
msgid "username"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ QA_GITLAB_URL="http://{GDK IP ADDRESS}:{GDK PORT}" \
|
|||
bundle exec rspec <path/to/spec.rb>
|
||||
```
|
||||
|
||||
For an explanation of the variables see the [additional examples below](#overriding-gitlab-address) and the [list of supported environment variables](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-environment-variables).
|
||||
For an explanation of the variables see the [additional examples below](#overriding-gitlab-address) and the [list of supported environment variables](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables).
|
||||
|
||||
#### Run the end-to-end tests on GitLab in Docker
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :object_storage, product_group: :package_registry, quarantine: {
|
||||
type: :waiting_on,
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/469067'
|
||||
} do
|
||||
RSpec.describe 'Package', :object_storage, product_group: :package_registry do
|
||||
describe 'PyPI Repository', :external_api_calls do
|
||||
include Runtime::Fixtures
|
||||
include Support::Helpers::MaskToken
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ function run_rubocop {
|
|||
done < <(find . -path './**/remote_development/*.rb' -print0)
|
||||
files_for_rubocop+=(
|
||||
"lib/gitlab/fp/rop_helpers.rb"
|
||||
"lib/result.rb"
|
||||
"spec/lib/result_spec.rb"
|
||||
"spec/lib/gitlab/fp/rop_helpers_spec.rb"
|
||||
"lib/gitlab/fp/result.rb"
|
||||
"spec/lib/gitlab/fp/result_spec.rb"
|
||||
"spec/support/matchers/invoke_rop_steps.rb"
|
||||
"spec/support/railway_oriented_programming.rb"
|
||||
"spec/support_specs/matchers/result_matchers_spec.rb"
|
||||
|
|
@ -73,7 +74,7 @@ function run_rspec_rails {
|
|||
"ee/spec/requests/api/internal/kubernetes_spec.rb"
|
||||
"spec/graphql/types/subscription_type_spec.rb"
|
||||
"spec/lib/gitlab/fp/rop_helpers_spec.rb"
|
||||
"spec/lib/result_spec.rb"
|
||||
"spec/lib/gitlab/fp/result_spec.rb"
|
||||
"spec/support_specs/matchers/result_matchers_spec.rb"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { merge } from 'lodash';
|
||||
import { GlCard, GlLoadingIcon, GlEmptyState, GlPagination, GlModal } from '@gitlab/ui';
|
||||
import { GlLoadingIcon, GlEmptyState, GlPagination, GlModal } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
import responseBody from 'test_fixtures/api/deploy_keys/index.json';
|
||||
import CrudComponent from '~/vue_shared/components/crud_component.vue';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
|
|
@ -45,8 +45,8 @@ describe('DeployKeysTable', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findCard = () => wrapper.findComponent(GlCard);
|
||||
const findCardTitle = () => findCard().find('.gl-new-card-title-wrapper');
|
||||
const findCrud = () => wrapper.findComponent(CrudComponent);
|
||||
const findCrudTitle = () => wrapper.findByTestId('crud-title');
|
||||
const findEditButton = (index) =>
|
||||
wrapper.findAllByLabelText(DeployKeysTable.i18n.edit, { selector: 'a' }).at(index);
|
||||
const findRemoveButton = (index) =>
|
||||
|
|
@ -132,11 +132,11 @@ describe('DeployKeysTable', () => {
|
|||
});
|
||||
|
||||
it('renders card with the deploy keys', () => {
|
||||
expect(findCard().exists()).toBe(true);
|
||||
expect(findCrud().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('shows the correct number of deploy keys', () => {
|
||||
expect(findCardTitle().text()).toMatchInterpolatedText(
|
||||
expect(findCrudTitle().text()).toMatchInterpolatedText(
|
||||
`Public deploy keys ${responseBody.length}`,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,9 +28,17 @@ describe('registry_header', () => {
|
|||
stubs: {
|
||||
GlSprintf,
|
||||
TitleArea,
|
||||
MetadataContainerScanning: true,
|
||||
},
|
||||
propsData,
|
||||
slots,
|
||||
provide() {
|
||||
return {
|
||||
config: {
|
||||
isGroupPage: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
await nextTick();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ describe('List Page', () => {
|
|||
RegistryHeader,
|
||||
TitleArea,
|
||||
DeleteImage,
|
||||
MetadataContainerScanning: true,
|
||||
},
|
||||
mocks: {
|
||||
$toast,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ RSpec.describe Types::Ci::CiCdSettingType, feature_category: :continuous_integra
|
|||
expected_fields = %w[
|
||||
inbound_job_token_scope_enabled job_token_scope_enabled
|
||||
keep_latest_artifact merge_pipelines_enabled project
|
||||
push_repository_for_job_token_allowed
|
||||
]
|
||||
|
||||
if Gitlab.ee?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ContainerRegistry::ContainerRegistryHelper, feature_category: :container_registry, type: :helper do
|
||||
include Devise::Test::ControllerHelpers
|
||||
|
||||
let_it_be(:project) { build_stubbed(:project, :repository) }
|
||||
let_it_be(:user) { build_stubbed(:user) }
|
||||
let_it_be(:admin) { build_stubbed(:admin) }
|
||||
let_it_be(:container_expiration_policy) { build_stubbed(:container_expiration_policy, project: project) }
|
||||
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
project.add_maintainer(admin)
|
||||
end
|
||||
|
||||
describe '#project_container_registry_template_data' do
|
||||
subject(:project_container_registry_template_data) do
|
||||
helper.project_container_registry_template_data(project, connection_error, invalid_path_error)
|
||||
end
|
||||
|
||||
let(:connection_error) { nil }
|
||||
let(:invalid_path_error) { nil }
|
||||
|
||||
it 'returns the correct template data' do
|
||||
allow(helper).to receive(:current_user).and_return(user)
|
||||
|
||||
expect(project_container_registry_template_data).to include(
|
||||
endpoint: helper.project_container_registry_index_path(project),
|
||||
expiration_policy: container_expiration_policy.to_json,
|
||||
help_page_path: help_page_path('user/packages/container_registry/index'),
|
||||
two_factor_auth_help_link: help_page_path('user/profile/account/two_factor_authentication'),
|
||||
personal_access_tokens_help_link: help_page_path('user/profile/personal_access_tokens'),
|
||||
no_containers_image: match_asset_path('illustrations/docker-empty-state.svg'),
|
||||
containers_error_image: match_asset_path('illustrations/docker-error-state.svg'),
|
||||
repository_url: escape_once(project.container_registry_url),
|
||||
registry_host_url_with_port: escape_once(Gitlab.config.registry.host_port),
|
||||
expiration_policy_help_page_path:
|
||||
help_page_path('user/packages/container_registry/reduce_container_registry_storage',
|
||||
anchor: 'cleanup-policy'),
|
||||
garbage_collection_help_page_path: help_page_path('administration/packages/container_registry',
|
||||
anchor: 'container-registry-garbage-collection'),
|
||||
run_cleanup_policies_help_page_path: help_page_path('administration/packages/container_registry',
|
||||
anchor: 'run-the-cleanup-policy-now'),
|
||||
project_path: project.full_path,
|
||||
gid_prefix: helper.container_repository_gid_prefix,
|
||||
is_admin: user.admin.to_s,
|
||||
show_cleanup_policy_link: helper.show_cleanup_policy_link(project).to_s,
|
||||
show_container_registry_settings: helper.show_container_registry_settings(project).to_s,
|
||||
cleanup_policies_settings_path: helper.project_settings_packages_and_registries_path(project),
|
||||
connection_error: (!!connection_error).to_s,
|
||||
invalid_path_error: (!!invalid_path_error).to_s,
|
||||
user_callouts_path: callouts_path,
|
||||
user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
|
||||
is_metadata_database_enabled: ContainerRegistry::GitlabApiClient.supports_gitlab_api?.to_s,
|
||||
show_unfinished_tag_cleanup_callout: helper.show_unfinished_tag_cleanup_callout?.to_s
|
||||
)
|
||||
end
|
||||
|
||||
context 'when there is a connection error' do
|
||||
let(:connection_error) { true }
|
||||
|
||||
it 'sets connection_error to true' do
|
||||
allow(helper).to receive(:current_user).and_return(user)
|
||||
expect(project_container_registry_template_data[:connection_error]).to eq('true')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is an invalid path error' do
|
||||
let(:invalid_path_error) { true }
|
||||
|
||||
it 'sets invalid_path_error to true' do
|
||||
allow(helper).to receive(:current_user).and_return(user)
|
||||
expect(project_container_registry_template_data[:invalid_path_error]).to eq('true')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current user is admin' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(admin)
|
||||
end
|
||||
|
||||
it 'sets is_admin to true' do
|
||||
expect(project_container_registry_template_data[:is_admin]).to eq('true')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#container_repository_gid_prefix' do
|
||||
subject { helper.container_repository_gid_prefix }
|
||||
|
||||
it { is_expected.to eq('gid://gitlab/ContainerRepository/') }
|
||||
end
|
||||
end
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ContainerRegistryHelper do
|
||||
describe '#container_repository_gid_prefix' do
|
||||
subject { helper.container_repository_gid_prefix }
|
||||
|
||||
it { is_expected.to eq('gid://gitlab/ContainerRepository/') }
|
||||
end
|
||||
end
|
||||
|
|
@ -9,18 +9,18 @@ require 'fast_spec_helper'
|
|||
# To support this, we have intentionally used some `rubocop:disable` comments to allow for more
|
||||
# explicit and readable examples.
|
||||
# rubocop:disable RSpec/DescribedClass, Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration -- intentionally disabled per comment above
|
||||
RSpec.describe Result, feature_category: :remote_development do
|
||||
describe 'usage of Result.ok and Result.err' do
|
||||
RSpec.describe Gitlab::Fp::Result, feature_category: :shared do
|
||||
describe 'usage of Gitlab::Fp::Result.ok and Gitlab::Fp::Result.err' do
|
||||
context 'when checked with .ok? and .err?' do
|
||||
it 'works with ok result' do
|
||||
result = Result.ok(:success)
|
||||
result = Gitlab::Fp::Result.ok(:success)
|
||||
expect(result.ok?).to eq(true)
|
||||
expect(result.err?).to eq(false)
|
||||
expect(result.unwrap).to eq(:success)
|
||||
end
|
||||
|
||||
it 'works with error result' do
|
||||
result = Result.err(:failure)
|
||||
result = Gitlab::Fp::Result.err(:failure)
|
||||
expect(result.err?).to eq(true)
|
||||
expect(result.ok?).to eq(false)
|
||||
expect(result.unwrap_err).to eq(:failure)
|
||||
|
|
@ -29,18 +29,18 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
|
||||
context 'when checked with destructuring' do
|
||||
it 'works with ok result' do
|
||||
Result.ok(:success) => { ok: } # example of rightward assignment
|
||||
Gitlab::Fp::Result.ok(:success) => { ok: } # example of rightward assignment
|
||||
expect(ok).to eq(:success)
|
||||
|
||||
Result.ok(:success) => { ok: success_value } # rightward assignment destructuring to different var
|
||||
Gitlab::Fp::Result.ok(:success) => { ok: success_value } # rightward assignment destructuring to different var
|
||||
expect(success_value).to eq(:success)
|
||||
end
|
||||
|
||||
it 'works with error result' do
|
||||
Result.err(:failure) => { err: }
|
||||
Gitlab::Fp::Result.err(:failure) => { err: }
|
||||
expect(err).to eq(:failure)
|
||||
|
||||
Result.err(:failure) => { err: error_value }
|
||||
Gitlab::Fp::Result.err(:failure) => { err: error_value }
|
||||
expect(error_value).to eq(:failure)
|
||||
end
|
||||
end
|
||||
|
|
@ -58,24 +58,24 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
end
|
||||
|
||||
it 'works with ok result' do
|
||||
ok_result = Result.ok(:success_symbol)
|
||||
ok_result = Gitlab::Fp::Result.ok(:success_symbol)
|
||||
expect(check_result_with_pattern_matching(ok_result)).to eq({ success: :success_symbol })
|
||||
end
|
||||
|
||||
it 'works with error result' do
|
||||
error_result = Result.err('failure string')
|
||||
error_result = Gitlab::Fp::Result.err('failure string')
|
||||
expect(check_result_with_pattern_matching(error_result)).to eq({ failure: 'failure string' })
|
||||
end
|
||||
|
||||
it 'raises error with unmatched type in pattern match' do
|
||||
unmatched_type_result = Result.ok([])
|
||||
unmatched_type_result = Gitlab::Fp::Result.ok([])
|
||||
expect do
|
||||
check_result_with_pattern_matching(unmatched_type_result)
|
||||
end.to raise_error(RuntimeError, 'Unmatched result type: Array')
|
||||
end
|
||||
|
||||
it 'raises error with invalid pattern matching key' do
|
||||
result = Result.ok(:success)
|
||||
result = Gitlab::Fp::Result.ok(:success)
|
||||
expect do
|
||||
case result
|
||||
in { invalid_pattern_match_because_it_is_not_ok_or_err: :value }
|
||||
|
|
@ -91,22 +91,22 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
describe 'usage of #and_then' do
|
||||
context 'when passed a proc' do
|
||||
it 'returns last ok value in successful chain' do
|
||||
initial_result = Result.ok(1)
|
||||
initial_result = Gitlab::Fp::Result.ok(1)
|
||||
final_result =
|
||||
initial_result
|
||||
.and_then(->(value) { Result.ok(value + 1) })
|
||||
.and_then(->(value) { Result.ok(value + 1) })
|
||||
.and_then(->(value) { Gitlab::Fp::Result.ok(value + 1) })
|
||||
.and_then(->(value) { Gitlab::Fp::Result.ok(value + 1) })
|
||||
|
||||
expect(final_result.ok?).to eq(true)
|
||||
expect(final_result.unwrap).to eq(3)
|
||||
end
|
||||
|
||||
it 'short-circuits the rest of the chain on the first err value encountered' do
|
||||
initial_result = Result.ok(1)
|
||||
initial_result = Gitlab::Fp::Result.ok(1)
|
||||
final_result =
|
||||
initial_result
|
||||
.and_then(->(value) { Result.err("invalid: #{value}") })
|
||||
.and_then(->(value) { Result.ok(value + 1) })
|
||||
.and_then(->(value) { Gitlab::Fp::Result.err("invalid: #{value}") })
|
||||
.and_then(->(value) { Gitlab::Fp::Result.ok(value + 1) })
|
||||
|
||||
expect(final_result.err?).to eq(true)
|
||||
expect(final_result.unwrap_err).to eq('invalid: 1')
|
||||
|
|
@ -116,22 +116,22 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
context 'when passed a module or class (singleton) method object' do
|
||||
module MyModuleUsingResult
|
||||
def self.double(value)
|
||||
Result.ok(value * 2)
|
||||
Gitlab::Fp::Result.ok(value * 2)
|
||||
end
|
||||
|
||||
def self.return_err(value)
|
||||
Result.err("invalid: #{value}")
|
||||
Gitlab::Fp::Result.err("invalid: #{value}")
|
||||
end
|
||||
|
||||
class MyClassUsingResult
|
||||
def self.triple(value)
|
||||
Result.ok(value * 3)
|
||||
Gitlab::Fp::Result.ok(value * 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns last ok value in successful chain' do
|
||||
initial_result = Result.ok(1)
|
||||
initial_result = Gitlab::Fp::Result.ok(1)
|
||||
final_result =
|
||||
initial_result
|
||||
.and_then(::MyModuleUsingResult.method(:double))
|
||||
|
|
@ -142,7 +142,7 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
end
|
||||
|
||||
it 'returns first err value in failed chain' do
|
||||
initial_result = Result.ok(1)
|
||||
initial_result = Gitlab::Fp::Result.ok(1)
|
||||
final_result =
|
||||
initial_result
|
||||
.and_then(::MyModuleUsingResult.method(:double))
|
||||
|
|
@ -161,25 +161,25 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
ex = TypeError
|
||||
msg = /expects a lambda or singleton method object/
|
||||
# noinspection RubyMismatchedArgumentType -- intentionally passing invalid types
|
||||
expect { Result.ok(1).and_then('string') }.to raise_error(ex, msg)
|
||||
expect { Result.ok(1).and_then(proc { Result.ok(1) }) }.to raise_error(ex, msg)
|
||||
expect { Result.ok(1).and_then(1.method(:to_s)) }.to raise_error(ex, msg)
|
||||
expect { Result.ok(1).and_then(Integer.method(:to_s)) }.to raise_error(ex, msg)
|
||||
expect { Gitlab::Fp::Result.ok(1).and_then('string') }.to raise_error(ex, msg)
|
||||
expect { Gitlab::Fp::Result.ok(1).and_then(proc { Gitlab::Fp::Result.ok(1) }) }.to raise_error(ex, msg)
|
||||
expect { Gitlab::Fp::Result.ok(1).and_then(1.method(:to_s)) }.to raise_error(ex, msg)
|
||||
expect { Gitlab::Fp::Result.ok(1).and_then(Integer.method(:to_s)) }.to raise_error(ex, msg)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'enforcement of argument arity' do
|
||||
it 'raises ArgumentError if passed lambda or singleton method object with an arity other than 1' do
|
||||
expect do
|
||||
Result.ok(1).and_then(->(a, b) { Result.ok(a + b) })
|
||||
Gitlab::Fp::Result.ok(1).and_then(->(a, b) { Gitlab::Fp::Result.ok(a + b) })
|
||||
end.to raise_error(ArgumentError, /expects .* with a single argument \(arity of 1\)/)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'enforcement that passed lambda or method returns a Result type' do
|
||||
describe 'enforcement that passed lambda or method returns a Gitlab::Fp::Result type' do
|
||||
it 'raises ArgumentError if passed lambda or singleton method object which returns non-Result type' do
|
||||
expect do
|
||||
Result.ok(1).and_then(->(a) { a + 1 })
|
||||
Gitlab::Fp::Result.ok(1).and_then(->(a) { a + 1 })
|
||||
end.to raise_error(TypeError, /expects .* which returns a 'Result' type/)
|
||||
end
|
||||
end
|
||||
|
|
@ -189,7 +189,7 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
describe 'usage of #map' do
|
||||
context 'when passed a proc' do
|
||||
it 'returns last ok value in successful chain' do
|
||||
initial_result = Result.ok(1)
|
||||
initial_result = Gitlab::Fp::Result.ok(1)
|
||||
final_result =
|
||||
initial_result
|
||||
.map(->(value) { value + 1 })
|
||||
|
|
@ -200,10 +200,10 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
end
|
||||
|
||||
it 'returns first err value in failed chain' do
|
||||
initial_result = Result.ok(1)
|
||||
initial_result = Gitlab::Fp::Result.ok(1)
|
||||
final_result =
|
||||
initial_result
|
||||
.and_then(->(value) { Result.err("invalid: #{value}") })
|
||||
.and_then(->(value) { Gitlab::Fp::Result.err("invalid: #{value}") })
|
||||
.map(->(value) { value + 1 })
|
||||
|
||||
expect(final_result.err?).to eq(true)
|
||||
|
|
@ -225,7 +225,7 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
end
|
||||
|
||||
it 'returns last ok value in successful chain' do
|
||||
initial_result = Result.ok(1)
|
||||
initial_result = Gitlab::Fp::Result.ok(1)
|
||||
final_result =
|
||||
initial_result
|
||||
.map(::MyModuleNotUsingResult.method(:double))
|
||||
|
|
@ -236,11 +236,11 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
end
|
||||
|
||||
it 'returns first err value in failed chain' do
|
||||
initial_result = Result.ok(1)
|
||||
initial_result = Gitlab::Fp::Result.ok(1)
|
||||
final_result =
|
||||
initial_result
|
||||
.map(::MyModuleNotUsingResult.method(:double))
|
||||
.and_then(->(value) { Result.err("invalid: #{value}") })
|
||||
.and_then(->(value) { Gitlab::Fp::Result.err("invalid: #{value}") })
|
||||
.map(::MyModuleUsingResult.method(:double))
|
||||
|
||||
expect(final_result.err?).to eq(true)
|
||||
|
|
@ -253,18 +253,18 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
it 'raises TypeError if passed anything other than a lambda or singleton method object' do
|
||||
ex = TypeError
|
||||
msg = /expects a lambda or singleton method object/
|
||||
# noinspection RubyMismatchedArgumentType
|
||||
expect { Result.ok(1).map('string') }.to raise_error(ex, msg)
|
||||
expect { Result.ok(1).map(proc { 1 }) }.to raise_error(ex, msg)
|
||||
expect { Result.ok(1).map(1.method(:to_s)) }.to raise_error(ex, msg)
|
||||
expect { Result.ok(1).map(Integer.method(:to_s)) }.to raise_error(ex, msg)
|
||||
# noinspection RubyMismatchedArgumentType -- intentionally passing invalid types
|
||||
expect { Gitlab::Fp::Result.ok(1).map('string') }.to raise_error(ex, msg)
|
||||
expect { Gitlab::Fp::Result.ok(1).map(proc { 1 }) }.to raise_error(ex, msg)
|
||||
expect { Gitlab::Fp::Result.ok(1).map(1.method(:to_s)) }.to raise_error(ex, msg)
|
||||
expect { Gitlab::Fp::Result.ok(1).map(Integer.method(:to_s)) }.to raise_error(ex, msg)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'enforcement of argument arity' do
|
||||
it 'raises ArgumentError if passed lambda or singleton method object with an arity other than 1' do
|
||||
expect do
|
||||
Result.ok(1).map(->(a, b) { a + b })
|
||||
Gitlab::Fp::Result.ok(1).map(->(a, b) { a + b })
|
||||
end.to raise_error(ArgumentError, /expects .* with a single argument \(arity of 1\)/)
|
||||
end
|
||||
end
|
||||
|
|
@ -272,7 +272,7 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
describe 'enforcement that passed lambda or method does not return a Result type' do
|
||||
it 'raises TypeError if passed lambda or singleton method object which returns non-Result type' do
|
||||
expect do
|
||||
Result.ok(1).map(->(a) { Result.ok(a + 1) })
|
||||
Gitlab::Fp::Result.ok(1).map(->(a) { Gitlab::Fp::Result.ok(a + 1) })
|
||||
end.to raise_error(TypeError, /expects .* which returns an unwrapped value, not a 'Result'/)
|
||||
end
|
||||
end
|
||||
|
|
@ -281,31 +281,33 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
|
||||
describe '#unwrap' do
|
||||
it 'returns wrapped value if ok' do
|
||||
expect(Result.ok(1).unwrap).to eq(1)
|
||||
expect(Gitlab::Fp::Result.ok(1).unwrap).to eq(1)
|
||||
end
|
||||
|
||||
it 'raises error if err' do
|
||||
expect { Result.err('error').unwrap }.to raise_error(RuntimeError, /called.*unwrap.*on an 'err' Result/i)
|
||||
expect { Gitlab::Fp::Result.err('error').unwrap }
|
||||
.to raise_error(RuntimeError, /called.*unwrap.*on an 'err' Result/i)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unwrap_err' do
|
||||
it 'returns wrapped value if err' do
|
||||
expect(Result.err('error').unwrap_err).to eq('error')
|
||||
expect(Gitlab::Fp::Result.err('error').unwrap_err).to eq('error')
|
||||
end
|
||||
|
||||
it 'raises error if ok' do
|
||||
expect { Result.ok(1).unwrap_err }.to raise_error(RuntimeError, /called.*unwrap_err.*on an 'ok' Result/i)
|
||||
expect { Gitlab::Fp::Result.ok(1).unwrap_err }
|
||||
.to raise_error(RuntimeError, /called.*unwrap_err.*on an 'ok' Result/i)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#==' do
|
||||
it 'implements equality' do
|
||||
expect(Result.ok(1)).to eq(Result.ok(1))
|
||||
expect(Result.err('error')).to eq(Result.err('error'))
|
||||
expect(Result.ok(1)).not_to eq(Result.ok(2))
|
||||
expect(Result.err('error')).not_to eq(Result.err('other error'))
|
||||
expect(Result.ok(1)).not_to eq(Result.err(1))
|
||||
expect(Gitlab::Fp::Result.ok(1)).to eq(Gitlab::Fp::Result.ok(1))
|
||||
expect(Gitlab::Fp::Result.err('error')).to eq(Gitlab::Fp::Result.err('error'))
|
||||
expect(Gitlab::Fp::Result.ok(1)).not_to eq(Gitlab::Fp::Result.ok(2))
|
||||
expect(Gitlab::Fp::Result.err('error')).not_to eq(Gitlab::Fp::Result.err('other error'))
|
||||
expect(Gitlab::Fp::Result.ok(1)).not_to eq(Gitlab::Fp::Result.err(1))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -313,12 +315,12 @@ RSpec.describe Result, feature_category: :remote_development do
|
|||
context 'for enforcing usage of only public interface' do
|
||||
context 'when private constructor is called with invalid params' do
|
||||
it 'raises ArgumentError if both ok_value and err_value are passed' do
|
||||
expect { Result.new(ok_value: :ignored, err_value: :ignored) }
|
||||
expect { Gitlab::Fp::Result.new(ok_value: :ignored, err_value: :ignored) }
|
||||
.to raise_error(ArgumentError, 'Do not directly use private constructor, use Result.ok or Result.err')
|
||||
end
|
||||
|
||||
it 'raises ArgumentError if neither ok_value nor err_value are passed' do
|
||||
expect { Result.new }
|
||||
expect { Gitlab::Fp::Result.new }
|
||||
.to raise_error(ArgumentError, 'Do not directly use private constructor, use Result.ok or Result.err')
|
||||
end
|
||||
end
|
||||
|
|
@ -32,7 +32,7 @@ RSpec.describe ::RemoteDevelopment::Settings::CurrentSettingsReader, feature_cat
|
|||
|
||||
context "when there are no errors" do
|
||||
it "returns ::Gitlab::CurrentSettings overridden settings and non-overridden settings" do
|
||||
expect(result).to eq(Result.ok(
|
||||
expect(result).to eq(Gitlab::Fp::Result.ok(
|
||||
{
|
||||
settings: {
|
||||
non_overridden_setting: "not_overridden",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ RSpec.describe RemoteDevelopment::Settings::EnvVarReader, :rd_fast, feature_cate
|
|||
let(:setting_type) { String }
|
||||
|
||||
it "uses the string value of the overridden ENV var value" do
|
||||
expect(result).to eq(Result.ok(
|
||||
expect(result).to eq(Gitlab::Fp::Result.ok(
|
||||
{
|
||||
settings: { the_setting: env_var_value },
|
||||
setting_types: { the_setting: setting_type }
|
||||
|
|
@ -49,7 +49,7 @@ RSpec.describe RemoteDevelopment::Settings::EnvVarReader, :rd_fast, feature_cate
|
|||
let(:setting_type) { Integer }
|
||||
|
||||
it "uses the casted type of the overridden ENV var value" do
|
||||
expect(result).to eq(Result.ok(
|
||||
expect(result).to eq(Gitlab::Fp::Result.ok(
|
||||
{
|
||||
settings: { the_setting: env_var_value.to_i },
|
||||
setting_types: { the_setting: setting_type }
|
||||
|
|
@ -63,7 +63,7 @@ RSpec.describe RemoteDevelopment::Settings::EnvVarReader, :rd_fast, feature_cate
|
|||
let(:setting_type) { Hash }
|
||||
|
||||
it "uses the casted type of the overridden ENV var value" do
|
||||
expect(result).to eq(Result.ok(
|
||||
expect(result).to eq(Gitlab::Fp::Result.ok(
|
||||
{
|
||||
settings: { the_setting: { a: 1 } },
|
||||
setting_types: { the_setting: setting_type }
|
||||
|
|
@ -77,7 +77,7 @@ RSpec.describe RemoteDevelopment::Settings::EnvVarReader, :rd_fast, feature_cate
|
|||
let(:setting_type) { Array }
|
||||
|
||||
it "uses the casted type of the overridden ENV var value" do
|
||||
expect(result).to eq(Result.ok(
|
||||
expect(result).to eq(Gitlab::Fp::Result.ok(
|
||||
{
|
||||
settings: { the_setting: ["a", 1] },
|
||||
setting_types: { the_setting: setting_type }
|
||||
|
|
@ -92,7 +92,7 @@ RSpec.describe RemoteDevelopment::Settings::EnvVarReader, :rd_fast, feature_cate
|
|||
let(:env_var_value) { "maybe some old deprecated setting, doesn't matter, it's ignored" }
|
||||
|
||||
it "ignores the ENV var" do
|
||||
expect(result).to eq(Result.ok(
|
||||
expect(result).to eq(Gitlab::Fp::Result.ok(
|
||||
{
|
||||
settings: { the_setting: default_setting_value },
|
||||
setting_types: { the_setting: setting_type }
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ RSpec.describe RemoteDevelopment::Settings::ExtensionsGalleryMetadataValidator,
|
|||
context "when vscode_extensions_gallery_metadata is valid" do
|
||||
shared_examples "success result" do
|
||||
it "return an ok Result containing the original context which was passed" do
|
||||
expect(result).to eq(Result.ok(context))
|
||||
expect(result).to eq(Gitlab::Fp::Result.ok(context))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ RSpec.describe RemoteDevelopment::Settings::ExtensionsGalleryValidator, :rd_fast
|
|||
context "when vscode_extensions_gallery is valid" do
|
||||
shared_examples "success result" do
|
||||
it "return an ok Result containing the original context which was passed" do
|
||||
expect(result).to eq(Result.ok(context))
|
||||
expect(result).to eq(Gitlab::Fp::Result.ok(context))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ RSpec.describe RemoteDevelopment::Settings::ReconciliationIntervalSecondsValidat
|
|||
|
||||
context "when partial_reconciliation_interval_seconds and full_reconciliation_interval_seconds is valid" do
|
||||
it "return an ok Result containing the original context which was passed" do
|
||||
expect(result).to eq(Result.ok(context))
|
||||
expect(result).to eq(Gitlab::Fp::Result.ok(context))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AddTicketWorkItemType, :migration, feature_category: :service_desk do
|
||||
RSpec.describe AddTicketWorkItemType, :migration, feature_category: :service_desk, allowed_to_be_slow: true do
|
||||
include MigrationHelpers::WorkItemTypesHelper
|
||||
|
||||
let(:work_item_types) { table(:work_item_types) }
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ RSpec.describe ProjectCiCdSetting, feature_category: :continuous_integration do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#push_repository_for_job_token_allowed' do
|
||||
it 'is false by default' do
|
||||
expect(described_class.new.push_repository_for_job_token_allowed).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe '#separated_caches' do
|
||||
it 'is true by default' do
|
||||
expect(described_class.new.separated_caches).to be_truthy
|
||||
|
|
|
|||
|
|
@ -3704,19 +3704,24 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
|
||||
let(:policy) { :build_push_code }
|
||||
|
||||
where(:user_role, :project_visibility, :push_repository_for_job_token_allowed, :self_referential_project, :allowed) do
|
||||
:maintainer | :public | true | true | true
|
||||
:owner | :public | true | true | true
|
||||
:maintainer | :private | true | true | true
|
||||
:developer | :public | true | true | true
|
||||
:reporter | :public | true | true | false
|
||||
:guest | :public | true | true | false
|
||||
:guest | :private | true | true | false
|
||||
:guest | :internal | true | true | false
|
||||
:anonymous | :public | true | true | false
|
||||
:maintainer | :public | false | true | false
|
||||
:maintainer | :public | true | false | false
|
||||
:maintainer | :public | false | false | false
|
||||
where(:user_role, :project_visibility, :push_repository_for_job_token_allowed, :self_referential_project, :allowed, :ff_disabled) do
|
||||
:maintainer | :public | true | true | true | false
|
||||
:owner | :public | true | true | true | false
|
||||
:maintainer | :private | true | true | true | false
|
||||
:developer | :public | true | true | true | false
|
||||
:reporter | :public | true | true | false | false
|
||||
:guest | :public | true | true | false | false
|
||||
:guest | :private | true | true | false | false
|
||||
:guest | :internal | true | true | false | false
|
||||
:anonymous | :public | true | true | false | false
|
||||
:maintainer | :public | false | true | false | false
|
||||
:maintainer | :public | true | false | false | false
|
||||
:maintainer | :public | false | false | false | false
|
||||
:maintainer | :public | true | true | false | true
|
||||
:owner | :public | true | true | false | true
|
||||
:maintainer | :private | true | true | false | true
|
||||
:developer | :public | true | true | false | true
|
||||
:reporter | :public | true | true | false | true
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
@ -3730,6 +3735,8 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
let(:scope_project) { public_send(:private_project) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(allow_push_repository_for_job_token: false) if ff_disabled
|
||||
|
||||
project.add_guest(guest)
|
||||
project.add_reporter(reporter)
|
||||
project.add_developer(developer)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ RSpec.describe 'Getting Ci Cd Setting', feature_category: :continuous_integratio
|
|||
expect(settings_data['jobTokenScopeEnabled']).to eql project.ci_cd_settings.job_token_scope_enabled?
|
||||
expect(settings_data['inboundJobTokenScopeEnabled']).to eql(
|
||||
project.ci_cd_settings.inbound_job_token_scope_enabled?)
|
||||
expect(settings_data['pushRepositoryForJobTokenAllowed']).to eql(
|
||||
project.ci_cd_settings.push_repository_for_job_token_allowed?)
|
||||
|
||||
if Gitlab.ee?
|
||||
expect(settings_data['mergeTrainsEnabled']).to eql project.ci_cd_settings.merge_trains_enabled?
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ RSpec.describe 'ProjectCiCdSettingsUpdate', feature_category: :continuous_integr
|
|||
full_path: project.full_path,
|
||||
keep_latest_artifact: false,
|
||||
job_token_scope_enabled: false,
|
||||
inbound_job_token_scope_enabled: false
|
||||
inbound_job_token_scope_enabled: false,
|
||||
push_repository_for_job_token_allowed: false
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -69,6 +70,23 @@ RSpec.describe 'ProjectCiCdSettingsUpdate', feature_category: :continuous_integr
|
|||
expect(project.ci_outbound_job_token_scope_enabled).to eq(false)
|
||||
end
|
||||
|
||||
context 'when push_repository_for_job_token_allowed requested to be true' do
|
||||
let(:variables) do
|
||||
{
|
||||
full_path: project.full_path,
|
||||
push_repository_for_job_token_allowed: true
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates push_repository_for_job_token_allowed' do
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
project.reload
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(project.ci_cd_settings.push_repository_for_job_token_allowed).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job_token_scope_enabled: true' do
|
||||
let(:variables) do
|
||||
{
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ ci_cd_settings:
|
|||
- push_repository_for_job_token_allowed
|
||||
remapped_attributes:
|
||||
pipeline_variables_minimum_override_role: ci_pipeline_variables_minimum_override_role
|
||||
push_repository_for_job_token_allowed: ci_push_repository_for_job_token_allowed
|
||||
default_git_depth: ci_default_git_depth
|
||||
forward_deployment_enabled: ci_forward_deployment_enabled
|
||||
forward_deployment_rollback_allowed: ci_forward_deployment_rollback_allowed
|
||||
|
|
|
|||
|
|
@ -3323,7 +3323,8 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
|
|||
'build_timeout',
|
||||
'auto_devops_enabled',
|
||||
'auto_devops_deploy_strategy',
|
||||
'import_error'
|
||||
'import_error',
|
||||
'ci_push_repository_for_job_token_allowed'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -4074,6 +4075,20 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
|
|||
let(:failed_status_code) { :not_found }
|
||||
end
|
||||
|
||||
describe 'updating ci_push_repository_for_job_token_allowed attribute' do
|
||||
it 'is disabled by default' do
|
||||
expect(project.ci_push_repository_for_job_token_allowed).to be_falsey
|
||||
end
|
||||
|
||||
it 'enables push to repository using job token' do
|
||||
put(api(path, user), params: { ci_push_repository_for_job_token_allowed: true })
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(project.reload.ci_push_repository_for_job_token_allowed).to be_truthy
|
||||
expect(json_response['ci_push_repository_for_job_token_allowed']).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'updating packages_enabled attribute' do
|
||||
it 'is enabled by default' do
|
||||
expect(project.packages_enabled).to be true
|
||||
|
|
|
|||
|
|
@ -3,21 +3,25 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ::Ci::Runners::AssignRunnerService, '#execute', feature_category: :runner do
|
||||
subject(:execute) { described_class.new(runner, new_project, user).execute }
|
||||
let(:service) { described_class.new(runner, new_project, user) }
|
||||
|
||||
let_it_be(:owner_group) { create(:group) }
|
||||
let_it_be(:owner_project) { create(:project, group: owner_group) }
|
||||
let_it_be(:new_project) { create(:project) }
|
||||
let_it_be(:organization1) { create(:organization) }
|
||||
let_it_be(:owner_group) { create(:group, organization: organization1) }
|
||||
let_it_be(:owner_project) { create(:project, group: owner_group, organization: organization1) }
|
||||
let_it_be(:new_project) { create(:project, organization: organization1) }
|
||||
let_it_be(:runner) { create(:ci_runner, :project, projects: [owner_project]) }
|
||||
|
||||
subject(:execute) { service.execute }
|
||||
|
||||
context 'without user' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'does not call assign_to on runner and returns error response', :aggregate_failures do
|
||||
expect(runner).not_to receive(:assign_to)
|
||||
|
||||
is_expected.to be_error
|
||||
expect(execute.message).to eq('user not allowed to assign runner')
|
||||
expect(execute).to be_error
|
||||
expect(execute.reason).to eq(:not_authorized_to_assign_runner)
|
||||
expect(execute.message).to eq(_('user not allowed to assign runner'))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -27,8 +31,9 @@ RSpec.describe ::Ci::Runners::AssignRunnerService, '#execute', feature_category:
|
|||
it 'does not call assign_to on runner and returns error message' do
|
||||
expect(runner).not_to receive(:assign_to)
|
||||
|
||||
is_expected.to be_error
|
||||
expect(execute.message).to eq('user not allowed to assign runner')
|
||||
expect(execute).to be_error
|
||||
expect(execute.reason).to eq(:not_authorized_to_assign_runner)
|
||||
expect(execute.message).to eq(_('user not allowed to assign runner'))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -44,7 +49,32 @@ RSpec.describe ::Ci::Runners::AssignRunnerService, '#execute', feature_category:
|
|||
it 'calls assign_to on runner and returns success response' do
|
||||
expect(runner).to receive(:assign_to).with(new_project, user).once.and_call_original
|
||||
|
||||
is_expected.to be_success
|
||||
expect(execute).to be_success
|
||||
end
|
||||
|
||||
context 'when runner returns error' do
|
||||
let(:new_project) { owner_project }
|
||||
|
||||
it 'returns error response' do
|
||||
expect(execute).to be_error
|
||||
expect(execute.reason).to eq(:runner_error)
|
||||
expect(execute.errors).to contain_exactly(
|
||||
'Assign to Validation failed: Runner projects runner has already been taken')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when new project is from a different organization' do
|
||||
let_it_be(:organization2) { create(:organization) }
|
||||
let_it_be(:new_project) { create(:project, organization: organization2) }
|
||||
|
||||
it 'returns error response and rolls back transaction' do
|
||||
expect(execute).to be_error
|
||||
expect(execute.reason).to eq(:project_not_in_same_organization)
|
||||
expect(execute.errors).to contain_exactly(
|
||||
_('runner can only be assigned to projects in the same organization')
|
||||
)
|
||||
expect(runner.reload.projects).to contain_exactly(owner_project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -56,8 +86,9 @@ RSpec.describe ::Ci::Runners::AssignRunnerService, '#execute', feature_category:
|
|||
it 'does not call assign_to on runner and returns error message', :aggregate_failures do
|
||||
expect(runner).not_to receive(:assign_to)
|
||||
|
||||
is_expected.to be_error
|
||||
expect(execute.message).to eq('user not allowed to add runners to project')
|
||||
expect(execute).to be_error
|
||||
expect(execute.reason).to eq(:not_authorized_to_add_runner_in_project)
|
||||
expect(execute.message).to eq(_('user is not authorized to add runners to project'))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -69,7 +100,8 @@ RSpec.describe ::Ci::Runners::AssignRunnerService, '#execute', feature_category:
|
|||
it 'does not call assign_to on runner and returns error message' do
|
||||
expect(runner).not_to receive(:assign_to)
|
||||
|
||||
is_expected.to be_error
|
||||
expect(execute).to be_error
|
||||
expect(execute.reason).to eq(:not_authorized_to_assign_runner)
|
||||
expect(execute.message).to eq('user not allowed to assign runner')
|
||||
end
|
||||
end
|
||||
|
|
@ -81,7 +113,7 @@ RSpec.describe ::Ci::Runners::AssignRunnerService, '#execute', feature_category:
|
|||
it 'calls assign_to on runner and returns success response' do
|
||||
expect(runner).to receive(:assign_to).with(new_project, user).once.and_call_original
|
||||
|
||||
is_expected.to be_success
|
||||
expect(execute).to be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ RSpec.describe ::Ci::Runners::SetRunnerAssociatedProjectsService, '#execute', fe
|
|||
described_class.new(runner: runner, current_user: user, project_ids: new_projects.map(&:id)).execute
|
||||
end
|
||||
|
||||
let_it_be(:owner_project) { create(:project) }
|
||||
let_it_be(:project2) { create(:project) }
|
||||
let_it_be(:organization1) { create(:organization) }
|
||||
let_it_be(:owner_project) { create(:project, organization: organization1) }
|
||||
let_it_be(:project2) { create(:project, organization: organization1) }
|
||||
|
||||
let(:original_projects) { [owner_project, project2] }
|
||||
let(:runner) { create(:ci_runner, :project, projects: original_projects) }
|
||||
|
|
@ -21,7 +22,8 @@ RSpec.describe ::Ci::Runners::SetRunnerAssociatedProjectsService, '#execute', fe
|
|||
expect(runner).not_to receive(:assign_to)
|
||||
|
||||
expect(execute).to be_error
|
||||
expect(execute.message).to eq('user not allowed to assign runner')
|
||||
expect(execute.reason).to eq(:not_authorized_to_assign_runner)
|
||||
expect(execute.message).to eq(_('user not allowed to assign runner'))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -33,13 +35,14 @@ RSpec.describe ::Ci::Runners::SetRunnerAssociatedProjectsService, '#execute', fe
|
|||
expect(runner).not_to receive(:assign_to)
|
||||
|
||||
expect(execute).to be_error
|
||||
expect(execute.message).to eq('user not allowed to assign runner')
|
||||
expect(execute.reason).to eq(:not_authorized_to_assign_runner)
|
||||
expect(execute.message).to eq(_('user not allowed to assign runner'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with authorized user' do
|
||||
let_it_be(:project3) { create(:project) }
|
||||
let_it_be(:project4) { create(:project) }
|
||||
let_it_be(:project3) { create(:project, organization: organization1) }
|
||||
let_it_be(:project4) { create(:project, organization: organization1) }
|
||||
|
||||
let(:projects_with_maintainer_access) { original_projects }
|
||||
|
||||
|
|
@ -57,7 +60,7 @@ RSpec.describe ::Ci::Runners::SetRunnerAssociatedProjectsService, '#execute', fe
|
|||
runner.reload
|
||||
|
||||
expect(runner.owner_project).to eq(owner_project)
|
||||
expect(runner.projects.ids).to match_array([owner_project.id] + new_projects.map(&:id))
|
||||
expect(runner.projects.ids).to match_array([owner_project, *new_projects].map(&:id))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -70,7 +73,7 @@ RSpec.describe ::Ci::Runners::SetRunnerAssociatedProjectsService, '#execute', fe
|
|||
runner.reload
|
||||
|
||||
expect(runner.owner_project).to eq(owner_project)
|
||||
expect(runner.projects.ids).to match_array([owner_project.id] + new_projects.map(&:id))
|
||||
expect(runner.projects.ids).to eq([owner_project, *new_projects].map(&:id))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -97,6 +100,7 @@ RSpec.describe ::Ci::Runners::SetRunnerAssociatedProjectsService, '#execute', fe
|
|||
end
|
||||
|
||||
expect(execute).to be_error
|
||||
expect(execute.reason).to eq(:failed_runner_project_destroy)
|
||||
expect(runner.reload.projects.order(:id)).to eq(original_projects)
|
||||
end
|
||||
end
|
||||
|
|
@ -116,10 +120,39 @@ RSpec.describe ::Ci::Runners::SetRunnerAssociatedProjectsService, '#execute', fe
|
|||
|
||||
it 'returns error response and rolls back transaction' do
|
||||
expect(execute).to be_error
|
||||
expect(execute.errors).to contain_exactly('user is not authorized to add runners to project')
|
||||
expect(execute.reason).to eq(:not_authorized_to_add_runner_in_project)
|
||||
expect(execute.errors).to contain_exactly(_('user is not authorized to add runners to project'))
|
||||
expect(runner.reload.projects.order(:id)).to eq(original_projects)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when some of the new projects are from a different organization' do
|
||||
let_it_be(:organization2) { create(:organization) }
|
||||
let_it_be(:project4) { create(:project, organization: organization2) }
|
||||
|
||||
it 'returns error response and rolls back transaction' do
|
||||
expect(execute).to be_error
|
||||
expect(execute.reason).to eq(:project_not_in_same_organization)
|
||||
expect(execute.errors).to contain_exactly(
|
||||
_('runner can only be assigned to projects in the same organization')
|
||||
)
|
||||
expect(runner.reload.projects.order(:id)).to eq(original_projects)
|
||||
end
|
||||
|
||||
context 'with multiple failures' do
|
||||
let(:projects_with_maintainer_access) { original_projects + [project4] }
|
||||
|
||||
it 'returns error response and rolls back transaction' do
|
||||
expect(execute).to be_error
|
||||
expect(execute.reason).to eq(:multiple_errors)
|
||||
expect(execute.errors).to contain_exactly(
|
||||
_('runner can only be assigned to projects in the same organization'),
|
||||
_('user is not authorized to add runners to project')
|
||||
)
|
||||
expect(runner.reload.projects.order(:id)).to eq(original_projects)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2916,6 +2916,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
|
||||
context 'crm_contact commands' do
|
||||
let_it_be(:new_contact) { create(:contact, group: group) }
|
||||
let_it_be(:another_contact) { create(:contact, group: group) }
|
||||
let_it_be(:existing_contact) { create(:contact, group: group) }
|
||||
|
||||
let(:add_command) { service.execute("/add_contacts #{new_contact.email}", issue) }
|
||||
|
|
@ -2926,18 +2927,62 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
create(:issue_customer_relations_contact, issue: issue, contact: existing_contact)
|
||||
end
|
||||
|
||||
it 'add_contacts command adds the contact' do
|
||||
_, updates, message = add_command
|
||||
describe 'add_contacts command' do
|
||||
it 'adds a contact' do
|
||||
_, updates, message = add_command
|
||||
|
||||
expect(updates).to eq(add_contacts: [new_contact.email])
|
||||
expect(message).to eq(_('One or more contacts were successfully added.'))
|
||||
expect(updates).to eq(add_contacts: [new_contact.email])
|
||||
expect(message).to eq(_('One or more contacts were successfully added.'))
|
||||
end
|
||||
|
||||
context 'with multiple contacts in the same command' do
|
||||
it 'adds both contacts' do
|
||||
_, updates, message = service.execute("/add_contacts #{new_contact.email} #{another_contact.email}", issue)
|
||||
|
||||
expect(updates).to eq(add_contacts: [new_contact.email, another_contact.email])
|
||||
expect(message).to eq(_('One or more contacts were successfully added.'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple commands' do
|
||||
it 'adds both contacts' do
|
||||
_, updates, message = service.execute("/add_contacts #{new_contact.email}\n/add_contacts #{another_contact.email}", issue)
|
||||
|
||||
expect(updates).to eq(add_contacts: [new_contact.email, another_contact.email])
|
||||
expect(message).to eq(_('One or more contacts were successfully added. One or more contacts were successfully added.'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'remove_contacts command removes the contact' do
|
||||
_, updates, message = remove_command
|
||||
describe 'remove_contacts command' do
|
||||
before do
|
||||
create(:issue_customer_relations_contact, issue: issue, contact: another_contact)
|
||||
end
|
||||
|
||||
expect(updates).to eq(remove_contacts: [existing_contact.email])
|
||||
expect(message).to eq(_('One or more contacts were successfully removed.'))
|
||||
it 'removes the contact' do
|
||||
_, updates, message = remove_command
|
||||
|
||||
expect(updates).to eq(remove_contacts: [existing_contact.email])
|
||||
expect(message).to eq(_('One or more contacts were successfully removed.'))
|
||||
end
|
||||
|
||||
context 'with multiple contacts in the same command' do
|
||||
it 'removes the contact' do
|
||||
_, updates, message = service.execute("/remove_contacts #{existing_contact.email} #{another_contact.email}", issue)
|
||||
|
||||
expect(updates).to eq(remove_contacts: [existing_contact.email, another_contact.email])
|
||||
expect(message).to eq(_('One or more contacts were successfully removed.'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple commands' do
|
||||
it 'removes the contact' do
|
||||
_, updates, message = service.execute("/remove_contacts #{existing_contact.email}\n/remove_contacts #{another_contact.email}", issue)
|
||||
|
||||
expect(updates).to eq(remove_contacts: [existing_contact.email, another_contact.email])
|
||||
expect(message).to eq(_('One or more contacts were successfully removed. One or more contacts were successfully removed.'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ module InvokeRopSteps
|
|||
result_type = :err
|
||||
step_class, returned_message = parse_result_for_step(err_result_for_step, result_type)
|
||||
|
||||
err_results_for_steps[step_class] = Result.err(returned_message)
|
||||
err_results_for_steps[step_class] = Gitlab::Fp::Result.err(returned_message)
|
||||
end
|
||||
|
||||
def add_ok_result_for_step(ok_result_for_step, ok_results_for_steps)
|
||||
result_type = :ok
|
||||
step_class, returned_message = parse_result_for_step(ok_result_for_step, result_type)
|
||||
|
||||
ok_results_for_steps[step_class] = Result.ok(returned_message)
|
||||
ok_results_for_steps[step_class] = Gitlab::Fp::Result.ok(returned_message)
|
||||
end
|
||||
|
||||
def parse_result_for_step(result_for_step, result_type)
|
||||
|
|
@ -88,9 +88,11 @@ module InvokeRopSteps
|
|||
end
|
||||
|
||||
def validate_expected_return_value(expected_return_value)
|
||||
if expected_return_value.is_a?(Hash) || expected_return_value.is_a?(Result) || expected_return_value < RuntimeError
|
||||
return
|
||||
end
|
||||
return_value_is_valid = expected_return_value.is_a?(Hash) ||
|
||||
expected_return_value.is_a?(Gitlab::Fp::Result) ||
|
||||
expected_return_value < RuntimeError
|
||||
|
||||
return if return_value_is_valid
|
||||
|
||||
raise "'and_return_expected_value' argument must be a Hash,Result or a subclass of RuntimeError, " \
|
||||
"but was a #{expected_return_value.class}"
|
||||
|
|
@ -130,7 +132,7 @@ module InvokeRopSteps
|
|||
elsif ok_results_for_steps.key?(step_class)
|
||||
expected_rop_step[:returned_object] = ok_results_for_steps[step_class]
|
||||
elsif step_action == :and_then
|
||||
expected_rop_step[:returned_object] = Result.ok(context_passed_along_steps)
|
||||
expected_rop_step[:returned_object] = Gitlab::Fp::Result.ok(context_passed_along_steps)
|
||||
elsif step_action == :map
|
||||
expected_rop_step[:returned_object] = context_passed_along_steps
|
||||
else
|
||||
|
|
@ -148,7 +150,7 @@ module InvokeRopSteps
|
|||
step => {
|
||||
step_class: Class => step_class,
|
||||
step_class_method: Symbol => step_class_method,
|
||||
returned_object: Result | Hash => returned_object
|
||||
returned_object: Gitlab::Fp::Result | Hash => returned_object
|
||||
}
|
||||
|
||||
set_up_step_class_expectation(
|
||||
|
|
@ -240,7 +242,7 @@ RSpec::Matchers.define :invoke_rop_steps do |rop_steps|
|
|||
validate_expected_return_value(value)
|
||||
expected_return_value = value
|
||||
# noinspection RubyUnusedLocalVariable -- TODO: open issue and add to https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues
|
||||
expected_return_value_matcher = if value.is_a?(Hash) || value.is_a?(Result)
|
||||
expected_return_value_matcher = if value.is_a?(Hash) || value.is_a?(Gitlab::Fp::Result)
|
||||
->(main) { expect(main.call).to eq(value) }
|
||||
else
|
||||
->(main) { expect { main.call }.to raise_error(value) }
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ module ResultMatchers
|
|||
def matches?(actual, &block)
|
||||
@actual = actual
|
||||
|
||||
raise "#{actual} must be a #{::Result}, but it was a #{actual.class}" unless actual.is_a?(::Result)
|
||||
raise "#{actual} must be a Result, but it was a #{actual.class}" unless actual.is_a?(Gitlab::Fp::Result)
|
||||
|
||||
@failure_message_suffix = "be an '#{ok_or_err}' type"
|
||||
return false unless actual.ok? == ok?
|
||||
|
|
|
|||
|
|
@ -849,6 +849,7 @@
|
|||
- './ee/spec/helpers/boards_helper_spec.rb'
|
||||
- './ee/spec/helpers/compliance_management/compliance_framework/group_settings_helper_spec.rb'
|
||||
- './ee/spec/helpers/credentials_inventory_helper_spec.rb'
|
||||
- './ee/spec/helpers/container_registry/container_registry_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/access_tokens_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/admin/identities_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/application_settings_helper_spec.rb'
|
||||
|
|
@ -4594,7 +4595,7 @@
|
|||
- './spec/helpers/commits_helper_spec.rb'
|
||||
- './spec/helpers/components_helper_spec.rb'
|
||||
- './spec/helpers/container_expiration_policies_helper_spec.rb'
|
||||
- './spec/helpers/container_registry_helper_spec.rb'
|
||||
- './spec/helpers/container_registry/container_registry_helper_spec.rb'
|
||||
- './spec/helpers/cookies_helper_spec.rb'
|
||||
- './spec/helpers/dashboard_helper_spec.rb'
|
||||
- './spec/helpers/deploy_tokens_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ RSpec.describe 'result matchers', feature_category: :remote_development do
|
|||
include ResultMatchers
|
||||
|
||||
it 'works with value asserted via argument' do
|
||||
expect(Result.ok(1)).to be_ok_result(1)
|
||||
expect(Result.ok(1)).not_to be_ok_result(2)
|
||||
expect(Result.ok(1)).not_to be_err_result(1)
|
||||
expect(Gitlab::Fp::Result.ok(1)).to be_ok_result(1)
|
||||
expect(Gitlab::Fp::Result.ok(1)).not_to be_ok_result(2)
|
||||
expect(Gitlab::Fp::Result.ok(1)).not_to be_err_result(1)
|
||||
end
|
||||
|
||||
it 'works with value asserted via block' do
|
||||
expect(Result.err('hello')).to be_err_result do |result_value|
|
||||
expect(Gitlab::Fp::Result.err('hello')).to be_err_result do |result_value|
|
||||
expect(result_value).to match(/hello/i)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue