Class: Namespace

Overview

A Namespace::TraversalHierarchy is the collection of namespaces that descend from a root Namespace as defined by the Namespace#traversal_ids attributes.

This class provides operations to be performed on the hierarchy itself, rather than individual namespaces.

This includes methods for synchronizing traversal_ids attributes to a correct state. We use recursive methods to determine the correct state so we don't have to depend on the integrity of the traversal_ids attribute values themselves.

Defined Under Namespace

Classes: AdminNote, AggregationSchedule, PackageSetting, PackageSettingPolicy, PackageSettingsType, RootStorageStatistics, RootStorageStatisticsPolicy, SharedRunnersSettingEnum, TraversalHierarchy

Constant Summary collapse

NUMBER_OF_ANCESTORS_ALLOWED =

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.

20
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
URL_MAX_LENGTH =
255
PATH_TRAILING_VIOLATIONS =
%w[.git .atom .].freeze

Constants included from BlocksUnsafeSerialization

BlocksUnsafeSerialization::UnsafeSerializationError

Constants included from Namespaces::Traversal::Linear

Namespaces::Traversal::Linear::UnboundedSearch

Constants included from Gitlab::SQL::Pattern

Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_WORD

Constants included from Gitlab::VisibilityLevel

Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::PUBLIC

Constants included from CacheMarkdownField

CacheMarkdownField::INVALIDATED_BY

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from BlocksUnsafeSerialization

#serializable_hash

Methods included from Gitlab::Utils::Override

#extended, extensions, #included, #method_added, #override, #prepended, #queue_verification, verify!

Methods included from Namespaces::Traversal::Linear

#ancestor_ids, #ancestors, #ancestors_upto, #descendants, #root_ancestor, #self_and_ancestor_ids, #self_and_ancestors, #self_and_descendant_ids, #self_and_descendants, #self_and_hierarchy, #sync_traversal_ids?, #use_traversal_ids?, #use_traversal_ids_for_ancestors?, #use_traversal_ids_for_ancestors_upto?, #use_traversal_ids_for_root_ancestor?, #use_traversal_ids_for_self_and_hierarchy?

Methods included from Namespaces::Traversal::Recursive

#ancestor_ids, #ancestors, #ancestors_upto, #descendants, #object_hierarchy, #root_ancestor, #self_and_ancestor_ids, #self_and_ancestors, #self_and_descendant_ids, #self_and_descendants, #self_and_hierarchy

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods included from FeatureGate

#flipper_id

Methods included from Storage::LegacyNamespace

#move_dir, #prepare_for_destroy

Methods included from Gitlab::ShellAdapter

#gitlab_shell

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from Routable

#build_full_path, find_by_full_path, #full_name, #full_path, #full_path_components, #owned_by?, #parent_loaded?, #route_loaded?

Methods included from Gitlab::VisibilityLevel

allowed_for?, allowed_level?, allowed_levels, closest_allowed_level, #internal?, level_name, level_value, levels_for_user, non_restricted_level?, options, #private?, #public?, public_visibility_restricted?, restricted_level?, string_level, string_options, string_values, valid_level?, #visibility, #visibility=, #visibility_attribute_present?, #visibility_attribute_value, #visibility_level_attributes, #visibility_level_previous_changes, #visibility_level_value

Methods included from CacheMarkdownField

#attribute_invalidated?, #banzai_render_context, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #local_version, #mentionable_attributes_changed?, #parent_user, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #skip_project_check?, #store_mentions!, #updated_cached_html_for

Methods inherited from ApplicationRecord

cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from SensitiveSerializableHash

#serializable_hash

Instance Attribute Details

#emails_disabled_memoized=(value) ⇒ Object (writeonly)

Make sure that the name is same as strong_memoize name in root_ancestor method


167
168
169
# File 'app/models/namespace.rb', line 167

def emails_disabled_memoized=(value)
  @emails_disabled_memoized = value
end

#root_ancestor=(value) ⇒ Object (writeonly)

Make sure that the name is same as strong_memoize name in root_ancestor method


167
168
169
# File 'app/models/namespace.rb', line 167

def root_ancestor=(value)
  @root_ancestor = value
end

Class Method Details

.by_path(path) ⇒ Object


183
184
185
# File 'app/models/namespace.rb', line 183

def by_path(path)
  find_by('lower(path) = :value', value: path.downcase)
end

.clean_name(value) ⇒ Object


231
232
233
# File 'app/models/namespace.rb', line 231

def clean_name(value)
  value.scan(Gitlab::Regex.group_name_regex_chars).join(' ')
end

.clean_path(path) ⇒ Object


207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'app/models/namespace.rb', line 207

def clean_path(path)
  path = path.dup
  # Get the email username by removing everything after an `@` sign.
  path.gsub!(/@.*\z/,                "")
  # Remove everything that's not in the list of allowed characters.
  path.gsub!(/[^a-zA-Z0-9_\-\.]/,    "")
  # Remove trailing violations ('.atom', '.git', or '.')
  loop do
    orig = path
    PATH_TRAILING_VIOLATIONS.each { |ext| path = path.chomp(ext) }
    break if orig == path
  end

  # Remove leading violations ('-')
  path.gsub!(/\A\-+/, "")

  # Users with the great usernames of "." or ".." would end up with a blank username.
  # Work around that by setting their username to "blank", followed by a counter.
  path = "blank" if path.blank?

  uniquify = Uniquify.new
  uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) }
end

.find_by_pages_host(host) ⇒ Object


235
236
237
238
239
240
241
242
# File 'app/models/namespace.rb', line 235

def find_by_pages_host(host)
  gitlab_host = "." + Settings.pages.host.downcase
  host = host.downcase
  return unless host.ends_with?(gitlab_host)

  name = host.delete_suffix(gitlab_host)
  Namespace.where(parent_id: nil).by_path(name)
end

.find_by_path_or_name(path) ⇒ Object

Case insensitive search for namespace by path or name


188
189
190
# File 'app/models/namespace.rb', line 188

def find_by_path_or_name(path)
  find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase)
end

.search(query, include_parents: false) ⇒ Object

Searches for namespaces matching the given query.

This method uses ILIKE on PostgreSQL.

query - The search query as a String.

Returns an ActiveRecord::Relation.


199
200
201
202
203
204
205
# File 'app/models/namespace.rb', line 199

def search(query, include_parents: false)
  if include_parents
    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))
  else
    without_project_namespaces.fuzzy_search(query, [:path, :name])
  end
end

.sti_class_for(type_name) ⇒ Object


170
171
172
173
174
175
176
177
178
179
180
181
# File 'app/models/namespace.rb', line 170

def sti_class_for(type_name)
  case type_name
  when Group.sti_name
    Group
  when Namespaces::ProjectNamespace.sti_name
    Namespaces::ProjectNamespace
  when Namespaces::UserNamespace.sti_name
    Namespaces::UserNamespace
  else
    Namespace
  end
end

.top_mostObject


244
245
246
# File 'app/models/namespace.rb', line 244

def top_most
  where(parent_id: nil)
end

Instance Method Details

#actual_limitsObject


451
452
453
454
455
456
# File 'app/models/namespace.rb', line 451

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
  actual_plan.actual_limits
end

#actual_planObject


443
444
445
# File 'app/models/namespace.rb', line 443

def actual_plan
  Plan.default
end

#actual_plan_nameObject


458
459
460
# File 'app/models/namespace.rb', line 458

def actual_plan_name
  actual_plan.name
end

#aggregation_scheduled?Boolean

Returns:

  • (Boolean)

422
423
424
# File 'app/models/namespace.rb', line 422

def aggregation_scheduled?
  aggregation_schedule.present?
end

#all_project_ids_except(ids) ⇒ Object


375
376
377
# File 'app/models/namespace.rb', line 375

def all_project_ids_except(ids)
  all_projects.where.not(id: ids).pluck(:id)
end

#all_projectsObject

Includes projects from this namespace and projects from all subgroups that belongs to this namespace


353
354
355
356
357
358
359
360
# File 'app/models/namespace.rb', line 353

def all_projects
  if Feature.enabled?(:recursive_approach_for_all_projects)
    namespace = user_namespace? ? self : self_and_descendant_ids
    Project.where(namespace: namespace)
  else
    Project.inside_path(full_path)
  end
end

#any_project_has_container_registry_tags?Boolean

Returns:

  • (Boolean)

269
270
271
# File 'app/models/namespace.rb', line 269

def any_project_has_container_registry_tags?
  all_projects.includes(:container_repositories).any?(&:has_container_registry_tags?)
end

#any_project_with_pages_deployed?Boolean

Returns:

  • (Boolean)

433
434
435
# File 'app/models/namespace.rb', line 433

def any_project_with_pages_deployed?
  all_projects.with_pages_deployed.any?
end

#any_project_with_shared_runners_enabled?Boolean

Returns:

  • (Boolean)

343
344
345
# File 'app/models/namespace.rb', line 343

def any_project_with_shared_runners_enabled?
  projects.with_shared_runners.any?
end

#auto_devops_enabled?Boolean

Returns:

  • (Boolean)

402
403
404
# File 'app/models/namespace.rb', line 402

def auto_devops_enabled?
  first_auto_devops_config[:status]
end

#certificate_based_clusters_enabled?Boolean

Returns:

  • (Boolean)

528
529
530
531
532
# File 'app/models/namespace.rb', line 528

def certificate_based_clusters_enabled?
  ::Gitlab::SafeRequestStore.fetch("certificate_based_clusters:ns:#{self.id}") do
    Feature.enabled?(:certificate_based_clusters, self, type: :ops)
  end
end

#changing_allow_descendants_override_disabled_shared_runners_is_allowedObject


470
471
472
473
474
475
476
477
478
479
480
# File 'app/models/namespace.rb', line 470

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

  if allow_descendants_override_disabled_shared_runners && has_parent? && parent.shared_runners_setting == SR_DISABLED_AND_UNOVERRIDABLE
    errors.add(:allow_descendants_override_disabled_shared_runners, _('cannot be enabled because parent group does not allow it'))
  end
end

#changing_shared_runners_enabled_is_allowedObject


462
463
464
465
466
467
468
# File 'app/models/namespace.rb', line 462

def changing_shared_runners_enabled_is_allowed
  return unless new_record? || changes.has_key?(:shared_runners_enabled)

  if shared_runners_enabled && has_parent? && parent.shared_runners_setting == SR_DISABLED_AND_UNOVERRIDABLE
    errors.add(:shared_runners_enabled, _('cannot be enabled because parent group has shared Runners disabled'))
  end
end

#closest_setting(name) ⇒ Object


437
438
439
440
441
# File 'app/models/namespace.rb', line 437

def closest_setting(name)
  self_and_ancestors(hierarchy_order: :asc)
    .find { |n| !n.read_attribute(name).nil? }
    .try(name)
end

#default_branch_protectionObject


253
254
255
# File 'app/models/namespace.rb', line 253

def default_branch_protection
  super || Gitlab::CurrentSettings.default_branch_protection
end

#emails_disabled?Boolean

any ancestor can disable emails for all descendants

Returns:

  • (Boolean)

328
329
330
331
332
333
334
335
336
# File 'app/models/namespace.rb', line 328

def emails_disabled?
  strong_memoize(:emails_disabled_memoized) do
    if parent_id
      self_and_ancestors.where(emails_disabled: true).exists?
    else
      !!emails_disabled
    end
  end
end

#feature_available?(feature, _user = nil) ⇒ Boolean

Deprecated, use #licensed_feature_available? instead. Remove once Namespace#feature_available? isn't used anymore.

Returns:

  • (Boolean)

380
381
382
# File 'app/models/namespace.rb', line 380

def feature_available?(feature, _user = nil)
  licensed_feature_available?(feature)
end

#find_fork_of(project) ⇒ Object


311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'app/models/namespace.rb', line 311

def find_fork_of(project)
  return unless project.fork_network

  if Gitlab::SafeRequestStore.active?
    forks_in_namespace = Gitlab::SafeRequestStore.fetch("namespaces:#{id}:forked_projects") do
      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
end

#first_auto_devops_configObject


406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'app/models/namespace.rb', line 406

def first_auto_devops_config
  return { scope: :group, status: auto_devops_enabled } unless auto_devops_enabled.nil?

  strong_memoize(:first_auto_devops_config) do
    if has_parent? && cache_first_auto_devops_config?
      Rails.cache.fetch(first_auto_devops_config_cache_key_for(id), expires_in: 1.day) do
        parent.first_auto_devops_config
      end
    elsif has_parent?
      parent.first_auto_devops_config
    else
      { scope: :instance, status: Gitlab::CurrentSettings.auto_devops_enabled? }
    end
  end
end

#first_ownerObject


307
308
309
# File 'app/models/namespace.rb', line 307

def first_owner
  owner
end

#first_project_with_container_registry_tagsObject


273
274
275
# File 'app/models/namespace.rb', line 273

def first_project_with_container_registry_tags
  all_projects.find(&:has_container_registry_tags?)
end

#full_path_before_last_saveObject


389
390
391
392
393
394
395
396
# File 'app/models/namespace.rb', line 389

def full_path_before_last_save
  if parent_id_before_last_save.nil?
    path_before_last_save
  else
    previous_parent = Group.find_by(id: parent_id_before_last_save)
    previous_parent.full_path + '/' + path_before_last_save
  end
end

#group_namespace?Boolean

Returns:

  • (Boolean)

290
291
292
# File 'app/models/namespace.rb', line 290

def group_namespace?
  type == Group.sti_name
end

#has_parent?Boolean

Returns:

  • (Boolean)

362
363
364
# File 'app/models/namespace.rb', line 362

def has_parent?
  parent_id.present? || parent.present?
end

#human_nameObject


265
266
267
# File 'app/models/namespace.rb', line 265

def human_name
  owner_name
end

#issue_repositioning_disabled?Boolean

Returns:

  • (Boolean)

518
519
520
# File 'app/models/namespace.rb', line 518

def issue_repositioning_disabled?
  Feature.enabled?(:block_issue_repositioning, self, type: :ops)
end

#kindObject


283
284
285
286
287
288
# File 'app/models/namespace.rb', line 283

def kind
  return 'group' if group_namespace?
  return 'project' if project_namespace?

  'user' # defaults to user
end

#lfs_enabled?Boolean

Returns:

  • (Boolean)

338
339
340
341
# File 'app/models/namespace.rb', line 338

def lfs_enabled?
  # User namespace will always default to the global setting
  Gitlab.config.lfs.enabled
end

#licensed_feature_available?(_feature) ⇒ Boolean

Overridden in EE::Namespace

Returns:

  • (Boolean)

385
386
387
# File 'app/models/namespace.rb', line 385

def licensed_feature_available?(_feature)
  false
end

#multiple_issue_boards_available?Boolean

Overridden on EE module

Returns:

  • (Boolean)

371
372
373
# File 'app/models/namespace.rb', line 371

def multiple_issue_boards_available?
  false
end

#owner_required?Boolean

Returns:

  • (Boolean)

303
304
305
# File 'app/models/namespace.rb', line 303

def owner_required?
  user_namespace?
end

#package_settingsObject


249
250
251
# File 'app/models/namespace.rb', line 249

def package_settings
  package_setting_relation || build_package_setting_relation
end

#pages_virtual_domainObject


426
427
428
429
430
431
# File 'app/models/namespace.rb', line 426

def pages_virtual_domain
  Pages::VirtualDomain.new(
    all_projects_with_pages.includes(:route, :project_feature, pages_metadatum: :pages_deployment),
    trim_prefix: full_path
  )
end

#paid?Boolean

Returns:

  • (Boolean)

447
448
449
# File 'app/models/namespace.rb', line 447

def paid?
  root? && actual_plan.paid?
end

#project_namespace?Boolean

Returns:

  • (Boolean)

294
295
296
# File 'app/models/namespace.rb', line 294

def project_namespace?
  type == Namespaces::ProjectNamespace.sti_name
end

#recent?Boolean

Returns:

  • (Boolean)

514
515
516
# File 'app/models/namespace.rb', line 514

def recent?
  created_at >= 90.days.ago
end

#refresh_project_authorizationsObject


398
399
400
# File 'app/models/namespace.rb', line 398

def refresh_project_authorizations
  owner.refresh_authorized_projects
end

#root?Boolean

Returns:

  • (Boolean)

510
511
512
# File 'app/models/namespace.rb', line 510

def root?
  !has_parent?
end

#send_update_instructionsObject


277
278
279
280
281
# File 'app/models/namespace.rb', line 277

def send_update_instructions
  projects.each do |project|
    project.send_move_instructions("#{full_path_before_last_save}/#{project.path}")
  end
end

#shared_runnersObject


506
507
508
# File 'app/models/namespace.rb', line 506

def shared_runners
  @shared_runners ||= shared_runners_enabled ? Ci::Runner.instance_type : Ci::Runner.none
end

#shared_runners_settingObject


482
483
484
485
486
487
488
489
490
491
492
# File 'app/models/namespace.rb', line 482

def shared_runners_setting
  if shared_runners_enabled
    SR_ENABLED
  else
    if allow_descendants_override_disabled_shared_runners
      SR_DISABLED_WITH_OVERRIDE
    else
      SR_DISABLED_AND_UNOVERRIDABLE
    end
  end
end

#shared_runners_setting_higher_than?(other_setting) ⇒ Boolean

Returns:

  • (Boolean)

494
495
496
497
498
499
500
501
502
503
504
# File 'app/models/namespace.rb', line 494

def shared_runners_setting_higher_than?(other_setting)
  if other_setting == SR_ENABLED
    false
  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
  else
    raise ArgumentError
  end
end

#storage_enforcement_dateObject


522
523
524
525
526
# File 'app/models/namespace.rb', line 522

def storage_enforcement_date
  # should return something like Date.new(2022, 02, 03)
  # TBD: https://gitlab.com/gitlab-org/gitlab/-/issues/350632
  nil
end

#subgroup?Boolean

Returns:

  • (Boolean)

366
367
368
# File 'app/models/namespace.rb', line 366

def subgroup?
  has_parent?
end

#to_paramObject


261
262
263
# File 'app/models/namespace.rb', line 261

def to_param
  full_path
end

#user_ids_for_project_authorizationsObject


347
348
349
# File 'app/models/namespace.rb', line 347

def user_ids_for_project_authorizations
  [owner_id]
end

#user_namespace?Boolean

Returns:

  • (Boolean)

298
299
300
301
# File 'app/models/namespace.rb', line 298

def user_namespace?
  # That last bit ensures we're considered a user namespace as a default
  type.nil? || type == Namespaces::UserNamespace.sti_name || !(group_namespace? || project_namespace?)
end

#visibility_level_fieldObject


257
258
259
# File 'app/models/namespace.rb', line 257

def visibility_level_field
  :visibility_level
end