Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-12-23 00:10:18 +00:00
parent 6c68583a42
commit 2994a84f01
48 changed files with 665 additions and 232 deletions

View File

@ -4564,7 +4564,6 @@ Layout/LineLength:
- 'spec/services/packages/nuget/search_service_spec.rb' - 'spec/services/packages/nuget/search_service_spec.rb'
- 'spec/services/packages/nuget/update_package_from_metadata_service_spec.rb' - 'spec/services/packages/nuget/update_package_from_metadata_service_spec.rb'
- 'spec/services/packages/rubygems/process_gem_service_spec.rb' - 'spec/services/packages/rubygems/process_gem_service_spec.rb'
- 'spec/services/packages/terraform_module/create_package_service_spec.rb'
- 'spec/services/personal_access_tokens/create_service_spec.rb' - 'spec/services/personal_access_tokens/create_service_spec.rb'
- 'spec/services/post_receive_service_spec.rb' - 'spec/services/post_receive_service_spec.rb'
- 'spec/services/projects/apple_target_platform_detector_service_spec.rb' - 'spec/services/projects/apple_target_platform_detector_service_spec.rb'

View File

@ -3690,15 +3690,11 @@ RSpec/FeatureCategory:
- 'spec/lib/gitlab/lazy_spec.rb' - 'spec/lib/gitlab/lazy_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb' - 'spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/client_spec.rb' - 'spec/lib/gitlab/legacy_github_import/client_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb' - 'spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb' - 'spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb' - 'spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/project_creator_spec.rb' - 'spec/lib/gitlab/legacy_github_import/project_creator_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb' - 'spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb' - 'spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb'
- 'spec/lib/gitlab/lets_encrypt/challenge_spec.rb' - 'spec/lib/gitlab/lets_encrypt/challenge_spec.rb'
- 'spec/lib/gitlab/lets_encrypt/client_spec.rb' - 'spec/lib/gitlab/lets_encrypt/client_spec.rb'

View File

@ -46,6 +46,7 @@ export default () => {
gl.mrWidgetData.can_create_pipeline_in_target_project, gl.mrWidgetData.can_create_pipeline_in_target_project,
), ),
commitPathTemplate: gl.mrWidgetData.commit_path_template, commitPathTemplate: gl.mrWidgetData.commit_path_template,
canAdminVulnerability: gl.mrWidgetData.can_admin_vulnerability,
dismissalDescriptions, dismissalDescriptions,
}, },
...MrWidgetOptions, ...MrWidgetOptions,

View File

@ -43,6 +43,7 @@ export default {
:key="opt.value" :key="opt.value"
:disabled="!!opt.disabled" :disabled="!!opt.disabled"
:selected="value === opt.value" :selected="value === opt.value"
v-bind="opt.props"
@click="$emit('input', opt.value)" @click="$emit('input', opt.value)"
> >
<slot name="button-content" v-bind="opt">{{ opt.text }}</slot> <slot name="button-content" v-bind="opt">{{ opt.text }}</slot>

View File

@ -51,6 +51,16 @@ module Mutations
required: false, required: false,
description: copy_field_description(Types::Namespace::PackageSettingsType, :nuget_duplicate_exception_regex) description: copy_field_description(Types::Namespace::PackageSettingsType, :nuget_duplicate_exception_regex)
argument :terraform_module_duplicates_allowed,
GraphQL::Types::Boolean,
required: false,
description: copy_field_description(Types::Namespace::PackageSettingsType, :terraform_module_duplicates_allowed)
argument :terraform_module_duplicate_exception_regex,
Types::UntrustedRegexp,
required: false,
description: copy_field_description(Types::Namespace::PackageSettingsType, :terraform_module_duplicate_exception_regex)
argument :maven_package_requests_forwarding, argument :maven_package_requests_forwarding,
GraphQL::Types::Boolean, GraphQL::Types::Boolean,
required: false, required: false,

View File

@ -35,6 +35,12 @@ module Types
field :pypi_package_requests_forwarding, GraphQL::Types::Boolean, field :pypi_package_requests_forwarding, GraphQL::Types::Boolean,
null: true, null: true,
description: 'Indicates whether PyPI package forwarding is allowed for this namespace.' description: 'Indicates whether PyPI package forwarding is allowed for this namespace.'
field :terraform_module_duplicate_exception_regex, Types::UntrustedRegexp,
null: true,
description: 'When terraform_module_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.'
field :terraform_module_duplicates_allowed, GraphQL::Types::Boolean,
null: false,
description: 'Indicates whether duplicate Terraform packages are allowed for this namespace.'
field :lock_maven_package_requests_forwarding, GraphQL::Types::Boolean, field :lock_maven_package_requests_forwarding, GraphQL::Types::Boolean,
null: false, null: false,

View File

@ -1,14 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
module EnvironmentHelper module EnvironmentHelper
# rubocop: disable CodeReuse/ActiveRecord
def environment_for_build(project, build)
return unless build.environment
project.environments.find_by(name: build.expanded_environment_name)
end
# rubocop: enable CodeReuse/ActiveRecord
def deployment_path(deployment) def deployment_path(deployment)
[deployment.project, deployment.deployable] [deployment.project, deployment.deployable]
end end

View File

@ -12,7 +12,7 @@ class Namespace::PackageSetting < ApplicationRecord
PackageSettingNotImplemented = Class.new(StandardError) PackageSettingNotImplemented = Class.new(StandardError)
PACKAGES_WITH_SETTINGS = %w[maven generic nuget].freeze PACKAGES_WITH_SETTINGS = %w[maven generic nuget terraform_module].freeze
belongs_to :namespace, inverse_of: :package_setting_relation belongs_to :namespace, inverse_of: :package_setting_relation
@ -24,6 +24,14 @@ class Namespace::PackageSetting < ApplicationRecord
validates :nuget_duplicates_allowed, inclusion: { in: [true, false] } validates :nuget_duplicates_allowed, inclusion: { in: [true, false] }
validates :nuget_duplicate_exception_regex, untrusted_regexp: true, length: { maximum: 255 } validates :nuget_duplicate_exception_regex, untrusted_regexp: true, length: { maximum: 255 }
validates :nuget_symbol_server_enabled, inclusion: { in: [true, false] } validates :nuget_symbol_server_enabled, inclusion: { in: [true, false] }
validates :terraform_module_duplicates_allowed, inclusion: { in: [true, false] }
validates :terraform_module_duplicate_exception_regex, untrusted_regexp: true, length: { maximum: 255 }
scope :namespace_id_in, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :with_terraform_module_duplicates_allowed_or_exception_regex, -> do
where(terraform_module_duplicates_allowed: true)
.or(where.not(terraform_module_duplicate_exception_regex: ''))
end
class << self class << self
def duplicates_allowed?(package) def duplicates_allowed?(package)

View File

@ -13,12 +13,14 @@ module Organizations
rule { admin }.policy do rule { admin }.policy do
enable :admin_organization enable :admin_organization
enable :create_group
enable :read_organization enable :read_organization
enable :read_organization_user enable :read_organization_user
end end
rule { organization_user }.policy do rule { organization_user }.policy do
enable :admin_organization enable :admin_organization
enable :create_group
enable :read_organization enable :read_organization
enable :read_organization_user enable :read_organization_user
end end

View File

@ -92,9 +92,32 @@ module Groups
end end
end end
unless organization_setting_valid?
# We are unsetting this here to match behavior of invalid parent_id above and protect against possible
# committing to the database of a value that isn't allowed.
@group.organization = nil
message = s_("CreateGroup|You don't have permission to create a group in the provided organization.")
@group.errors.add(:organization_id, message)
return false
end
true true
end end
def organization_setting_valid?
# we check for the params presence explicitly since:
# 1. We have a default organization_id at db level set and organization exists and may not have the entry
# in organization_users table to allow authorization. This shouldn't be the case longterm as we
# plan on populating organization_users correctly.
# 2. We shouldn't need to check if this is allowed if the user didn't try to set it themselves. i.e.
# provided in the params
return true if params[:organization_id].blank?
return true if @group.organization.blank?
can?(current_user, :create_group, @group.organization)
end
def can_use_visibility_level? def can_use_visibility_level?
unless Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level) unless Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level)
deny_visibility_level(@group) deny_visibility_level(@group)

View File

@ -12,6 +12,8 @@ module Namespaces
maven_package_requests_forwarding maven_package_requests_forwarding
nuget_duplicates_allowed nuget_duplicates_allowed
nuget_duplicate_exception_regex nuget_duplicate_exception_regex
terraform_module_duplicates_allowed
terraform_module_duplicate_exception_regex
npm_package_requests_forwarding npm_package_requests_forwarding
pypi_package_requests_forwarding pypi_package_requests_forwarding
lock_maven_package_requests_forwarding lock_maven_package_requests_forwarding

View File

@ -6,10 +6,20 @@ module Packages
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
def execute def execute
return error('Version is empty.', 400) if params[:module_version].blank? if params[:module_version].blank?
return error('Access Denied', 403) if current_package_exists_elsewhere? return ServiceResponse.error(message: 'Version is empty.', reason: :bad_request)
return error('Package version already exists.', 403) if current_package_version_exists? end
return error('File is too large.', 400) if file_size_exceeded?
if duplicates_not_allowed? && current_package_exists_elsewhere?
return ServiceResponse.error(
message: 'A package with the same name already exists in the namespace',
reason: :forbidden
)
end
if current_package_version_exists?
return ServiceResponse.error(message: 'Package version already exists.', reason: :forbidden)
end
ApplicationRecord.transaction { create_terraform_module_package! } ApplicationRecord.transaction { create_terraform_module_package! }
end end
@ -24,6 +34,15 @@ module Packages
package package
end end
def duplicates_not_allowed?
return true if package_settings_with_duplicates_allowed.blank?
package_settings_with_duplicates_allowed.none? do |setting|
setting.terraform_module_duplicates_allowed ||
::Gitlab::UntrustedRegexp.new("\\A#{setting.terraform_module_duplicate_exception_regex}\\z").match?(name)
end
end
def current_package_exists_elsewhere? def current_package_exists_elsewhere?
::Packages::Package ::Packages::Package
.for_projects(project.root_namespace.all_projects.id_not_in(project.id)) .for_projects(project.root_namespace.all_projects.id_not_in(project.id))
@ -62,9 +81,13 @@ module Packages
} }
end end
def file_size_exceeded? def package_settings_with_duplicates_allowed
project.actual_limits.exceeded?(:generic_packages_max_file_size, params[:file].size) ::Namespace::PackageSetting
.select(:terraform_module_duplicates_allowed, :terraform_module_duplicate_exception_regex)
.namespace_id_in(project.namespace.self_and_ancestor_ids)
.with_terraform_module_duplicates_allowed_or_exception_regex
end end
strong_memoize_attr :package_settings_with_duplicates_allowed
end end
end end
end end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
class AddTerraformModuleDuplicatesAllowedToNamespacePackageSettings < Gitlab::Database::Migration[2.2]
milestone '16.8'
disable_ddl_transaction!
def up
with_lock_retries do
add_column(:namespace_package_settings,
:terraform_module_duplicates_allowed,
:boolean,
null: false,
default: false,
if_not_exists: true
)
add_column(:namespace_package_settings,
:terraform_module_duplicate_exception_regex,
:text,
null: false,
default: '',
if_not_exists: true
)
end
add_text_limit(:namespace_package_settings, :terraform_module_duplicate_exception_regex, 255)
end
def down
with_lock_retries do
remove_column(:namespace_package_settings, :terraform_module_duplicates_allowed, if_exists: true)
remove_column(:namespace_package_settings, :terraform_module_duplicate_exception_regex, if_exists: true)
end
end
end

View File

@ -3,24 +3,10 @@
class ChangeICodeReviewCreateMrKeysFromRedisHllToRedis < Gitlab::Database::Migration[2.2] class ChangeICodeReviewCreateMrKeysFromRedisHllToRedis < Gitlab::Database::Migration[2.2]
milestone '16.8' milestone '16.8'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
REDIS_HLL_PREFIX = '{hll_counters}_i_code_review_create_mr'
REDIS_PREFIX = '{event_counters}_i_code_review_user_create_mr'
def up def up
# For each old (redis_hll) counter we find the corresponding target (redis) counter and add # no-op
# old value to migrate a metric. If the Redis counter does not exist, it will get created. #
# Since the RedisHLL keys expire after 6 weeks, we will migrate 6 keys at the most. # Removed due to https://gitlab.com/gitlab-com/gl-infra/production/-/issues/17321
Gitlab::Redis::SharedState.with do |redis|
redis.scan_each(match: "#{REDIS_HLL_PREFIX}-*") do |key|
redis_key = key.sub(REDIS_HLL_PREFIX, REDIS_PREFIX)
redis_hll_value = redis.pfcount(key)
redis.incrby(redis_key, redis_hll_value)
end
end
end end
def down def down

View File

@ -0,0 +1 @@
a18e718e99c23ae6db929929a905af0db72e3a3734d3c33e12ec2cdb44467f6d

View File

@ -19505,9 +19505,12 @@ CREATE TABLE namespace_package_settings (
nuget_duplicates_allowed boolean DEFAULT true NOT NULL, nuget_duplicates_allowed boolean DEFAULT true NOT NULL,
nuget_duplicate_exception_regex text DEFAULT ''::text NOT NULL, nuget_duplicate_exception_regex text DEFAULT ''::text NOT NULL,
nuget_symbol_server_enabled boolean DEFAULT false NOT NULL, nuget_symbol_server_enabled boolean DEFAULT false NOT NULL,
terraform_module_duplicates_allowed boolean DEFAULT false NOT NULL,
terraform_module_duplicate_exception_regex text DEFAULT ''::text NOT NULL,
CONSTRAINT check_31340211b1 CHECK ((char_length(generic_duplicate_exception_regex) <= 255)), CONSTRAINT check_31340211b1 CHECK ((char_length(generic_duplicate_exception_regex) <= 255)),
CONSTRAINT check_d63274b2b6 CHECK ((char_length(maven_duplicate_exception_regex) <= 255)), CONSTRAINT check_d63274b2b6 CHECK ((char_length(maven_duplicate_exception_regex) <= 255)),
CONSTRAINT check_eedcf85c48 CHECK ((char_length(nuget_duplicate_exception_regex) <= 255)) CONSTRAINT check_eedcf85c48 CHECK ((char_length(nuget_duplicate_exception_regex) <= 255)),
CONSTRAINT check_f10503f1ad CHECK ((char_length(terraform_module_duplicate_exception_regex) <= 255))
); );
CREATE TABLE namespace_root_storage_statistics ( CREATE TABLE namespace_root_storage_statistics (

View File

@ -7788,6 +7788,8 @@ Input type: `UpdateNamespacePackageSettingsInput`
| <a id="mutationupdatenamespacepackagesettingsnugetduplicatesallowed"></a>`nugetDuplicatesAllowed` | [`Boolean`](#boolean) | Indicates whether duplicate NuGet packages are allowed for this namespace. | | <a id="mutationupdatenamespacepackagesettingsnugetduplicatesallowed"></a>`nugetDuplicatesAllowed` | [`Boolean`](#boolean) | Indicates whether duplicate NuGet packages are allowed for this namespace. |
| <a id="mutationupdatenamespacepackagesettingsnugetsymbolserverenabled"></a>`nugetSymbolServerEnabled` | [`Boolean`](#boolean) | Indicates wheather the NuGet symbol server is enabled for this namespace. | | <a id="mutationupdatenamespacepackagesettingsnugetsymbolserverenabled"></a>`nugetSymbolServerEnabled` | [`Boolean`](#boolean) | Indicates wheather the NuGet symbol server is enabled for this namespace. |
| <a id="mutationupdatenamespacepackagesettingspypipackagerequestsforwarding"></a>`pypiPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether PyPI package forwarding is allowed for this namespace. | | <a id="mutationupdatenamespacepackagesettingspypipackagerequestsforwarding"></a>`pypiPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether PyPI package forwarding is allowed for this namespace. |
| <a id="mutationupdatenamespacepackagesettingsterraformmoduleduplicateexceptionregex"></a>`terraformModuleDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When terraform_module_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
| <a id="mutationupdatenamespacepackagesettingsterraformmoduleduplicatesallowed"></a>`terraformModuleDuplicatesAllowed` | [`Boolean`](#boolean) | Indicates whether duplicate Terraform packages are allowed for this namespace. |
#### Fields #### Fields
@ -23730,6 +23732,8 @@ Namespace-level Package Registry settings.
| <a id="packagesettingsnugetsymbolserverenabled"></a>`nugetSymbolServerEnabled` | [`Boolean!`](#boolean) | Indicates wheather the NuGet symbol server is enabled for this namespace. | | <a id="packagesettingsnugetsymbolserverenabled"></a>`nugetSymbolServerEnabled` | [`Boolean!`](#boolean) | Indicates wheather the NuGet symbol server is enabled for this namespace. |
| <a id="packagesettingspypipackagerequestsforwarding"></a>`pypiPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether PyPI package forwarding is allowed for this namespace. | | <a id="packagesettingspypipackagerequestsforwarding"></a>`pypiPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether PyPI package forwarding is allowed for this namespace. |
| <a id="packagesettingspypipackagerequestsforwardinglocked"></a>`pypiPackageRequestsForwardingLocked` | [`Boolean!`](#boolean) | Indicates whether PyPI package forwarding settings are locked by a parent namespace. | | <a id="packagesettingspypipackagerequestsforwardinglocked"></a>`pypiPackageRequestsForwardingLocked` | [`Boolean!`](#boolean) | Indicates whether PyPI package forwarding settings are locked by a parent namespace. |
| <a id="packagesettingsterraformmoduleduplicateexceptionregex"></a>`terraformModuleDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When terraform_module_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
| <a id="packagesettingsterraformmoduleduplicatesallowed"></a>`terraformModuleDuplicatesAllowed` | [`Boolean!`](#boolean) | Indicates whether duplicate Terraform packages are allowed for this namespace. |
### `PackageTag` ### `PackageTag`

View File

@ -816,31 +816,32 @@ POST /groups
Parameters: Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ------------------------------------------------------- | ------- | -------- | ----------- | | ------------------------------------------------------- | ------- | -------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name` | string | yes | The name of the group. | | `name` | string | yes | The name of the group. |
| `path` | string | yes | The path of the group. | | `path` | string | yes | The path of the group. |
| `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. | | `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. |
| `avatar` | mixed | no | Image file for avatar of the group. [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/-/issues/36681) | | `avatar` | mixed | no | Image file for avatar of the group. [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/-/issues/36681) |
| `default_branch_protection` | integer | no | See [Options for `default_branch_protection`](#options-for-default_branch_protection). Default to the global level default branch protection setting. | | `default_branch_protection` | integer | no | See [Options for `default_branch_protection`](#options-for-default_branch_protection). Default to the global level default branch protection setting. |
| `default_branch_protection_defaults` | hash | no | See [Options for `default_branch_protection_defaults`](#options-for-default_branch_protection_defaults). | | `default_branch_protection_defaults` | hash | no | See [Options for `default_branch_protection_defaults`](#options-for-default_branch_protection_defaults). |
| `description` | string | no | The group's description. | | `description` | string | no | The group's description. |
| `emails_disabled` | boolean | no | _([Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127899) in GitLab 16.5.)_ Disable email notifications. Use `emails_enabled` instead. | | `emails_disabled` | boolean | no | _([Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127899) in GitLab 16.5.)_ Disable email notifications. Use `emails_enabled` instead. |
| `emails_enabled` | boolean | no | Enable email notifications. | | `emails_enabled` | boolean | no | Enable email notifications. |
| `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. | | `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned. | | `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned. |
| `parent_id` | integer | no | The parent group ID for creating nested group. | | `organization_id` | integer | no | The organization ID for the group. |
| `parent_id` | integer | no | The parent group ID for creating nested group. |
| `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (users with the Maintainer role), or `developer` (users with the Developer or Maintainer role). | | `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (users with the Maintainer role), or `developer` (users with the Developer or Maintainer role). |
| `request_access_enabled` | boolean | no | Allow users to request member access. | | `request_access_enabled` | boolean | no | Allow users to request member access. |
| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. | | `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. |
| `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. | | `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. |
| `subgroup_creation_level` | string | no | Allowed to [create subgroups](../user/group/subgroups/index.md#create-a-subgroup). Can be `owner` (Owners), or `maintainer` (users with the Maintainer role). | | `subgroup_creation_level` | string | no | Allowed to [create subgroups](../user/group/subgroups/index.md#create-a-subgroup). Can be `owner` (Owners), or `maintainer` (users with the Maintainer role). |
| `two_factor_grace_period` | integer | no | Time before Two-factor authentication is enforced (in hours). | | `two_factor_grace_period` | integer | no | Time before Two-factor authentication is enforced (in hours). |
| `visibility` | string | no | The group's visibility. Can be `private`, `internal`, or `public`. | | `visibility` | string | no | The group's visibility. Can be `private`, `internal`, or `public`. |
| `membership_lock` **(PREMIUM ALL)** | boolean | no | Users cannot be added to projects in this group. | | `membership_lock` **(PREMIUM ALL)** | boolean | no | Users cannot be added to projects in this group. |
| `extra_shared_runners_minutes_limit` **(PREMIUM SELF)** | integer | no | Can be set by administrators only. Additional compute minutes for this group. | | `extra_shared_runners_minutes_limit` **(PREMIUM SELF)** | integer | no | Can be set by administrators only. Additional compute minutes for this group. |
| `shared_runners_minutes_limit` **(PREMIUM SELF)** | integer | no | Can be set by administrators only. Maximum number of monthly compute minutes for this group. Can be `nil` (default; inherit system default), `0` (unlimited), or `> 0`. | | `shared_runners_minutes_limit` **(PREMIUM SELF)** | integer | no | Can be set by administrators only. Maximum number of monthly compute minutes for this group. Can be `nil` (default; inherit system default), `0` (unlimited), or `> 0`. |
| `wiki_access_level` **(PREMIUM ALL)** | string | no | The wiki access level. Can be `disabled`, `private`, or `enabled`. | | `wiki_access_level` **(PREMIUM ALL)** | string | no | The wiki access level. Can be `disabled`, `private`, or `enabled`. |
### Options for `default_branch_protection` ### Options for `default_branch_protection`

View File

@ -70,6 +70,8 @@ Setting | Table | Description
`nuget_duplicates_allowed` | `namespace_package_settings` | Allow or prevent duplicate NuGet packages. `nuget_duplicates_allowed` | `namespace_package_settings` | Allow or prevent duplicate NuGet packages.
`nuget_duplicate_exception_regex` | `namespace_package_settings` | Regex defining NuGet packages that are allowed to be duplicate when duplicates are not allowed. `nuget_duplicate_exception_regex` | `namespace_package_settings` | Regex defining NuGet packages that are allowed to be duplicate when duplicates are not allowed.
`nuget_symbol_server_enabled` | `namespace_package_settings` | Enable or disable the NuGet symbol server. `nuget_symbol_server_enabled` | `namespace_package_settings` | Enable or disable the NuGet symbol server.
`terraform_module_duplicates_allowed` | `namespace_package_settings` | Allow or prevent duplicate Terraform module packages.
`terraform_module_duplicate_exception_regex` | `namespace_package_settings` | Regex defining Terraform module packages that are allowed to be duplicate when duplicates are not allowed.
Dependency Proxy Cleanup Policies - `ttl` | `dependency_proxy_image_ttl_group_policies` | Number of days to retain an unused Dependency Proxy file before it is removed. Dependency Proxy Cleanup Policies - `ttl` | `dependency_proxy_image_ttl_group_policies` | Number of days to retain an unused Dependency Proxy file before it is removed.
Dependency Proxy - `enabled` | `dependency_proxy_image_ttl_group_policies` | Enable or disable the Dependency Proxy cleanup policy. Dependency Proxy - `enabled` | `dependency_proxy_image_ttl_group_policies` | Enable or disable the Dependency Proxy cleanup policy.

View File

@ -48,10 +48,10 @@ You can publish Terraform modules by using the [Terraform Module Registry API](.
Prerequisites: Prerequisites:
- The package name and version [must be unique in the top-level namespace](#how-module-resolution-works). - Unless [duplicates are allowed](#allow-duplicate-terraform-modules), the package name and version [must be unique in the top-level namespace](#how-module-resolution-works).
- Your project and group names must not include a dot (`.`). For example, `source = "gitlab.example.com/my.group/project.name"`. - Your project and group names must not include a dot (`.`). For example, `source = "gitlab.example.com/my.group/project.name"`.
- You must [authenticate with the API](../../../api/rest/index.md#authentication). If authenticating with a deploy token, it must be configured with the `write_package_registry` scope. - You must [authenticate with the API](../../../api/rest/index.md#authentication). If authenticating with a deploy token, it must be configured with the `write_package_registry` scope.
- The name of a module [must be unique in the scope of its group](#how-module-resolution-works), otherwise an - Unless [duplicates are allowed](#allow-duplicate-terraform-modules), the name of a module [must be unique in the scope of its group](#how-module-resolution-works), otherwise an
[error occurs](#troubleshooting). [error occurs](#troubleshooting).
```plaintext ```plaintext
@ -157,6 +157,22 @@ upload:
To trigger this upload job, add a Git tag to your commit. Ensure the tag follows the [Semantic versioning specification](https://semver.org/) that Terraform requires. The `rules:if: $CI_COMMIT_TAG` ensures that only tagged commits to your repository trigger the module upload job. To trigger this upload job, add a Git tag to your commit. Ensure the tag follows the [Semantic versioning specification](https://semver.org/) that Terraform requires. The `rules:if: $CI_COMMIT_TAG` ensures that only tagged commits to your repository trigger the module upload job.
For other ways to control jobs in your CI/CD pipeline, refer to the [`.gitlab-ci.yml`](../../../ci/yaml/index.md) keyword reference. For other ways to control jobs in your CI/CD pipeline, refer to the [`.gitlab-ci.yml`](../../../ci/yaml/index.md) keyword reference.
### Allow duplicate Terraform modules
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368040) in GitLab 16.8.
By default, the Terraform Module Registry enforces uniqueness for module names in the same namespace. To allow publishing duplicate module names:
- Enable `terraform_module_duplicates_allowed` for the namespace with the [GraphQl API](../../../api/graphql/reference/index.md#packagesettings).
To allow duplicates with specific names:
1. Ensure `terraform_module_duplicates_allowed` is disabled.
1. Use `terraform_module_duplicate_exception_regex` to define a regex pattern for the module names you want to allow duplicates for.
The top-level namespace setting takes precedence over the child namespace settings.
For example, if you enable `terraform_module_duplicates_allowed` for a group, and disable it for a subgroup, duplicates are allowed for all projects in the group and its subgroups.
## Reference a Terraform module ## Reference a Terraform module
Prerequisites: Prerequisites:
@ -209,6 +225,8 @@ module "<module>" {
If you need to reference the latest version of a module, you can omit the `<module-version>` from the source URL. To prevent future issues, you should reference a specific version if possible. If you need to reference the latest version of a module, you can omit the `<module-version>` from the source URL. To prevent future issues, you should reference a specific version if possible.
If there are [duplicate module names](#allow-duplicate-terraform-modules) in the same namespace, referencing the module from the namespace level installs the recently published module. To reference a specific version of a duplicate module, use the [project-level](#from-a-project) source type.
## Download a Terraform module ## Download a Terraform module
To download a Terraform module: To download a Terraform module:
@ -275,4 +293,4 @@ For examples of the Terraform Module Registry, check the projects below:
## Troubleshooting ## Troubleshooting
- Publishing a module with a duplicate name results in a `{"message":"Access Denied"}` error. There's an ongoing discussion about allowing duplicate module names [in this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/368040). - Publishing a module with a duplicate name results in a `{"message":"A package with the same name already exists in the namespace"}` error.

View File

@ -395,6 +395,6 @@ third-party Git clients.
### Branch names are case-sensitive ### Branch names are case-sensitive
Branch names in `git` are case-sensitive. When configuring your protected branch Branch names in `git` are case-sensitive. When configuring your protected branch,
or [target branch rule](repository/branches/index.md#configure-rules-for-target-branches), or your [target branch workflow](repository/branches/index.md#configure-workflows-for-target-branches),
`dev` is not the same `DEV` or `Dev`. `dev` is not the same `DEV` or `Dev`.

View File

@ -287,58 +287,58 @@ To do this:
1. Select **Delete merged branches**. 1. Select **Delete merged branches**.
1. In the dialog, enter the word `delete` to confirm, then select **Delete merged branches**. 1. In the dialog, enter the word `delete` to confirm, then select **Delete merged branches**.
## Configure rules for target branches **(PREMIUM ALL)** ## Configure workflows for target branches **(PREMIUM ALL)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127115) in GitLab 16.4 [with a flag](../../../../administration/feature_flags.md) named `target_branch_rules_flag`. Enabled by default. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127115) in GitLab 16.4 [with a flag](../../../../administration/feature_flags.md) named `target_branch_rules_flag`. Enabled by default.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136431) in GitLab 16.7. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136431) in GitLab 16.7.
Some projects use multiple long-term branches for development, like `develop` and `qa`. Some projects use multiple long-term branches for development, like `develop` and `qa`.
In these projects, you might want to keep `main` as the default branch, but expect In these projects, you might want to keep `main` as the default branch, but expect
merge requests to target `develop` or `qa` instead. Target branch rules help ensure merge requests to target `develop` or `qa` instead. Target branch workflows help ensure
merge requests target the appropriate development branch for your project. merge requests target the appropriate development branch for your project.
When you create a merge request, the rule checks the name of the branch. If the When you create a merge request, the workflow checks the name of the branch. If the
branch name matches the rule, the merge request targets the branch you specify branch name matches the workflow, the merge request targets the branch you specify. If the branch name does not match, the merge request targets the
in the rule. If the branch name does not match, the merge request targets the
default branch of the project. default branch of the project.
Prerequisites: Prerequisites:
- You must have at least the Maintainer role. - You must have at least the Maintainer role.
To create a target branch rule: To create a target branch workflow:
1. On the left sidebar, select **Search or go to** and find your project. 1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > Merge requests**. 1. Select **Settings > Merge requests**.
1. Select **Add target branch rule**. 1. Scroll down to **Merge request branch workflow**
1. For **Rule name**, provide a string or wild card to compare against branch names. 1. Select **Add branch target**.
1. Select the **Target branch** to use when the branch name matches the **Rule name**. 1. For **Branch name pattern**, provide a string or wild card to compare against branch names.
1. Select the **Target branch** to use when the branch name matches the **Branch name pattern**.
1. Select **Save**. 1. Select **Save**.
### Example ### Example
You could configure your project to have the following target branch rules: You could configure your project to have the following target branch workflows:
| Rule name | Target branch | | Branch name pattern | Target branch |
|-------------|---------------| |-------------|---------------|
| `feature/*` | `develop` | | `feature/*` | `develop` |
| `bug/*` | `develop` | | `bug/*` | `develop` |
| `release/*` | `main` | | `release/*` | `main` |
These rules simplify the process of creating merge requests for a project that: These target branches simplify the process of creating merge requests for a project that:
- Uses `main` to represent the deployed state of your application. - Uses `main` to represent the deployed state of your application.
- Tracks current, unreleased development work in another long-running branch, like `develop`. - Tracks current, unreleased development work in another long-running branch, like `develop`.
If your workflow initially places new features in `develop` instead of `main`, these rules If your workflow initially places new features in `develop` instead of `main`, these target branches
ensure all branches matching either `feature/*` or `bug/*` do not target `main` by mistake. ensure all branches matching either `feature/*` or `bug/*` do not target `main` by mistake.
When you're ready to release to `main`, create a branch named `release/*`, and the rules When you're ready to release to `main`, create a branch named `release/*`, and
ensure this branch targets `main`. ensure this branch targets `main`.
## Delete a target branch rule ## Delete a target branch workflow
When you remove a target branch rule, existing merge requests remain unchanged. When you remove a target branch workflow, existing merge requests remain unchanged.
Prerequisites: Prerequisites:
@ -348,7 +348,7 @@ To do this:
1. On the left sidebar, select **Search or go to** and find your project. 1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > Merge requests**. 1. Select **Settings > Merge requests**.
1. Select **Delete** on the rule you want to delete. 1. Select **Delete** on the branch target you want to delete.
## Related topics ## Related topics

View File

@ -23,6 +23,7 @@ module API
expose :full_name, :full_path expose :full_name, :full_path
expose :created_at expose :created_at
expose :parent_id expose :parent_id
expose :organization_id
expose :shared_runners_setting expose :shared_runners_setting
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes

View File

@ -213,11 +213,15 @@ module API
requires :name, type: String, desc: 'The name of the group' requires :name, type: String, desc: 'The name of the group'
requires :path, type: String, desc: 'The path of the group' requires :path, type: String, desc: 'The path of the group'
optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group' optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
optional :organization_id, type: Integer, desc: 'The organization id for the group'
use :optional_params use :optional_params
end end
post feature_category: :groups_and_projects, urgency: :low do post feature_category: :groups_and_projects, urgency: :low do
parent_group = find_group!(params[:parent_id]) if params[:parent_id].present? organization = find_organization!(params[:organization_id]) if params[:organization_id].present?
authorize! :create_group, organization if organization
parent_group = find_group!(params[:parent_id], organization: organization) if params[:parent_id].present?
if parent_group if parent_group
authorize! :create_subgroup, parent_group authorize! :create_subgroup, parent_group
else else

View File

@ -211,18 +211,25 @@ module API
not_found!('Pipeline') not_found!('Pipeline')
end end
def find_organization!(id)
organization = Organizations::Organization.find_by_id(id)
check_organization_access(organization)
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def find_group(id) def find_group(id, organization: nil)
collection = organization.present? ? Group.in_organization(organization) : Group.all
if id.to_s =~ INTEGER_ID_REGEX if id.to_s =~ INTEGER_ID_REGEX
Group.find_by(id: id) collection.find_by(id: id)
else else
Group.find_by_full_path(id) collection.find_by_full_path(id)
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def find_group!(id) def find_group!(id, organization: nil)
group = find_group(id) group = find_group(id, organization: organization)
check_group_access(group) check_group_access(group)
end end
@ -835,6 +842,12 @@ module API
@sudo_identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER] @sudo_identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
end end
def check_organization_access(organization)
return organization if can?(current_user, :read_organization, organization)
not_found!('Organization')
end
def secret_token def secret_token
Gitlab::Shell.secret_token Gitlab::Shell.secret_token
end end

View File

@ -171,7 +171,7 @@ module API
.new(authorized_user_project, current_user, create_package_file_params) .new(authorized_user_project, current_user, create_package_file_params)
.execute .execute
render_api_error!(result[:message], result[:http_status]) if result[:status] == :error render_api_error!(result.message, result.reason) if result.error?
track_package_event('push_package', :terraform_module, project: authorized_user_project, track_package_event('push_package', :terraform_module, project: authorized_user_project,
namespace: authorized_user_project.namespace) namespace: authorized_user_project.namespace)

View File

@ -23,7 +23,7 @@ module Gitlab
def gitlab_id def gitlab_id
return @gitlab_id if defined?(@gitlab_id) return @gitlab_id if defined?(@gitlab_id)
@gitlab_id = find_by_external_uid || find_by_email @gitlab_id = find_by_email
end end
private private
@ -45,14 +45,6 @@ module Gitlab
User.find_by_any_email(email) User.find_by_any_email(email)
.try(:id) .try(:id)
end end
# rubocop: disable CodeReuse/ActiveRecord
def find_by_external_uid
return unless id
User.by_provider_and_extern_uid(:github, id).select(:id).first&.id
end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
end end

View File

@ -14578,6 +14578,9 @@ msgstr ""
msgid "CreateGitTag|Set tag message" msgid "CreateGitTag|Set tag message"
msgstr "" msgstr ""
msgid "CreateGroup|You don't have permission to create a group in the provided organization."
msgstr ""
msgid "CreateGroup|You dont have permission to create a subgroup in this group." msgid "CreateGroup|You dont have permission to create a subgroup in this group."
msgstr "" msgstr ""

View File

@ -15,6 +15,9 @@ FactoryBot.define do
nuget_symbol_server_enabled { false } nuget_symbol_server_enabled { false }
terraform_module_duplicates_allowed { false }
terraform_module_duplicate_exception_regex { 'foo' }
trait :group do trait :group do
namespace { association(:group) } namespace { association(:group) }
end end

View File

@ -122,6 +122,7 @@ describe('~/vue_shared/components/segmented_control_button_group.vue', () => {
[[{ value: '1' }]], [[{ value: '1' }]],
[[{ value: 1, disabled: true }]], [[{ value: 1, disabled: true }]],
[[{ value: true, disabled: false }]], [[{ value: true, disabled: false }]],
[[{ value: true, props: { 'data-testid': 'test' } }]],
])('with options=%j, passes validation', (options) => { ])('with options=%j, passes validation', (options) => {
createComponent({ options }); createComponent({ options });

View File

@ -39,7 +39,9 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update, feature_category:
lock_npm_package_requests_forwarding: false, lock_npm_package_requests_forwarding: false,
pypi_package_requests_forwarding: nil, pypi_package_requests_forwarding: nil,
lock_pypi_package_requests_forwarding: false, lock_pypi_package_requests_forwarding: false,
nuget_symbol_server_enabled: false nuget_symbol_server_enabled: false,
terraform_module_duplicates_allowed: false,
terraform_module_duplicate_exception_regex: 'foo'
}, to: { }, to: {
maven_duplicates_allowed: false, maven_duplicates_allowed: false,
maven_duplicate_exception_regex: 'RELEASE', maven_duplicate_exception_regex: 'RELEASE',
@ -53,7 +55,9 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update, feature_category:
lock_npm_package_requests_forwarding: true, lock_npm_package_requests_forwarding: true,
pypi_package_requests_forwarding: true, pypi_package_requests_forwarding: true,
lock_pypi_package_requests_forwarding: true, lock_pypi_package_requests_forwarding: true,
nuget_symbol_server_enabled: true nuget_symbol_server_enabled: true,
terraform_module_duplicates_allowed: true,
terraform_module_duplicate_exception_regex: 'bar'
} }
it_behaves_like 'returning a success' it_behaves_like 'returning a success'
@ -109,7 +113,9 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update, feature_category:
lock_npm_package_requests_forwarding: true, lock_npm_package_requests_forwarding: true,
pypi_package_requests_forwarding: true, pypi_package_requests_forwarding: true,
lock_pypi_package_requests_forwarding: true, lock_pypi_package_requests_forwarding: true,
nuget_symbol_server_enabled: true nuget_symbol_server_enabled: true,
terraform_module_duplicates_allowed: true,
terraform_module_duplicate_exception_regex: 'bar'
} }
end end

View File

@ -33,6 +33,8 @@ RSpec.describe GitlabSchema.types['PackageSettings'], feature_category: :package
npm_package_requests_forwarding_locked npm_package_requests_forwarding_locked
pypi_package_requests_forwarding_locked pypi_package_requests_forwarding_locked
nuget_symbol_server_enabled nuget_symbol_server_enabled
terraform_module_duplicates_allowed
terraform_module_duplicate_exception_regex
] ]
expect(described_class).to include_graphql_fields(*expected_fields) expect(described_class).to include_graphql_fields(*expected_fields)

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Entities::Group, feature_category: :groups_and_projects do
let_it_be(:group) do
base_group = create(:group) { |g| create(:project_statistics, namespace_id: g.id) }
Group.with_statistics.find(base_group.id)
end
subject(:json) { described_class.new(group, { with_custom_attributes: true, statistics: true }).as_json }
it 'returns expected data' do
expect(json.keys).to(
include(
:organization_id, :path, :description, :visibility, :share_with_group_lock, :require_two_factor_authentication,
:two_factor_grace_period, :project_creation_level, :auto_devops_enabled,
:subgroup_creation_level, :emails_disabled, :emails_enabled, :lfs_enabled, :default_branch_protection,
:default_branch_protection_defaults, :avatar_url, :request_access_enabled, :full_name, :full_path, :created_at,
:parent_id, :organization_id, :shared_runners_setting, :custom_attributes, :statistics
)
)
end
end

View File

@ -406,6 +406,37 @@ RSpec.describe API::Helpers, feature_category: :shared do
end end
end end
describe '#find_organization!' do
let_it_be(:organization) { create(:organization) }
let_it_be(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:initial_current_user).and_return(user)
end
context 'when user is authenticated' do
it 'returns requested organization' do
expect(helper.find_organization!(organization.id)).to eq(organization)
end
end
context 'when user is not authenticated' do
let(:user) { nil }
it 'returns requested organization' do
expect(helper.find_organization!(organization.id)).to eq(organization)
end
end
context 'when organization does not exist' do
it 'returns nil' do
expect(helper).to receive(:render_api_error!).with('404 Organization Not Found', 404)
expect(helper.find_organization!(non_existing_record_id)).to be_nil
end
end
end
describe '#find_group!' do describe '#find_group!' do
let_it_be(:group) { create(:group, :public) } let_it_be(:group) { create(:group, :public) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
@ -457,7 +488,7 @@ RSpec.describe API::Helpers, feature_category: :shared do
end end
end end
context 'support for IDs and paths as arguments' do context 'with support for IDs and paths as arguments' do
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:user) { group.first_owner } let(:user) { group.first_owner }
@ -505,6 +536,34 @@ RSpec.describe API::Helpers, feature_category: :shared do
end end
end end
context 'with support for organization as an argument' do
let_it_be(:group) { create(:group) }
let_it_be(:organization) { create(:organization) }
before do
allow(helper).to receive(:current_user).and_return(group.first_owner)
allow(helper).to receive(:job_token_authentication?).and_return(false)
allow(helper).to receive(:authenticate_non_public?).and_return(false)
end
subject { helper.find_group!(group.id, organization: organization) }
context 'when group exists in the organization' do
before do
group.update!(organization: organization)
end
it { is_expected.to eq(group) }
end
context 'when group does not exist in the organization' do
it 'returns nil' do
expect(helper).to receive(:render_api_error!).with('404 Group Not Found', 404)
is_expected.to be_nil
end
end
end
describe '#find_group_by_full_path!' do describe '#find_group_by_full_path!' do
let_it_be(:group) { create(:group, :public) } let_it_be(:group) { create(:group, :public) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter do RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter, feature_category: :importers do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let(:client) { double } let(:client) { double }
let(:octocat) { { id: 123456, login: 'octocat', email: 'octocat@example.com' } } let(:octocat) { { id: 123456, login: 'octocat', email: 'octocat@example.com' } }
@ -76,12 +76,6 @@ RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter do
context 'when author is a GitLab user' do context 'when author is a GitLab user' do
let(:raw) { base.merge(user: octocat) } let(:raw) { base.merge(user: octocat) }
it 'returns GitLab user id associated with GitHub id as author_id' do
gl_user = create(:omniauth_user, extern_uid: octocat[:id], provider: 'github')
expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
end
it 'returns GitLab user id associated with GitHub email as author_id' do it 'returns GitLab user id associated with GitHub email as author_id' do
gl_user = create(:user, email: octocat[:email]) gl_user = create(:user, email: octocat[:email])
@ -89,7 +83,7 @@ RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter do
end end
it 'returns note without created at tag line' do it 'returns note without created at tag line' do
create(:omniauth_user, extern_uid: octocat[:id], provider: 'github') create(:user, email: octocat[:email])
expect(comment.attributes.fetch(:note)).to eq("I'm having a problem with this.") expect(comment.attributes.fetch(:note)).to eq("I'm having a problem with this.")
end end

View File

@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter, feature_category: :importers do
let_it_be(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } let_it_be(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
let(:client) { double } let(:client) { double }
let(:octocat) { { id: 123456, login: 'octocat', email: 'octocat@example.com' } } let(:octocat) { { id: 123456, login: 'octocat', email: 'octocat@example.com' } }
@ -82,12 +82,6 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do
expect(issue.attributes.fetch(:assignee_ids)).to be_empty expect(issue.attributes.fetch(:assignee_ids)).to be_empty
end end
it 'returns GitLab user id associated with GitHub id as assignee_id' do
gl_user = create(:omniauth_user, extern_uid: octocat[:id], provider: 'github')
expect(issue.attributes.fetch(:assignee_ids)).to eq [gl_user.id]
end
it 'returns GitLab user id associated with GitHub email as assignee_id' do it 'returns GitLab user id associated with GitHub email as assignee_id' do
gl_user = create(:user, email: octocat[:email]) gl_user = create(:user, email: octocat[:email])
@ -117,12 +111,6 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do
expect(issue.attributes.fetch(:author_id)).to eq project.creator_id expect(issue.attributes.fetch(:author_id)).to eq project.creator_id
end end
it 'returns GitLab user id associated with GitHub id as author_id' do
gl_user = create(:omniauth_user, extern_uid: octocat[:id], provider: 'github')
expect(issue.attributes.fetch(:author_id)).to eq gl_user.id
end
it 'returns GitLab user id associated with GitHub email as author_id' do it 'returns GitLab user id associated with GitHub email as author_id' do
gl_user = create(:user, email: octocat[:email]) gl_user = create(:user, email: octocat[:email])
@ -130,7 +118,7 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do
end end
it 'returns description without created at tag line' do it 'returns description without created at tag line' do
create(:omniauth_user, extern_uid: octocat[:id], provider: 'github') create(:user, email: octocat[:email])
expect(issue.attributes.fetch(:description)).to eq("I'm having a problem with this.") expect(issue.attributes.fetch(:description)).to eq("I'm having a problem with this.")
end end

View File

@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter, feature_category: :importers do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:client) { double } let(:client) { double }
let(:source_sha) { create(:commit, project: project).id } let(:source_sha) { create(:commit, project: project).id }
@ -136,12 +136,6 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do
expect(pull_request.attributes.fetch(:assignee_id)).to be_nil expect(pull_request.attributes.fetch(:assignee_id)).to be_nil
end end
it 'returns GitLab user id associated with GitHub id as assignee_id' do
gl_user = create(:omniauth_user, extern_uid: octocat[:id], provider: 'github')
expect(pull_request.attributes.fetch(:assignee_id)).to eq gl_user.id
end
it 'returns GitLab user id associated with GitHub email as assignee_id' do it 'returns GitLab user id associated with GitHub email as assignee_id' do
gl_user = create(:user, email: octocat[:email]) gl_user = create(:user, email: octocat[:email])
@ -156,12 +150,6 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do
expect(pull_request.attributes.fetch(:author_id)).to eq project.creator_id expect(pull_request.attributes.fetch(:author_id)).to eq project.creator_id
end end
it 'returns GitLab user id associated with GitHub id as author_id' do
gl_user = create(:omniauth_user, extern_uid: octocat[:id], provider: 'github')
expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
end
it 'returns GitLab user id associated with GitHub email as author_id' do it 'returns GitLab user id associated with GitHub email as author_id' do
gl_user = create(:user, email: octocat[:email]) gl_user = create(:user, email: octocat[:email])
@ -169,7 +157,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do
end end
it 'returns description without created at tag line' do it 'returns description without created at tag line' do
create(:omniauth_user, extern_uid: octocat[:id], provider: 'github') create(:user, email: octocat[:email])
expect(pull_request.attributes.fetch(:description)).to eq('Please pull these awesome changes') expect(pull_request.attributes.fetch(:description)).to eq('Please pull these awesome changes')
end end

View File

@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::LegacyGithubImport::UserFormatter do RSpec.describe Gitlab::LegacyGithubImport::UserFormatter, feature_category: :importers do
let(:client) { double } let(:client) { double }
let(:octocat) { { id: 123456, login: 'octocat', email: 'octocat@example.com' } } let(:octocat) { { id: 123456, login: 'octocat', email: 'octocat@example.com' } }
let(:gitea_ghost) { { id: -1, login: 'Ghost', email: '' } } let(:gitea_ghost) { { id: -1, login: 'Ghost', email: '' } }
@ -15,12 +15,6 @@ RSpec.describe Gitlab::LegacyGithubImport::UserFormatter do
end end
context 'when GitHub user is a GitLab user' do context 'when GitHub user is a GitLab user' do
it 'return GitLab user id when user associated their account with GitHub' do
gl_user = create(:omniauth_user, extern_uid: octocat[:id], provider: 'github')
expect(user.gitlab_id).to eq gl_user.id
end
it 'returns GitLab user id when user confirmed primary email matches GitHub email' do it 'returns GitLab user id when user confirmed primary email matches GitHub email' do
gl_user = create(:user, email: octocat[:email]) gl_user = create(:user, email: octocat[:email])

View File

@ -1,41 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe ChangeICodeReviewCreateMrKeysFromRedisHllToRedis, :migration, :clean_gitlab_redis_cache, feature_category: :service_ping do
def set_redis_hll(key, value)
Gitlab::Redis::HLL.add(key: key, value: value, expiry: 6.weeks)
end
def get_int_from_redis(key)
Gitlab::Redis::SharedState.with { |redis| redis.get(key)&.to_i }
end
describe "#up" do
before do
set_redis_hll('{hll_counters}_i_code_review_create_mr-2023-16', 1)
set_redis_hll('{hll_counters}_i_code_review_create_mr-2023-16', 2)
set_redis_hll('{hll_counters}_i_code_review_create_mr-2023-47', 3)
set_redis_hll('{hll_counters}_i_code_review_create_mr-2023-48', 1)
set_redis_hll('{hll_counters}_i_code_review_create_mr-2023-49', 2)
set_redis_hll('{hll_counters}_i_code_review_create_mr-2023-49', 4)
set_redis_hll('{hll_counters}_some_other_event-2023-49', 7)
end
it 'migrates all RedisHLL keys for i_code_review_create_mr', :aggregate_failures do
migrate!
expect(get_int_from_redis('{event_counters}_i_code_review_user_create_mr-2023-16')).to eq(2)
expect(get_int_from_redis('{event_counters}_i_code_review_user_create_mr-2023-47')).to eq(1)
expect(get_int_from_redis('{event_counters}_i_code_review_user_create_mr-2023-48')).to eq(1)
expect(get_int_from_redis('{event_counters}_i_code_review_user_create_mr-2023-49')).to eq(2)
end
it 'does not not migrate other RedisHLL keys' do
migrate!
expect(get_int_from_redis('{event_counters}_some_other_event-2023-16')).to be_nil
end
end
end

View File

@ -12,13 +12,21 @@ RSpec.describe Namespace::PackageSetting, feature_category: :package_registry do
describe '#maven_duplicates_allowed' do describe '#maven_duplicates_allowed' do
it { is_expected.to validate_inclusion_of(:maven_duplicates_allowed).in_array([true, false]) } it { is_expected.to validate_inclusion_of(:maven_duplicates_allowed).in_array([true, false]) }
it { is_expected.to validate_inclusion_of(:generic_duplicates_allowed).in_array([true, false]) } it { is_expected.to validate_length_of(:maven_duplicate_exception_regex).is_at_most(255) }
it { is_expected.to validate_inclusion_of(:nuget_duplicates_allowed).in_array([true, false]) }
end end
it { is_expected.to allow_value(true, false).for(:nuget_symbol_server_enabled) } it { is_expected.to allow_value(true, false).for(:nuget_symbol_server_enabled) }
it { is_expected.not_to allow_value(nil).for(:nuget_symbol_server_enabled) } it { is_expected.not_to allow_value(nil).for(:nuget_symbol_server_enabled) }
it { is_expected.to validate_inclusion_of(:generic_duplicates_allowed).in_array([true, false]) }
it { is_expected.to validate_length_of(:generic_duplicate_exception_regex).is_at_most(255) }
it { is_expected.to validate_inclusion_of(:nuget_duplicates_allowed).in_array([true, false]) }
it { is_expected.to validate_length_of(:nuget_duplicate_exception_regex).is_at_most(255) }
it { is_expected.to allow_value(true, false).for(:terraform_module_duplicates_allowed) }
it { is_expected.not_to allow_value(nil).for(:terraform_module_duplicates_allowed) }
it { is_expected.to validate_length_of(:terraform_module_duplicate_exception_regex).is_at_most(255) }
describe 'regex values' do describe 'regex values' do
let_it_be(:package_settings) { create(:namespace_package_setting) } let_it_be(:package_settings) { create(:namespace_package_setting) }
@ -39,6 +47,50 @@ RSpec.describe Namespace::PackageSetting, feature_category: :package_registry do
end end
end end
describe 'scopes' do
describe '.namespace_id_in' do
let_it_be(:package_settings) { create(:namespace_package_setting) }
let_it_be(:other_package_settings) { create(:namespace_package_setting) }
subject { described_class.namespace_id_in([package_settings.namespace_id]) }
it { is_expected.to eq([package_settings]) }
end
describe '.with_terraform_module_duplicates_allowed_or_exception_regex' do
let_it_be(:package_settings) { create(:namespace_package_setting) }
subject { described_class.with_terraform_module_duplicates_allowed_or_exception_regex }
context 'when terraform_module_duplicates_allowed is true' do
before do
package_settings.update_column(:terraform_module_duplicates_allowed, true)
end
it { is_expected.to eq([package_settings]) }
end
context 'when terraform_module_duplicate_exception_regex is present' do
before do
package_settings.update_column(:terraform_module_duplicate_exception_regex, 'foo')
end
it { is_expected.to eq([package_settings]) }
end
context 'when terraform_module_duplicates_allowed is false and terraform_module_duplicate_exception_regex is empty' do
before do
package_settings.update_columns(
terraform_module_duplicates_allowed: false,
terraform_module_duplicate_exception_regex: ''
)
end
it { is_expected.to be_empty }
end
end
end
describe '#duplicates_allowed?' do describe '#duplicates_allowed?' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
@ -46,9 +98,14 @@ RSpec.describe Namespace::PackageSetting, feature_category: :package_registry do
context 'package types with package_settings' do context 'package types with package_settings' do
# As more package types gain settings they will be added to this list # As more package types gain settings they will be added to this list
%i[maven_package generic_package nuget_package].each do |format| [
context "with package_type:#{format}" do { format: :maven_package, package_name: 'foo' },
let_it_be(:package) { create(format, name: 'foo', version: '1.0.0-beta') } { format: :generic_package, package_name: 'foo' },
{ format: :nuget_package, package_name: 'foo' },
{ format: :terraform_module_package, package_name: 'foo/bar' }
].each do |type|
context "with package_type: #{type[:format]}" do
let_it_be(:package) { create(type[:format], name: type[:package_name], version: '1.0.0-beta') }
let_it_be(:package_type) { package.package_type } let_it_be(:package_type) { package.package_type }
let_it_be(:package_setting) { package.project.namespace.package_settings } let_it_be(:package_setting) { package.project.namespace.package_settings }
@ -61,7 +118,7 @@ RSpec.describe Namespace::PackageSetting, feature_category: :package_registry do
end end
with_them do with_them do
context "for #{format}" do context "for #{type[:format]}" do
before do before do
package_setting.update!( package_setting.update!(
"#{package_type}_duplicates_allowed" => duplicates_allowed, "#{package_type}_duplicates_allowed" => duplicates_allowed,

View File

@ -20,6 +20,7 @@ RSpec.describe Organizations::OrganizationPolicy, feature_category: :cell do
context 'when admin mode is enabled', :enable_admin_mode do context 'when admin mode is enabled', :enable_admin_mode do
it { is_expected.to be_allowed(:admin_organization) } it { is_expected.to be_allowed(:admin_organization) }
it { is_expected.to be_allowed(:create_group) }
it { is_expected.to be_allowed(:read_organization) } it { is_expected.to be_allowed(:read_organization) }
it { is_expected.to be_allowed(:read_organization_user) } it { is_expected.to be_allowed(:read_organization_user) }
end end
@ -36,12 +37,14 @@ RSpec.describe Organizations::OrganizationPolicy, feature_category: :cell do
end end
it { is_expected.to be_allowed(:admin_organization) } it { is_expected.to be_allowed(:admin_organization) }
it { is_expected.to be_allowed(:create_group) }
it { is_expected.to be_allowed(:read_organization) } it { is_expected.to be_allowed(:read_organization) }
it { is_expected.to be_allowed(:read_organization_user) } it { is_expected.to be_allowed(:read_organization_user) }
end end
context 'when the user is not part of the organization' do context 'when the user is not part of the organization' do
it { is_expected.to be_disallowed(:admin_organization) } it { is_expected.to be_disallowed(:admin_organization) }
it { is_expected.to be_disallowed(:create_group) }
it { is_expected.to be_disallowed(:read_organization_user) } it { is_expected.to be_disallowed(:read_organization_user) }
# All organizations are currently public, and hence they are allowed to be read # All organizations are currently public, and hence they are allowed to be read
# even if the user is not a part of the organization. # even if the user is not a part of the organization.

View File

@ -23,7 +23,9 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
lock_npm_package_requests_forwarding: true, lock_npm_package_requests_forwarding: true,
pypi_package_requests_forwarding: true, pypi_package_requests_forwarding: true,
lock_pypi_package_requests_forwarding: true, lock_pypi_package_requests_forwarding: true,
nuget_symbol_server_enabled: true nuget_symbol_server_enabled: true,
terraform_module_duplicates_allowed: true,
terraform_module_duplicate_exception_regex: 'foo-.*'
} }
end end
@ -44,6 +46,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
pypiPackageRequestsForwarding pypiPackageRequestsForwarding
lockPypiPackageRequestsForwarding lockPypiPackageRequestsForwarding
nugetSymbolServerEnabled nugetSymbolServerEnabled
terraformModuleDuplicatesAllowed
terraformModuleDuplicateExceptionRegex
} }
errors errors
QL QL
@ -73,6 +77,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
expect(package_settings_response['npmPackageRequestsForwarding']).to eq(params[:npm_package_requests_forwarding]) expect(package_settings_response['npmPackageRequestsForwarding']).to eq(params[:npm_package_requests_forwarding])
expect(package_settings_response['lockNpmPackageRequestsForwarding']).to eq(params[:lock_npm_package_requests_forwarding]) expect(package_settings_response['lockNpmPackageRequestsForwarding']).to eq(params[:lock_npm_package_requests_forwarding])
expect(package_settings_response['nugetSymbolServerEnabled']).to eq(params[:nuget_symbol_server_enabled]) expect(package_settings_response['nugetSymbolServerEnabled']).to eq(params[:nuget_symbol_server_enabled])
expect(package_settings_response['terraformModuleDuplicatesAllowed']).to eq(params[:terraform_module_duplicates_allowed])
expect(package_settings_response['terraformModuleDuplicateExceptionRegex']).to eq(params[:terraform_module_duplicate_exception_regex])
end end
end end
@ -115,7 +121,9 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
lock_npm_package_requests_forwarding: false, lock_npm_package_requests_forwarding: false,
pypi_package_requests_forwarding: nil, pypi_package_requests_forwarding: nil,
lock_pypi_package_requests_forwarding: false, lock_pypi_package_requests_forwarding: false,
nuget_symbol_server_enabled: false nuget_symbol_server_enabled: false,
terraform_module_duplicates_allowed: false,
terraform_module_duplicate_exception_regex: 'foo'
}, to: { }, to: {
maven_duplicates_allowed: false, maven_duplicates_allowed: false,
maven_duplicate_exception_regex: 'foo-.*', maven_duplicate_exception_regex: 'foo-.*',
@ -129,7 +137,9 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
lock_npm_package_requests_forwarding: true, lock_npm_package_requests_forwarding: true,
pypi_package_requests_forwarding: true, pypi_package_requests_forwarding: true,
lock_pypi_package_requests_forwarding: true, lock_pypi_package_requests_forwarding: true,
nuget_symbol_server_enabled: true nuget_symbol_server_enabled: true,
terraform_module_duplicates_allowed: true,
terraform_module_duplicate_exception_regex: 'foo-.*'
} }
it_behaves_like 'returning a success' it_behaves_like 'returning a success'

View File

@ -1937,6 +1937,59 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
end end
end end
context 'when group is within a provided organization' do
let_it_be(:organization) { create(:organization) }
context 'when user is an organization user' do
before_all do
create(:organization_user, user: user3, organization: organization)
end
it 'creates group within organization' do
post api('/groups', user3), params: attributes_for_group_api(organization_id: organization.id)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['organization_id']).to eq(organization.id)
end
context 'when parent_group is not part of the organization' do
it 'does not create the group with not_found' do
post(
api('/groups', user3),
params: attributes_for_group_api(parent_id: group2.id, organization_id: organization.id)
)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when organization does not exist' do
it 'does not create the group with not_found' do
post api('/groups', user3), params: attributes_for_group_api(organization_id: non_existing_record_id)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user is not an organization user' do
it 'does not create the group' do
post api('/groups', user3), params: attributes_for_group_api(organization_id: organization.id)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when user is an admin' do
it 'creates group within organization' do
post api('/groups', admin, admin_mode: true), params: attributes_for_group_api(organization_id: organization.id)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['organization_id']).to eq(organization.id)
end
end
end
context "when authenticated as user with group permissions" do context "when authenticated as user with group permissions" do
it "creates group", :aggregate_failures do it "creates group", :aggregate_failures do
group = attributes_for_group_api request_access_enabled: false group = attributes_for_group_api request_access_enabled: false

View File

@ -103,7 +103,28 @@ RSpec.describe API::Terraform::Modules::V1::ProjectPackages, feature_category: :
) )
end end
shared_examples 'creating a package' do
it 'creates a package' do
expect { api_request }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
end
end
shared_examples 'not creating a package' do |expected_status|
it 'does not create a package' do
expect { api_request }
.to change { project.packages.count }.by(0)
.and change { Packages::PackageFile.count }.by(0)
expect(response).to have_gitlab_http_status(expected_status)
end
end
context 'with valid project' do context 'with valid project' do
let(:user_headers) { { 'PRIVATE-TOKEN' => personal_access_token.token } }
let(:headers) { user_headers.merge(workhorse_headers) }
where(:visibility, :user_role, :member, :token_header, :token_type, :shared_examples_name, :expected_status) do where(:visibility, :user_role, :member, :token_header, :token_type, :shared_examples_name, :expected_status) do
:public | :developer | true | 'PRIVATE-TOKEN' | :personal_access_token | 'process terraform module upload' | :created :public | :developer | true | 'PRIVATE-TOKEN' | :personal_access_token | 'process terraform module upload' | :created
:public | :guest | true | 'PRIVATE-TOKEN' | :personal_access_token | 'rejects terraform module packages access' | :forbidden :public | :guest | true | 'PRIVATE-TOKEN' | :personal_access_token | 'rejects terraform module packages access' | :forbidden
@ -147,7 +168,6 @@ RSpec.describe API::Terraform::Modules::V1::ProjectPackages, feature_category: :
with_them do with_them do
let(:user_headers) { user_role == :anonymous ? {} : { token_header => token } } let(:user_headers) { user_role == :anonymous ? {} : { token_header => token } }
let(:headers) { user_headers.merge(workhorse_headers) }
let(:snowplow_gitlab_standard_context) do let(:snowplow_gitlab_standard_context) do
{ project: project, namespace: project.namespace, user: snowplow_user, { project: project, namespace: project.namespace, user: snowplow_user,
property: 'i_package_terraform_module_user' } property: 'i_package_terraform_module_user' }
@ -172,43 +192,73 @@ RSpec.describe API::Terraform::Modules::V1::ProjectPackages, feature_category: :
end end
context 'when failed package file save' do context 'when failed package file save' do
let(:user_headers) { { 'PRIVATE-TOKEN' => personal_access_token.token } } before do
let(:headers) { user_headers.merge(workhorse_headers) } project.add_developer(user)
allow(Packages::CreatePackageFileService).to receive(:new).and_raise(StandardError)
end
it_behaves_like 'not creating a package', :error
end
context 'with an existing package in the same project' do
let_it_be_with_reload(:existing_package) do
create(:terraform_module_package, name: 'mymodule/mysystem', version: '1.0.0', project: project)
end
before do before do
project.add_developer(user) project.add_developer(user)
end end
it 'does not create package record', :aggregate_failures do it_behaves_like 'not creating a package', :forbidden
allow(Packages::CreatePackageFileService).to receive(:new).and_raise(StandardError)
expect { api_request } context 'when marked as pending_destruction' do
.to change { project.packages.count }.by(0) before do
.and change { Packages::PackageFile.count }.by(0) existing_package.pending_destruction!
expect(response).to have_gitlab_http_status(:error) end
it_behaves_like 'creating a package'
end
end
context 'with existing package in another project' do
let_it_be(:package_settings) { create(:namespace_package_setting, namespace: group) }
let_it_be(:project2) { create(:project, namespace: group) }
let!(:existing_package) { create(:terraform_module_package, name: 'mymodule/mysystem', project: project2) }
before do
project.add_developer(user)
end end
context 'with an existing package' do context 'when duplicates not allowed' do
let_it_be_with_reload(:existing_package) do it_behaves_like 'not creating a package', :forbidden
create(:terraform_module_package, name: 'mymodule/mysystem', version: '1.0.0', project: project) end
context 'when duplicates allowed' do
before do
package_settings.update_column(:terraform_module_duplicates_allowed, true)
end end
it 'does not create a new package' do it_behaves_like 'creating a package'
expect { api_request } end
.to change { project.packages.count }.by(0)
.and change { Packages::PackageFile.count }.by(0) context 'with duplicate regex exception' do
expect(response).to have_gitlab_http_status(:forbidden) before do
package_settings.update_columns(
terraform_module_duplicates_allowed: false,
terraform_module_duplicate_exception_regex: regex
)
end end
context 'when marked as pending_destruction' do context 'when regex matches' do
it 'does create a new package' do let(:regex) { ".*#{existing_package.name.last(3)}.*" }
existing_package.pending_destruction!
expect { api_request } it_behaves_like 'creating a package'
.to change { project.packages.count }.by(1) end
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(:created) context 'when regex does not match' do
end let(:regex) { '.*non-matching-regex.*' }
it_behaves_like 'not creating a package', :forbidden
end end
end end
end end

View File

@ -6,7 +6,7 @@ RSpec.describe Groups::CreateService, '#execute', feature_category: :groups_and_
let!(:user) { create(:user) } let!(:user) { create(:user) }
let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } } let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
subject { service.execute } subject(:execute) { service.execute }
shared_examples 'has sync-ed traversal_ids' do shared_examples 'has sync-ed traversal_ids' do
specify { expect(subject.reload.traversal_ids).to eq([subject.parent&.traversal_ids, subject.id].flatten.compact) } specify { expect(subject.reload.traversal_ids).to eq([subject.parent&.traversal_ids, subject.id].flatten.compact) }
@ -119,6 +119,49 @@ RSpec.describe Groups::CreateService, '#execute', feature_category: :groups_and_
end end
end end
describe 'creating a group within an organization' do
let(:current_user) { user }
let(:service) { described_class.new(current_user, params) }
context 'when organization is provided' do
let_it_be(:organization) { create(:organization) }
let(:params) { group_params.merge(organization_id: organization.id) }
context 'when user can create the group' do
before do
create(:organization_user, user: user, organization: organization)
end
it { is_expected.to be_persisted }
end
context 'when user is an admin', :enable_admin_mode do
let(:current_user) { create(:admin) }
it { is_expected.to be_persisted }
end
context 'when user can not create the group' do
it 'does not save group and returns an error' do
expect(execute).not_to be_persisted
expect(execute.errors[:organization_id].first)
.to eq(s_("CreateGroup|You don't have permission to create a group in the provided organization."))
expect(execute.organization_id).to be_nil
end
end
end
context 'when organization is the default organization and not set by params' do
let(:params) { group_params }
before do
create(:organization, :default)
end
it { is_expected.to be_persisted }
end
end
describe 'creating subgroup' do describe 'creating subgroup' do
let!(:group) { create(:group) } let!(:group) { create(:group) }
let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) } let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) }

View File

@ -46,7 +46,9 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService, feature_category: :
lock_npm_package_requests_forwarding: false, lock_npm_package_requests_forwarding: false,
pypi_package_requests_forwarding: nil, pypi_package_requests_forwarding: nil,
lock_pypi_package_requests_forwarding: false, lock_pypi_package_requests_forwarding: false,
nuget_symbol_server_enabled: false nuget_symbol_server_enabled: false,
terraform_module_duplicates_allowed: false,
terraform_module_duplicate_exception_regex: 'foo'
}, to: { }, to: {
maven_duplicates_allowed: false, maven_duplicates_allowed: false,
maven_duplicate_exception_regex: 'RELEASE', maven_duplicate_exception_regex: 'RELEASE',
@ -60,7 +62,9 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService, feature_category: :
lock_npm_package_requests_forwarding: true, lock_npm_package_requests_forwarding: true,
pypi_package_requests_forwarding: true, pypi_package_requests_forwarding: true,
lock_pypi_package_requests_forwarding: true, lock_pypi_package_requests_forwarding: true,
nuget_symbol_server_enabled: true nuget_symbol_server_enabled: true,
terraform_module_duplicates_allowed: true,
terraform_module_duplicate_exception_regex: 'bar'
} }
it_behaves_like 'returning a success' it_behaves_like 'returning a success'
@ -112,7 +116,9 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService, feature_category: :
lock_npm_package_requests_forwarding: true, lock_npm_package_requests_forwarding: true,
pypi_package_requests_forwarding: true, pypi_package_requests_forwarding: true,
lock_pypi_package_requests_forwarding: true, lock_pypi_package_requests_forwarding: true,
nuget_symbol_server_enabled: true nuget_symbol_server_enabled: true,
terraform_module_duplicates_allowed: true,
terraform_module_duplicate_exception_regex: 'bar'
} }
end end

View File

@ -2,10 +2,11 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Packages::TerraformModule::CreatePackageService, feature_category: :package_registry do RSpec.describe Packages::TerraformModule::CreatePackageService, feature_category: :package_registry do
let_it_be(:namespace) { create(:namespace) } let_it_be(:namespace) { create(:group) }
let_it_be(:project) { create(:project, namespace: namespace) } let_it_be(:project) { create(:project, namespace: namespace) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:sha256) { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' } let_it_be(:sha256) { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' }
let_it_be(:package_settings) { create(:namespace_package_setting, namespace: namespace) }
let(:overrides) { {} } let(:overrides) { {} }
@ -36,10 +37,72 @@ RSpec.describe Packages::TerraformModule::CreatePackageService, feature_category
context 'package already exists elsewhere' do context 'package already exists elsewhere' do
let(:project2) { create(:project, namespace: namespace) } let(:project2) { create(:project, namespace: namespace) }
let!(:existing_package) { create(:terraform_module_package, project: project2, name: 'foo/bar', version: '1.0.0') } let!(:existing_package) do
create(:terraform_module_package, project: project2, name: 'foo/bar', version: '1.0.0')
end
it { expect(subject[:http_status]).to eq 403 } context 'when duplicates not allowed' do
it { expect(subject[:message]).to be 'Access Denied' } it { expect(subject.reason).to eq :forbidden }
it { expect(subject.message).to be 'A package with the same name already exists in the namespace' }
end
context 'when duplicates allowed' do
before do
package_settings.update_column(:terraform_module_duplicates_allowed, true)
end
it_behaves_like 'creating a package'
end
context 'with duplicate regex exception' do
before do
package_settings.update_columns(
terraform_module_duplicates_allowed: false,
terraform_module_duplicate_exception_regex: regex
)
end
context 'when regex matches' do
let(:regex) { ".*#{existing_package.name.last(3)}.*" }
it_behaves_like 'creating a package'
end
context 'when regex does not match' do
let(:regex) { '.*not-a-match.*' }
it { expect(subject.reason).to eq :forbidden }
it { expect(subject.message).to be 'A package with the same name already exists in the namespace' }
end
end
context 'for ancestor namespace' do
let_it_be(:package_settings) { create(:namespace_package_setting, :group) }
let_it_be(:parent_namespace) { package_settings.namespace }
before do
namespace.update!(parent: parent_namespace)
end
context 'when duplicates allowed in an ancestor' do
before do
package_settings.update_column(:terraform_module_duplicates_allowed, true)
end
it_behaves_like 'creating a package'
end
context 'when duplicates allowed in an ancestor with exception' do
before do
package_settings.update_columns(
terraform_module_duplicates_allowed: false,
terraform_module_duplicate_exception_regex: ".*#{existing_package.name.last(3)}.*"
)
end
it_behaves_like 'creating a package'
end
end
context 'marked as pending_destruction' do context 'marked as pending_destruction' do
before do before do
@ -53,7 +116,7 @@ RSpec.describe Packages::TerraformModule::CreatePackageService, feature_category
context 'version already exists' do context 'version already exists' do
let!(:existing_version) { create(:terraform_module_package, project: project, name: 'foo/bar', version: '1.0.1') } let!(:existing_version) { create(:terraform_module_package, project: project, name: 'foo/bar', version: '1.0.1') }
it { expect(subject[:http_status]).to eq 403 } it { expect(subject[:reason]).to eq :forbidden }
it { expect(subject[:message]).to be 'Package version already exists.' } it { expect(subject[:message]).to be 'Package version already exists.' }
context 'marked as pending_destruction' do context 'marked as pending_destruction' do
@ -68,7 +131,7 @@ RSpec.describe Packages::TerraformModule::CreatePackageService, feature_category
context 'with empty version' do context 'with empty version' do
let(:overrides) { { module_version: '' } } let(:overrides) { { module_version: '' } }
it { expect(subject[:http_status]).to eq 400 } it { expect(subject[:reason]).to eq :bad_request }
it { expect(subject[:message]).to eq 'Version is empty.' } it { expect(subject[:message]).to eq 'Version is empty.' }
end end
end end

View File

@ -12,6 +12,8 @@ RSpec.shared_examples 'updating the namespace package setting attributes' do |to
.and change { namespace.package_settings.reload.nuget_duplicates_allowed }.from(from[:nuget_duplicates_allowed]).to(to[:nuget_duplicates_allowed]) .and change { namespace.package_settings.reload.nuget_duplicates_allowed }.from(from[:nuget_duplicates_allowed]).to(to[:nuget_duplicates_allowed])
.and change { namespace.package_settings.reload.nuget_duplicate_exception_regex }.from(from[:nuget_duplicate_exception_regex]).to(to[:nuget_duplicate_exception_regex]) .and change { namespace.package_settings.reload.nuget_duplicate_exception_regex }.from(from[:nuget_duplicate_exception_regex]).to(to[:nuget_duplicate_exception_regex])
.and change { namespace.package_settings.reload.nuget_symbol_server_enabled }.from(from[:nuget_symbol_server_enabled]).to(to[:nuget_symbol_server_enabled]) .and change { namespace.package_settings.reload.nuget_symbol_server_enabled }.from(from[:nuget_symbol_server_enabled]).to(to[:nuget_symbol_server_enabled])
.and change { namespace.package_settings.reload.terraform_module_duplicates_allowed }.from(from[:terraform_module_duplicates_allowed]).to(to[:terraform_module_duplicates_allowed])
.and change { namespace.package_settings.reload.terraform_module_duplicate_exception_regex }.from(from[:terraform_module_duplicate_exception_regex]).to(to[:terraform_module_duplicate_exception_regex])
end end
end end
@ -36,6 +38,8 @@ RSpec.shared_examples 'creating the namespace package setting' do
expect(namespace.package_setting_relation.nuget_duplicates_allowed).to eq(package_settings[:nuget_duplicates_allowed]) expect(namespace.package_setting_relation.nuget_duplicates_allowed).to eq(package_settings[:nuget_duplicates_allowed])
expect(namespace.package_setting_relation.nuget_duplicate_exception_regex).to eq(package_settings[:nuget_duplicate_exception_regex]) expect(namespace.package_setting_relation.nuget_duplicate_exception_regex).to eq(package_settings[:nuget_duplicate_exception_regex])
expect(namespace.package_setting_relation.nuget_symbol_server_enabled).to eq(package_settings[:nuget_symbol_server_enabled]) expect(namespace.package_setting_relation.nuget_symbol_server_enabled).to eq(package_settings[:nuget_symbol_server_enabled])
expect(namespace.package_setting_relation.terraform_module_duplicates_allowed).to eq(package_settings[:terraform_module_duplicates_allowed])
expect(namespace.package_setting_relation.terraform_module_duplicate_exception_regex).to eq(package_settings[:terraform_module_duplicate_exception_regex])
end end
it_behaves_like 'returning a success' it_behaves_like 'returning a success'