Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-23 03:10:18 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-23 03:10:18 +0300
commit2994a84f0137ecf313e87bd3a79f433ab615f984 (patch)
tree082e6125a63b0e93667738637aa5e27359c85364
parent6c68583a42998b0bb15971785f372197bfc55cd7 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/layout/line_length.yml1
-rw-r--r--.rubocop_todo/rspec/feature_category.yml4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/index.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue1
-rw-r--r--app/graphql/mutations/namespace/package_settings/update.rb10
-rw-r--r--app/graphql/types/namespace/package_settings_type.rb6
-rw-r--r--app/helpers/environment_helper.rb8
-rw-r--r--app/models/namespace/package_setting.rb10
-rw-r--r--app/policies/organizations/organization_policy.rb2
-rw-r--r--app/services/groups/create_service.rb23
-rw-r--r--app/services/namespaces/package_settings/update_service.rb2
-rw-r--r--app/services/packages/terraform_module/create_package_service.rb35
-rw-r--r--db/migrate/20231129173649_add_terraform_module_duplicates_allowed_to_namespace_package_settings.rb35
-rw-r--r--db/post_migrate/20231204095802_change_i_code_review_create_mr_keys_from_redis_hll_to_redis.rb20
-rw-r--r--db/schema_migrations/202311291736491
-rw-r--r--db/structure.sql5
-rw-r--r--doc/api/graphql/reference/index.md4
-rw-r--r--doc/api/groups.md49
-rw-r--r--doc/development/packages/settings.md2
-rw-r--r--doc/user/packages/terraform_module_registry/index.md24
-rw-r--r--doc/user/project/protected_branches.md4
-rw-r--r--doc/user/project/repository/branches/index.md34
-rw-r--r--lib/api/entities/group.rb1
-rw-r--r--lib/api/groups.rb6
-rw-r--r--lib/api/helpers.rb23
-rw-r--r--lib/api/terraform/modules/v1/project_packages.rb2
-rw-r--r--lib/gitlab/legacy_github_import/user_formatter.rb10
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/factories/namespace_package_settings.rb3
-rw-r--r--spec/frontend/vue_shared/components/segmented_control_button_group_spec.js1
-rw-r--r--spec/graphql/mutations/namespace/package_settings/update_spec.rb12
-rw-r--r--spec/graphql/types/namespace/package_settings_type_spec.rb2
-rw-r--r--spec/lib/api/entities/group_spec.rb24
-rw-r--r--spec/lib/api/helpers_spec.rb61
-rw-r--r--spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb10
-rw-r--r--spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb16
-rw-r--r--spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb16
-rw-r--r--spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb8
-rw-r--r--spec/migrations/20231204095802_change_i_code_review_create_mr_keys_from_redis_hll_to_redis_spec.rb41
-rw-r--r--spec/models/namespace/package_setting_spec.rb69
-rw-r--r--spec/policies/organizations/organization_policy_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb16
-rw-r--r--spec/requests/api/groups_spec.rb53
-rw-r--r--spec/requests/api/terraform/modules/v1/project_packages_spec.rb100
-rw-r--r--spec/services/groups/create_service_spec.rb45
-rw-r--r--spec/services/namespaces/package_settings/update_service_spec.rb12
-rw-r--r--spec/services/packages/terraform_module/create_package_service_spec.rb75
-rw-r--r--spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb4
48 files changed, 665 insertions, 232 deletions
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index 4e78d896a89..8cab670cc83 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -4564,7 +4564,6 @@ Layout/LineLength:
- 'spec/services/packages/nuget/search_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/terraform_module/create_package_service_spec.rb'
- 'spec/services/personal_access_tokens/create_service_spec.rb'
- 'spec/services/post_receive_service_spec.rb'
- 'spec/services/projects/apple_target_platform_detector_service_spec.rb'
diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml
index cdda257b9d0..ab4170d5a2f 100644
--- a/.rubocop_todo/rspec/feature_category.yml
+++ b/.rubocop_todo/rspec/feature_category.yml
@@ -3690,15 +3690,11 @@ RSpec/FeatureCategory:
- 'spec/lib/gitlab/lazy_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/comment_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/milestone_formatter_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/user_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/client_spec.rb'
diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js
index 5e9b72e13cf..db1daa3ce01 100644
--- a/app/assets/javascripts/vue_merge_request_widget/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/index.js
@@ -46,6 +46,7 @@ export default () => {
gl.mrWidgetData.can_create_pipeline_in_target_project,
),
commitPathTemplate: gl.mrWidgetData.commit_path_template,
+ canAdminVulnerability: gl.mrWidgetData.can_admin_vulnerability,
dismissalDescriptions,
},
...MrWidgetOptions,
diff --git a/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue b/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue
index e0e8200580a..fff70d003b7 100644
--- a/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue
+++ b/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue
@@ -43,6 +43,7 @@ export default {
:key="opt.value"
:disabled="!!opt.disabled"
:selected="value === opt.value"
+ v-bind="opt.props"
@click="$emit('input', opt.value)"
>
<slot name="button-content" v-bind="opt">{{ opt.text }}</slot>
diff --git a/app/graphql/mutations/namespace/package_settings/update.rb b/app/graphql/mutations/namespace/package_settings/update.rb
index 813c5687642..a429dd06a7c 100644
--- a/app/graphql/mutations/namespace/package_settings/update.rb
+++ b/app/graphql/mutations/namespace/package_settings/update.rb
@@ -51,6 +51,16 @@ module Mutations
required: false,
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,
GraphQL::Types::Boolean,
required: false,
diff --git a/app/graphql/types/namespace/package_settings_type.rb b/app/graphql/types/namespace/package_settings_type.rb
index 7bf76ae7de5..621cb091019 100644
--- a/app/graphql/types/namespace/package_settings_type.rb
+++ b/app/graphql/types/namespace/package_settings_type.rb
@@ -35,6 +35,12 @@ module Types
field :pypi_package_requests_forwarding, GraphQL::Types::Boolean,
null: true,
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,
null: false,
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index fa47a12a72c..adb2b03cd0a 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -1,14 +1,6 @@
# frozen_string_literal: true
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)
[deployment.project, deployment.deployable]
end
diff --git a/app/models/namespace/package_setting.rb b/app/models/namespace/package_setting.rb
index a5a393ad8a2..5f5bef4409c 100644
--- a/app/models/namespace/package_setting.rb
+++ b/app/models/namespace/package_setting.rb
@@ -12,7 +12,7 @@ class Namespace::PackageSetting < ApplicationRecord
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
@@ -24,6 +24,14 @@ class Namespace::PackageSetting < ApplicationRecord
validates :nuget_duplicates_allowed, inclusion: { in: [true, false] }
validates :nuget_duplicate_exception_regex, untrusted_regexp: true, length: { maximum: 255 }
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
def duplicates_allowed?(package)
diff --git a/app/policies/organizations/organization_policy.rb b/app/policies/organizations/organization_policy.rb
index d538b786f78..afd8c6e144f 100644
--- a/app/policies/organizations/organization_policy.rb
+++ b/app/policies/organizations/organization_policy.rb
@@ -13,12 +13,14 @@ module Organizations
rule { admin }.policy do
enable :admin_organization
+ enable :create_group
enable :read_organization
enable :read_organization_user
end
rule { organization_user }.policy do
enable :admin_organization
+ enable :create_group
enable :read_organization
enable :read_organization_user
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index 21d3c6499a0..06c6560f0fe 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -92,9 +92,32 @@ module Groups
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
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?
unless Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level)
deny_visibility_level(@group)
diff --git a/app/services/namespaces/package_settings/update_service.rb b/app/services/namespaces/package_settings/update_service.rb
index d7ab6828346..06a15671f25 100644
--- a/app/services/namespaces/package_settings/update_service.rb
+++ b/app/services/namespaces/package_settings/update_service.rb
@@ -12,6 +12,8 @@ module Namespaces
maven_package_requests_forwarding
nuget_duplicates_allowed
nuget_duplicate_exception_regex
+ terraform_module_duplicates_allowed
+ terraform_module_duplicate_exception_regex
npm_package_requests_forwarding
pypi_package_requests_forwarding
lock_maven_package_requests_forwarding
diff --git a/app/services/packages/terraform_module/create_package_service.rb b/app/services/packages/terraform_module/create_package_service.rb
index 9df722db529..eb48b481dd8 100644
--- a/app/services/packages/terraform_module/create_package_service.rb
+++ b/app/services/packages/terraform_module/create_package_service.rb
@@ -6,10 +6,20 @@ module Packages
include Gitlab::Utils::StrongMemoize
def execute
- return error('Version is empty.', 400) if params[:module_version].blank?
- return error('Access Denied', 403) if current_package_exists_elsewhere?
- return error('Package version already exists.', 403) if current_package_version_exists?
- return error('File is too large.', 400) if file_size_exceeded?
+ if params[:module_version].blank?
+ return ServiceResponse.error(message: 'Version is empty.', reason: :bad_request)
+ end
+
+ 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! }
end
@@ -24,6 +34,15 @@ module Packages
package
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?
::Packages::Package
.for_projects(project.root_namespace.all_projects.id_not_in(project.id))
@@ -62,9 +81,13 @@ module Packages
}
end
- def file_size_exceeded?
- project.actual_limits.exceeded?(:generic_packages_max_file_size, params[:file].size)
+ def package_settings_with_duplicates_allowed
+ ::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
+ strong_memoize_attr :package_settings_with_duplicates_allowed
end
end
end
diff --git a/db/migrate/20231129173649_add_terraform_module_duplicates_allowed_to_namespace_package_settings.rb b/db/migrate/20231129173649_add_terraform_module_duplicates_allowed_to_namespace_package_settings.rb
new file mode 100644
index 00000000000..ea465de5873
--- /dev/null
+++ b/db/migrate/20231129173649_add_terraform_module_duplicates_allowed_to_namespace_package_settings.rb
@@ -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
diff --git a/db/post_migrate/20231204095802_change_i_code_review_create_mr_keys_from_redis_hll_to_redis.rb b/db/post_migrate/20231204095802_change_i_code_review_create_mr_keys_from_redis_hll_to_redis.rb
index 66b5ec0698e..d006e0f3121 100644
--- a/db/post_migrate/20231204095802_change_i_code_review_create_mr_keys_from_redis_hll_to_redis.rb
+++ b/db/post_migrate/20231204095802_change_i_code_review_create_mr_keys_from_redis_hll_to_redis.rb
@@ -3,24 +3,10 @@
class ChangeICodeReviewCreateMrKeysFromRedisHllToRedis < Gitlab::Database::Migration[2.2]
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
- # For each old (redis_hll) counter we find the corresponding target (redis) counter and add
- # 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.
- 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
+ # no-op
+ #
+ # Removed due to https://gitlab.com/gitlab-com/gl-infra/production/-/issues/17321
end
def down
diff --git a/db/schema_migrations/20231129173649 b/db/schema_migrations/20231129173649
new file mode 100644
index 00000000000..a70b975dab6
--- /dev/null
+++ b/db/schema_migrations/20231129173649
@@ -0,0 +1 @@
+a18e718e99c23ae6db929929a905af0db72e3a3734d3c33e12ec2cdb44467f6d \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 8d7f7c2af3c..84f5fa79ab9 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -19505,9 +19505,12 @@ CREATE TABLE namespace_package_settings (
nuget_duplicates_allowed boolean DEFAULT true NOT NULL,
nuget_duplicate_exception_regex text DEFAULT ''::text 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_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 (
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d8507c9d49e..cc021811b06 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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="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="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
@@ -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="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="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`
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 053b55d0a7d..938cd9dbab1 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -816,31 +816,32 @@ POST /groups
Parameters:
-| Attribute | Type | Required | Description |
-| ------------------------------------------------------- | ------- | -------- | ----------- |
-| `name` | string | yes | The name 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. |
-| `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_defaults` | hash | no | See [Options for `default_branch_protection_defaults`](#options-for-default_branch_protection_defaults). |
-| `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_enabled` | boolean | no | Enable email notifications. |
-| `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. |
-| `parent_id` | integer | no | The parent group ID for creating nested group. |
+| Attribute | Type | Required | Description |
+| ------------------------------------------------------- | ------- | -------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `name` | string | yes | The name 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. |
+| `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_defaults` | hash | no | See [Options for `default_branch_protection_defaults`](#options-for-default_branch_protection_defaults). |
+| `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_enabled` | boolean | no | Enable email notifications. |
+| `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. |
+| `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). |
-| `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. |
-| `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). |
-| `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`. |
-| `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. |
-| `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`. |
+| `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. |
+| `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). |
+| `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`. |
+| `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. |
+| `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`. |
### Options for `default_branch_protection`
diff --git a/doc/development/packages/settings.md b/doc/development/packages/settings.md
index 89f91f41f4c..690f9ccae93 100644
--- a/doc/development/packages/settings.md
+++ b/doc/development/packages/settings.md
@@ -70,6 +70,8 @@ Setting | Table | Description
`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_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 - `enabled` | `dependency_proxy_image_ttl_group_policies` | Enable or disable the Dependency Proxy cleanup policy.
diff --git a/doc/user/packages/terraform_module_registry/index.md b/doc/user/packages/terraform_module_registry/index.md
index 8612acc37cb..fdc832570c0 100644
--- a/doc/user/packages/terraform_module_registry/index.md
+++ b/doc/user/packages/terraform_module_registry/index.md
@@ -48,10 +48,10 @@ You can publish Terraform modules by using the [Terraform Module Registry API](.
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"`.
- 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).
```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.
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
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 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
To download a Terraform module:
@@ -275,4 +293,4 @@ For examples of the Terraform Module Registry, check the projects below:
## 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.
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index 0377ab389a5..60b862a4d3b 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -395,6 +395,6 @@ third-party Git clients.
### Branch names are case-sensitive
-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),
+Branch names in `git` are case-sensitive. When configuring your protected branch,
+or your [target branch workflow](repository/branches/index.md#configure-workflows-for-target-branches),
`dev` is not the same `DEV` or `Dev`.
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index eeb5be2fab6..1e52aaaa211 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -287,58 +287,58 @@ To do this:
1. 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.
> - [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`.
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.
-When you create a merge request, the rule checks the name of the branch. If the
-branch name matches the rule, the merge request targets the branch you specify
-in the rule. If the branch name does not match, the merge request targets the
+When you create a merge request, the workflow checks the name of the branch. If the
+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
default branch of the project.
Prerequisites:
- 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. Select **Settings > Merge requests**.
-1. Select **Add target branch rule**.
-1. For **Rule name**, provide a string or wild card to compare against branch names.
-1. Select the **Target branch** to use when the branch name matches the **Rule name**.
+1. Scroll down to **Merge request branch workflow**
+1. Select **Add branch target**.
+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**.
### 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` |
| `bug/*` | `develop` |
| `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.
- 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.
-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`.
-## 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:
@@ -348,7 +348,7 @@ To do this:
1. On the left sidebar, select **Search or go to** and find your project.
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
diff --git a/lib/api/entities/group.rb b/lib/api/entities/group.rb
index 1a1765c2e0a..14491c2396a 100644
--- a/lib/api/entities/group.rb
+++ b/lib/api/entities/group.rb
@@ -23,6 +23,7 @@ module API
expose :full_name, :full_path
expose :created_at
expose :parent_id
+ expose :organization_id
expose :shared_runners_setting
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 1ff64cd2ffd..7b755a76f29 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -213,11 +213,15 @@ module API
requires :name, type: String, desc: 'The name 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 :organization_id, type: Integer, desc: 'The organization id for the group'
use :optional_params
end
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
authorize! :create_subgroup, parent_group
else
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 6cb9d19a2ad..a59734d643d 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -211,18 +211,25 @@ module API
not_found!('Pipeline')
end
+ def find_organization!(id)
+ organization = Organizations::Organization.find_by_id(id)
+ check_organization_access(organization)
+ end
+
# 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
- Group.find_by(id: id)
+ collection.find_by(id: id)
else
- Group.find_by_full_path(id)
+ collection.find_by_full_path(id)
end
end
# rubocop: enable CodeReuse/ActiveRecord
- def find_group!(id)
- group = find_group(id)
+ def find_group!(id, organization: nil)
+ group = find_group(id, organization: organization)
check_group_access(group)
end
@@ -835,6 +842,12 @@ module API
@sudo_identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
end
+ def check_organization_access(organization)
+ return organization if can?(current_user, :read_organization, organization)
+
+ not_found!('Organization')
+ end
+
def secret_token
Gitlab::Shell.secret_token
end
diff --git a/lib/api/terraform/modules/v1/project_packages.rb b/lib/api/terraform/modules/v1/project_packages.rb
index ff330b8479f..c0a84c7b36c 100644
--- a/lib/api/terraform/modules/v1/project_packages.rb
+++ b/lib/api/terraform/modules/v1/project_packages.rb
@@ -171,7 +171,7 @@ module API
.new(authorized_user_project, current_user, create_package_file_params)
.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,
namespace: authorized_user_project.namespace)
diff --git a/lib/gitlab/legacy_github_import/user_formatter.rb b/lib/gitlab/legacy_github_import/user_formatter.rb
index 8fd8354e59c..a57edcc7ba4 100644
--- a/lib/gitlab/legacy_github_import/user_formatter.rb
+++ b/lib/gitlab/legacy_github_import/user_formatter.rb
@@ -23,7 +23,7 @@ module Gitlab
def gitlab_id
return @gitlab_id if defined?(@gitlab_id)
- @gitlab_id = find_by_external_uid || find_by_email
+ @gitlab_id = find_by_email
end
private
@@ -45,14 +45,6 @@ module Gitlab
User.find_by_any_email(email)
.try(:id)
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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b17af61a3bf..04b9a1c15c7 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -14578,6 +14578,9 @@ msgstr ""
msgid "CreateGitTag|Set tag message"
msgstr ""
+msgid "CreateGroup|You don't have permission to create a group in the provided organization."
+msgstr ""
+
msgid "CreateGroup|You don’t have permission to create a subgroup in this group."
msgstr ""
diff --git a/spec/factories/namespace_package_settings.rb b/spec/factories/namespace_package_settings.rb
index 33f290d0a2d..8c5f7193cde 100644
--- a/spec/factories/namespace_package_settings.rb
+++ b/spec/factories/namespace_package_settings.rb
@@ -15,6 +15,9 @@ FactoryBot.define do
nuget_symbol_server_enabled { false }
+ terraform_module_duplicates_allowed { false }
+ terraform_module_duplicate_exception_regex { 'foo' }
+
trait :group do
namespace { association(:group) }
end
diff --git a/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js b/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js
index 623a8739907..a3bf3ca23e3 100644
--- a/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js
+++ b/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js
@@ -122,6 +122,7 @@ describe('~/vue_shared/components/segmented_control_button_group.vue', () => {
[[{ value: '1' }]],
[[{ value: 1, disabled: true }]],
[[{ value: true, disabled: false }]],
+ [[{ value: true, props: { 'data-testid': 'test' } }]],
])('with options=%j, passes validation', (options) => {
createComponent({ options });
diff --git a/spec/graphql/mutations/namespace/package_settings/update_spec.rb b/spec/graphql/mutations/namespace/package_settings/update_spec.rb
index b184baaca3e..f5bd9ad93be 100644
--- a/spec/graphql/mutations/namespace/package_settings/update_spec.rb
+++ b/spec/graphql/mutations/namespace/package_settings/update_spec.rb
@@ -39,7 +39,9 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update, feature_category:
lock_npm_package_requests_forwarding: false,
pypi_package_requests_forwarding: nil,
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: {
maven_duplicates_allowed: false,
maven_duplicate_exception_regex: 'RELEASE',
@@ -53,7 +55,9 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update, feature_category:
lock_npm_package_requests_forwarding: true,
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'
@@ -109,7 +113,9 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update, feature_category:
lock_npm_package_requests_forwarding: true,
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
diff --git a/spec/graphql/types/namespace/package_settings_type_spec.rb b/spec/graphql/types/namespace/package_settings_type_spec.rb
index 0e731c1e2bf..0e958aca586 100644
--- a/spec/graphql/types/namespace/package_settings_type_spec.rb
+++ b/spec/graphql/types/namespace/package_settings_type_spec.rb
@@ -33,6 +33,8 @@ RSpec.describe GitlabSchema.types['PackageSettings'], feature_category: :package
npm_package_requests_forwarding_locked
pypi_package_requests_forwarding_locked
nuget_symbol_server_enabled
+ terraform_module_duplicates_allowed
+ terraform_module_duplicate_exception_regex
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/lib/api/entities/group_spec.rb b/spec/lib/api/entities/group_spec.rb
new file mode 100644
index 00000000000..270ac323c7d
--- /dev/null
+++ b/spec/lib/api/entities/group_spec.rb
@@ -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
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index c76694b60d3..d1dee70e34d 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -406,6 +406,37 @@ RSpec.describe API::Helpers, feature_category: :shared do
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
let_it_be(:group) { create(:group, :public) }
let_it_be(:user) { create(:user) }
@@ -457,7 +488,7 @@ RSpec.describe API::Helpers, feature_category: :shared do
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(:user) { group.first_owner }
@@ -505,6 +536,34 @@ RSpec.describe API::Helpers, feature_category: :shared do
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
let_it_be(:group) { create(:group, :public) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb
index 8d6415b8179..8b6d628833e 100644
--- a/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb
@@ -2,7 +2,7 @@
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(:client) { double }
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
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
gl_user = create(:user, email: octocat[:email])
@@ -89,7 +83,7 @@ RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter do
end
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.")
end
diff --git a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
index d3548fecbcd..9baf234b14b 100644
--- a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
@@ -2,7 +2,7 @@
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(:client) { double }
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
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
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
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
gl_user = create(:user, email: octocat[:email])
@@ -130,7 +118,7 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do
end
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.")
end
diff --git a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
index 90469693820..1555e3e0d4c 100644
--- a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
@@ -2,7 +2,7 @@
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(:client) { double }
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
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
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
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
gl_user = create(:user, email: octocat[:email])
@@ -169,7 +157,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do
end
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')
end
diff --git a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
index 0844ab7eccc..d387d79aa30 100644
--- a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::LegacyGithubImport::UserFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::UserFormatter, feature_category: :importers do
let(:client) { double }
let(:octocat) { { id: 123456, login: 'octocat', email: 'octocat@example.com' } }
let(:gitea_ghost) { { id: -1, login: 'Ghost', email: '' } }
@@ -15,12 +15,6 @@ RSpec.describe Gitlab::LegacyGithubImport::UserFormatter do
end
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
gl_user = create(:user, email: octocat[:email])
diff --git a/spec/migrations/20231204095802_change_i_code_review_create_mr_keys_from_redis_hll_to_redis_spec.rb b/spec/migrations/20231204095802_change_i_code_review_create_mr_keys_from_redis_hll_to_redis_spec.rb
deleted file mode 100644
index 25ca0936dbe..00000000000
--- a/spec/migrations/20231204095802_change_i_code_review_create_mr_keys_from_redis_hll_to_redis_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/models/namespace/package_setting_spec.rb b/spec/models/namespace/package_setting_spec.rb
index f06490d7999..e326e8cace8 100644
--- a/spec/models/namespace/package_setting_spec.rb
+++ b/spec/models/namespace/package_setting_spec.rb
@@ -12,13 +12,21 @@ RSpec.describe Namespace::PackageSetting, feature_category: :package_registry 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(:generic_duplicates_allowed).in_array([true, false]) }
- it { is_expected.to validate_inclusion_of(:nuget_duplicates_allowed).in_array([true, false]) }
+ it { is_expected.to validate_length_of(:maven_duplicate_exception_regex).is_at_most(255) }
end
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.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
let_it_be(:package_settings) { create(:namespace_package_setting) }
@@ -39,6 +47,50 @@ RSpec.describe Namespace::PackageSetting, feature_category: :package_registry do
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
using RSpec::Parameterized::TableSyntax
@@ -46,9 +98,14 @@ RSpec.describe Namespace::PackageSetting, feature_category: :package_registry do
context 'package types with package_settings' do
# 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
- let_it_be(:package) { create(format, name: 'foo', version: '1.0.0-beta') }
+ [
+ { format: :maven_package, package_name: 'foo' },
+ { 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_setting) { package.project.namespace.package_settings }
@@ -61,7 +118,7 @@ RSpec.describe Namespace::PackageSetting, feature_category: :package_registry do
end
with_them do
- context "for #{format}" do
+ context "for #{type[:format]}" do
before do
package_setting.update!(
"#{package_type}_duplicates_allowed" => duplicates_allowed,
diff --git a/spec/policies/organizations/organization_policy_spec.rb b/spec/policies/organizations/organization_policy_spec.rb
index 7eed497d644..a1a2f1db305 100644
--- a/spec/policies/organizations/organization_policy_spec.rb
+++ b/spec/policies/organizations/organization_policy_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe Organizations::OrganizationPolicy, feature_category: :cell 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(:create_group) }
it { is_expected.to be_allowed(:read_organization) }
it { is_expected.to be_allowed(:read_organization_user) }
end
@@ -36,12 +37,14 @@ RSpec.describe Organizations::OrganizationPolicy, feature_category: :cell do
end
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_user) }
end
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(:create_group) }
it { is_expected.to be_disallowed(:read_organization_user) }
# All organizations are currently public, and hence they are allowed to be read
# even if the user is not a part of the organization.
diff --git a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb
index 05c1a2d96d9..7c5d86b9f5c 100644
--- a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb
@@ -23,7 +23,9 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
lock_npm_package_requests_forwarding: true,
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
@@ -44,6 +46,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
pypiPackageRequestsForwarding
lockPypiPackageRequestsForwarding
nugetSymbolServerEnabled
+ terraformModuleDuplicatesAllowed
+ terraformModuleDuplicateExceptionRegex
}
errors
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['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['terraformModuleDuplicatesAllowed']).to eq(params[:terraform_module_duplicates_allowed])
+ expect(package_settings_response['terraformModuleDuplicateExceptionRegex']).to eq(params[:terraform_module_duplicate_exception_regex])
end
end
@@ -115,7 +121,9 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
lock_npm_package_requests_forwarding: false,
pypi_package_requests_forwarding: nil,
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: {
maven_duplicates_allowed: false,
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,
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'
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 327dfd0a76b..d1158cba16e 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -1937,6 +1937,59 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
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
it "creates group", :aggregate_failures do
group = attributes_for_group_api request_access_enabled: false
diff --git a/spec/requests/api/terraform/modules/v1/project_packages_spec.rb b/spec/requests/api/terraform/modules/v1/project_packages_spec.rb
index 3377f8d6647..70f7ec64d40 100644
--- a/spec/requests/api/terraform/modules/v1/project_packages_spec.rb
+++ b/spec/requests/api/terraform/modules/v1/project_packages_spec.rb
@@ -103,7 +103,28 @@ RSpec.describe API::Terraform::Modules::V1::ProjectPackages, feature_category: :
)
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
+ 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
: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
@@ -147,7 +168,6 @@ RSpec.describe API::Terraform::Modules::V1::ProjectPackages, feature_category: :
with_them do
let(:user_headers) { user_role == :anonymous ? {} : { token_header => token } }
- let(:headers) { user_headers.merge(workhorse_headers) }
let(:snowplow_gitlab_standard_context) do
{ project: project, namespace: project.namespace, user: snowplow_user,
property: 'i_package_terraform_module_user' }
@@ -172,43 +192,73 @@ RSpec.describe API::Terraform::Modules::V1::ProjectPackages, feature_category: :
end
context 'when failed package file save' do
- let(:user_headers) { { 'PRIVATE-TOKEN' => personal_access_token.token } }
- let(:headers) { user_headers.merge(workhorse_headers) }
+ before do
+ 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
project.add_developer(user)
end
- it 'does not create package record', :aggregate_failures do
- allow(Packages::CreatePackageFileService).to receive(:new).and_raise(StandardError)
+ it_behaves_like 'not creating a package', :forbidden
+
+ context 'when marked as pending_destruction' do
+ before do
+ existing_package.pending_destruction!
+ 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
+
+ context 'when duplicates not allowed' do
+ it_behaves_like 'not creating a package', :forbidden
+ end
+
+ context 'when duplicates allowed' do
+ before do
+ package_settings.update_column(:terraform_module_duplicates_allowed, true)
+ end
- expect { api_request }
- .to change { project.packages.count }.by(0)
- .and change { Packages::PackageFile.count }.by(0)
- expect(response).to have_gitlab_http_status(:error)
+ it_behaves_like 'creating a package'
end
- context 'with an existing package' do
- let_it_be_with_reload(:existing_package) do
- create(:terraform_module_package, name: 'mymodule/mysystem', version: '1.0.0', project: project)
+ context 'with duplicate regex exception' do
+ before do
+ package_settings.update_columns(
+ terraform_module_duplicates_allowed: false,
+ terraform_module_duplicate_exception_regex: regex
+ )
end
- it 'does not create a new 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(:forbidden)
+ context 'when regex matches' do
+ let(:regex) { ".*#{existing_package.name.last(3)}.*" }
+
+ it_behaves_like 'creating a package'
end
- context 'when marked as pending_destruction' do
- it 'does create a new package' do
- existing_package.pending_destruction!
+ context 'when regex does not match' do
+ let(:regex) { '.*non-matching-regex.*' }
- 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
+ it_behaves_like 'not creating a package', :forbidden
end
end
end
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index b2b27a1a075..a6c2b593e40 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Groups::CreateService, '#execute', feature_category: :groups_and_
let!(:user) { create(:user) }
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
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
+ 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
let!(:group) { create(:group) }
let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) }
diff --git a/spec/services/namespaces/package_settings/update_service_spec.rb b/spec/services/namespaces/package_settings/update_service_spec.rb
index 41f3499a1bb..002c7df9284 100644
--- a/spec/services/namespaces/package_settings/update_service_spec.rb
+++ b/spec/services/namespaces/package_settings/update_service_spec.rb
@@ -46,7 +46,9 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService, feature_category: :
lock_npm_package_requests_forwarding: false,
pypi_package_requests_forwarding: nil,
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: {
maven_duplicates_allowed: false,
maven_duplicate_exception_regex: 'RELEASE',
@@ -60,7 +62,9 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService, feature_category: :
lock_npm_package_requests_forwarding: true,
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'
@@ -112,7 +116,9 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService, feature_category: :
lock_npm_package_requests_forwarding: true,
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
diff --git a/spec/services/packages/terraform_module/create_package_service_spec.rb b/spec/services/packages/terraform_module/create_package_service_spec.rb
index 3355dfcf5ec..c1a41cd9676 100644
--- a/spec/services/packages/terraform_module/create_package_service_spec.rb
+++ b/spec/services/packages/terraform_module/create_package_service_spec.rb
@@ -2,10 +2,11 @@
require 'spec_helper'
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(:user) { create(:user) }
let_it_be(:sha256) { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' }
+ let_it_be(:package_settings) { create(:namespace_package_setting, namespace: namespace) }
let(:overrides) { {} }
@@ -36,10 +37,72 @@ RSpec.describe Packages::TerraformModule::CreatePackageService, feature_category
context 'package already exists elsewhere' do
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
+
+ context 'when duplicates not allowed' do
+ 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
- it { expect(subject[:http_status]).to eq 403 }
- it { expect(subject[:message]).to be 'Access Denied' }
+ 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
before do
@@ -53,7 +116,7 @@ RSpec.describe Packages::TerraformModule::CreatePackageService, feature_category
context 'version already exists' do
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.' }
context 'marked as pending_destruction' do
@@ -68,7 +131,7 @@ RSpec.describe Packages::TerraformModule::CreatePackageService, feature_category
context 'with empty version' do
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.' }
end
end
diff --git a/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb b/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb
index d288c74ae4b..f4bab4d0ad6 100644
--- a/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb
+++ b/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb
@@ -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_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.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
@@ -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_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.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
it_behaves_like 'returning a success'