Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									66fc7ba6f3
								
							
						
					
					
						commit
						e7e44c0e4c
					
				
							
								
								
									
										2
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										2
									
								
								Gemfile
								
								
								
								
							| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
source 'https://rubygems.org'
 | 
					source 'https://rubygems.org'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gem 'rails', '~> 6.1.4.4'
 | 
					gem 'rails', '~> 6.1.4.6'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gem 'bootsnap', '~> 1.9.1', require: false
 | 
					gem 'bootsnap', '~> 1.9.1', require: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										108
									
								
								Gemfile.lock
								
								
								
								
							
							
						
						
									
										108
									
								
								Gemfile.lock
								
								
								
								
							| 
						 | 
					@ -11,63 +11,63 @@ GEM
 | 
				
			||||||
    RedCloth (4.3.2)
 | 
					    RedCloth (4.3.2)
 | 
				
			||||||
    acme-client (2.0.9)
 | 
					    acme-client (2.0.9)
 | 
				
			||||||
      faraday (>= 0.17, < 2.0.0)
 | 
					      faraday (>= 0.17, < 2.0.0)
 | 
				
			||||||
    actioncable (6.1.4.4)
 | 
					    actioncable (6.1.4.6)
 | 
				
			||||||
      actionpack (= 6.1.4.4)
 | 
					      actionpack (= 6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      nio4r (~> 2.0)
 | 
					      nio4r (~> 2.0)
 | 
				
			||||||
      websocket-driver (>= 0.6.1)
 | 
					      websocket-driver (>= 0.6.1)
 | 
				
			||||||
    actionmailbox (6.1.4.4)
 | 
					    actionmailbox (6.1.4.6)
 | 
				
			||||||
      actionpack (= 6.1.4.4)
 | 
					      actionpack (= 6.1.4.6)
 | 
				
			||||||
      activejob (= 6.1.4.4)
 | 
					      activejob (= 6.1.4.6)
 | 
				
			||||||
      activerecord (= 6.1.4.4)
 | 
					      activerecord (= 6.1.4.6)
 | 
				
			||||||
      activestorage (= 6.1.4.4)
 | 
					      activestorage (= 6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      mail (>= 2.7.1)
 | 
					      mail (>= 2.7.1)
 | 
				
			||||||
    actionmailer (6.1.4.4)
 | 
					    actionmailer (6.1.4.6)
 | 
				
			||||||
      actionpack (= 6.1.4.4)
 | 
					      actionpack (= 6.1.4.6)
 | 
				
			||||||
      actionview (= 6.1.4.4)
 | 
					      actionview (= 6.1.4.6)
 | 
				
			||||||
      activejob (= 6.1.4.4)
 | 
					      activejob (= 6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      mail (~> 2.5, >= 2.5.4)
 | 
					      mail (~> 2.5, >= 2.5.4)
 | 
				
			||||||
      rails-dom-testing (~> 2.0)
 | 
					      rails-dom-testing (~> 2.0)
 | 
				
			||||||
    actionpack (6.1.4.4)
 | 
					    actionpack (6.1.4.6)
 | 
				
			||||||
      actionview (= 6.1.4.4)
 | 
					      actionview (= 6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      rack (~> 2.0, >= 2.0.9)
 | 
					      rack (~> 2.0, >= 2.0.9)
 | 
				
			||||||
      rack-test (>= 0.6.3)
 | 
					      rack-test (>= 0.6.3)
 | 
				
			||||||
      rails-dom-testing (~> 2.0)
 | 
					      rails-dom-testing (~> 2.0)
 | 
				
			||||||
      rails-html-sanitizer (~> 1.0, >= 1.2.0)
 | 
					      rails-html-sanitizer (~> 1.0, >= 1.2.0)
 | 
				
			||||||
    actiontext (6.1.4.4)
 | 
					    actiontext (6.1.4.6)
 | 
				
			||||||
      actionpack (= 6.1.4.4)
 | 
					      actionpack (= 6.1.4.6)
 | 
				
			||||||
      activerecord (= 6.1.4.4)
 | 
					      activerecord (= 6.1.4.6)
 | 
				
			||||||
      activestorage (= 6.1.4.4)
 | 
					      activestorage (= 6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      nokogiri (>= 1.8.5)
 | 
					      nokogiri (>= 1.8.5)
 | 
				
			||||||
    actionview (6.1.4.4)
 | 
					    actionview (6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      builder (~> 3.1)
 | 
					      builder (~> 3.1)
 | 
				
			||||||
      erubi (~> 1.4)
 | 
					      erubi (~> 1.4)
 | 
				
			||||||
      rails-dom-testing (~> 2.0)
 | 
					      rails-dom-testing (~> 2.0)
 | 
				
			||||||
      rails-html-sanitizer (~> 1.1, >= 1.2.0)
 | 
					      rails-html-sanitizer (~> 1.1, >= 1.2.0)
 | 
				
			||||||
    activejob (6.1.4.4)
 | 
					    activejob (6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      globalid (>= 0.3.6)
 | 
					      globalid (>= 0.3.6)
 | 
				
			||||||
    activemodel (6.1.4.4)
 | 
					    activemodel (6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
    activerecord (6.1.4.4)
 | 
					    activerecord (6.1.4.6)
 | 
				
			||||||
      activemodel (= 6.1.4.4)
 | 
					      activemodel (= 6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
    activerecord-explain-analyze (0.1.0)
 | 
					    activerecord-explain-analyze (0.1.0)
 | 
				
			||||||
      activerecord (>= 4)
 | 
					      activerecord (>= 4)
 | 
				
			||||||
      pg
 | 
					      pg
 | 
				
			||||||
    activestorage (6.1.4.4)
 | 
					    activestorage (6.1.4.6)
 | 
				
			||||||
      actionpack (= 6.1.4.4)
 | 
					      actionpack (= 6.1.4.6)
 | 
				
			||||||
      activejob (= 6.1.4.4)
 | 
					      activejob (= 6.1.4.6)
 | 
				
			||||||
      activerecord (= 6.1.4.4)
 | 
					      activerecord (= 6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      marcel (~> 1.0.0)
 | 
					      marcel (~> 1.0.0)
 | 
				
			||||||
      mini_mime (>= 1.1.0)
 | 
					      mini_mime (>= 1.1.0)
 | 
				
			||||||
    activesupport (6.1.4.4)
 | 
					    activesupport (6.1.4.6)
 | 
				
			||||||
      concurrent-ruby (~> 1.0, >= 1.0.2)
 | 
					      concurrent-ruby (~> 1.0, >= 1.0.2)
 | 
				
			||||||
      i18n (>= 1.6, < 2)
 | 
					      i18n (>= 1.6, < 2)
 | 
				
			||||||
      minitest (>= 5.1)
 | 
					      minitest (>= 5.1)
 | 
				
			||||||
| 
						 | 
					@ -967,20 +967,20 @@ GEM
 | 
				
			||||||
    rack-test (1.1.0)
 | 
					    rack-test (1.1.0)
 | 
				
			||||||
      rack (>= 1.0, < 3)
 | 
					      rack (>= 1.0, < 3)
 | 
				
			||||||
    rack-timeout (0.5.2)
 | 
					    rack-timeout (0.5.2)
 | 
				
			||||||
    rails (6.1.4.4)
 | 
					    rails (6.1.4.6)
 | 
				
			||||||
      actioncable (= 6.1.4.4)
 | 
					      actioncable (= 6.1.4.6)
 | 
				
			||||||
      actionmailbox (= 6.1.4.4)
 | 
					      actionmailbox (= 6.1.4.6)
 | 
				
			||||||
      actionmailer (= 6.1.4.4)
 | 
					      actionmailer (= 6.1.4.6)
 | 
				
			||||||
      actionpack (= 6.1.4.4)
 | 
					      actionpack (= 6.1.4.6)
 | 
				
			||||||
      actiontext (= 6.1.4.4)
 | 
					      actiontext (= 6.1.4.6)
 | 
				
			||||||
      actionview (= 6.1.4.4)
 | 
					      actionview (= 6.1.4.6)
 | 
				
			||||||
      activejob (= 6.1.4.4)
 | 
					      activejob (= 6.1.4.6)
 | 
				
			||||||
      activemodel (= 6.1.4.4)
 | 
					      activemodel (= 6.1.4.6)
 | 
				
			||||||
      activerecord (= 6.1.4.4)
 | 
					      activerecord (= 6.1.4.6)
 | 
				
			||||||
      activestorage (= 6.1.4.4)
 | 
					      activestorage (= 6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      bundler (>= 1.15.0)
 | 
					      bundler (>= 1.15.0)
 | 
				
			||||||
      railties (= 6.1.4.4)
 | 
					      railties (= 6.1.4.6)
 | 
				
			||||||
      sprockets-rails (>= 2.0.0)
 | 
					      sprockets-rails (>= 2.0.0)
 | 
				
			||||||
    rails-controller-testing (1.0.5)
 | 
					    rails-controller-testing (1.0.5)
 | 
				
			||||||
      actionpack (>= 5.0.1.rc1)
 | 
					      actionpack (>= 5.0.1.rc1)
 | 
				
			||||||
| 
						 | 
					@ -994,9 +994,9 @@ GEM
 | 
				
			||||||
    rails-i18n (6.0.0)
 | 
					    rails-i18n (6.0.0)
 | 
				
			||||||
      i18n (>= 0.7, < 2)
 | 
					      i18n (>= 0.7, < 2)
 | 
				
			||||||
      railties (>= 6.0.0, < 7)
 | 
					      railties (>= 6.0.0, < 7)
 | 
				
			||||||
    railties (6.1.4.4)
 | 
					    railties (6.1.4.6)
 | 
				
			||||||
      actionpack (= 6.1.4.4)
 | 
					      actionpack (= 6.1.4.6)
 | 
				
			||||||
      activesupport (= 6.1.4.4)
 | 
					      activesupport (= 6.1.4.6)
 | 
				
			||||||
      method_source
 | 
					      method_source
 | 
				
			||||||
      rake (>= 0.13)
 | 
					      rake (>= 0.13)
 | 
				
			||||||
      thor (~> 1.0)
 | 
					      thor (~> 1.0)
 | 
				
			||||||
| 
						 | 
					@ -1580,7 +1580,7 @@ DEPENDENCIES
 | 
				
			||||||
  rack-oauth2 (~> 1.16.0)
 | 
					  rack-oauth2 (~> 1.16.0)
 | 
				
			||||||
  rack-proxy (~> 0.6.0)
 | 
					  rack-proxy (~> 0.6.0)
 | 
				
			||||||
  rack-timeout (~> 0.5.1)
 | 
					  rack-timeout (~> 0.5.1)
 | 
				
			||||||
  rails (~> 6.1.4.4)
 | 
					  rails (~> 6.1.4.6)
 | 
				
			||||||
  rails-controller-testing
 | 
					  rails-controller-testing
 | 
				
			||||||
  rails-i18n (~> 6.0)
 | 
					  rails-i18n (~> 6.0)
 | 
				
			||||||
  rainbow (~> 3.0)
 | 
					  rainbow (~> 3.0)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -266,7 +266,7 @@ export default {
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
              <span class="gl-font-lg">·</span>
 | 
					              <span class="gl-font-lg">·</span>
 | 
				
			||||||
              <span data-testid="vsa-stage-event-date">
 | 
					              <span data-testid="vsa-stage-event-date">
 | 
				
			||||||
                {{ s__('OpenedNDaysAgo|Opened') }}
 | 
					                {{ s__('OpenedNDaysAgo|Created') }}
 | 
				
			||||||
                <gl-link class="gl-text-black-normal" :href="item.url">{{
 | 
					                <gl-link class="gl-text-black-normal" :href="item.url">{{
 | 
				
			||||||
                  item.createdAt
 | 
					                  item.createdAt
 | 
				
			||||||
                }}</gl-link>
 | 
					                }}</gl-link>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,7 +102,7 @@ export default {
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <span>
 | 
					        <span>
 | 
				
			||||||
          {{ __('Opened') }}
 | 
					          {{ __('Created') }}
 | 
				
			||||||
          <time-ago-tooltip data-testid="startTimeItem" :time="createdAt" />
 | 
					          <time-ago-tooltip data-testid="startTimeItem" :time="createdAt" />
 | 
				
			||||||
          {{ __('by') }}
 | 
					          {{ __('by') }}
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ module Projects
 | 
				
			||||||
        avenues = [authorizable_project_members]
 | 
					        avenues = [authorizable_project_members]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        avenues << if project.personal?
 | 
					        avenues << if project.personal?
 | 
				
			||||||
                     project_owner_acting_as_maintainer
 | 
					                     project_owner
 | 
				
			||||||
                   else
 | 
					                   else
 | 
				
			||||||
                     authorizable_group_members
 | 
					                     authorizable_group_members
 | 
				
			||||||
                   end
 | 
					                   end
 | 
				
			||||||
| 
						 | 
					@ -85,9 +85,15 @@ module Projects
 | 
				
			||||||
        Member.from_union(members)
 | 
					        Member.from_union(members)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      def project_owner_acting_as_maintainer
 | 
					      # workaround until we migrate Project#owners to have membership with
 | 
				
			||||||
 | 
					      # OWNER access level
 | 
				
			||||||
 | 
					      def project_owner
 | 
				
			||||||
        user_id = project.namespace.owner.id
 | 
					        user_id = project.namespace.owner.id
 | 
				
			||||||
        access_level = Gitlab::Access::MAINTAINER
 | 
					        access_level = if ::Feature.enabled?(:personal_project_owner_with_owner_access, default_enabled: :yaml)
 | 
				
			||||||
 | 
					                         Gitlab::Access::OWNER
 | 
				
			||||||
 | 
					                       else
 | 
				
			||||||
 | 
					                         Gitlab::Access::MAINTAINER
 | 
				
			||||||
 | 
					                       end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Member
 | 
					        Member
 | 
				
			||||||
          .from(generate_from_statement([[user_id, access_level]])) # rubocop: disable CodeReuse/ActiveRecord
 | 
					          .from(generate_from_statement([[user_id, access_level]])) # rubocop: disable CodeReuse/ActiveRecord
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,8 +8,14 @@ module SelectForProjectAuthorization
 | 
				
			||||||
      select("projects.id AS project_id", "members.access_level")
 | 
					      select("projects.id AS project_id", "members.access_level")
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def select_as_maintainer_for_project_authorization
 | 
					    # workaround until we migrate Project#owners to have membership with
 | 
				
			||||||
      select(["projects.id AS project_id", "#{Gitlab::Access::MAINTAINER} AS access_level"])
 | 
					    # OWNER access level
 | 
				
			||||||
 | 
					    def select_project_owner_for_project_authorization
 | 
				
			||||||
 | 
					      if ::Feature.enabled?(:personal_project_owner_with_owner_access, default_enabled: :yaml)
 | 
				
			||||||
 | 
					        select(["projects.id AS project_id", "#{Gitlab::Access::OWNER} AS access_level"])
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        select(["projects.id AS project_id", "#{Gitlab::Access::MAINTAINER} AS access_level"])
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -459,7 +459,7 @@ class Project < ApplicationRecord
 | 
				
			||||||
  delegate :name, to: :owner, allow_nil: true, prefix: true
 | 
					  delegate :name, to: :owner, allow_nil: true, prefix: true
 | 
				
			||||||
  delegate :members, to: :team, prefix: true
 | 
					  delegate :members, to: :team, prefix: true
 | 
				
			||||||
  delegate :add_user, :add_users, to: :team
 | 
					  delegate :add_user, :add_users, to: :team
 | 
				
			||||||
  delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team
 | 
					  delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role, to: :team
 | 
				
			||||||
  delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true
 | 
					  delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true
 | 
				
			||||||
  delegate :root_ancestor, to: :namespace, allow_nil: true
 | 
					  delegate :root_ancestor, to: :namespace, allow_nil: true
 | 
				
			||||||
  delegate :last_pipeline, to: :commit, allow_nil: true
 | 
					  delegate :last_pipeline, to: :commit, allow_nil: true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,10 @@ class ProjectTeam
 | 
				
			||||||
    add_user(user, :maintainer, current_user: current_user)
 | 
					    add_user(user, :maintainer, current_user: current_user)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def add_owner(user, current_user: nil)
 | 
				
			||||||
 | 
					    add_user(user, :owner, current_user: current_user)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def add_role(user, role, current_user: nil)
 | 
					  def add_role(user, role, current_user: nil)
 | 
				
			||||||
    public_send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
 | 
					    public_send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					@ -103,7 +107,9 @@ class ProjectTeam
 | 
				
			||||||
      if group
 | 
					      if group
 | 
				
			||||||
        group.owners
 | 
					        group.owners
 | 
				
			||||||
      else
 | 
					      else
 | 
				
			||||||
        [project.owner]
 | 
					        # workaround until we migrate Project#owners to have membership with
 | 
				
			||||||
 | 
					        # OWNER access level
 | 
				
			||||||
 | 
					        Array.wrap(fetch_members(Gitlab::Access::OWNER)) | Array.wrap(project.owner)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ module Members
 | 
				
			||||||
  module Projects
 | 
					  module Projects
 | 
				
			||||||
    class CreatorService < Members::CreatorService
 | 
					    class CreatorService < Members::CreatorService
 | 
				
			||||||
      def self.access_levels
 | 
					      def self.access_levels
 | 
				
			||||||
        Gitlab::Access.sym_options
 | 
					        Gitlab::Access.sym_options_with_owner
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      private
 | 
					      private
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ module NotificationRecipients
 | 
				
			||||||
        return [] unless project
 | 
					        return [] unless project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        add_recipients(project.team.maintainers, :mention, nil)
 | 
					        add_recipients(project.team.maintainers, :mention, nil)
 | 
				
			||||||
 | 
					        add_recipients(project.team.owners, :mention, nil)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      def acting_user
 | 
					      def acting_user
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -147,7 +147,11 @@ module Projects
 | 
				
			||||||
          priority: UserProjectAccessChangedService::LOW_PRIORITY
 | 
					          priority: UserProjectAccessChangedService::LOW_PRIORITY
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      else
 | 
					      else
 | 
				
			||||||
        @project.add_maintainer(@project.namespace.owner, current_user: current_user)
 | 
					        if ::Feature.enabled?(:personal_project_owner_with_owner_access, default_enabled: :yaml)
 | 
				
			||||||
 | 
					          @project.add_owner(@project.namespace.owner, current_user: current_user)
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          @project.add_maintainer(@project.namespace.owner, current_user: current_user)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,21 @@
 | 
				
			||||||
- page_title _('Monitor Settings')
 | 
					- page_title _('Monitor Settings')
 | 
				
			||||||
- breadcrumb_title _('Monitor Settings')
 | 
					- breadcrumb_title _('Monitor Settings')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.gl-alert.gl-alert-danger.gl-mb-5
 | 
				
			||||||
 | 
					  - removal_epic_link_url = 'https://gitlab.com/groups/gitlab-org/-/epics/7188'
 | 
				
			||||||
 | 
					  - removal_epic_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="gl-link">'.html_safe % { url: removal_epic_link_url }
 | 
				
			||||||
 | 
					  - opstrace_link_url = 'https://gitlab.com/groups/gitlab-org/-/epics/6976'
 | 
				
			||||||
 | 
					  - opstrace_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="gl-link">'.html_safe % { url: opstrace_link_url }
 | 
				
			||||||
 | 
					  - link_end = '</a>'.html_safe
 | 
				
			||||||
 | 
					  .gl-alert-container
 | 
				
			||||||
 | 
					    = sprite_icon('error', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
 | 
				
			||||||
 | 
					    .gl-alert-content
 | 
				
			||||||
 | 
					      .gl-alert-title
 | 
				
			||||||
 | 
					        = s_('Deprecations|Feature deprecation and removal')
 | 
				
			||||||
 | 
					      .gl-alert-body
 | 
				
			||||||
 | 
					        %p
 | 
				
			||||||
 | 
					          = html_escape(s_('Deprecations|The metrics, logs and tracing features were deprecated in GitLab 14.7, and are %{removal_link_start} scheduled for removal %{link_end} in GitLab 15.0. For information on a possible replacement, %{opstrace_link_start} learn more about Opstrace %{link_end}.')) % {removal_link_start: removal_epic_link_start, opstrace_link_start: opstrace_link_start, link_end: link_end }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
= render 'projects/settings/operations/metrics_dashboard'
 | 
					= render 'projects/settings/operations/metrics_dashboard'
 | 
				
			||||||
= render 'projects/settings/operations/tracing'
 | 
					= render 'projects/settings/operations/tracing'
 | 
				
			||||||
= render 'projects/settings/operations/error_tracking'
 | 
					= render 'projects/settings/operations/error_tracking'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,4 +9,5 @@ raise "METRICS_SERVER_TARGET cannot be blank" if target.blank?
 | 
				
			||||||
metrics_dir = ENV["prometheus_multiproc_dir"] || File.absolute_path("tmp/prometheus_multiproc_dir/#{target}")
 | 
					metrics_dir = ENV["prometheus_multiproc_dir"] || File.absolute_path("tmp/prometheus_multiproc_dir/#{target}")
 | 
				
			||||||
wipe_metrics_dir = Gitlab::Utils.to_boolean(ENV['WIPE_METRICS_DIR']) || false
 | 
					wipe_metrics_dir = Gitlab::Utils.to_boolean(ENV['WIPE_METRICS_DIR']) || false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Process.wait(MetricsServer.spawn(target, metrics_dir: metrics_dir, wipe_metrics_dir: wipe_metrics_dir))
 | 
					server = MetricsServer.new(target, metrics_dir, wipe_metrics_dir)
 | 
				
			||||||
 | 
					server.start
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343917
 | 
				
			||||||
milestone: '14.5'
 | 
					milestone: '14.5'
 | 
				
			||||||
type: development
 | 
					type: development
 | 
				
			||||||
group: group::container security
 | 
					group: group::container security
 | 
				
			||||||
default_enabled: false
 | 
					default_enabled: true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					name: personal_project_owner_with_owner_access
 | 
				
			||||||
 | 
					introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78193
 | 
				
			||||||
 | 
					rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351919
 | 
				
			||||||
 | 
					milestone: '14.8'
 | 
				
			||||||
 | 
					type: development
 | 
				
			||||||
 | 
					group: group::workspace
 | 
				
			||||||
 | 
					default_enabled: false
 | 
				
			||||||
| 
						 | 
					@ -1,8 +0,0 @@
 | 
				
			||||||
---
 | 
					 | 
				
			||||||
name: prohibit_hexadecimal_branch_names
 | 
					 | 
				
			||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/28439ca4b1dd14f22a5a6ad14530f6bf1046f8bc
 | 
					 | 
				
			||||||
rollout_issue_url: 
 | 
					 | 
				
			||||||
milestone: '12.10'
 | 
					 | 
				
			||||||
type: development
 | 
					 | 
				
			||||||
group: group::source code
 | 
					 | 
				
			||||||
default_enabled: true
 | 
					 | 
				
			||||||
| 
						 | 
					@ -185,17 +185,17 @@ This allows you to create a single file. For creating multiple files with a sing
 | 
				
			||||||
POST /projects/:id/repository/files/:file_path
 | 
					POST /projects/:id/repository/files/:file_path
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Attribute        | Type           | Required | Description                                                                                                     |
 | 
					| Attribute        | Type           | Required | Description |
 | 
				
			||||||
|------------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------|
 | 
					| ---------------- | -------------- | -------- | ----------- |
 | 
				
			||||||
| `id`             | integer or string | yes   | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user  |
 | 
					| `id`             | integer or string | yes   | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
 | 
				
			||||||
| `file_path`      | string         | yes      | URL encoded full path to new file. Ex. `lib%2Fclass%2Erb`.                                                      |
 | 
					| `file_path`      | string         | yes      | URL-encoded full path to new file. For example:  `lib%2Fclass%2Erb`. |
 | 
				
			||||||
| `branch`         | string         | yes      | Name of the branch                                                                                              |
 | 
					| `branch`         | string         | yes      | Name of the new branch to create. The commit is added to this branch. |
 | 
				
			||||||
| `start_branch`   | string         | no       | Name of the branch to start the new commit from                                                                 |
 | 
					| `start_branch`   | string         | no       | Name of the base branch to create the new branch from. |
 | 
				
			||||||
| `encoding`       | string         | no       | Change encoding to `base64`. Default is `text`.                                                                 |
 | 
					| `encoding`       | string         | no       | Change encoding to `base64`. Default is `text`. |
 | 
				
			||||||
| `author_email`   | string         | no       | Specify the commit author's email address                                                                       |
 | 
					| `author_email`   | string         | no       | The commit author's email address. |
 | 
				
			||||||
| `author_name`    | string         | no       | Specify the commit author's name                                                                                |
 | 
					| `author_name`    | string         | no       | The commit author's name. |
 | 
				
			||||||
| `content`        | string         | yes      | File content                                                                                                    |
 | 
					| `content`        | string         | yes      | The file's content. |
 | 
				
			||||||
| `commit_message` | string         | yes      | Commit message                                                                                                  |
 | 
					| `commit_message` | string         | yes      | The commit message. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```shell
 | 
					```shell
 | 
				
			||||||
curl --request POST --header 'PRIVATE-TOKEN: <your_access_token>' \
 | 
					curl --request POST --header 'PRIVATE-TOKEN: <your_access_token>' \
 | 
				
			||||||
| 
						 | 
					@ -222,18 +222,18 @@ This allows you to update a single file. For updating multiple files with a sing
 | 
				
			||||||
PUT /projects/:id/repository/files/:file_path
 | 
					PUT /projects/:id/repository/files/:file_path
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Attribute        | Type           | Required | Description                                                                                                     |
 | 
					| Attribute        | Type           | Required | Description |
 | 
				
			||||||
|------------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------|
 | 
					| ---------------- | -------------- | -------- | ----------- |
 | 
				
			||||||
| `id`             | integer or string | yes   | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user  |
 | 
					| `id`             | integer or string | yes   | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user  |
 | 
				
			||||||
| `file_path`      | string         | yes      | URL encoded full path to new file. Ex. `lib%2Fclass%2Erb`.                                                      |
 | 
					| `file_path`      | string         | yes      | URL-encoded full path to new file. For example: `lib%2Fclass%2Erb`. |
 | 
				
			||||||
| `branch`         | string         | yes      | Name of the branch                                                                                              |
 | 
					| `branch`         | string         | yes      | Name of the new branch to create. The commit is added to this branch. |
 | 
				
			||||||
| `start_branch`   | string         | no       | Name of the branch to start the new commit from                                                                 |
 | 
					| `start_branch`   | string         | no       | Name of the base branch to create the new branch from. |
 | 
				
			||||||
| `encoding`       | string         | no       | Change encoding to `base64`. Default is `text`.                                                                 |
 | 
					| `encoding`       | string         | no       | Change encoding to `base64`. Default is `text`.  |
 | 
				
			||||||
| `author_email`   | string         | no       | Specify the commit author's email address                                                                       |
 | 
					| `author_email`   | string         | no       | The commit author's email address. |
 | 
				
			||||||
| `author_name`    | string         | no       | Specify the commit author's name                                                                                |
 | 
					| `author_name`    | string         | no       | The commit author's name. |
 | 
				
			||||||
| `content`        | string         | yes      | File content                                                                                                    |
 | 
					| `content`        | string         | yes      | The file's content. |
 | 
				
			||||||
| `commit_message` | string         | yes      | Commit message                                                                                                  |
 | 
					| `commit_message` | string         | yes      | The commit message. |
 | 
				
			||||||
| `last_commit_id` | string         | no       | Last known file commit ID                                                                                       |
 | 
					| `last_commit_id` | string         | no       | Last known file commit ID. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```shell
 | 
					```shell
 | 
				
			||||||
curl --request PUT --header 'PRIVATE-TOKEN: <your_access_token>' \
 | 
					curl --request PUT --header 'PRIVATE-TOKEN: <your_access_token>' \
 | 
				
			||||||
| 
						 | 
					@ -270,16 +270,16 @@ This allows you to delete a single file. For deleting multiple files with a sing
 | 
				
			||||||
DELETE /projects/:id/repository/files/:file_path
 | 
					DELETE /projects/:id/repository/files/:file_path
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Attribute        | Type           | Required | Description                                                                                                     |
 | 
					| Attribute        | Type           | Required | Description |
 | 
				
			||||||
|------------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------|
 | 
					| ---------------- | -------------- | -------- | ----------- |
 | 
				
			||||||
| `id`             | integer or string | yes   | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user  |
 | 
					| `id`             | integer or string | yes   | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
 | 
				
			||||||
| `file_path`      | string         | yes      | URL encoded full path to new file. Ex. `lib%2Fclass%2Erb`.                                                      |
 | 
					| `file_path`      | string         | yes      | URL-encoded full path to new file. For example: `lib%2Fclass%2Erb`. |
 | 
				
			||||||
| `branch`         | string         | yes      | Name of the branch                                                                                              |
 | 
					| `branch`         | string         | yes      | Name of the new branch to create. The commit is added to this branch. |
 | 
				
			||||||
| `start_branch`   | string         | no       | Name of the branch to start the new commit from                                                                 |
 | 
					| `start_branch`   | string         | no       | Name of the base branch to create the new branch from. |
 | 
				
			||||||
| `author_email`   | string         | no       | Specify the commit author's email address.                                                                      |
 | 
					| `author_email`   | string         | no       | The commit author's email address. |
 | 
				
			||||||
| `author_name`    | string         | no       | Specify the commit author's name.                                                                               |
 | 
					| `author_name`    | string         | no       | The commit author's name. |
 | 
				
			||||||
| `commit_message` | string         | yes      | Commit message.                                                                                                 |
 | 
					| `commit_message` | string         | yes      | The commit message. |
 | 
				
			||||||
| `last_commit_id` | string         | no       | Last known file commit ID.                                                                                      |
 | 
					| `last_commit_id` | string         | no       | Last known file commit ID. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```shell
 | 
					```shell
 | 
				
			||||||
curl --request DELETE --header 'PRIVATE-TOKEN: <your_access_token>' \
 | 
					curl --request DELETE --header 'PRIVATE-TOKEN: <your_access_token>' \
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -398,3 +398,14 @@ end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Be sure to update the [GitLab CE/EE documentation](../administration/logs.md) and the [GitLab.com
 | 
					1. Be sure to update the [GitLab CE/EE documentation](../administration/logs.md) and the [GitLab.com
 | 
				
			||||||
   runbooks](https://gitlab.com/gitlab-com/runbooks/blob/master/docs/logging/README.md).
 | 
					   runbooks](https://gitlab.com/gitlab-com/runbooks/blob/master/docs/logging/README.md).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Control logging visibility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					An increase in the logs can cause a growing backlog of unacknowledged messages. When adding new log messages, make sure they don't increase the overall volume of logging by more than 10%.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Deprecation notices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If the expected volume of deprecation notices is large:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Only log them in the development environment.
 | 
				
			||||||
 | 
					- If needed, log them in the testing environment.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,7 +58,7 @@ You can configure the following security controls:
 | 
				
			||||||
- [API Fuzzing](../api_fuzzing/index.md)
 | 
					- [API Fuzzing](../api_fuzzing/index.md)
 | 
				
			||||||
  - Select **Enable API Fuzzing** to use API Fuzzing for the current project. For more details, read [API Fuzzing](../../../user/application_security/api_fuzzing/index.md#enable-web-api-fuzzing).
 | 
					  - Select **Enable API Fuzzing** to use API Fuzzing for the current project. For more details, read [API Fuzzing](../../../user/application_security/api_fuzzing/index.md#enable-web-api-fuzzing).
 | 
				
			||||||
- [Coverage Fuzzing](../coverage_fuzzing/index.md)
 | 
					- [Coverage Fuzzing](../coverage_fuzzing/index.md)
 | 
				
			||||||
  - Can be configured with `.gitlab-ci.yml`. For more details, read [Coverage Fuzzing](../../../user/application_security/coverage_fuzzing/index.md#configuration).
 | 
					  - Can be configured with `.gitlab-ci.yml`. For more details, read [Coverage Fuzzing](../../../user/application_security/coverage_fuzzing/index.md#enable-coverage-guided-fuzz-testing).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Compliance **(ULTIMATE)**
 | 
					## Compliance **(ULTIMATE)**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,8 +22,14 @@ The fuzz testing process:
 | 
				
			||||||
1. Compiles the target application.
 | 
					1. Compiles the target application.
 | 
				
			||||||
1. Runs the instrumented application, using the `gitlab-cov-fuzz` tool.
 | 
					1. Runs the instrumented application, using the `gitlab-cov-fuzz` tool.
 | 
				
			||||||
1. Parses and analyzes the exception information output by the fuzzer.
 | 
					1. Parses and analyzes the exception information output by the fuzzer.
 | 
				
			||||||
1. Downloads the [corpus](../terminology/index.md#corpus) and crash events from previous pipelines.
 | 
					1. Downloads the [corpus](../terminology/index.md#corpus) from either:
 | 
				
			||||||
 | 
					   - The previous pipelines.
 | 
				
			||||||
 | 
					   - If `COVFUZZ_USE_REGISTRY` is set to `true`, the [corpus registry](#corpus-registry).
 | 
				
			||||||
 | 
					1. Downloads crash events from previous pipeline.
 | 
				
			||||||
1. Outputs the parsed crash events and data to the `gl-coverage-fuzzing-report.json` file.
 | 
					1. Outputs the parsed crash events and data to the `gl-coverage-fuzzing-report.json` file.
 | 
				
			||||||
 | 
					1. Updates the corpus, either:
 | 
				
			||||||
 | 
					   - In the job's pipeline.
 | 
				
			||||||
 | 
					   - If `COVFUZZ_USE_REGISTRY` is set to `true`, in the corpus registry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The results of the coverage-guided fuzz testing are available in the CI/CD pipeline.
 | 
					The results of the coverage-guided fuzz testing are available in the CI/CD pipeline.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,9 +49,20 @@ You can use the following fuzzing engines to test the specified languages.
 | 
				
			||||||
| Python                                      | [`pythonfuzz`](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/pythonfuzz)         | [pythonfuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/pythonfuzz-fuzzing-example) |
 | 
					| Python                                      | [`pythonfuzz`](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/pythonfuzz)         | [pythonfuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/pythonfuzz-fuzzing-example) |
 | 
				
			||||||
| AFL (any language that works on top of AFL) | [AFL](https://lcamtuf.coredump.cx/afl/)                                                              | [afl-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/afl-fuzzing-example)               |
 | 
					| AFL (any language that works on top of AFL) | [AFL](https://lcamtuf.coredump.cx/afl/)                                                              | [afl-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/afl-fuzzing-example)               |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Configuration
 | 
					## Confirm status of coverage-guided fuzz testing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To enable coverage-guided fuzz testing, edit the `.gitlab-ci.yml` file:
 | 
					To confirm the status of coverage-guided fuzz testing:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. On the top bar, select **Menu > Projects** and find your project.
 | 
				
			||||||
 | 
					1. On the left sidebar, select **Security & Compliance > Configuration**.
 | 
				
			||||||
 | 
					1. In the **Coverage Fuzzing** section the status is:
 | 
				
			||||||
 | 
					   - **Not configured**
 | 
				
			||||||
 | 
					   - **Enabled**
 | 
				
			||||||
 | 
					   - A prompt to upgrade to GitLab Ultimate.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Enable coverage-guided fuzz testing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To enable coverage-guided fuzz testing, edit `.gitlab-ci.yml`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Add the `fuzz` stage to the list of stages.
 | 
					1. Add the `fuzz` stage to the list of stages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -98,10 +115,13 @@ Use the following variables to configure coverage-guided fuzz testing in your CI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| CI/CD variable            | Description                                                                     |
 | 
					| CI/CD variable            | Description                                                                     |
 | 
				
			||||||
|---------------------------|---------------------------------------------------------------------------------|
 | 
					|---------------------------|---------------------------------------------------------------------------------|
 | 
				
			||||||
| `COVFUZZ_ADDITIONAL_ARGS` | Arguments passed to `gitlab-cov-fuzz`. Used to customize the behavior of the underlying fuzzing engine. Read the fuzzing engine's documentation for a complete list of arguments.                       |
 | 
					| `COVFUZZ_ADDITIONAL_ARGS` | Arguments passed to `gitlab-cov-fuzz`. Used to customize the behavior of the underlying fuzzing engine. Read the fuzzing engine's documentation for a complete list of arguments. |
 | 
				
			||||||
| `COVFUZZ_BRANCH`          | The branch on which long-running fuzzing jobs are to be run. On all other branches, only fuzzing regression tests are run. Default: Repository's default branch. |
 | 
					| `COVFUZZ_BRANCH`          | The branch on which long-running fuzzing jobs are to be run. On all other branches, only fuzzing regression tests are run. Default: Repository's default branch. |
 | 
				
			||||||
| `COVFUZZ_SEED_CORPUS`     | Path to a seed corpus directory. Default: empty. |
 | 
					| `COVFUZZ_SEED_CORPUS`     | Path to a seed corpus directory. Default: empty. |
 | 
				
			||||||
| `COVFUZZ_URL_PREFIX`      | Path to the `gitlab-cov-fuzz` repository cloned for use with an offline environment. You should only change this value when using an offline environment. Default: `https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw`. |
 | 
					| `COVFUZZ_URL_PREFIX`      | Path to the `gitlab-cov-fuzz` repository cloned for use with an offline environment. You should only change this value when using an offline environment. Default: `https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw`. |
 | 
				
			||||||
 | 
					| `COVFUZZ_USE_REGISTRY`    | Set to `true` to have the corpus stored in the GitLab corpus registry. The variables `COVFUZZ_CORPUS_NAME` and `COVFUZZ_GITLAB_TOKEN` are required if this variable is set to `true`. Default: `false`. [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5017) in GitLab 14.8. |
 | 
				
			||||||
 | 
					| `COVFUZZ_CORPUS_NAME`     | Name of the corpus to be used in the job.  [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5017) in GitLab 14.8. |
 | 
				
			||||||
 | 
					| `COVFUZZ_GITLAB_TOKEN`    | Environment variable configured with [Personal Access Token](../../../user/profile/personal_access_tokens.md#create-a-personal-access-token) with API read/write access. [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5017) in GitLab 14.8. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Seed corpus
 | 
					#### Seed corpus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -122,7 +142,91 @@ Each fuzzing step outputs these artifacts:
 | 
				
			||||||
You can download the JSON report file from the CI/CD pipelines page. For more information, see
 | 
					You can download the JSON report file from the CI/CD pipelines page. For more information, see
 | 
				
			||||||
[Downloading artifacts](../../../ci/pipelines/job_artifacts.md#download-job-artifacts).
 | 
					[Downloading artifacts](../../../ci/pipelines/job_artifacts.md#download-job-artifacts).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Coverage-guided fuzz testing report
 | 
					## Corpus registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5017) in GitLab 14.8.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FLAG:
 | 
				
			||||||
 | 
					On self-managed GitLab, by default this feature is available. To hide the feature, ask an
 | 
				
			||||||
 | 
					administrator to [disable the feature flags](../../../administration/feature_flags.md) named
 | 
				
			||||||
 | 
					`corpus_management` and `corpus_management_ui`. On GitLab.com, this feature is available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The corpus registry is a library of corpuses. Corpuses in a project's registry are available to
 | 
				
			||||||
 | 
					all jobs in that project. A project-wide registry is a more efficient way to manage corpuses than
 | 
				
			||||||
 | 
					the default option of one corpus per job.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The corpus registry uses the package registry to store the project's corpuses. Corpuses stored in
 | 
				
			||||||
 | 
					the registry are hidden to ensure data integrity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In the GitLab UI, with corpus management you can:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- View details of the corpus registry.
 | 
				
			||||||
 | 
					- Download a corpus.
 | 
				
			||||||
 | 
					- Delete a corpus.
 | 
				
			||||||
 | 
					- Create a new corpus.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When you download a corpus, the file is named `artifacts.zip`, regardless of the filename used when
 | 
				
			||||||
 | 
					the corpus was initially uploaded. This file contains only the corpus, which is different to the
 | 
				
			||||||
 | 
					artifacts files you can download from the CI/CD pipeline.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### View details of the corpus registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To view details of the corpus registry:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. On the top bar, select **Menu > Projects** and find your project.
 | 
				
			||||||
 | 
					1. On the left sidebar, select **Security & Compliance > Configuration**.
 | 
				
			||||||
 | 
					1. In the **Coverage Fuzzing** section, select **Manage corpus**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Create a corpus in the corpus registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To create a corpus in the corpus registry, either:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Create a corpus in a pipeline
 | 
				
			||||||
 | 
					- Upload an existing corpus file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Create a corpus in a pipeline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To create a corpus in a pipeline:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. In the `.gitlab-ci.yml` file, edit the `my_fuzz_target` job.
 | 
				
			||||||
 | 
					1. Set the following variables:
 | 
				
			||||||
 | 
					   - Set `COVFUZZ_USE_REGISTRY` to `true`.
 | 
				
			||||||
 | 
					   - Set `COVFUZZ_CORPUS_NAME` to name the corpus.
 | 
				
			||||||
 | 
					   - Set `COVFUZZ_GITLAB_TOKEN` to the value of the personal access token.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					After the `my_fuzz_target` job runs, the corpus is stored in the corpus registry, with the name
 | 
				
			||||||
 | 
					provided by the `COVFUZZ_CORPUS_NAME` variable. The corpus is updated on every pipeline run.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Upload a corpus file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To upload an existing corpus file:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. On the top bar, select **Menu > Projects** and find your project.
 | 
				
			||||||
 | 
					1. On the left sidebar, select **Security & Compliance > Configuration**.
 | 
				
			||||||
 | 
					1. In the **Coverage Fuzzing** section, select **Manage corpus**.
 | 
				
			||||||
 | 
					1. Select **New corpus**.
 | 
				
			||||||
 | 
					1. Complete the fields.
 | 
				
			||||||
 | 
					1. Select **Upload file**.
 | 
				
			||||||
 | 
					1. Select **Add**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can now reference the corpus in the `.gitlab-ci.yml` file. Ensure the value used in the
 | 
				
			||||||
 | 
					`COVFUZZ_CORPUS_NAME` variable matches exactly the name given to the uploaded corpus file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Use a corpus stored in the corpus registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To use a corpus stored in the corpus registry, you must reference it by its name. To confirm the
 | 
				
			||||||
 | 
					name of the relevant corpus, view details of the corpus registry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Prerequisites:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Enable coverage-guide fuzz testing](#enable-coverage-guided-fuzz-testing) in the project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Set the following variables in the `.gitlab-ci.yml` file:
 | 
				
			||||||
 | 
					   - Set `COVFUZZ_USE_REGISTRY` to `true`.
 | 
				
			||||||
 | 
					   - Set `COVFUZZ_CORPUS_NAME` to the name of the corpus.
 | 
				
			||||||
 | 
					   - Set `COVFUZZ_GITLAB_TOKEN` to the value of the personal access token.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Coverage-guided fuzz testing report
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220062) in GitLab 13.3 as an [Alpha feature](../../../policy/alpha-beta-support.md#alpha-features).
 | 
					> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220062) in GitLab 13.3 as an [Alpha feature](../../../policy/alpha-beta-support.md#alpha-features).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -255,3 +359,16 @@ vulnerability:
 | 
				
			||||||
  engines listed in [Supported fuzzing engines and languages](#supported-fuzzing-engines-and-languages).
 | 
					  engines listed in [Supported fuzzing engines and languages](#supported-fuzzing-engines-and-languages).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- vale gitlab.Acronyms = YES -->
 | 
					<!-- vale gitlab.Acronyms = YES -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Troubleshooting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Error "Unable to extract corpus folder from artifacts zip file"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you see this error message, and `COVFUZZ_USE_REGISTRY` is set to `true`, ensure that the uploaded
 | 
				
			||||||
 | 
					corpus file extracts into a folder named `corpus`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Error "400 Bad request - Duplicate package is not allowed"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you see this error message when running the fuzzing job with `COVFUZZ_USE_REGISTRY` set to `true`,
 | 
				
			||||||
 | 
					ensure that duplicates are allowed. For more details, see
 | 
				
			||||||
 | 
					[duplicate Generic packages](../../packages/generic_packages/#do-not-allow-duplicate-generic-packages).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -196,7 +196,7 @@ see the [related epic](https://gitlab.com/groups/gitlab-org/-/epics/4739).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### View vulnerabilities in cluster images **(ULTIMATE)**
 | 
					### View vulnerabilities in cluster images **(ULTIMATE)**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6346) in GitLab 14.8 [with a flag](../../../../administration/feature_flags.md) named `cluster_vulnerabilities`. Disabled by default.
 | 
					> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6346) in GitLab 14.8 [with a flag](../../../../administration/feature_flags.md) named `cluster_vulnerabilities`. Enabled by default.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Users with at least the [Developer role](../../../permissions.md)
 | 
					Users with at least the [Developer role](../../../permissions.md)
 | 
				
			||||||
can view cluster vulnerabilities. You can access them through the [vulnerability report](../../../application_security/vulnerabilities/index.md)
 | 
					can view cluster vulnerabilities. You can access them through the [vulnerability report](../../../application_security/vulnerabilities/index.md)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -280,10 +280,15 @@ To view the activity feed in Atom format, select the
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Share a group with another group
 | 
					## Share a group with another group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18328) in GitLab 12.7.
 | 
					> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18328) in GitLab 12.7.
 | 
				
			||||||
 | 
					> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11 from a form to a modal window [with a flag](../feature_flags.md). Disabled by default.
 | 
				
			||||||
 | 
					> - Modal window [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 14.8.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NOTE:
 | 
					FLAG:
 | 
				
			||||||
In GitLab 13.11, you can [replace this form with a modal window](#share-a-group-modal-window).
 | 
					On self-managed GitLab, by default the modal window feature is available.
 | 
				
			||||||
 | 
					To hide the feature, ask an administrator to [disable the feature flag](../../administration/feature_flags.md)
 | 
				
			||||||
 | 
					named `invite_members_group_modal`.
 | 
				
			||||||
 | 
					On GitLab.com, this feature is available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Similar to how you [share a project with a group](../project/members/share_project_with_groups.md),
 | 
					Similar to how you [share a project with a group](../project/members/share_project_with_groups.md),
 | 
				
			||||||
you can share a group with another group. Members get direct access
 | 
					you can share a group with another group. Members get direct access
 | 
				
			||||||
| 
						 | 
					@ -293,35 +298,14 @@ To share a given group, for example, `Frontend` with another group, for example,
 | 
				
			||||||
`Engineering`:
 | 
					`Engineering`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Go to the `Frontend` group.
 | 
					1. Go to the `Frontend` group.
 | 
				
			||||||
1. From the left menu, select **Group information > Members**.
 | 
					1. On the left sidebar, select **Group information > Members**.
 | 
				
			||||||
1. Select the **Invite group** tab.
 | 
					1. Select **Invite a group**.
 | 
				
			||||||
1. In the **Select a group to invite** list, select `Engineering`.
 | 
					1. In the **Select a group to invite** list, select `Engineering`.
 | 
				
			||||||
1. For the **Max role**, select a [role](../permissions.md).
 | 
					1. Select a [role](../permissions.md).
 | 
				
			||||||
1. Select **Invite**.
 | 
					1. Select **Invite**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All the members of the `Engineering` group are added to the `Frontend` group.
 | 
					All the members of the `Engineering` group are added to the `Frontend` group.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Share a group modal window
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11.
 | 
					 | 
				
			||||||
> - [Deployed behind a feature flag](../feature_flags.md), disabled by default.
 | 
					 | 
				
			||||||
> - Enabled on GitLab.com.
 | 
					 | 
				
			||||||
> - Recommended for production use.
 | 
					 | 
				
			||||||
> - Replaces the existing form with buttons to open a modal window.
 | 
					 | 
				
			||||||
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](../project/members/index.md#enable-or-disable-modal-window).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WARNING:
 | 
					 | 
				
			||||||
This feature might not be available to you. Check the **version history** note above for details.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
In GitLab 13.11, you can optionally replace the sharing form with a modal window.
 | 
					 | 
				
			||||||
To share a group after enabling this feature:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. Go to your group's page.
 | 
					 | 
				
			||||||
1. On the left sidebar, go to **Group information > Members**, and then select **Invite a group**.
 | 
					 | 
				
			||||||
1. Select a group, and select a **Max role**.
 | 
					 | 
				
			||||||
1. Optional. Select an **Access expiration date**.
 | 
					 | 
				
			||||||
1. Select **Invite**.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Manage group memberships via LDAP **(PREMIUM SELF)**
 | 
					## Manage group memberships via LDAP **(PREMIUM SELF)**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Group syncing allows LDAP groups to be mapped to GitLab groups. This provides more control over per-group user management. To configure group syncing, edit the `group_base` **DN** (`'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org'`). This **OU** contains all groups that will be associated with GitLab groups.
 | 
					Group syncing allows LDAP groups to be mapped to GitLab groups. This provides more control over per-group user management. To configure group syncing, edit the `group_base` **DN** (`'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org'`). This **OU** contains all groups that will be associated with GitLab groups.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,14 +33,27 @@ usernames. A GitLab administrator can configure the GitLab instance to
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Project members permissions
 | 
					## Project members permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219299) in GitLab 14.8, personal namespace owners appear with Owner role in new projects in their namespace. Introduced [with a flag](../administration/feature_flags.md) named `personal_project_owner_with_owner_access`. Disabled by default.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FLAG:
 | 
				
			||||||
 | 
					On self-managed GitLab, personal namespace owners appearing with the Owner role in new projects in their namespace is disabled. To make it available,
 | 
				
			||||||
 | 
					ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `personal_project_owner_with_owner_access`.
 | 
				
			||||||
 | 
					The feature is not ready for production use.
 | 
				
			||||||
 | 
					On GitLab.com, this feature is not available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A user's role determines what permissions they have on a project. The Owner role provides all permissions but is
 | 
					A user's role determines what permissions they have on a project. The Owner role provides all permissions but is
 | 
				
			||||||
available only:
 | 
					available only:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- For group owners. The role is inherited for a group's projects.
 | 
					- For group owners. The role is inherited for a group's projects.
 | 
				
			||||||
- For Administrators.
 | 
					- For Administrators.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Personal namespace owners have the same permissions as an Owner, but are displayed with the Maintainer role on projects created in their personal namespace.
 | 
					Personal [namespace](group/index.md#namespaces) owners:
 | 
				
			||||||
For more information, see [projects members documentation](project/members/index.md).
 | 
					
 | 
				
			||||||
 | 
					- Are displayed as having the Maintainer role on projects in the namespace, but have the same permissions as a user with the Owner role.
 | 
				
			||||||
 | 
					- (Disabled by default) In GitLab 14.8 and later, for new projects in the namespace, are displayed as having the Owner role.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more information about how to manage project members, see
 | 
				
			||||||
 | 
					[members of a project](project/members/index.md).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The following table lists project permissions available for each role:
 | 
					The following table lists project permissions available for each role:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,15 @@ Each member gets a role, which determines what they can do in the project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Add users to a project
 | 
					## Add users to a project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11 from a form to a modal window [with a flag](../../feature_flags.md). Disabled by default.
 | 
				
			||||||
 | 
					> - Modal window [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 14.8.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FLAG:
 | 
				
			||||||
 | 
					On self-managed GitLab, by default the modal window feature is available.
 | 
				
			||||||
 | 
					To hide the feature, ask an administrator to [disable the feature flag](../../../administration/feature_flags.md)
 | 
				
			||||||
 | 
					named `invite_members_group_modal`.
 | 
				
			||||||
 | 
					On GitLab.com, this feature is available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Add users to a project so they become members and have permission
 | 
					Add users to a project so they become members and have permission
 | 
				
			||||||
to perform actions.
 | 
					to perform actions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,11 +30,12 @@ Prerequisite:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To add a user to a project:
 | 
					To add a user to a project:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Go to your project and select **Project information > Members**.
 | 
					1. On the top bar, select **Menu > Projects** and find your project.
 | 
				
			||||||
1. On the **Invite member** tab, under **GitLab member or Email address**, type the username or email address.
 | 
					1. On the left sidebar, select **Project information > Members**.
 | 
				
			||||||
   In GitLab 13.11 and later, you can [replace this form with a modal window](#add-a-member-modal-window).
 | 
					1. Select **Invite members**.
 | 
				
			||||||
1. Select a [role](../../permissions.md).
 | 
					1. Enter an email address and select a [role](../../permissions.md).
 | 
				
			||||||
1. Optional. Choose an expiration date. On that date, the user can no longer access the project.
 | 
					1. Optional. Select an **Access expiration date**.
 | 
				
			||||||
 | 
					   On that date, the user can no longer access the project.
 | 
				
			||||||
1. Select **Invite**.
 | 
					1. Select **Invite**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If the user has a GitLab account, they are added to the members list.
 | 
					If the user has a GitLab account, they are added to the members list.
 | 
				
			||||||
| 
						 | 
					@ -40,6 +50,15 @@ using the email address the invitation was sent to.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Add groups to a project
 | 
					## Add groups to a project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11 from a form to a modal window [with a flag](../../feature_flags.md). Disabled by default.
 | 
				
			||||||
 | 
					> - Modal window [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 14.8.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FLAG:
 | 
				
			||||||
 | 
					On self-managed GitLab, by default the modal window feature is available.
 | 
				
			||||||
 | 
					To hide the feature, ask an administrator to [disable the feature flag](../../../administration/feature_flags.md)
 | 
				
			||||||
 | 
					named `invite_members_group_modal`.
 | 
				
			||||||
 | 
					On GitLab.com, this feature is available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When you add a group to a project, each user in the group gets access to the project.
 | 
					When you add a group to a project, each user in the group gets access to the project.
 | 
				
			||||||
Each user's access is based on:
 | 
					Each user's access is based on:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,9 +73,10 @@ To add groups to a project:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. On the top bar, select **Menu > Projects** and find your project.
 | 
					1. On the top bar, select **Menu > Projects** and find your project.
 | 
				
			||||||
1. On the left sidebar, select **Project information > Members**.
 | 
					1. On the left sidebar, select **Project information > Members**.
 | 
				
			||||||
1. On the **Invite group** tab, under **Select a group to invite**, choose a group.
 | 
					1. Select **Invite a group**.
 | 
				
			||||||
1. Select the highest max [role](../../permissions.md) for users in the group.
 | 
					1. Select a group.
 | 
				
			||||||
1. Optional. Choose an expiration date. On that date, the user can no longer access the project.
 | 
					1. Select the highest [role](../../permissions.md) for users in the group.
 | 
				
			||||||
 | 
					1. Optional. Select an **Access expiration date**. On that date, the group can no longer access the project.
 | 
				
			||||||
1. Select **Invite**.
 | 
					1. Select **Invite**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The members of the group are not displayed on the **Members** tab.
 | 
					The members of the group are not displayed on the **Members** tab.
 | 
				
			||||||
| 
						 | 
					@ -203,40 +223,3 @@ Prerequisite:
 | 
				
			||||||
## Share a project with a group
 | 
					## Share a project with a group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Instead of adding users one by one, you can [share a project with an entire group](share_project_with_groups.md).
 | 
					Instead of adding users one by one, you can [share a project with an entire group](share_project_with_groups.md).
 | 
				
			||||||
 | 
					 | 
				
			||||||
### Add a member modal window
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11 [with a flag](../../feature_flags.md). Disabled by default.
 | 
					 | 
				
			||||||
> - Replaces the existing form with buttons to open a modal window.
 | 
					 | 
				
			||||||
> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 14.8.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FLAG:
 | 
					 | 
				
			||||||
On self-managed GitLab, by default this feature is available.
 | 
					 | 
				
			||||||
To hide the feature, ask an administrator to [disable the feature flag](#enable-or-disable-modal-window).
 | 
					 | 
				
			||||||
On GitLab.com, this feature is available.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. On the top bar, select **Menu > Projects** and find your project.
 | 
					 | 
				
			||||||
1. On the left sidebar, select **Project information > Members**.
 | 
					 | 
				
			||||||
1. Select **Invite members**.
 | 
					 | 
				
			||||||
1. Enter an email address and select a role.
 | 
					 | 
				
			||||||
1. Optional. Select an **Access expiration date**.
 | 
					 | 
				
			||||||
1. Select **Invite**.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Enable or disable modal window **(FREE SELF)**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The modal window for adding a member is under development and is ready for production use. It is
 | 
					 | 
				
			||||||
deployed behind a feature flag that is **enabled by default**.
 | 
					 | 
				
			||||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
 | 
					 | 
				
			||||||
can enable it.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
To enable it:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```ruby
 | 
					 | 
				
			||||||
Feature.enable(:invite_members_group_modal)
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
To disable it:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```ruby
 | 
					 | 
				
			||||||
Feature.disable(:invite_members_group_modal)
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,13 @@ module Gitlab
 | 
				
			||||||
    MAINTAINER_SUBGROUP_ACCESS = 1
 | 
					    MAINTAINER_SUBGROUP_ACCESS = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class << self
 | 
					    class << self
 | 
				
			||||||
      delegate :values, to: :options
 | 
					      def values
 | 
				
			||||||
 | 
					        if ::Feature.enabled?(:personal_project_owner_with_owner_access, default_enabled: :yaml)
 | 
				
			||||||
 | 
					          options_with_owner.values
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          options.values
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      def all_values
 | 
					      def all_values
 | 
				
			||||||
        options_with_owner.values
 | 
					        options_with_owner.values
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,7 +41,6 @@ module Gitlab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      def prohibited_branch_checks
 | 
					      def prohibited_branch_checks
 | 
				
			||||||
        return if deletion?
 | 
					        return if deletion?
 | 
				
			||||||
        return unless Feature.enabled?(:prohibit_hexadecimal_branch_names, project, default_enabled: true)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if branch_name =~ /\A\h{40}\z/
 | 
					        if branch_name =~ /\A\h{40}\z/
 | 
				
			||||||
          raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
 | 
					          raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@ module Gitlab
 | 
				
			||||||
        user.projects_with_active_memberships.select_for_project_authorization,
 | 
					        user.projects_with_active_memberships.select_for_project_authorization,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # The personal projects of the user.
 | 
					        # The personal projects of the user.
 | 
				
			||||||
        user.personal_projects.select_as_maintainer_for_project_authorization,
 | 
					        user.personal_projects.select_project_owner_for_project_authorization,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Projects that belong directly to any of the groups the user has
 | 
					        # Projects that belong directly to any of the groups the user has
 | 
				
			||||||
        # access to.
 | 
					        # access to.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12510,10 +12510,10 @@ msgstr ""
 | 
				
			||||||
msgid "DevopsAdoption|At least one deploy"
 | 
					msgid "DevopsAdoption|At least one deploy"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "DevopsAdoption|At least one issue opened"
 | 
					msgid "DevopsAdoption|At least one issue created"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "DevopsAdoption|At least one merge request opened"
 | 
					msgid "DevopsAdoption|At least one merge request created"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "DevopsAdoption|At least one pipeline successfully run"
 | 
					msgid "DevopsAdoption|At least one pipeline successfully run"
 | 
				
			||||||
| 
						 | 
					@ -17143,7 +17143,7 @@ msgstr ""
 | 
				
			||||||
msgid "Group: %{name}"
 | 
					msgid "Group: %{name}"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "GroupActivityMetrics|Issues opened"
 | 
					msgid "GroupActivityMetrics|Issues created"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "GroupActivityMetrics|Last 90 days"
 | 
					msgid "GroupActivityMetrics|Last 90 days"
 | 
				
			||||||
| 
						 | 
					@ -17152,7 +17152,7 @@ msgstr ""
 | 
				
			||||||
msgid "GroupActivityMetrics|Members added"
 | 
					msgid "GroupActivityMetrics|Members added"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "GroupActivityMetrics|Merge Requests opened"
 | 
					msgid "GroupActivityMetrics|Merge Requests created"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "GroupActivityMetrics|Recent activity"
 | 
					msgid "GroupActivityMetrics|Recent activity"
 | 
				
			||||||
| 
						 | 
					@ -20164,6 +20164,9 @@ msgstr ""
 | 
				
			||||||
msgid "IssueAnalytics|Assignees"
 | 
					msgid "IssueAnalytics|Assignees"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "IssueAnalytics|Created by"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "IssueAnalytics|Due date"
 | 
					msgid "IssueAnalytics|Due date"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20176,9 +20179,6 @@ msgstr ""
 | 
				
			||||||
msgid "IssueAnalytics|Milestone"
 | 
					msgid "IssueAnalytics|Milestone"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "IssueAnalytics|Opened by"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
msgid "IssueAnalytics|Status"
 | 
					msgid "IssueAnalytics|Status"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20302,10 +20302,10 @@ msgstr ""
 | 
				
			||||||
msgid "IssuesAnalytics|Avg/Month:"
 | 
					msgid "IssuesAnalytics|Avg/Month:"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "IssuesAnalytics|Issues opened"
 | 
					msgid "IssuesAnalytics|Issues created"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "IssuesAnalytics|Issues opened per month"
 | 
					msgid "IssuesAnalytics|Issues created per month"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "IssuesAnalytics|Last 12 months"
 | 
					msgid "IssuesAnalytics|Last 12 months"
 | 
				
			||||||
| 
						 | 
					@ -25429,7 +25429,7 @@ msgstr ""
 | 
				
			||||||
msgid "Opened issues"
 | 
					msgid "Opened issues"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "OpenedNDaysAgo|Opened"
 | 
					msgid "OpenedNDaysAgo|Created"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Opens in a new window"
 | 
					msgid "Opens in a new window"
 | 
				
			||||||
| 
						 | 
					@ -43756,9 +43756,6 @@ msgstr ""
 | 
				
			||||||
msgid "open issue"
 | 
					msgid "open issue"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "opened %{timeAgo}"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
msgid "or"
 | 
					msgid "or"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,23 @@ require_relative 'dependencies'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MetricsServer # rubocop:disable Gitlab/NamespacedClass
 | 
					class MetricsServer # rubocop:disable Gitlab/NamespacedClass
 | 
				
			||||||
  class << self
 | 
					  class << self
 | 
				
			||||||
    def spawn(target, metrics_dir:, wipe_metrics_dir: false, trapped_signals: [])
 | 
					    def spawn(target, metrics_dir:, gitlab_config: nil, wipe_metrics_dir: false)
 | 
				
			||||||
      raise "Target must be one of [puma,sidekiq]" unless %w(puma sidekiq).include?(target)
 | 
					      ensure_valid_target!(target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      cmd = "#{Rails.root}/bin/metrics-server"
 | 
				
			||||||
 | 
					      env = {
 | 
				
			||||||
 | 
					        'METRICS_SERVER_TARGET' => target,
 | 
				
			||||||
 | 
					        'WIPE_METRICS_DIR' => wipe_metrics_dir ? '1' : '0'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      env['GITLAB_CONFIG'] = gitlab_config if gitlab_config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Process.spawn(env, cmd, err: $stderr, out: $stdout, pgroup: true).tap do |pid|
 | 
				
			||||||
 | 
					        Process.detach(pid)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def fork(target, metrics_dir:, wipe_metrics_dir: false, reset_signals: [])
 | 
				
			||||||
 | 
					      ensure_valid_target!(target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      pid = Process.fork
 | 
					      pid = Process.fork
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +30,7 @@ class MetricsServer # rubocop:disable Gitlab/NamespacedClass
 | 
				
			||||||
        # Remove any custom signal handlers the parent process had registered, since we do
 | 
					        # Remove any custom signal handlers the parent process had registered, since we do
 | 
				
			||||||
        # not want to inherit them, and Ruby forks with a `clone` that has the `CLONE_SIGHAND`
 | 
					        # not want to inherit them, and Ruby forks with a `clone` that has the `CLONE_SIGHAND`
 | 
				
			||||||
        # flag set.
 | 
					        # flag set.
 | 
				
			||||||
        Gitlab::ProcessManagement.modify_signals(trapped_signals, 'DEFAULT')
 | 
					        Gitlab::ProcessManagement.modify_signals(reset_signals, 'DEFAULT')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        server = MetricsServer.new(target, metrics_dir, wipe_metrics_dir)
 | 
					        server = MetricsServer.new(target, metrics_dir, wipe_metrics_dir)
 | 
				
			||||||
        # This rewrites /proc/cmdline, since otherwise tools like `top` will show the
 | 
					        # This rewrites /proc/cmdline, since otherwise tools like `top` will show the
 | 
				
			||||||
| 
						 | 
					@ -29,6 +44,12 @@ class MetricsServer # rubocop:disable Gitlab/NamespacedClass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      pid
 | 
					      pid
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def ensure_valid_target!(target)
 | 
				
			||||||
 | 
					      raise "Target must be one of [puma,sidekiq]" unless %w(puma sidekiq).include?(target)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def initialize(target, metrics_dir, wipe_metrics_dir)
 | 
					  def initialize(target, metrics_dir, wipe_metrics_dir)
 | 
				
			||||||
| 
						 | 
					@ -40,7 +61,7 @@ class MetricsServer # rubocop:disable Gitlab/NamespacedClass
 | 
				
			||||||
  def start
 | 
					  def start
 | 
				
			||||||
    ::Prometheus::Client.configure do |config|
 | 
					    ::Prometheus::Client.configure do |config|
 | 
				
			||||||
      config.multiprocess_files_dir = @metrics_dir
 | 
					      config.multiprocess_files_dir = @metrics_dir
 | 
				
			||||||
      config.pid_provider = proc { "#{@target}_exporter" }
 | 
					      config.pid_provider = proc { name }
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FileUtils.mkdir_p(@metrics_dir, mode: 0700)
 | 
					    FileUtils.mkdir_p(@metrics_dir, mode: 0700)
 | 
				
			||||||
| 
						 | 
					@ -57,16 +78,18 @@ class MetricsServer # rubocop:disable Gitlab/NamespacedClass
 | 
				
			||||||
      case @target
 | 
					      case @target
 | 
				
			||||||
      when 'puma'
 | 
					      when 'puma'
 | 
				
			||||||
        Gitlab::Metrics::Exporter::WebExporter.instance(**default_opts)
 | 
					        Gitlab::Metrics::Exporter::WebExporter.instance(**default_opts)
 | 
				
			||||||
      else
 | 
					      when 'sidekiq'
 | 
				
			||||||
        exporter_class = "Gitlab::Metrics::Exporter::#{@target.camelize}Exporter".constantize
 | 
					 | 
				
			||||||
        settings = Settings.new(Settings.monitoring[name])
 | 
					        settings = Settings.new(Settings.monitoring[name])
 | 
				
			||||||
        exporter_class.instance(settings, **default_opts)
 | 
					        Gitlab::Metrics::Exporter::SidekiqExporter.instance(settings, **default_opts)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    exporter.start
 | 
					    exporter.start
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def name
 | 
					  def name
 | 
				
			||||||
    "#{@target}_exporter"
 | 
					    case @target
 | 
				
			||||||
 | 
					    when 'puma' then 'web_exporter'
 | 
				
			||||||
 | 
					    when 'sidekiq' then 'sidekiq_exporter'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,8 +59,8 @@
 | 
				
			||||||
    "@gitlab/svgs": "2.5.0",
 | 
					    "@gitlab/svgs": "2.5.0",
 | 
				
			||||||
    "@gitlab/ui": "36.1.0",
 | 
					    "@gitlab/ui": "36.1.0",
 | 
				
			||||||
    "@gitlab/visual-review-tools": "1.6.1",
 | 
					    "@gitlab/visual-review-tools": "1.6.1",
 | 
				
			||||||
    "@rails/actioncable": "6.1.4-1",
 | 
					    "@rails/actioncable": "6.1.4-6",
 | 
				
			||||||
    "@rails/ujs": "6.1.4-1",
 | 
					    "@rails/ujs": "6.1.4-6",
 | 
				
			||||||
    "@sentry/browser": "5.30.0",
 | 
					    "@sentry/browser": "5.30.0",
 | 
				
			||||||
    "@sourcegraph/code-host-integration": "0.0.60",
 | 
					    "@sourcegraph/code-host-integration": "0.0.60",
 | 
				
			||||||
    "@tiptap/core": "^2.0.0-beta.171",
 | 
					    "@tiptap/core": "^2.0.0-beta.171",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
source 'https://rubygems.org'
 | 
					source 'https://rubygems.org'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gem 'gitlab-qa', require: 'gitlab/qa'
 | 
					gem 'gitlab-qa', require: 'gitlab/qa'
 | 
				
			||||||
gem 'activesupport', '~> 6.1.4.1' # This should stay in sync with the root's Gemfile
 | 
					gem 'activesupport', '~> 6.1.4.6' # This should stay in sync with the root's Gemfile
 | 
				
			||||||
gem 'allure-rspec', '~> 2.15.0'
 | 
					gem 'allure-rspec', '~> 2.15.0'
 | 
				
			||||||
gem 'capybara', '~> 3.35.0'
 | 
					gem 'capybara', '~> 3.35.0'
 | 
				
			||||||
gem 'capybara-screenshot', '~> 1.0.23'
 | 
					gem 'capybara-screenshot', '~> 1.0.23'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ GEM
 | 
				
			||||||
  remote: https://rubygems.org/
 | 
					  remote: https://rubygems.org/
 | 
				
			||||||
  specs:
 | 
					  specs:
 | 
				
			||||||
    abstract_type (0.0.7)
 | 
					    abstract_type (0.0.7)
 | 
				
			||||||
    activesupport (6.1.4.1)
 | 
					    activesupport (6.1.4.6)
 | 
				
			||||||
      concurrent-ruby (~> 1.0, >= 1.0.2)
 | 
					      concurrent-ruby (~> 1.0, >= 1.0.2)
 | 
				
			||||||
      i18n (>= 1.6, < 2)
 | 
					      i18n (>= 1.6, < 2)
 | 
				
			||||||
      minitest (>= 5.1)
 | 
					      minitest (>= 5.1)
 | 
				
			||||||
| 
						 | 
					@ -226,6 +226,7 @@ GEM
 | 
				
			||||||
    rack (2.2.3)
 | 
					    rack (2.2.3)
 | 
				
			||||||
    rack-test (1.1.0)
 | 
					    rack-test (1.1.0)
 | 
				
			||||||
      rack (>= 1.0, < 3)
 | 
					      rack (>= 1.0, < 3)
 | 
				
			||||||
 | 
					    rainbow (3.0.0)
 | 
				
			||||||
    rake (13.0.6)
 | 
					    rake (13.0.6)
 | 
				
			||||||
    regexp_parser (2.1.1)
 | 
					    regexp_parser (2.1.1)
 | 
				
			||||||
    representable (3.1.1)
 | 
					    representable (3.1.1)
 | 
				
			||||||
| 
						 | 
					@ -321,7 +322,7 @@ PLATFORMS
 | 
				
			||||||
  ruby
 | 
					  ruby
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES
 | 
					DEPENDENCIES
 | 
				
			||||||
  activesupport (~> 6.1.4.1)
 | 
					  activesupport (~> 6.1.4.6)
 | 
				
			||||||
  airborne (~> 0.3.4)
 | 
					  airborne (~> 0.3.4)
 | 
				
			||||||
  allure-rspec (~> 2.15.0)
 | 
					  allure-rspec (~> 2.15.0)
 | 
				
			||||||
  capybara (~> 3.35.0)
 | 
					  capybara (~> 3.35.0)
 | 
				
			||||||
| 
						 | 
					@ -339,6 +340,7 @@ DEPENDENCIES
 | 
				
			||||||
  parallel (~> 1.19)
 | 
					  parallel (~> 1.19)
 | 
				
			||||||
  parallel_tests (~> 2.29)
 | 
					  parallel_tests (~> 2.29)
 | 
				
			||||||
  pry-byebug (~> 3.5.1)
 | 
					  pry-byebug (~> 3.5.1)
 | 
				
			||||||
 | 
					  rainbow (~> 3.0.0)
 | 
				
			||||||
  rake (~> 13)
 | 
					  rake (~> 13)
 | 
				
			||||||
  rest-client (~> 2.1.0)
 | 
					  rest-client (~> 2.1.0)
 | 
				
			||||||
  rotp (~> 3.1.0)
 | 
					  rotp (~> 3.1.0)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -191,11 +191,11 @@ module Gitlab
 | 
				
			||||||
        return unless metrics_server_enabled?
 | 
					        return unless metrics_server_enabled?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @logger.info("Starting metrics server on port #{sidekiq_exporter_port}")
 | 
					        @logger.info("Starting metrics server on port #{sidekiq_exporter_port}")
 | 
				
			||||||
        @metrics_server_pid = MetricsServer.spawn(
 | 
					        @metrics_server_pid = MetricsServer.fork(
 | 
				
			||||||
          'sidekiq',
 | 
					          'sidekiq',
 | 
				
			||||||
          metrics_dir: @metrics_dir,
 | 
					          metrics_dir: @metrics_dir,
 | 
				
			||||||
          wipe_metrics_dir: wipe_metrics_dir,
 | 
					          wipe_metrics_dir: wipe_metrics_dir,
 | 
				
			||||||
          trapped_signals: TERMINATE_SIGNALS + FORWARD_SIGNALS
 | 
					          reset_signals: TERMINATE_SIGNALS + FORWARD_SIGNALS
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,13 +38,7 @@ RSpec.describe 'bin/metrics-server', :aggregate_failures do
 | 
				
			||||||
        config_file.write(YAML.dump(config))
 | 
					        config_file.write(YAML.dump(config))
 | 
				
			||||||
        config_file.close
 | 
					        config_file.close
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        env = {
 | 
					        @pid = MetricsServer.spawn(target, metrics_dir: metrics_dir, gitlab_config: config_file.path, wipe_metrics_dir: true)
 | 
				
			||||||
          'GITLAB_CONFIG' => config_file.path,
 | 
					 | 
				
			||||||
          'METRICS_SERVER_TARGET' => target,
 | 
					 | 
				
			||||||
          'WIPE_METRICS_DIR' => '1',
 | 
					 | 
				
			||||||
          'prometheus_multiproc_dir' => metrics_dir
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        @pid = Process.spawn(env, 'bin/metrics-server', pgroup: true)
 | 
					 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      after do
 | 
					      after do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -303,7 +303,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            it 'does not start a sidekiq metrics server' do
 | 
					            it 'does not start a sidekiq metrics server' do
 | 
				
			||||||
              expect(MetricsServer).not_to receive(:spawn)
 | 
					              expect(MetricsServer).not_to receive(:fork)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              cli.run(%w(foo))
 | 
					              cli.run(%w(foo))
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
| 
						 | 
					@ -320,7 +320,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            it 'does not start a sidekiq metrics server' do
 | 
					            it 'does not start a sidekiq metrics server' do
 | 
				
			||||||
              expect(MetricsServer).not_to receive(:spawn)
 | 
					              expect(MetricsServer).not_to receive(:fork)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              cli.run(%w(foo))
 | 
					              cli.run(%w(foo))
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
| 
						 | 
					@ -350,7 +350,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            it 'does not start a sidekiq metrics server' do
 | 
					            it 'does not start a sidekiq metrics server' do
 | 
				
			||||||
              expect(MetricsServer).not_to receive(:spawn)
 | 
					              expect(MetricsServer).not_to receive(:fork)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              cli.run(%w(foo))
 | 
					              cli.run(%w(foo))
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
| 
						 | 
					@ -376,7 +376,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            it 'does not start a sidekiq metrics server' do
 | 
					            it 'does not start a sidekiq metrics server' do
 | 
				
			||||||
              expect(MetricsServer).not_to receive(:spawn)
 | 
					              expect(MetricsServer).not_to receive(:fork)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              cli.run(%w(foo))
 | 
					              cli.run(%w(foo))
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
| 
						 | 
					@ -406,9 +406,9 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              specify do
 | 
					              specify do
 | 
				
			||||||
                if start_metrics_server
 | 
					                if start_metrics_server
 | 
				
			||||||
                  expect(MetricsServer).to receive(:spawn).with('sidekiq', metrics_dir: metrics_dir, wipe_metrics_dir: true, trapped_signals: trapped_signals)
 | 
					                  expect(MetricsServer).to receive(:fork).with('sidekiq', metrics_dir: metrics_dir, wipe_metrics_dir: true, reset_signals: trapped_signals)
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                  expect(MetricsServer).not_to receive(:spawn)
 | 
					                  expect(MetricsServer).not_to receive(:fork)
 | 
				
			||||||
                end
 | 
					                end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                cli.run(%w(foo))
 | 
					                cli.run(%w(foo))
 | 
				
			||||||
| 
						 | 
					@ -421,7 +421,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
 | 
				
			||||||
          let(:sidekiq_exporter_enabled) { true }
 | 
					          let(:sidekiq_exporter_enabled) { true }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          it 'does not start the server' do
 | 
					          it 'does not start the server' do
 | 
				
			||||||
            expect(MetricsServer).not_to receive(:spawn)
 | 
					            expect(MetricsServer).not_to receive(:fork)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            cli.run(%w(foo --dryrun))
 | 
					            cli.run(%w(foo --dryrun))
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
| 
						 | 
					@ -434,7 +434,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        before do
 | 
					        before do
 | 
				
			||||||
          allow(cli).to receive(:sleep).with(a_kind_of(Numeric))
 | 
					          allow(cli).to receive(:sleep).with(a_kind_of(Numeric))
 | 
				
			||||||
          allow(MetricsServer).to receive(:spawn).and_return(99)
 | 
					          allow(MetricsServer).to receive(:fork).and_return(99)
 | 
				
			||||||
          cli.start_metrics_server
 | 
					          cli.start_metrics_server
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -453,8 +453,8 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
 | 
				
			||||||
          allow(Gitlab::ProcessManagement).to receive(:all_alive?).with(an_instance_of(Array)).and_return(false)
 | 
					          allow(Gitlab::ProcessManagement).to receive(:all_alive?).with(an_instance_of(Array)).and_return(false)
 | 
				
			||||||
          allow(cli).to receive(:stop_metrics_server)
 | 
					          allow(cli).to receive(:stop_metrics_server)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expect(MetricsServer).to receive(:spawn).with(
 | 
					          expect(MetricsServer).to receive(:fork).with(
 | 
				
			||||||
            'sidekiq', metrics_dir: metrics_dir, wipe_metrics_dir: false, trapped_signals: trapped_signals
 | 
					            'sidekiq', metrics_dir: metrics_dir, wipe_metrics_dir: false, reset_signals: trapped_signals
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          cli.start_loop
 | 
					          cli.start_loop
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -665,7 +665,7 @@ RSpec.describe Projects::ProjectMembersController do
 | 
				
			||||||
        sign_in(user)
 | 
					        sign_in(user)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'does not create a member' do
 | 
					      it 'creates a member' do
 | 
				
			||||||
        expect do
 | 
					        expect do
 | 
				
			||||||
          post :create, params: {
 | 
					          post :create, params: {
 | 
				
			||||||
                          user_ids: stranger.id,
 | 
					                          user_ids: stranger.id,
 | 
				
			||||||
| 
						 | 
					@ -673,7 +673,9 @@ RSpec.describe Projects::ProjectMembersController do
 | 
				
			||||||
                          access_level: Member::OWNER,
 | 
					                          access_level: Member::OWNER,
 | 
				
			||||||
                          project_id: project
 | 
					                          project_id: project
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
        end.to change { project.members.count }.by(0)
 | 
					        end.to change { project.members.count }.by(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(project.team_members).to include(user)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,21 +11,40 @@ RSpec.describe Projects::Members::EffectiveAccessLevelFinder, '#execute' do
 | 
				
			||||||
  context 'for a personal project' do
 | 
					  context 'for a personal project' do
 | 
				
			||||||
    let_it_be(:project) { create(:project) }
 | 
					    let_it_be(:project) { create(:project) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    shared_examples_for 'includes access level of the owner of the project as Maintainer' do
 | 
					    shared_examples_for 'includes access level of the owner of the project' do
 | 
				
			||||||
      it 'includes access level of the owner of the project as Maintainer' do
 | 
					      context 'when personal_project_owner_with_owner_access feature flag is enabled' do
 | 
				
			||||||
        expect(subject).to(
 | 
					        it 'includes access level of the owner of the project as Owner' do
 | 
				
			||||||
          contain_exactly(
 | 
					          expect(subject).to(
 | 
				
			||||||
            hash_including(
 | 
					            contain_exactly(
 | 
				
			||||||
              'user_id' => project.namespace.owner.id,
 | 
					              hash_including(
 | 
				
			||||||
              'access_level' => Gitlab::Access::MAINTAINER
 | 
					                'user_id' => project.namespace.owner.id,
 | 
				
			||||||
 | 
					                'access_level' => Gitlab::Access::OWNER
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
        )
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context 'when personal_project_owner_with_owner_access feature flag is disabled' do
 | 
				
			||||||
 | 
					        before do
 | 
				
			||||||
 | 
					          stub_feature_flags(personal_project_owner_with_owner_access: false)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it 'includes access level of the owner of the project as Maintainer' do
 | 
				
			||||||
 | 
					          expect(subject).to(
 | 
				
			||||||
 | 
					            contain_exactly(
 | 
				
			||||||
 | 
					              hash_including(
 | 
				
			||||||
 | 
					                'user_id' => project.namespace.owner.id,
 | 
				
			||||||
 | 
					                'access_level' => Gitlab::Access::MAINTAINER
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'when the project owner is a member of the project' do
 | 
					    context 'when the project owner is a member of the project' do
 | 
				
			||||||
      it_behaves_like 'includes access level of the owner of the project as Maintainer'
 | 
					      it_behaves_like 'includes access level of the owner of the project'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'when the project owner is not explicitly a member of the project' do
 | 
					    context 'when the project owner is not explicitly a member of the project' do
 | 
				
			||||||
| 
						 | 
					@ -33,7 +52,7 @@ RSpec.describe Projects::Members::EffectiveAccessLevelFinder, '#execute' do
 | 
				
			||||||
        project.members.find_by(user_id: project.namespace.owner.id).destroy!
 | 
					        project.members.find_by(user_id: project.namespace.owner.id).destroy!
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it_behaves_like 'includes access level of the owner of the project as Maintainer'
 | 
					      it_behaves_like 'includes access level of the owner of the project'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -84,17 +103,32 @@ RSpec.describe Projects::Members::EffectiveAccessLevelFinder, '#execute' do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  context 'for a project within a group' do
 | 
					  context 'for a project within a group' do
 | 
				
			||||||
    context 'project in a root group' do
 | 
					    context 'project in a root group' do
 | 
				
			||||||
      it 'includes access levels of users who are direct members of the parent group' do
 | 
					      context 'includes access levels of users who are direct members of the parent group' do
 | 
				
			||||||
        group_member = create(:group_member, :developer, source: group)
 | 
					        it 'when access level is developer' do
 | 
				
			||||||
 | 
					          group_member = create(:group_member, :developer, source: group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(subject).to(
 | 
					          expect(subject).to(
 | 
				
			||||||
          include(
 | 
					            include(
 | 
				
			||||||
            hash_including(
 | 
					              hash_including(
 | 
				
			||||||
              'user_id' => group_member.user.id,
 | 
					                'user_id' => group_member.user.id,
 | 
				
			||||||
              'access_level' => Gitlab::Access::DEVELOPER
 | 
					                'access_level' => Gitlab::Access::DEVELOPER
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
        )
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it 'when access level is owner' do
 | 
				
			||||||
 | 
					          group_member = create(:group_member, :owner, source: group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect(subject).to(
 | 
				
			||||||
 | 
					            include(
 | 
				
			||||||
 | 
					              hash_including(
 | 
				
			||||||
 | 
					                'user_id' => group_member.user.id,
 | 
				
			||||||
 | 
					                'access_level' => Gitlab::Access::OWNER
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,15 +40,6 @@ RSpec.describe Gitlab::Checks::BranchCheck do
 | 
				
			||||||
          expect { subject.validate! }.not_to raise_error
 | 
					          expect { subject.validate! }.not_to raise_error
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					 | 
				
			||||||
      context "the feature flag is disabled" do
 | 
					 | 
				
			||||||
        it "doesn't prohibit a 40-character hexadecimal branch name" do
 | 
					 | 
				
			||||||
          stub_feature_flags(prohibit_hexadecimal_branch_names: false)
 | 
					 | 
				
			||||||
          allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          expect { subject.validate! }.not_to raise_error
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'protected branches check' do
 | 
					    context 'protected branches check' do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,13 +47,15 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def loggable_data(count:, db_count: nil)
 | 
					    def loggable_data(count:, db_count: nil)
 | 
				
			||||||
      keys = %w[
 | 
					      database_name = Ci::ApplicationRecord.connection.pool.db_config.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      keys = %W[
 | 
				
			||||||
        expensive_operation_duration_s
 | 
					        expensive_operation_duration_s
 | 
				
			||||||
        expensive_operation_db_count
 | 
					        expensive_operation_db_count
 | 
				
			||||||
        expensive_operation_db_primary_count
 | 
					        expensive_operation_db_primary_count
 | 
				
			||||||
        expensive_operation_db_primary_duration_s
 | 
					        expensive_operation_db_primary_duration_s
 | 
				
			||||||
        expensive_operation_db_main_count
 | 
					        expensive_operation_db_#{database_name}_count
 | 
				
			||||||
        expensive_operation_db_main_duration_s
 | 
					        expensive_operation_db_#{database_name}_duration_s
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      data = keys.each.with_object({}) do |key, accumulator|
 | 
					      data = keys.each.with_object({}) do |key, accumulator|
 | 
				
			||||||
| 
						 | 
					@ -75,7 +77,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'with a single query' do
 | 
					    context 'with a single query' do
 | 
				
			||||||
      let(:operation) { -> { Project.count } }
 | 
					      let(:operation) { -> { Ci::Pipeline.count } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it { is_expected.to eq(operation.call) }
 | 
					      it { is_expected.to eq(operation.call) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -439,6 +439,12 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  context 'when the migration is running against the ci database', if: Gitlab::Database.has_config?(:ci) do
 | 
					  context 'when the migration is running against the ci database', if: Gitlab::Database.has_config?(:ci) do
 | 
				
			||||||
 | 
					    around do |example|
 | 
				
			||||||
 | 
					      Gitlab::Database::SharedModel.using_connection(::Ci::ApplicationRecord.connection) do
 | 
				
			||||||
 | 
					        example.run
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it_behaves_like 'helpers that enqueue background migrations', BackgroundMigration::CiDatabaseWorker, 'ci'
 | 
					    it_behaves_like 'helpers that enqueue background migrations', BackgroundMigration::CiDatabaseWorker, 'ci'
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,12 +34,28 @@ RSpec.describe Gitlab::ProjectAuthorizations do
 | 
				
			||||||
        .to include(owned_project.id, other_project.id, group_project.id)
 | 
					        .to include(owned_project.id, other_project.id, group_project.id)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'includes the correct access levels' do
 | 
					    context 'when personal_project_owner_with_owner_access feature flag is enabled' do
 | 
				
			||||||
      mapping = map_access_levels(authorizations)
 | 
					      it 'includes the correct access levels' do
 | 
				
			||||||
 | 
					        mapping = map_access_levels(authorizations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(mapping[owned_project.id]).to eq(Gitlab::Access::MAINTAINER)
 | 
					        expect(mapping[owned_project.id]).to eq(Gitlab::Access::OWNER)
 | 
				
			||||||
      expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER)
 | 
					        expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER)
 | 
				
			||||||
      expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
 | 
					        expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when personal_project_owner_with_owner_access feature flag is disabled' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        stub_feature_flags(personal_project_owner_with_owner_access: false)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'includes the correct access levels' do
 | 
				
			||||||
 | 
					        mapping = map_access_levels(authorizations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(mapping[owned_project.id]).to eq(Gitlab::Access::MAINTAINER)
 | 
				
			||||||
 | 
					        expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER)
 | 
				
			||||||
 | 
					        expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,13 +36,13 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  %w(puma sidekiq).each do |target|
 | 
					  %w(puma sidekiq).each do |target|
 | 
				
			||||||
    context "when targeting #{target}" do
 | 
					    context "when targeting #{target}" do
 | 
				
			||||||
      describe '.spawn' do
 | 
					      describe '.fork' do
 | 
				
			||||||
        context 'when in parent process' do
 | 
					        context 'when in parent process' do
 | 
				
			||||||
          it 'forks into a new process and detaches it' do
 | 
					          it 'forks into a new process and detaches it' do
 | 
				
			||||||
            expect(Process).to receive(:fork).and_return(99)
 | 
					            expect(Process).to receive(:fork).and_return(99)
 | 
				
			||||||
            expect(Process).to receive(:detach).with(99)
 | 
					            expect(Process).to receive(:detach).with(99)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            described_class.spawn(target, metrics_dir: metrics_dir)
 | 
					            described_class.fork(target, metrics_dir: metrics_dir)
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,13 +58,47 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
 | 
				
			||||||
              expect(server).to receive(:start)
 | 
					              expect(server).to receive(:start)
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            described_class.spawn(target, metrics_dir: metrics_dir)
 | 
					            described_class.fork(target, metrics_dir: metrics_dir)
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          it 'resets signal handlers from parent process' do
 | 
					          it 'resets signal handlers from parent process' do
 | 
				
			||||||
            expect(Gitlab::ProcessManagement).to receive(:modify_signals).with(%i[A B], 'DEFAULT')
 | 
					            expect(Gitlab::ProcessManagement).to receive(:modify_signals).with(%i[A B], 'DEFAULT')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            described_class.spawn(target, metrics_dir: metrics_dir, trapped_signals: %i[A B])
 | 
					            described_class.fork(target, metrics_dir: metrics_dir, reset_signals: %i[A B])
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      describe '.spawn' do
 | 
				
			||||||
 | 
					        let(:expected_env) do
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            'METRICS_SERVER_TARGET' => target,
 | 
				
			||||||
 | 
					            'WIPE_METRICS_DIR' => '0'
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it 'spawns a new server process and returns its PID' do
 | 
				
			||||||
 | 
					          expect(Process).to receive(:spawn).with(
 | 
				
			||||||
 | 
					            expected_env,
 | 
				
			||||||
 | 
					            end_with('bin/metrics-server'),
 | 
				
			||||||
 | 
					            hash_including(pgroup: true)
 | 
				
			||||||
 | 
					          ).and_return(99)
 | 
				
			||||||
 | 
					          expect(Process).to receive(:detach).with(99)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          pid = described_class.spawn(target, metrics_dir: metrics_dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect(pid).to eq(99)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context 'when path to gitlab.yml is passed' do
 | 
				
			||||||
 | 
					          it 'sets the GITLAB_CONFIG environment variable' do
 | 
				
			||||||
 | 
					            expect(Process).to receive(:spawn).with(
 | 
				
			||||||
 | 
					              expected_env.merge('GITLAB_CONFIG' => 'path/to/config/gitlab.yml'),
 | 
				
			||||||
 | 
					              end_with('bin/metrics-server'),
 | 
				
			||||||
 | 
					              hash_including(pgroup: true)
 | 
				
			||||||
 | 
					            ).and_return(99)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            described_class.spawn(target, metrics_dir: metrics_dir, gitlab_config: 'path/to/config/gitlab.yml')
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					@ -72,6 +106,14 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  context 'when targeting invalid target' do
 | 
					  context 'when targeting invalid target' do
 | 
				
			||||||
 | 
					    describe '.fork' do
 | 
				
			||||||
 | 
					      it 'raises an error' do
 | 
				
			||||||
 | 
					        expect { described_class.fork('unsupported', metrics_dir: metrics_dir) }.to(
 | 
				
			||||||
 | 
					          raise_error('Target must be one of [puma,sidekiq]')
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    describe '.spawn' do
 | 
					    describe '.spawn' do
 | 
				
			||||||
      it 'raises an error' do
 | 
					      it 'raises an error' do
 | 
				
			||||||
        expect { described_class.spawn('unsupported', metrics_dir: metrics_dir) }.to(
 | 
					        expect { described_class.spawn('unsupported', metrics_dir: metrics_dir) }.to(
 | 
				
			||||||
| 
						 | 
					@ -81,64 +123,86 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe '#start' do
 | 
					  shared_examples 'a metrics exporter' do |target, expected_name|
 | 
				
			||||||
    let(:exporter_class) { Class.new(Gitlab::Metrics::Exporter::BaseExporter) }
 | 
					    describe '#start' do
 | 
				
			||||||
    let(:exporter_double) { double('fake_exporter', start: true) }
 | 
					      let(:exporter_double) { double('exporter', start: true) }
 | 
				
			||||||
    let(:settings) { { "fake_exporter" => { "enabled" => true } } }
 | 
					      let(:wipe_metrics_dir) { true }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subject(:metrics_server) { described_class.new('fake', metrics_dir, true)}
 | 
					      subject(:metrics_server) { described_class.new(target, metrics_dir, wipe_metrics_dir) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    before do
 | 
					      it 'configures ::Prometheus::Client' do
 | 
				
			||||||
      stub_const('Gitlab::Metrics::Exporter::FakeExporter', exporter_class)
 | 
					        metrics_server.start
 | 
				
			||||||
      expect(exporter_class).to receive(:instance).with(
 | 
					 | 
				
			||||||
        settings['fake_exporter'], gc_requests: true, synchronous: true
 | 
					 | 
				
			||||||
      ).and_return(exporter_double)
 | 
					 | 
				
			||||||
      expect(Settings).to receive(:monitoring).and_return(settings)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'configures ::Prometheus::Client' do
 | 
					        expect(prometheus_config.multiprocess_files_dir).to eq metrics_dir
 | 
				
			||||||
      metrics_server.start
 | 
					        expect(::Prometheus::Client.configuration.pid_provider.call).to eq expected_name
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(prometheus_config.multiprocess_files_dir).to eq metrics_dir
 | 
					      it 'ensures that metrics directory exists in correct mode (0700)' do
 | 
				
			||||||
      expect(::Prometheus::Client.configuration.pid_provider.call).to eq 'fake_exporter'
 | 
					        expect(FileUtils).to receive(:mkdir_p).with(metrics_dir, mode: 0700)
 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'ensures that metrics directory exists in correct mode (0700)' do
 | 
					        metrics_server.start
 | 
				
			||||||
      expect(FileUtils).to receive(:mkdir_p).with(metrics_dir, mode: 0700)
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      metrics_server.start
 | 
					      context 'when wipe_metrics_dir is true' do
 | 
				
			||||||
    end
 | 
					        it 'removes any old metrics files' do
 | 
				
			||||||
 | 
					          FileUtils.touch("#{metrics_dir}/remove_this.db")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'when wipe_metrics_dir is true' do
 | 
					          expect { metrics_server.start }.to change { Dir.empty?(metrics_dir) }.from(false).to(true)
 | 
				
			||||||
      subject(:metrics_server) { described_class.new('fake', metrics_dir, true)}
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'removes any old metrics files' do
 | 
					      context 'when wipe_metrics_dir is false' do
 | 
				
			||||||
        FileUtils.touch("#{metrics_dir}/remove_this.db")
 | 
					        let(:wipe_metrics_dir) { false }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect { metrics_server.start }.to change { Dir.empty?(metrics_dir) }.from(false).to(true)
 | 
					        it 'does not remove any old metrics files' do
 | 
				
			||||||
 | 
					          FileUtils.touch("#{metrics_dir}/remove_this.db")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect { metrics_server.start }.not_to change { Dir.empty?(metrics_dir) }.from(false)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'starts a metrics server' do
 | 
				
			||||||
 | 
					        expect(exporter_double).to receive(:start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        metrics_server.start
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'starts a RubySampler instance' do
 | 
				
			||||||
 | 
					        expect(ruby_sampler_double).to receive(:start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        subject.start
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'when wipe_metrics_dir is false' do
 | 
					    describe '#name' do
 | 
				
			||||||
      subject(:metrics_server) { described_class.new('fake', metrics_dir, false)}
 | 
					      let(:exporter_double) { double('exporter', start: true) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'does not remove any old metrics files' do
 | 
					      subject(:name) { described_class.new(target, metrics_dir, true).name }
 | 
				
			||||||
        FileUtils.touch("#{metrics_dir}/remove_this.db")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect { metrics_server.start }.not_to change { Dir.empty?(metrics_dir) }.from(false)
 | 
					      it { is_expected.to eq(expected_name) }
 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'starts a metrics server' do
 | 
					 | 
				
			||||||
      expect(exporter_double).to receive(:start)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      metrics_server.start
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'starts a RubySampler instance' do
 | 
					 | 
				
			||||||
      expect(ruby_sampler_double).to receive(:start)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      subject.start
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  context 'for puma' do
 | 
				
			||||||
 | 
					    before do
 | 
				
			||||||
 | 
					      allow(Gitlab::Metrics::Exporter::WebExporter).to receive(:instance).with(
 | 
				
			||||||
 | 
					        gc_requests: true, synchronous: true
 | 
				
			||||||
 | 
					      ).and_return(exporter_double)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it_behaves_like 'a metrics exporter', 'puma', 'web_exporter'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  context 'for sidekiq' do
 | 
				
			||||||
 | 
					    let(:settings) { { "sidekiq_exporter" => { "enabled" => true } } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    before do
 | 
				
			||||||
 | 
					      allow(::Settings).to receive(:monitoring).and_return(settings)
 | 
				
			||||||
 | 
					      allow(Gitlab::Metrics::Exporter::SidekiqExporter).to receive(:instance).with(
 | 
				
			||||||
 | 
					        settings['sidekiq_exporter'], gc_requests: true, synchronous: true
 | 
				
			||||||
 | 
					      ).and_return(exporter_double)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it_behaves_like 'a metrics exporter', 'sidekiq', 'sidekiq_exporter'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -225,7 +225,7 @@ RSpec.describe ProjectTeam do
 | 
				
			||||||
    let_it_be(:maintainer) { create(:user) }
 | 
					    let_it_be(:maintainer) { create(:user) }
 | 
				
			||||||
    let_it_be(:developer) { create(:user) }
 | 
					    let_it_be(:developer) { create(:user) }
 | 
				
			||||||
    let_it_be(:guest) { create(:user) }
 | 
					    let_it_be(:guest) { create(:user) }
 | 
				
			||||||
    let_it_be(:project) { create(:project, namespace: maintainer.namespace) }
 | 
					    let_it_be(:project) { create(:project, group: create(:group)) }
 | 
				
			||||||
    let_it_be(:access_levels) { [Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER] }
 | 
					    let_it_be(:access_levels) { [Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subject(:members_with_access_levels) { project.team.members_with_access_levels(access_levels) }
 | 
					    subject(:members_with_access_levels) { project.team.members_with_access_levels(access_levels) }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3717,7 +3717,7 @@ RSpec.describe User do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'with min_access_level' do
 | 
					    context 'with min_access_level' do
 | 
				
			||||||
      let!(:user) { create(:user) }
 | 
					      let!(:user) { create(:user) }
 | 
				
			||||||
      let!(:project) { create(:project, :private, namespace: user.namespace) }
 | 
					      let!(:project) { create(:project, :private, group: create(:group)) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      before do
 | 
					      before do
 | 
				
			||||||
        project.add_developer(user)
 | 
					        project.add_developer(user)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -675,13 +675,30 @@ RSpec.describe API::Members do
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'adding owner to project' do
 | 
					    context 'adding owner to project' do
 | 
				
			||||||
      it 'returns 403' do
 | 
					      context 'when personal_project_owner_with_owner_access feature flag is enabled' do
 | 
				
			||||||
        expect do
 | 
					        it 'returns created status' do
 | 
				
			||||||
          post api("/projects/#{project.id}/members", maintainer),
 | 
					          expect do
 | 
				
			||||||
               params: { user_id: stranger.id, access_level: Member::OWNER }
 | 
					            post api("/projects/#{project.id}/members", maintainer),
 | 
				
			||||||
 | 
					                 params: { user_id: stranger.id, access_level: Member::OWNER }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expect(response).to have_gitlab_http_status(:bad_request)
 | 
					            expect(response).to have_gitlab_http_status(:created)
 | 
				
			||||||
        end.not_to change { project.members.count }
 | 
					          end.to change { project.members.count }.by(1)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context 'when personal_project_owner_with_owner_access feature flag is disabled' do
 | 
				
			||||||
 | 
					        before do
 | 
				
			||||||
 | 
					          stub_feature_flags(personal_project_owner_with_owner_access: false)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it 'returns created status' do
 | 
				
			||||||
 | 
					          expect do
 | 
				
			||||||
 | 
					            post api("/projects/#{project.id}/members", maintainer),
 | 
				
			||||||
 | 
					                 params: { user_id: stranger.id, access_level: Member::OWNER }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            expect(response).to have_gitlab_http_status(:bad_request)
 | 
				
			||||||
 | 
					          end.not_to change { project.members.count }
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
 | 
				
			||||||
        it 'is called' do
 | 
					        it 'is called' do
 | 
				
			||||||
          ProjectAuthorization.delete_all
 | 
					          ProjectAuthorization.delete_all
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expect(callback).to receive(:call).with(project.id, Gitlab::Access::MAINTAINER).once
 | 
					          expect(callback).to receive(:call).with(project.id, Gitlab::Access::OWNER).once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          service.execute
 | 
					          service.execute
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
| 
						 | 
					@ -60,20 +60,20 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          to_be_removed = [project2.id]
 | 
					          to_be_removed = [project2.id]
 | 
				
			||||||
          to_be_added = [
 | 
					          to_be_added = [
 | 
				
			||||||
            { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
 | 
					            { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expect(service.execute).to eq([to_be_removed, to_be_added])
 | 
					          expect(service.execute).to eq([to_be_removed, to_be_added])
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'finds duplicate entries that has to be removed' do
 | 
					        it 'finds duplicate entries that has to be removed' do
 | 
				
			||||||
          [Gitlab::Access::MAINTAINER, Gitlab::Access::REPORTER].each do |access_level|
 | 
					          [Gitlab::Access::OWNER, Gitlab::Access::REPORTER].each do |access_level|
 | 
				
			||||||
            user.project_authorizations.create!(project: project, access_level: access_level)
 | 
					            user.project_authorizations.create!(project: project, access_level: access_level)
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          to_be_removed = [project.id]
 | 
					          to_be_removed = [project.id]
 | 
				
			||||||
          to_be_added = [
 | 
					          to_be_added = [
 | 
				
			||||||
            { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
 | 
					            { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expect(service.execute).to eq([to_be_removed, to_be_added])
 | 
					          expect(service.execute).to eq([to_be_removed, to_be_added])
 | 
				
			||||||
| 
						 | 
					@ -85,7 +85,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          to_be_removed = [project.id]
 | 
					          to_be_removed = [project.id]
 | 
				
			||||||
          to_be_added = [
 | 
					          to_be_added = [
 | 
				
			||||||
            { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
 | 
					            { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expect(service.execute).to eq([to_be_removed, to_be_added])
 | 
					          expect(service.execute).to eq([to_be_removed, to_be_added])
 | 
				
			||||||
| 
						 | 
					@ -143,16 +143,16 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'sets the keys to the project IDs' do
 | 
					    it 'sets the keys to the project IDs' do
 | 
				
			||||||
      expect(hash.keys).to eq([project.id])
 | 
					      expect(hash.keys).to match_array([project.id])
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'sets the values to the access levels' do
 | 
					    it 'sets the values to the access levels' do
 | 
				
			||||||
      expect(hash.values).to eq([Gitlab::Access::MAINTAINER])
 | 
					      expect(hash.values).to match_array([Gitlab::Access::OWNER])
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'personal projects' do
 | 
					    context 'personal projects' do
 | 
				
			||||||
      it 'includes the project with the right access level' do
 | 
					      it 'includes the project with the right access level' do
 | 
				
			||||||
        expect(hash[project.id]).to eq(Gitlab::Access::MAINTAINER)
 | 
					        expect(hash[project.id]).to eq(Gitlab::Access::OWNER)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -242,7 +242,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
 | 
				
			||||||
      value = hash.values[0]
 | 
					      value = hash.values[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(value.project_id).to eq(project.id)
 | 
					      expect(value.project_id).to eq(project.id)
 | 
				
			||||||
      expect(value.access_level).to eq(Gitlab::Access::MAINTAINER)
 | 
					      expect(value.access_level).to eq(Gitlab::Access::OWNER)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -267,7 +267,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'includes the access level for every row' do
 | 
					      it 'includes the access level for every row' do
 | 
				
			||||||
        expect(row.access_level).to eq(Gitlab::Access::MAINTAINER)
 | 
					        expect(row.access_level).to eq(Gitlab::Access::OWNER)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					@ -283,7 +283,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
 | 
				
			||||||
      rows = service.fresh_authorizations.to_a
 | 
					      rows = service.fresh_authorizations.to_a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(rows.length).to eq(1)
 | 
					      expect(rows.length).to eq(1)
 | 
				
			||||||
      expect(rows.first.access_level).to eq(Gitlab::Access::MAINTAINER)
 | 
					      expect(rows.first.access_level).to eq(Gitlab::Access::OWNER)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'every returned row' do
 | 
					    context 'every returned row' do
 | 
				
			||||||
| 
						 | 
					@ -294,7 +294,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'includes the access level' do
 | 
					      it 'includes the access level' do
 | 
				
			||||||
        expect(row.access_level).to eq(Gitlab::Access::MAINTAINER)
 | 
					        expect(row.access_level).to eq(Gitlab::Access::OWNER)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,8 @@ RSpec.describe Members::Projects::CreatorService do
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe '.access_levels' do
 | 
					  describe '.access_levels' do
 | 
				
			||||||
    it 'returns Gitlab::Access.sym_options' do
 | 
					    it 'returns Gitlab::Access.sym_options_with_owner' do
 | 
				
			||||||
      expect(described_class.access_levels).to eq(Gitlab::Access.sym_options)
 | 
					      expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3312,7 +3312,7 @@ RSpec.describe NotificationService, :mailer do
 | 
				
			||||||
      describe "##{sym}" do
 | 
					      describe "##{sym}" do
 | 
				
			||||||
        subject(:notify!) { notification.send(sym, domain) }
 | 
					        subject(:notify!) { notification.send(sym, domain) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'emails current watching maintainers' do
 | 
					        it 'emails current watching maintainers and owners' do
 | 
				
			||||||
          expect(Notify).to receive(:"#{sym}_email").at_least(:once).and_call_original
 | 
					          expect(Notify).to receive(:"#{sym}_email").at_least(:once).and_call_original
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          notify!
 | 
					          notify!
 | 
				
			||||||
| 
						 | 
					@ -3410,7 +3410,7 @@ RSpec.describe NotificationService, :mailer do
 | 
				
			||||||
        reset_delivered_emails!
 | 
					        reset_delivered_emails!
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'emails current watching maintainers' do
 | 
					      it 'emails current watching maintainers and owners' do
 | 
				
			||||||
        notification.remote_mirror_update_failed(remote_mirror)
 | 
					        notification.remote_mirror_update_failed(remote_mirror)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        should_only_email(u_maintainer1, u_maintainer2, u_owner)
 | 
					        should_only_email(u_maintainer1, u_maintainer2, u_owner)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,14 +116,34 @@ RSpec.describe Projects::CreateService, '#execute' do
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  context 'user namespace' do
 | 
					  context 'user namespace' do
 | 
				
			||||||
    it 'creates a project in user namespace' do
 | 
					    context 'when personal_project_owner_with_owner_access feature flag is enabled' do
 | 
				
			||||||
      project = create_project(user, opts)
 | 
					      it 'creates a project in user namespace' do
 | 
				
			||||||
 | 
					        project = create_project(user, opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(project).to be_valid
 | 
					        expect(project).to be_valid
 | 
				
			||||||
      expect(project.first_owner).to eq(user)
 | 
					        expect(project.first_owner).to eq(user)
 | 
				
			||||||
      expect(project.team.maintainers).to include(user)
 | 
					        expect(project.team.maintainers).not_to include(user)
 | 
				
			||||||
      expect(project.namespace).to eq(user.namespace)
 | 
					        expect(project.team.owners).to contain_exactly(user)
 | 
				
			||||||
      expect(project.project_namespace).to be_in_sync_with_project(project)
 | 
					        expect(project.namespace).to eq(user.namespace)
 | 
				
			||||||
 | 
					        expect(project.project_namespace).to be_in_sync_with_project(project)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when personal_project_owner_with_owner_access feature flag is disabled' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        stub_feature_flags(personal_project_owner_with_owner_access: false)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'creates a project in user namespace' do
 | 
				
			||||||
 | 
					        project = create_project(user, opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(project).to be_valid
 | 
				
			||||||
 | 
					        expect(project.first_owner).to eq(user)
 | 
				
			||||||
 | 
					        expect(project.team.maintainers).to contain_exactly(user)
 | 
				
			||||||
 | 
					        expect(project.team.owners).to contain_exactly(user)
 | 
				
			||||||
 | 
					        expect(project.namespace).to eq(user.namespace)
 | 
				
			||||||
 | 
					        expect(project.project_namespace).to be_in_sync_with_project(project)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -162,7 +182,7 @@ RSpec.describe Projects::CreateService, '#execute' do
 | 
				
			||||||
        expect(project).to be_persisted
 | 
					        expect(project).to be_persisted
 | 
				
			||||||
        expect(project.owner).to eq(user)
 | 
					        expect(project.owner).to eq(user)
 | 
				
			||||||
        expect(project.first_owner).to eq(user)
 | 
					        expect(project.first_owner).to eq(user)
 | 
				
			||||||
        expect(project.team.maintainers).to contain_exactly(user)
 | 
					        expect(project.team.owners).to contain_exactly(user)
 | 
				
			||||||
        expect(project.namespace).to eq(user.namespace)
 | 
					        expect(project.namespace).to eq(user.namespace)
 | 
				
			||||||
        expect(project.project_namespace).to be_in_sync_with_project(project)
 | 
					        expect(project.project_namespace).to be_in_sync_with_project(project)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,7 +52,7 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
 | 
				
			||||||
        it 'is called' do
 | 
					        it 'is called' do
 | 
				
			||||||
          ProjectAuthorization.delete_all
 | 
					          ProjectAuthorization.delete_all
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expect(callback).to receive(:call).with(project.id, Gitlab::Access::MAINTAINER).once
 | 
					          expect(callback).to receive(:call).with(project.id, Gitlab::Access::OWNER).once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          service.execute
 | 
					          service.execute
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
| 
						 | 
					@ -73,7 +73,7 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
 | 
				
			||||||
      to_be_removed = [project_authorization.project_id]
 | 
					      to_be_removed = [project_authorization.project_id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      to_be_added = [
 | 
					      to_be_added = [
 | 
				
			||||||
        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
 | 
					        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(service).to receive(:update_authorizations)
 | 
					      expect(service).to receive(:update_authorizations)
 | 
				
			||||||
| 
						 | 
					@ -83,14 +83,14 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'removes duplicate entries' do
 | 
					    it 'removes duplicate entries' do
 | 
				
			||||||
      [Gitlab::Access::MAINTAINER, Gitlab::Access::REPORTER].each do |access_level|
 | 
					      [Gitlab::Access::OWNER, Gitlab::Access::REPORTER].each do |access_level|
 | 
				
			||||||
        user.project_authorizations.create!(project: project, access_level: access_level)
 | 
					        user.project_authorizations.create!(project: project, access_level: access_level)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      to_be_removed = [project.id]
 | 
					      to_be_removed = [project.id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      to_be_added = [
 | 
					      to_be_added = [
 | 
				
			||||||
        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
 | 
					        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
      expect(service).to(
 | 
					      expect(service).to(
 | 
				
			||||||
        receive(:update_authorizations)
 | 
					        receive(:update_authorizations)
 | 
				
			||||||
| 
						 | 
					@ -103,7 +103,7 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
 | 
				
			||||||
      project_authorization = ProjectAuthorization.where(
 | 
					      project_authorization = ProjectAuthorization.where(
 | 
				
			||||||
        project_id: project.id,
 | 
					        project_id: project.id,
 | 
				
			||||||
        user_id: user.id,
 | 
					        user_id: user.id,
 | 
				
			||||||
        access_level: Gitlab::Access::MAINTAINER)
 | 
					        access_level: Gitlab::Access::OWNER)
 | 
				
			||||||
      expect(project_authorization).to exist
 | 
					      expect(project_authorization).to exist
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,7 +116,7 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
 | 
				
			||||||
      to_be_removed = [project_authorization.project_id]
 | 
					      to_be_removed = [project_authorization.project_id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      to_be_added = [
 | 
					      to_be_added = [
 | 
				
			||||||
        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
 | 
					        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(service).to receive(:update_authorizations)
 | 
					      expect(service).to receive(:update_authorizations)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										16
									
								
								yarn.lock
								
								
								
								
							
							
						
						
									
										16
									
								
								yarn.lock
								
								
								
								
							| 
						 | 
					@ -1436,15 +1436,15 @@
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
 | 
					  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
 | 
				
			||||||
  integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==
 | 
					  integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@rails/actioncable@6.1.4-1":
 | 
					"@rails/actioncable@6.1.4-6":
 | 
				
			||||||
  version "6.1.4-1"
 | 
					  version "6.1.4-6"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.4-1.tgz#69982e7f352d732f71fda0cc01b7ba8269c9945b"
 | 
					  resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.4-6.tgz#22dd0f60e634f237f2a19d031f4e7afa26a924b4"
 | 
				
			||||||
  integrity sha512-b6sLoMop3gX22Wm2P5LPpKcZGwsf1ZoAGS+g1HrTrdlsZ/ENOKIBiSNnHOJajHwcYlF0TefBs7e7jIYZHVYihQ==
 | 
					  integrity sha512-cGwo5AWlEg6Q5JeUl2r8cmgaSlJtgR9BOOGF7Yb1PyKOinuWod6PW6UeQLgXf+n2MNiWz+yqldb1m3+Aun/2lg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@rails/ujs@6.1.4-1":
 | 
					"@rails/ujs@6.1.4-6":
 | 
				
			||||||
  version "6.1.4-1"
 | 
					  version "6.1.4-6"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.4-1.tgz#37507fe288a1c7c3a593602aa4dea42e5cb5797f"
 | 
					  resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.4-6.tgz#244520a1580b5791cebc81471978bc6b3c8966c0"
 | 
				
			||||||
  integrity sha512-Fewm2wHk1n6Kf4E86dzzHDJOFg4EWcSHH3FsMEGs59bTdmf7099mjkOssOQtBqju4R39iaAOQNui7r8P+Q5Dgg==
 | 
					  integrity sha512-j2ejw0ShVHiDWtq1Yv1PGX/GFCDiXX+5YiUY2Z17eeMJhQkxXeg3maQZkkT5OT/YBOI+jiWqtp03GM1Hdp/arA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@sentry/browser@5.30.0":
 | 
					"@sentry/browser@5.30.0":
 | 
				
			||||||
  version "5.30.0"
 | 
					  version "5.30.0"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue