2018-07-25 05:30:33 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-11-22 04:35:28 -05:00
|
|
|
class Namespace < ApplicationRecord
|
2016-10-06 17:17:11 -04:00
|
|
|
include CacheMarkdownField
|
2015-02-05 17:20:55 -05:00
|
|
|
include Sortable
|
2017-07-17 06:40:17 -04:00
|
|
|
include Gitlab::VisibilityLevel
|
2016-10-31 07:00:53 -04:00
|
|
|
include Routable
|
2017-06-01 17:36:04 -04:00
|
|
|
include AfterCommitQueue
|
2017-07-04 20:19:02 -04:00
|
|
|
include Storage::LegacyNamespace
|
2017-11-24 05:45:19 -05:00
|
|
|
include Gitlab::SQL::Pattern
|
2018-08-15 15:48:05 -04:00
|
|
|
include FeatureGate
|
2018-09-11 11:31:34 -04:00
|
|
|
include FromUnion
|
2019-03-12 06:15:33 -04:00
|
|
|
include Gitlab::Utils::StrongMemoize
|
2020-04-14 20:09:27 -04:00
|
|
|
include IgnorableColumns
|
2021-02-05 10:09:28 -05:00
|
|
|
include Namespaces::Traversal::Recursive
|
2021-03-19 17:08:58 -04:00
|
|
|
include Namespaces::Traversal::Linear
|
2021-05-13 05:10:44 -04:00
|
|
|
include EachBatch
|
2022-03-22 08:07:28 -04:00
|
|
|
include BlocksUnsafeSerialization
|
2022-05-11 17:08:09 -04:00
|
|
|
include Ci::NamespaceSettings
|
2020-04-14 20:09:27 -04:00
|
|
|
|
2021-11-12 10:12:37 -05:00
|
|
|
# Temporary column used for back-filling project namespaces.
|
|
|
|
# Remove it once the back-filling of all project namespaces is done.
|
|
|
|
ignore_column :tmp_project_id, remove_with: '14.7', remove_after: '2022-01-22'
|
2021-03-25 17:09:13 -04:00
|
|
|
|
2021-10-18 20:10:29 -04:00
|
|
|
# Tells ActiveRecord not to store the full class name, in order to save some space
|
2021-09-08 17:10:58 -04:00
|
|
|
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69794
|
|
|
|
self.store_full_sti_class = false
|
|
|
|
self.store_full_class_name = false
|
|
|
|
|
2017-02-06 10:16:50 -05:00
|
|
|
# Prevent users from creating unreasonably deep level of nesting.
|
|
|
|
# The number 20 was taken based on maximum nesting level of
|
|
|
|
# Android repo (15) + some extra backup.
|
|
|
|
NUMBER_OF_ANCESTORS_ALLOWED = 20
|
|
|
|
|
2021-10-13 11:12:51 -04:00
|
|
|
SR_DISABLED_AND_UNOVERRIDABLE = 'disabled_and_unoverridable'
|
|
|
|
SR_DISABLED_WITH_OVERRIDE = 'disabled_with_override'
|
|
|
|
SR_ENABLED = 'enabled'
|
|
|
|
SHARED_RUNNERS_SETTINGS = [SR_DISABLED_AND_UNOVERRIDABLE, SR_DISABLED_WITH_OVERRIDE, SR_ENABLED].freeze
|
2021-07-21 08:09:35 -04:00
|
|
|
URL_MAX_LENGTH = 255
|
2020-09-30 11:09:46 -04:00
|
|
|
|
2021-10-28 14:14:18 -04:00
|
|
|
PATH_TRAILING_VIOLATIONS = %w[.git .atom .].freeze
|
|
|
|
|
2016-10-06 17:17:11 -04:00
|
|
|
cache_markdown_field :description, pipeline: :description
|
|
|
|
|
2017-06-08 11:16:27 -04:00
|
|
|
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
2016-11-22 11:58:10 -05:00
|
|
|
has_many :project_statistics
|
2020-07-16 11:09:38 -04:00
|
|
|
has_one :namespace_settings, inverse_of: :namespace, class_name: 'NamespaceSetting', autosave: true
|
2022-05-09 08:08:55 -04:00
|
|
|
has_one :ci_cd_settings, inverse_of: :namespace, class_name: 'NamespaceCiCdSetting', autosave: true
|
2022-02-09 07:12:04 -05:00
|
|
|
has_one :namespace_statistics
|
2022-01-13 07:14:38 -05:00
|
|
|
has_one :namespace_route, foreign_key: :namespace_id, autosave: false, inverse_of: :namespace, class_name: 'Route'
|
2022-01-18 01:11:59 -05:00
|
|
|
has_many :namespace_members, foreign_key: :member_namespace_id, inverse_of: :member_namespace, class_name: 'Member'
|
2018-01-31 11:51:05 -05:00
|
|
|
|
2018-05-28 07:07:28 -04:00
|
|
|
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
|
2018-05-02 11:43:20 -04:00
|
|
|
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
|
2021-08-03 08:09:42 -04:00
|
|
|
has_many :pending_builds, class_name: 'Ci::PendingBuild'
|
2021-01-11 07:10:41 -05:00
|
|
|
has_one :onboarding_progress
|
2018-05-02 11:43:20 -04:00
|
|
|
|
2018-01-31 11:51:05 -05:00
|
|
|
# This should _not_ be `inverse_of: :namespace`, because that would also set
|
|
|
|
# `user.namespace` when this user creates a group with themselves as `owner`.
|
2021-11-23 19:12:33 -05:00
|
|
|
belongs_to :owner, class_name: 'User'
|
2012-11-22 13:34:16 -05:00
|
|
|
|
2016-10-31 07:00:53 -04:00
|
|
|
belongs_to :parent, class_name: "Namespace"
|
2021-10-20 11:12:43 -04:00
|
|
|
has_many :children, -> { where(type: Group.sti_name) }, class_name: "Namespace", foreign_key: :parent_id
|
2020-06-30 08:08:57 -04:00
|
|
|
has_many :custom_emoji, inverse_of: :namespace
|
2017-06-08 11:16:27 -04:00
|
|
|
has_one :chat_team, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
2019-06-12 17:11:14 -04:00
|
|
|
has_one :root_storage_statistics, class_name: 'Namespace::RootStorageStatistics'
|
|
|
|
has_one :aggregation_schedule, class_name: 'Namespace::AggregationSchedule'
|
2021-01-05 13:10:25 -05:00
|
|
|
has_one :package_setting_relation, inverse_of: :namespace, class_name: 'PackageSetting'
|
2016-10-31 07:00:53 -04:00
|
|
|
|
2021-03-29 14:09:37 -04:00
|
|
|
has_one :admin_note, inverse_of: :namespace
|
|
|
|
accepts_nested_attributes_for :admin_note, update_only: true
|
|
|
|
|
2021-12-10 10:10:24 -05:00
|
|
|
has_one :ci_namespace_mirror, class_name: 'Ci::NamespaceMirror'
|
|
|
|
has_many :sync_events, class_name: 'Namespaces::SyncEvent'
|
|
|
|
|
2022-05-20 11:09:10 -04:00
|
|
|
has_one :cluster_enabled_grant, inverse_of: :namespace, class_name: 'Clusters::ClusterEnabledGrant'
|
|
|
|
|
2021-09-16 08:09:35 -04:00
|
|
|
validates :owner, presence: true, if: ->(n) { n.owner_required? }
|
2015-02-03 00:15:44 -05:00
|
|
|
validates :name,
|
2015-12-07 16:17:24 -05:00
|
|
|
presence: true,
|
2019-06-24 12:15:03 -04:00
|
|
|
length: { maximum: 255 }
|
2015-02-03 00:15:44 -05:00
|
|
|
|
2016-12-02 07:54:57 -05:00
|
|
|
validates :description, length: { maximum: 255 }
|
2021-10-02 08:10:14 -04:00
|
|
|
|
2015-02-03 00:15:44 -05:00
|
|
|
validates :path,
|
2015-12-07 16:17:12 -05:00
|
|
|
presence: true,
|
2021-10-02 08:10:14 -04:00
|
|
|
length: { maximum: URL_MAX_LENGTH }
|
|
|
|
|
|
|
|
validates :path, namespace_path: true, if: ->(n) { !n.project_namespace? }
|
|
|
|
# Project path validator is used for project namespaces for now to assure
|
|
|
|
# compatibility with project paths
|
|
|
|
# Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/341764
|
|
|
|
validates :path, project_path: true, if: ->(n) { n.project_namespace? }
|
2012-11-22 13:34:16 -05:00
|
|
|
|
2020-07-06 05:09:20 -04:00
|
|
|
# Introduce minimal path length of 2 characters.
|
|
|
|
# Allow change of other attributes without forcing users to
|
|
|
|
# rename their user or group. At the same time prevent changing
|
|
|
|
# the path without complying with new 2 chars requirement.
|
|
|
|
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/225214
|
2021-10-02 08:10:14 -04:00
|
|
|
#
|
|
|
|
# For ProjectNamespace we don't check minimal path length to keep
|
|
|
|
# compatibility with existing project restrictions.
|
|
|
|
# Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/341764
|
|
|
|
validates :path, length: { minimum: 2 }, if: :enforce_minimum_path_length?
|
2020-07-06 05:09:20 -04:00
|
|
|
|
2019-12-20 04:24:38 -05:00
|
|
|
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
|
|
|
|
|
2021-11-26 07:12:49 -05:00
|
|
|
validate :validate_parent_type
|
2021-10-27 11:13:41 -04:00
|
|
|
|
|
|
|
# ProjectNamespaces excluded as they are not meant to appear in the group hierarchy at the moment.
|
|
|
|
validate :nesting_level_allowed, unless: -> { project_namespace? }
|
|
|
|
validate :changing_shared_runners_enabled_is_allowed, unless: -> { project_namespace? }
|
|
|
|
validate :changing_allow_descendants_override_disabled_shared_runners_is_allowed, unless: -> { project_namespace? }
|
2017-02-06 10:16:50 -05:00
|
|
|
|
2012-11-22 13:34:16 -05:00
|
|
|
delegate :name, to: :owner, allow_nil: true, prefix: true
|
2019-04-16 12:16:52 -04:00
|
|
|
delegate :avatar_url, to: :owner, allow_nil: true
|
2022-05-09 11:07:50 -04:00
|
|
|
delegate :prevent_sharing_groups_outside_hierarchy, :prevent_sharing_groups_outside_hierarchy=,
|
|
|
|
to: :namespace_settings, allow_nil: true
|
2012-11-22 13:34:16 -05:00
|
|
|
|
2021-12-10 10:10:24 -05:00
|
|
|
after_save :schedule_sync_event_worker, if: -> { saved_change_to_id? || saved_change_to_parent_id? }
|
|
|
|
|
2016-11-21 08:33:58 -05:00
|
|
|
after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') }
|
2016-06-22 17:04:51 -04:00
|
|
|
|
2017-09-01 19:49:09 -04:00
|
|
|
before_create :sync_share_with_group_lock_with_parent
|
|
|
|
before_update :sync_share_with_group_lock_with_parent, if: :parent_changed?
|
2019-01-15 16:05:36 -05:00
|
|
|
after_update :force_share_with_group_lock_on_descendants, if: -> { saved_change_to_share_with_group_lock? && share_with_group_lock? }
|
2022-03-07 10:52:05 -05:00
|
|
|
after_update :expire_first_auto_devops_config_cache, if: -> { saved_change_to_auto_devops_enabled? }
|
2017-09-01 19:49:09 -04:00
|
|
|
|
2017-07-04 20:19:02 -04:00
|
|
|
# Legacy Storage specific hooks
|
|
|
|
|
2021-10-04 14:12:46 -04:00
|
|
|
after_update :move_dir, if: :saved_change_to_path_or_parent?, unless: -> { is_a?(Namespaces::ProjectNamespace) }
|
2017-02-16 02:50:05 -05:00
|
|
|
before_destroy(prepend: true) { prepare_for_destroy }
|
2012-11-21 00:54:05 -05:00
|
|
|
after_destroy :rm_dir
|
2021-05-13 05:10:44 -04:00
|
|
|
after_commit :expire_child_caches, on: :update, if: -> {
|
2022-05-06 11:09:03 -04:00
|
|
|
Feature.enabled?(:cached_route_lookups, self, type: :ops) &&
|
2021-05-13 05:10:44 -04:00
|
|
|
saved_change_to_name? || saved_change_to_path? || saved_change_to_parent_id?
|
|
|
|
}
|
2012-11-23 01:11:09 -05:00
|
|
|
|
2021-12-09 01:11:13 -05:00
|
|
|
scope :user_namespaces, -> { where(type: Namespaces::UserNamespace.sti_name) }
|
|
|
|
scope :without_project_namespaces, -> { where(Namespace.arel_table[:type].not_eq(Namespaces::ProjectNamespace.sti_name)) }
|
2022-04-19 05:08:55 -04:00
|
|
|
scope :sort_by_type, -> { order(arel_table[:type].asc.nulls_first) }
|
2020-10-11 23:08:20 -04:00
|
|
|
scope :include_route, -> { includes(:route) }
|
2021-04-19 14:09:09 -04:00
|
|
|
scope :by_parent, -> (parent) { where(parent_id: parent) }
|
|
|
|
scope :filter_by_path, -> (query) { where('lower(path) = :query', query: query.downcase) }
|
2012-11-22 23:24:09 -05:00
|
|
|
|
2016-11-22 11:58:10 -05:00
|
|
|
scope :with_statistics, -> do
|
|
|
|
joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id')
|
|
|
|
.group('namespaces.id')
|
|
|
|
.select(
|
|
|
|
'namespaces.*',
|
|
|
|
'COALESCE(SUM(ps.storage_size), 0) AS storage_size',
|
|
|
|
'COALESCE(SUM(ps.repository_size), 0) AS repository_size',
|
2019-02-13 17:38:11 -05:00
|
|
|
'COALESCE(SUM(ps.wiki_size), 0) AS wiki_size',
|
2020-06-26 11:08:45 -04:00
|
|
|
'COALESCE(SUM(ps.snippets_size), 0) AS snippets_size',
|
2016-11-22 11:58:10 -05:00
|
|
|
'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size',
|
2019-05-02 12:04:15 -04:00
|
|
|
'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size',
|
2021-10-28 08:10:22 -04:00
|
|
|
'COALESCE(SUM(ps.pipeline_artifacts_size), 0) AS pipeline_artifacts_size',
|
2020-11-13 19:09:32 -05:00
|
|
|
'COALESCE(SUM(ps.packages_size), 0) AS packages_size',
|
|
|
|
'COALESCE(SUM(ps.uploads_size), 0) AS uploads_size'
|
2016-11-22 11:58:10 -05:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2021-06-17 11:10:03 -04:00
|
|
|
scope :sorted_by_similarity_and_parent_id_desc, -> (search) do
|
|
|
|
order_expression = Gitlab::Database::SimilarityScore.build_expression(search: search, rules: [
|
|
|
|
{ column: arel_table["path"], multiplier: 1 },
|
|
|
|
{ column: arel_table["name"], multiplier: 0.7 }
|
|
|
|
])
|
|
|
|
reorder(order_expression.desc, Namespace.arel_table['parent_id'].desc.nulls_last, Namespace.arel_table['id'].desc)
|
|
|
|
end
|
|
|
|
|
2021-01-25 07:09:07 -05:00
|
|
|
# Make sure that the name is same as strong_memoize name in root_ancestor
|
|
|
|
# method
|
2021-04-08 11:09:06 -04:00
|
|
|
attr_writer :root_ancestor, :emails_disabled_memoized
|
2021-01-25 07:09:07 -05:00
|
|
|
|
2015-03-24 09:53:30 -04:00
|
|
|
class << self
|
2021-09-08 17:10:58 -04:00
|
|
|
def sti_class_for(type_name)
|
|
|
|
case type_name
|
2021-09-22 08:12:04 -04:00
|
|
|
when Group.sti_name
|
2021-09-08 17:10:58 -04:00
|
|
|
Group
|
2021-09-22 08:12:04 -04:00
|
|
|
when Namespaces::ProjectNamespace.sti_name
|
2021-09-08 17:10:58 -04:00
|
|
|
Namespaces::ProjectNamespace
|
2021-09-22 08:12:04 -04:00
|
|
|
when Namespaces::UserNamespace.sti_name
|
2021-09-23 08:11:29 -04:00
|
|
|
Namespaces::UserNamespace
|
2021-09-08 17:10:58 -04:00
|
|
|
else
|
|
|
|
Namespace
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-03-24 09:53:30 -04:00
|
|
|
def by_path(path)
|
2015-12-14 21:53:52 -05:00
|
|
|
find_by('lower(path) = :value', value: path.downcase)
|
2015-03-24 09:53:30 -04:00
|
|
|
end
|
|
|
|
|
2018-10-30 06:53:01 -04:00
|
|
|
# Case insensitive search for namespace by path or name
|
2015-03-24 09:53:30 -04:00
|
|
|
def find_by_path_or_name(path)
|
|
|
|
find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase)
|
|
|
|
end
|
|
|
|
|
2016-03-04 06:06:25 -05:00
|
|
|
# Searches for namespaces matching the given query.
|
|
|
|
#
|
2020-06-15 23:08:24 -04:00
|
|
|
# This method uses ILIKE on PostgreSQL.
|
2016-03-04 06:06:25 -05:00
|
|
|
#
|
2020-06-15 23:08:24 -04:00
|
|
|
# query - The search query as a String.
|
2016-03-04 06:06:25 -05:00
|
|
|
#
|
2020-06-15 23:08:24 -04:00
|
|
|
# Returns an ActiveRecord::Relation.
|
2020-10-25 23:08:41 -04:00
|
|
|
def search(query, include_parents: false)
|
|
|
|
if include_parents
|
2021-11-02 08:12:25 -04:00
|
|
|
without_project_namespaces.where(id: Route.for_routable_type(Namespace.name).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]]).select(:source_id))
|
2020-10-25 23:08:41 -04:00
|
|
|
else
|
2021-11-02 08:12:25 -04:00
|
|
|
without_project_namespaces.fuzzy_search(query, [:path, :name])
|
2020-10-25 23:08:41 -04:00
|
|
|
end
|
2015-03-24 09:53:30 -04:00
|
|
|
end
|
|
|
|
|
2022-05-25 08:08:10 -04:00
|
|
|
def clean_path(path, limited_to: Namespace.all)
|
2015-04-17 06:03:54 -04:00
|
|
|
path = path.dup
|
2015-04-21 09:49:16 -04:00
|
|
|
# Get the email username by removing everything after an `@` sign.
|
2016-08-25 12:48:08 -04:00
|
|
|
path.gsub!(/@.*\z/, "")
|
2015-04-21 09:49:16 -04:00
|
|
|
# Remove everything that's not in the list of allowed characters.
|
2016-08-25 12:48:08 -04:00
|
|
|
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
|
|
|
|
# Remove trailing violations ('.atom', '.git', or '.')
|
2021-10-28 14:14:18 -04:00
|
|
|
loop do
|
|
|
|
orig = path
|
|
|
|
PATH_TRAILING_VIOLATIONS.each { |ext| path = path.chomp(ext) }
|
|
|
|
break if orig == path
|
|
|
|
end
|
|
|
|
|
2016-08-25 12:48:08 -04:00
|
|
|
# Remove leading violations ('-')
|
2021-10-28 14:14:18 -04:00
|
|
|
path.gsub!(/\A\-+/, "")
|
2015-03-24 09:53:30 -04:00
|
|
|
|
2015-04-21 06:00:08 -04:00
|
|
|
# Users with the great usernames of "." or ".." would end up with a blank username.
|
2015-06-03 08:57:12 -04:00
|
|
|
# Work around that by setting their username to "blank", followed by a counter.
|
2015-04-21 06:00:08 -04:00
|
|
|
path = "blank" if path.blank?
|
|
|
|
|
2017-02-03 07:55:50 -05:00
|
|
|
uniquify = Uniquify.new
|
2022-05-25 08:08:10 -04:00
|
|
|
uniquify.string(path) { |s| limited_to.find_by_path_or_name(s) }
|
2015-03-24 09:53:30 -04:00
|
|
|
end
|
2019-09-25 05:06:04 -04:00
|
|
|
|
2020-08-25 08:04:30 -04:00
|
|
|
def clean_name(value)
|
|
|
|
value.scan(Gitlab::Regex.group_name_regex_chars).join(' ')
|
|
|
|
end
|
|
|
|
|
2019-09-25 05:06:04 -04:00
|
|
|
def find_by_pages_host(host)
|
|
|
|
gitlab_host = "." + Settings.pages.host.downcase
|
2019-12-11 07:08:10 -05:00
|
|
|
host = host.downcase
|
|
|
|
return unless host.ends_with?(gitlab_host)
|
2019-09-25 05:06:04 -04:00
|
|
|
|
2019-12-11 07:08:10 -05:00
|
|
|
name = host.delete_suffix(gitlab_host)
|
2022-05-13 11:07:43 -04:00
|
|
|
Namespace.top_most.by_path(name)
|
2019-09-25 05:06:04 -04:00
|
|
|
end
|
2021-02-18 13:10:41 -05:00
|
|
|
|
|
|
|
def top_most
|
2022-05-13 11:07:43 -04:00
|
|
|
by_parent(nil)
|
2021-02-18 13:10:41 -05:00
|
|
|
end
|
2012-11-27 01:31:15 -05:00
|
|
|
end
|
|
|
|
|
2021-01-05 13:10:25 -05:00
|
|
|
def package_settings
|
|
|
|
package_setting_relation || build_package_setting_relation
|
|
|
|
end
|
|
|
|
|
2020-03-02 07:07:57 -05:00
|
|
|
def default_branch_protection
|
|
|
|
super || Gitlab::CurrentSettings.default_branch_protection
|
|
|
|
end
|
|
|
|
|
2017-07-17 06:40:17 -04:00
|
|
|
def visibility_level_field
|
|
|
|
:visibility_level
|
|
|
|
end
|
|
|
|
|
2012-11-22 13:34:16 -05:00
|
|
|
def to_param
|
2016-10-31 07:00:53 -04:00
|
|
|
full_path
|
2012-11-22 13:34:16 -05:00
|
|
|
end
|
2012-11-22 23:11:09 -05:00
|
|
|
|
|
|
|
def human_name
|
|
|
|
owner_name
|
|
|
|
end
|
2012-11-23 01:11:09 -05:00
|
|
|
|
2016-05-09 15:41:48 -04:00
|
|
|
def any_project_has_container_registry_tags?
|
2021-04-29 08:09:58 -04:00
|
|
|
all_projects.includes(:container_repositories).any?(&:has_container_registry_tags?)
|
2018-10-01 23:13:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def first_project_with_container_registry_tags
|
|
|
|
all_projects.find(&:has_container_registry_tags?)
|
2016-05-09 15:41:48 -04:00
|
|
|
end
|
|
|
|
|
2017-08-01 01:45:04 -04:00
|
|
|
def send_update_instructions
|
|
|
|
projects.each do |project|
|
2019-04-23 05:30:18 -04:00
|
|
|
project.send_move_instructions("#{full_path_before_last_save}/#{project.path}")
|
2017-08-01 01:45:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-11-15 08:25:37 -05:00
|
|
|
def kind
|
2021-09-21 11:12:11 -04:00
|
|
|
return 'group' if group_namespace?
|
|
|
|
return 'project' if project_namespace?
|
2014-11-14 09:06:39 -05:00
|
|
|
|
2021-09-08 17:10:58 -04:00
|
|
|
'user' # defaults to user
|
2019-04-16 12:16:52 -04:00
|
|
|
end
|
|
|
|
|
2021-09-21 11:12:11 -04:00
|
|
|
def group_namespace?
|
2021-09-08 17:10:58 -04:00
|
|
|
type == Group.sti_name
|
|
|
|
end
|
|
|
|
|
2021-09-21 11:12:11 -04:00
|
|
|
def project_namespace?
|
2021-09-08 17:10:58 -04:00
|
|
|
type == Namespaces::ProjectNamespace.sti_name
|
|
|
|
end
|
|
|
|
|
2021-09-21 11:12:11 -04:00
|
|
|
def user_namespace?
|
2021-09-08 17:10:58 -04:00
|
|
|
# That last bit ensures we're considered a user namespace as a default
|
2021-09-21 11:12:11 -04:00
|
|
|
type.nil? || type == Namespaces::UserNamespace.sti_name || !(group_namespace? || project_namespace?)
|
2020-05-07 11:09:29 -04:00
|
|
|
end
|
|
|
|
|
2021-09-16 08:09:35 -04:00
|
|
|
def owner_required?
|
2021-09-21 11:12:11 -04:00
|
|
|
user_namespace?
|
2021-09-16 08:09:35 -04:00
|
|
|
end
|
|
|
|
|
2022-01-19 19:15:02 -05:00
|
|
|
def first_owner
|
|
|
|
owner
|
|
|
|
end
|
|
|
|
|
2014-11-14 09:06:39 -05:00
|
|
|
def find_fork_of(project)
|
2019-02-08 07:19:53 -05:00
|
|
|
return unless project.fork_network
|
2017-10-03 11:06:09 -04:00
|
|
|
|
2018-09-20 18:40:15 -04:00
|
|
|
if Gitlab::SafeRequestStore.active?
|
|
|
|
forks_in_namespace = Gitlab::SafeRequestStore.fetch("namespaces:#{id}:forked_projects") do
|
2017-11-30 08:26:08 -05:00
|
|
|
Hash.new do |found_forks, project|
|
|
|
|
found_forks[project] = project.fork_network.find_forks_in(projects).first
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
forks_in_namespace[project]
|
|
|
|
else
|
|
|
|
project.fork_network.find_forks_in(projects).first
|
|
|
|
end
|
2014-11-14 09:06:39 -05:00
|
|
|
end
|
2016-06-22 17:04:51 -04:00
|
|
|
|
2019-08-15 13:37:36 -04:00
|
|
|
# any ancestor can disable emails for all descendants
|
|
|
|
def emails_disabled?
|
2021-04-08 11:09:06 -04:00
|
|
|
strong_memoize(:emails_disabled_memoized) do
|
2020-01-16 16:08:24 -05:00
|
|
|
if parent_id
|
|
|
|
self_and_ancestors.where(emails_disabled: true).exists?
|
|
|
|
else
|
|
|
|
!!emails_disabled
|
|
|
|
end
|
2019-08-15 13:37:36 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-09-01 19:49:48 -04:00
|
|
|
def lfs_enabled?
|
|
|
|
# User namespace will always default to the global setting
|
|
|
|
Gitlab.config.lfs.enabled
|
|
|
|
end
|
|
|
|
|
2020-07-06 05:09:20 -04:00
|
|
|
def any_project_with_shared_runners_enabled?
|
2017-01-20 06:25:53 -05:00
|
|
|
projects.with_shared_runners.any?
|
|
|
|
end
|
|
|
|
|
2017-02-07 08:55:42 -05:00
|
|
|
def user_ids_for_project_authorizations
|
|
|
|
[owner_id]
|
|
|
|
end
|
|
|
|
|
2017-04-03 11:48:17 -04:00
|
|
|
# Includes projects from this namespace and projects from all subgroups
|
|
|
|
# that belongs to this namespace
|
|
|
|
def all_projects
|
2022-05-06 11:09:03 -04:00
|
|
|
if Feature.enabled?(:recursive_approach_for_all_projects)
|
2021-09-21 11:12:11 -04:00
|
|
|
namespace = user_namespace? ? self : self_and_descendant_ids
|
2021-06-30 11:08:27 -04:00
|
|
|
Project.where(namespace: namespace)
|
|
|
|
else
|
|
|
|
Project.inside_path(full_path)
|
|
|
|
end
|
2017-04-03 11:48:17 -04:00
|
|
|
end
|
|
|
|
|
2017-04-11 10:21:52 -04:00
|
|
|
def has_parent?
|
2020-05-22 05:08:09 -04:00
|
|
|
parent_id.present? || parent.present?
|
2017-04-11 10:21:52 -04:00
|
|
|
end
|
|
|
|
|
2017-08-29 06:15:19 -04:00
|
|
|
def subgroup?
|
|
|
|
has_parent?
|
|
|
|
end
|
|
|
|
|
2018-03-06 09:15:19 -05:00
|
|
|
# Overridden on EE module
|
2018-02-19 14:06:16 -05:00
|
|
|
def multiple_issue_boards_available?
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2022-04-25 05:08:29 -04:00
|
|
|
def all_project_ids_except(ids)
|
|
|
|
all_projects.where.not(id: ids).pluck(:id)
|
|
|
|
end
|
|
|
|
|
2021-03-29 14:09:37 -04:00
|
|
|
# Deprecated, use #licensed_feature_available? instead. Remove once Namespace#feature_available? isn't used anymore.
|
2022-04-07 08:10:21 -04:00
|
|
|
def feature_available?(feature, _user = nil)
|
2021-03-29 14:09:37 -04:00
|
|
|
licensed_feature_available?(feature)
|
|
|
|
end
|
|
|
|
|
2019-07-11 13:22:35 -04:00
|
|
|
# Overridden in EE::Namespace
|
2021-03-29 14:09:37 -04:00
|
|
|
def licensed_feature_available?(_feature)
|
2019-07-11 13:22:35 -04:00
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2019-04-23 05:30:18 -04:00
|
|
|
def full_path_before_last_save
|
|
|
|
if parent_id_before_last_save.nil?
|
|
|
|
path_before_last_save
|
2018-02-05 19:10:58 -05:00
|
|
|
else
|
2019-04-23 05:30:18 -04:00
|
|
|
previous_parent = Group.find_by(id: parent_id_before_last_save)
|
|
|
|
previous_parent.full_path + '/' + path_before_last_save
|
2018-02-05 19:10:58 -05:00
|
|
|
end
|
2018-01-23 14:03:02 -05:00
|
|
|
end
|
|
|
|
|
2018-04-06 11:23:49 -04:00
|
|
|
def refresh_project_authorizations
|
|
|
|
owner.refresh_authorized_projects
|
|
|
|
end
|
|
|
|
|
2019-03-12 06:15:33 -04:00
|
|
|
def auto_devops_enabled?
|
|
|
|
first_auto_devops_config[:status]
|
|
|
|
end
|
|
|
|
|
|
|
|
def first_auto_devops_config
|
|
|
|
return { scope: :group, status: auto_devops_enabled } unless auto_devops_enabled.nil?
|
|
|
|
|
|
|
|
strong_memoize(:first_auto_devops_config) do
|
2022-05-25 17:08:58 -04:00
|
|
|
if has_parent?
|
2022-03-07 10:52:05 -05:00
|
|
|
Rails.cache.fetch(first_auto_devops_config_cache_key_for(id), expires_in: 1.day) do
|
|
|
|
parent.first_auto_devops_config
|
|
|
|
end
|
2019-03-12 06:15:33 -04:00
|
|
|
else
|
|
|
|
{ scope: :instance, status: Gitlab::CurrentSettings.auto_devops_enabled? }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-02 10:44:39 -04:00
|
|
|
def aggregation_scheduled?
|
|
|
|
aggregation_schedule.present?
|
|
|
|
end
|
|
|
|
|
2022-05-20 05:09:15 -04:00
|
|
|
def container_repositories_size
|
|
|
|
strong_memoize(:container_repositories_size) do
|
|
|
|
next unless Gitlab.com?
|
|
|
|
next unless ContainerRegistry::GitlabApiClient.supports_gitlab_api?
|
|
|
|
next 0 if all_container_repositories.empty?
|
|
|
|
next unless all_container_repositories.all_migrated?
|
|
|
|
|
|
|
|
ContainerRegistry::GitlabApiClient.deduplicated_size(full_path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def all_container_repositories
|
|
|
|
ContainerRepository.for_project_id(all_projects)
|
|
|
|
end
|
|
|
|
|
2019-09-25 05:06:04 -04:00
|
|
|
def pages_virtual_domain
|
2020-02-20 04:09:13 -05:00
|
|
|
Pages::VirtualDomain.new(
|
2020-11-10 16:08:51 -05:00
|
|
|
all_projects_with_pages.includes(:route, :project_feature, pages_metadatum: :pages_deployment),
|
2020-02-20 04:09:13 -05:00
|
|
|
trim_prefix: full_path
|
|
|
|
)
|
2019-09-25 05:06:04 -04:00
|
|
|
end
|
|
|
|
|
2020-08-27 11:10:21 -04:00
|
|
|
def any_project_with_pages_deployed?
|
|
|
|
all_projects.with_pages_deployed.any?
|
|
|
|
end
|
|
|
|
|
2019-10-08 08:06:01 -04:00
|
|
|
def closest_setting(name)
|
|
|
|
self_and_ancestors(hierarchy_order: :asc)
|
|
|
|
.find { |n| !n.read_attribute(name).nil? }
|
|
|
|
.try(name)
|
|
|
|
end
|
|
|
|
|
2020-04-22 11:09:27 -04:00
|
|
|
def actual_plan
|
|
|
|
Plan.default
|
|
|
|
end
|
|
|
|
|
2021-03-26 08:09:15 -04:00
|
|
|
def paid?
|
|
|
|
root? && actual_plan.paid?
|
|
|
|
end
|
|
|
|
|
2020-04-22 11:09:27 -04:00
|
|
|
def actual_limits
|
|
|
|
# We default to PlanLimits.new otherwise a lot of specs would fail
|
|
|
|
# On production each plan should already have associated limits record
|
|
|
|
# https://gitlab.com/gitlab-org/gitlab/issues/36037
|
2020-05-05 17:09:42 -04:00
|
|
|
actual_plan.actual_limits
|
2020-04-22 11:09:27 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def actual_plan_name
|
|
|
|
actual_plan.name
|
|
|
|
end
|
|
|
|
|
2020-09-30 11:09:46 -04:00
|
|
|
def changing_shared_runners_enabled_is_allowed
|
|
|
|
return unless new_record? || changes.has_key?(:shared_runners_enabled)
|
|
|
|
|
2021-10-13 11:12:51 -04:00
|
|
|
if shared_runners_enabled && has_parent? && parent.shared_runners_setting == SR_DISABLED_AND_UNOVERRIDABLE
|
2020-09-30 11:09:46 -04:00
|
|
|
errors.add(:shared_runners_enabled, _('cannot be enabled because parent group has shared Runners disabled'))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def changing_allow_descendants_override_disabled_shared_runners_is_allowed
|
|
|
|
return unless new_record? || changes.has_key?(:allow_descendants_override_disabled_shared_runners)
|
|
|
|
|
|
|
|
if shared_runners_enabled && !new_record?
|
|
|
|
errors.add(:allow_descendants_override_disabled_shared_runners, _('cannot be changed if shared runners are enabled'))
|
|
|
|
end
|
|
|
|
|
2021-10-13 11:12:51 -04:00
|
|
|
if allow_descendants_override_disabled_shared_runners && has_parent? && parent.shared_runners_setting == SR_DISABLED_AND_UNOVERRIDABLE
|
2020-09-30 11:09:46 -04:00
|
|
|
errors.add(:allow_descendants_override_disabled_shared_runners, _('cannot be enabled because parent group does not allow it'))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def shared_runners_setting
|
|
|
|
if shared_runners_enabled
|
2021-10-13 11:12:51 -04:00
|
|
|
SR_ENABLED
|
2020-09-30 11:09:46 -04:00
|
|
|
else
|
|
|
|
if allow_descendants_override_disabled_shared_runners
|
2021-10-13 11:12:51 -04:00
|
|
|
SR_DISABLED_WITH_OVERRIDE
|
2020-09-30 11:09:46 -04:00
|
|
|
else
|
2021-10-13 11:12:51 -04:00
|
|
|
SR_DISABLED_AND_UNOVERRIDABLE
|
2020-09-30 11:09:46 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def shared_runners_setting_higher_than?(other_setting)
|
2021-10-13 11:12:51 -04:00
|
|
|
if other_setting == SR_ENABLED
|
2020-09-30 11:09:46 -04:00
|
|
|
false
|
2021-10-13 11:12:51 -04:00
|
|
|
elsif other_setting == SR_DISABLED_WITH_OVERRIDE
|
|
|
|
shared_runners_setting == SR_ENABLED
|
|
|
|
elsif other_setting == SR_DISABLED_AND_UNOVERRIDABLE
|
|
|
|
shared_runners_setting == SR_ENABLED || shared_runners_setting == SR_DISABLED_WITH_OVERRIDE
|
2020-09-30 11:09:46 -04:00
|
|
|
else
|
|
|
|
raise ArgumentError
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-02-02 04:16:48 -05:00
|
|
|
def shared_runners
|
|
|
|
@shared_runners ||= shared_runners_enabled ? Ci::Runner.instance_type : Ci::Runner.none
|
|
|
|
end
|
|
|
|
|
2020-12-23 07:10:26 -05:00
|
|
|
def root?
|
|
|
|
!has_parent?
|
|
|
|
end
|
|
|
|
|
2021-02-18 10:09:43 -05:00
|
|
|
def recent?
|
|
|
|
created_at >= 90.days.ago
|
|
|
|
end
|
|
|
|
|
2021-05-17 14:10:42 -04:00
|
|
|
def issue_repositioning_disabled?
|
2022-05-06 11:09:03 -04:00
|
|
|
Feature.enabled?(:block_issue_repositioning, self, type: :ops)
|
2021-05-17 14:10:42 -04:00
|
|
|
end
|
|
|
|
|
2022-02-15 16:12:52 -05:00
|
|
|
def storage_enforcement_date
|
|
|
|
# should return something like Date.new(2022, 02, 03)
|
|
|
|
# TBD: https://gitlab.com/gitlab-org/gitlab/-/issues/350632
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2022-05-05 05:08:00 -04:00
|
|
|
def certificate_based_clusters_enabled?
|
2022-05-20 11:09:10 -04:00
|
|
|
cluster_enabled_granted? || certificate_based_clusters_enabled_ff?
|
2022-05-05 05:08:00 -04:00
|
|
|
end
|
|
|
|
|
2016-06-22 17:04:51 -04:00
|
|
|
private
|
|
|
|
|
2022-05-20 11:09:10 -04:00
|
|
|
def cluster_enabled_granted?
|
2022-05-23 14:08:14 -04:00
|
|
|
(Gitlab.com? || Gitlab.dev_or_test_env?) && root_ancestor.cluster_enabled_grant.present?
|
2022-05-20 11:09:10 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def certificate_based_clusters_enabled_ff?
|
|
|
|
Feature.enabled?(:certificate_based_clusters, type: :ops)
|
|
|
|
end
|
|
|
|
|
2021-05-13 05:10:44 -04:00
|
|
|
def expire_child_caches
|
|
|
|
Namespace.where(id: descendants).each_batch do |namespaces|
|
|
|
|
namespaces.touch_all
|
|
|
|
end
|
|
|
|
|
|
|
|
all_projects.each_batch do |projects|
|
|
|
|
projects.touch_all
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-09-25 05:06:04 -04:00
|
|
|
def all_projects_with_pages
|
|
|
|
all_projects.with_pages_deployed
|
|
|
|
end
|
|
|
|
|
2019-04-23 05:30:18 -04:00
|
|
|
def parent_changed?
|
|
|
|
parent_id_changed?
|
|
|
|
end
|
|
|
|
|
|
|
|
def saved_change_to_parent?
|
|
|
|
saved_change_to_parent_id?
|
|
|
|
end
|
|
|
|
|
|
|
|
def saved_change_to_path_or_parent?
|
2019-01-15 16:05:36 -05:00
|
|
|
saved_change_to_path? || saved_change_to_parent_id?
|
2018-02-05 19:10:58 -05:00
|
|
|
end
|
|
|
|
|
2016-11-21 08:33:58 -05:00
|
|
|
def refresh_access_of_projects_invited_groups
|
2021-07-26 08:10:08 -04:00
|
|
|
if Feature.enabled?(:specialized_worker_for_group_lock_update_auth_recalculation)
|
|
|
|
Project
|
|
|
|
.where(namespace_id: id)
|
|
|
|
.joins(:project_group_links)
|
|
|
|
.distinct
|
|
|
|
.find_each do |project|
|
|
|
|
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(project.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Until we compare the inconsistency rates of the new specialized worker and
|
|
|
|
# the old approach, we still run AuthorizedProjectsWorker
|
|
|
|
# but with some delay and lower urgency as a safety net.
|
2021-10-28 14:14:18 -04:00
|
|
|
enqueue_jobs_for_groups_requiring_authorizations_refresh(priority: UserProjectAccessChangedService::LOW_PRIORITY)
|
2021-07-26 08:10:08 -04:00
|
|
|
else
|
2021-10-28 14:14:18 -04:00
|
|
|
enqueue_jobs_for_groups_requiring_authorizations_refresh(priority: UserProjectAccessChangedService::HIGH_PRIORITY)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def enqueue_jobs_for_groups_requiring_authorizations_refresh(priority:)
|
|
|
|
groups_requiring_authorizations_refresh = Group
|
|
|
|
.joins(project_group_links: :project)
|
|
|
|
.where(projects: { namespace_id: id })
|
|
|
|
.distinct
|
|
|
|
|
|
|
|
groups_requiring_authorizations_refresh.find_each do |group|
|
|
|
|
group.refresh_members_authorized_projects(
|
|
|
|
blocking: false,
|
|
|
|
priority: priority
|
|
|
|
)
|
2021-07-26 08:10:08 -04:00
|
|
|
end
|
2016-11-21 08:33:58 -05:00
|
|
|
end
|
2016-10-31 07:00:53 -04:00
|
|
|
|
2017-02-06 10:16:50 -05:00
|
|
|
def nesting_level_allowed
|
|
|
|
if ancestors.count > Group::NUMBER_OF_ANCESTORS_ALLOWED
|
2021-09-16 11:12:47 -04:00
|
|
|
errors.add(:parent_id, _('has too deep level of nesting'))
|
2017-02-06 10:16:50 -05:00
|
|
|
end
|
|
|
|
end
|
2017-09-01 19:49:09 -04:00
|
|
|
|
2021-03-01 10:10:53 -05:00
|
|
|
def validate_parent_type
|
2021-09-16 11:12:47 -04:00
|
|
|
unless has_parent?
|
2021-09-21 11:12:11 -04:00
|
|
|
if project_namespace?
|
2021-09-16 11:12:47 -04:00
|
|
|
errors.add(:parent_id, _('must be set for a project namespace'))
|
|
|
|
end
|
|
|
|
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2022-05-25 23:09:21 -04:00
|
|
|
if parent&.project_namespace?
|
2021-09-16 11:12:47 -04:00
|
|
|
errors.add(:parent_id, _('project namespace cannot be the parent of another namespace'))
|
|
|
|
end
|
2021-03-01 10:10:53 -05:00
|
|
|
|
2021-09-21 11:12:11 -04:00
|
|
|
if user_namespace?
|
2021-10-20 11:12:43 -04:00
|
|
|
errors.add(:parent_id, _('cannot be used for user namespace'))
|
2021-09-21 11:12:11 -04:00
|
|
|
elsif group_namespace?
|
|
|
|
errors.add(:parent_id, _('user namespace cannot be the parent of another namespace')) if parent.user_namespace?
|
2021-03-01 10:10:53 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-01 19:49:09 -04:00
|
|
|
def sync_share_with_group_lock_with_parent
|
2017-09-05 20:10:30 -04:00
|
|
|
if parent&.share_with_group_lock?
|
2017-09-01 19:49:09 -04:00
|
|
|
self.share_with_group_lock = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def force_share_with_group_lock_on_descendants
|
2017-09-15 09:34:41 -04:00
|
|
|
# We can't use `descendants.update_all` since Rails will throw away the WITH
|
|
|
|
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
|
|
|
|
# different table aliases, hence we're just using WHERE IN. Since we have a
|
|
|
|
# maximum of 20 nested groups this should be fine.
|
|
|
|
Namespace.where(id: descendants.select(:id))
|
|
|
|
.update_all(share_with_group_lock: true)
|
2017-09-01 19:49:09 -04:00
|
|
|
end
|
2017-12-08 12:42:43 -05:00
|
|
|
|
2022-03-07 10:52:05 -05:00
|
|
|
def expire_first_auto_devops_config_cache
|
|
|
|
descendants_to_expire = self_and_descendants.as_ids
|
|
|
|
return if descendants_to_expire.load.empty?
|
|
|
|
|
|
|
|
keys = descendants_to_expire.map { |group| first_auto_devops_config_cache_key_for(group.id) }
|
|
|
|
Rails.cache.delete_multi(keys)
|
|
|
|
end
|
|
|
|
|
2017-12-21 12:32:08 -05:00
|
|
|
def write_projects_repository_config
|
|
|
|
all_projects.find_each do |project|
|
2021-07-29 02:10:15 -04:00
|
|
|
project.set_full_path
|
2018-12-21 10:31:14 -05:00
|
|
|
project.track_project_repository
|
2017-12-19 14:42:51 -05:00
|
|
|
end
|
|
|
|
end
|
2021-10-02 08:10:14 -04:00
|
|
|
|
|
|
|
def enforce_minimum_path_length?
|
|
|
|
path_changed? && !project_namespace?
|
|
|
|
end
|
2021-12-10 10:10:24 -05:00
|
|
|
|
|
|
|
# SyncEvents are created by PG triggers (with the function `insert_namespaces_sync_event`)
|
|
|
|
def schedule_sync_event_worker
|
|
|
|
run_after_commit do
|
|
|
|
Namespaces::SyncEvent.enqueue_worker
|
|
|
|
end
|
|
|
|
end
|
2022-03-07 10:52:05 -05:00
|
|
|
|
|
|
|
def first_auto_devops_config_cache_key_for(group_id)
|
|
|
|
# Use SHA2 of `traversal_ids` to account for moving a namespace within the same root ancestor hierarchy.
|
|
|
|
"namespaces:{#{traversal_ids.first}}:first_auto_devops_config:#{group_id}:#{Digest::SHA2.hexdigest(traversal_ids.join(' '))}"
|
|
|
|
end
|
2012-11-22 13:34:16 -05:00
|
|
|
end
|
2019-09-13 09:26:31 -04:00
|
|
|
|
2021-05-11 17:10:21 -04:00
|
|
|
Namespace.prepend_mod_with('Namespace')
|