diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-03 21:08:19 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-03 21:08:19 +0300 |
commit | d83bbccfcd07ddab93be73959e3b149b75831e28 (patch) | |
tree | d8156497696dd1951f6556307cb3689952933eed | |
parent | a99d0fa6922be88307b1c80330a257c55c5d8270 (diff) |
Add latest changes from gitlab-org/gitlab@master
61 files changed, 956 insertions, 438 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index e50ed641116..1c20838c039 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -4c15523cf680c107c5aa2b8268674cd0345a6b78 +7397b0e1c1a4a8fc0290347da1ddf7ac11547a18 diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index edf83a33812..5665231e613 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -158,7 +158,7 @@ const createAlert = function createAlert({ onDismiss(); } this.$destroy(); - this.$el.parentNode.removeChild(this.$el); + this.$el.parentNode?.removeChild(this.$el); }, }, render(h) { diff --git a/app/assets/javascripts/issuable/auto_width_dropdown_select.js b/app/assets/javascripts/issuable/auto_width_dropdown_select.js deleted file mode 100644 index 243d82f55aa..00000000000 --- a/app/assets/javascripts/issuable/auto_width_dropdown_select.js +++ /dev/null @@ -1,56 +0,0 @@ -import $ from 'jquery'; -import { loadCSSFile } from '../lib/utils/css_utils'; - -let instanceCount = 0; - -class AutoWidthDropdownSelect { - constructor(selectElement) { - this.$selectElement = $(selectElement); - this.dropdownClass = `js-auto-width-select-dropdown-${instanceCount}`; - instanceCount += 1; - } - - init() { - const { dropdownClass } = this; - import(/* webpackChunkName: 'select2' */ 'select2/select2') - .then(() => { - // eslint-disable-next-line promise/no-nesting - loadCSSFile(gon.select2_css_path) - .then(() => { - this.$selectElement.select2({ - dropdownCssClass: dropdownClass, - ...AutoWidthDropdownSelect.selectOptions(this.dropdownClass), - }); - }) - .catch(() => {}); - }) - .catch(() => {}); - - return this; - } - - static selectOptions(dropdownClass) { - return { - dropdownCss() { - let resultantWidth = 'auto'; - const $dropdown = $(`.${dropdownClass}`); - - // We have to look at the parent because - // `offsetParent` on a `display: none;` is `null` - const offsetParentWidth = $(this).parent().offsetParent().width(); - // Reset any width to let it naturally flow - $dropdown.css('width', 'auto'); - if ($dropdown.outerWidth(false) > offsetParentWidth) { - resultantWidth = offsetParentWidth; - } - - return { - width: resultantWidth, - maxWidth: offsetParentWidth, - }; - }, - }; - } -} - -export default AutoWidthDropdownSelect; diff --git a/app/assets/javascripts/issuable/issuable_form.js b/app/assets/javascripts/issuable/issuable_form.js index 81bf7ca6ccc..e8ba99e0e9e 100644 --- a/app/assets/javascripts/issuable/issuable_form.js +++ b/app/assets/javascripts/issuable/issuable_form.js @@ -2,10 +2,7 @@ import $ from 'jquery'; import Pikaday from 'pikaday'; import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete'; import Autosave from '~/autosave'; -import AutoWidthDropdownSelect from '~/issuable/auto_width_dropdown_select'; -import { loadCSSFile } from '~/lib/utils/css_utils'; import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility'; -import { select2AxiosTransport } from '~/lib/utils/select2_utils'; import { queryToObject, objectToQuery } from '~/lib/utils/url_utility'; import UsersSelect from '~/users_select'; import ZenMode from '~/zen_mode'; @@ -118,12 +115,6 @@ export default class IssuableForm { }); calendar.setDate(parsePikadayDate($issuableDueDate.val())); } - - this.$targetBranchSelect = $('.js-target-branch-select', this.form); - - if (this.$targetBranchSelect.length) { - this.initTargetBranchDropdown(); - } } initAutosave() { @@ -214,47 +205,4 @@ export default class IssuableForm { addWip() { this.titleField.val(`Draft: ${this.titleField.val()}`); } - - initTargetBranchDropdown() { - import(/* webpackChunkName: 'select2' */ 'select2/select2') - .then(() => { - // eslint-disable-next-line promise/no-nesting - loadCSSFile(gon.select2_css_path) - .then(() => { - this.$targetBranchSelect.select2({ - ...AutoWidthDropdownSelect.selectOptions('js-target-branch-select'), - ajax: { - url: this.$targetBranchSelect.data('endpoint'), - dataType: 'JSON', - quietMillis: 250, - data(search) { - return { - search, - }; - }, - results({ results }) { - return { - // `data` keys are translated so we can't just access them with a string based key - results: results[Object.keys(results)[0]].map((name) => ({ - id: name, - text: name, - })), - }; - }, - transport: select2AxiosTransport, - }, - initSelection(el, callback) { - const val = el.val(); - - callback({ - id: val, - text: val, - }); - }, - }); - }) - .catch(() => {}); - }) - .catch(() => {}); - } } diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index 9550b5440d6..7bef2934371 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -834,6 +834,10 @@ $tabs-holder-z-index: 250; @include gl-ml-auto; @include gl-rounded-pill; @include gl-w-9; + + &.is-checked:hover { + background-color: $blue-500; + } } } diff --git a/app/controllers/concerns/registrations_tracking.rb b/app/controllers/concerns/registrations_tracking.rb new file mode 100644 index 00000000000..14743349c1a --- /dev/null +++ b/app/controllers/concerns/registrations_tracking.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module RegistrationsTracking + extend ActiveSupport::Concern + + included do + helper_method :glm_tracking_params + end + + private + + def glm_tracking_params + params.permit(:glm_source, :glm_content) + end +end diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb index 652f12e34ba..0ca0fa729c0 100644 --- a/app/controllers/groups/runners_controller.rb +++ b/app/controllers/groups/runners_controller.rb @@ -2,7 +2,7 @@ class Groups::RunnersController < Groups::ApplicationController before_action :authorize_read_group_runners!, only: [:index, :show] - before_action :authorize_admin_group_runners!, only: [:edit, :update, :destroy, :pause, :resume] + before_action :authorize_update_runner!, only: [:edit, :update, :destroy, :pause, :resume] before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show] before_action only: [:show] do @@ -37,7 +37,10 @@ class Groups::RunnersController < Groups::ApplicationController private def runner - @runner ||= Ci::RunnersFinder.new(current_user: current_user, params: { group: @group }).execute + group_params = { group: @group } + group_params[:membership] = :all_available if Feature.enabled?(:runners_finder_all_available, @group) + + @runner ||= Ci::RunnersFinder.new(current_user: current_user, params: group_params).execute .except(:limit, :offset) .find(params[:id]) end @@ -45,6 +48,12 @@ class Groups::RunnersController < Groups::ApplicationController def runner_params params.require(:runner).permit(Ci::Runner::FORM_EDITABLE) end + + def authorize_update_runner! + return if can?(current_user, :admin_group_runners, group) && can?(current_user, :update_runner, runner) + + render_404 + end end Groups::RunnersController.prepend_mod diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5a212e9a152..8abffa4f2bd 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -451,15 +451,16 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo return :failed end + squashing = params.fetch(:squash, false) merge_service = ::MergeRequests::MergeService.new(project: @project, current_user: current_user, params: merge_params) - unless merge_service.hooks_validation_pass?(@merge_request) + unless merge_service.hooks_validation_pass?(@merge_request, validate_squash_message: squashing) return :hook_validation_error end return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha - @merge_request.update(merge_error: nil, squash: params.fetch(:squash, false)) + @merge_request.update(merge_error: nil, squash: squashing) if auto_merge_requested? if merge_request.auto_merge_enabled? diff --git a/app/controllers/registrations/welcome_controller.rb b/app/controllers/registrations/welcome_controller.rb index 829905db955..a49b82319da 100644 --- a/app/controllers/registrations/welcome_controller.rb +++ b/app/controllers/registrations/welcome_controller.rb @@ -4,6 +4,7 @@ module Registrations class WelcomeController < ApplicationController include OneTrustCSP include GoogleAnalyticsCSP + include RegistrationsTracking layout 'minimal' skip_before_action :authenticate_user!, :required_signup_info, :check_two_factor_requirement, only: [:show, :update] diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index f931a4b3018..31fe30f3f06 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -8,6 +8,7 @@ class RegistrationsController < Devise::RegistrationsController include OneTrustCSP include BizibleCSP include GoogleAnalyticsCSP + include RegistrationsTracking layout 'devise' @@ -114,13 +115,18 @@ class RegistrationsController < Devise::RegistrationsController def after_sign_up_path_for(user) Gitlab::AppLogger.info(user_created_message(confirmed: user.confirmed?)) - users_sign_up_welcome_path + users_sign_up_welcome_path(glm_tracking_params) end def after_inactive_sign_up_path_for(resource) Gitlab::AppLogger.info(user_created_message) return new_user_session_path(anchor: 'login-pane') if resource.blocked_pending_approval? return dashboard_projects_path if Feature.enabled?(:soft_email_confirmation) + + # when email confirmation is enabled, path to redirect is saved + # after user confirms and comes back, he will be redirected + store_location_for(:redirect, users_sign_up_welcome_path(glm_tracking_params)) + return identity_verification_redirect_path if custom_confirmation_enabled?(resource) users_almost_there_path(email: resource.email) diff --git a/app/graphql/types/projects/branch_rule_type.rb b/app/graphql/types/projects/branch_rule_type.rb index 866cff0f439..1b0a080bd5d 100644 --- a/app/graphql/types/projects/branch_rule_type.rb +++ b/app/graphql/types/projects/branch_rule_type.rb @@ -13,6 +13,13 @@ module Types null: false, description: 'Branch name, with wildcards, for the branch rules.' + field :is_default, + type: GraphQL::Types::Boolean, + null: false, + method: :default_branch?, + calls_gitaly: true, + description: "Check if this branch rule protects the project's default branch." + field :branch_protection, type: Types::BranchRules::BranchProtectionType, null: false, diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 98d829e9253..6d412d49083 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -6,12 +6,14 @@ module Ci class BuildMetadata < Ci::ApplicationRecord BuildTimeout = Struct.new(:value, :source) + include Ci::Partitionable include Presentable include ChronicDurationAttribute include Gitlab::Utils::StrongMemoize self.table_name = 'ci_builds_metadata' self.primary_key = 'id' + partitionable scope: :build belongs_to :build, class_name: 'CommitStatus' belongs_to :project diff --git a/app/models/concerns/ci/partitionable.rb b/app/models/concerns/ci/partitionable.rb index 710ee1ba64f..df803180e77 100644 --- a/app/models/concerns/ci/partitionable.rb +++ b/app/models/concerns/ci/partitionable.rb @@ -19,7 +19,32 @@ module Ci extend ActiveSupport::Concern include ::Gitlab::Utils::StrongMemoize + module Testing + InclusionError = Class.new(StandardError) + + PARTITIONABLE_MODELS = %w[ + CommitStatus + Ci::BuildMetadata + Ci::Stage + Ci::JobArtifact + Ci::PipelineVariable + Ci::Pipeline + ].freeze + + def self.check_inclusion(klass) + return if PARTITIONABLE_MODELS.include?(klass.name) + + raise Partitionable::Testing::InclusionError, + "#{klass} must be included in PARTITIONABLE_MODELS" + + rescue InclusionError => e + Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e) + end + end + included do + Partitionable::Testing.check_inclusion(self) + before_validation :set_partition_id, on: :create validates :partition_id, presence: true @@ -37,6 +62,8 @@ module Ci def partitionable(scope:) define_method(:partition_scope_value) do strong_memoize(:partition_scope_value) do + next Ci::Pipeline.current_partition_value if respond_to?(:importing?) && importing? + record = scope.to_proc.call(self) record.respond_to?(:partition_id) ? record.partition_id : record end diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index b3a918d8952..dfd5c315f6e 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -95,6 +95,10 @@ class ProtectedBranch < ApplicationRecord def self.downcase_humanized_name name.underscore.humanize.downcase end + + def default_branch? + name == project.default_branch + end end ProtectedBranch.prepend_mod_with('ProtectedBranch') diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb index 3e630d40b3d..2a3c1e8bc26 100644 --- a/app/services/merge_requests/merge_base_service.rb +++ b/app/services/merge_requests/merge_base_service.rb @@ -9,12 +9,12 @@ module MergeRequests attr_reader :merge_request # Overridden in EE. - def hooks_validation_pass?(_merge_request) + def hooks_validation_pass?(merge_request, validate_squash_message: false) true end # Overridden in EE. - def hooks_validation_error(_merge_request) + def hooks_validation_error(merge_request, validate_squash_message: false) # No-op end diff --git a/app/services/packages/rpm/repository_metadata/base_builder.rb b/app/services/packages/rpm/repository_metadata/base_builder.rb index 9d76336d764..00d2f4fb0dc 100644 --- a/app/services/packages/rpm/repository_metadata/base_builder.rb +++ b/app/services/packages/rpm/repository_metadata/base_builder.rb @@ -11,7 +11,7 @@ module Packages def build_empty_structure Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| - xml.public_send(self.class::ROOT_TAG, self.class::ROOT_ATTRIBUTES) # rubocop:disable GitlabSecurity/PublicSend + xml.method_missing(self.class::ROOT_TAG, self.class::ROOT_ATTRIBUTES) end.to_xml end end diff --git a/app/services/packages/rpm/repository_metadata/build_repomd_xml.rb b/app/services/packages/rpm/repository_metadata/build_repomd_xml.rb index c6cfd77815d..84614196254 100644 --- a/app/services/packages/rpm/repository_metadata/build_repomd_xml.rb +++ b/app/services/packages/rpm/repository_metadata/build_repomd_xml.rb @@ -9,6 +9,7 @@ module Packages xmlns: 'http://linux.duke.edu/metadata/repo', 'xmlns:rpm': 'http://linux.duke.edu/metadata/rpm' }.freeze + ALLOWED_DATA_VALUE_KEYS = %i[checksum open-checksum location timestamp size open-size].freeze # Expected `data` structure # @@ -48,9 +49,9 @@ module Packages end def build_file_info(info, xml) - info.each do |key, attributes| + info.slice(*ALLOWED_DATA_VALUE_KEYS).each do |key, attributes| value = attributes.delete(:value) - xml.public_send(key, value, attributes) # rubocop:disable GitlabSecurity/PublicSend + xml.method_missing(key, value, attributes) end end end diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index b6719834358..8a960602536 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -9,7 +9,7 @@ .signup-page = render 'devise/shared/signup_box', - url: registration_path(resource_name), + url: registration_path(resource_name, glm_tracking_params.to_hash), button_text: _('Register'), borderless: Feature.enabled?(:restyle_login_page, @project), show_omniauth_providers: omniauth_enabled? && button_based_providers_enabled? diff --git a/app/views/registrations/welcome/show.html.haml b/app/views/registrations/welcome/show.html.haml index 911ba5e8042..5cff0f562e5 100644 --- a/app/views/registrations/welcome/show.html.haml +++ b/app/views/registrations/welcome/show.html.haml @@ -17,7 +17,9 @@ %p.gl-text-center= html_escape(_('%{gitlab_experience_text}. We won\'t share this information with anyone.')) % { gitlab_experience_text: gitlab_experience_text } - else %p.gl-text-center= html_escape(_('%{gitlab_experience_text}. Don\'t worry, this information isn\'t shared outside of your self-managed GitLab instance.')) % { gitlab_experience_text: gitlab_experience_text } - = gitlab_ui_form_for(current_user, url: users_sign_up_welcome_path, html: { class: 'card gl-w-full! gl-p-5 js-users-signup-welcome', 'aria-live' => 'assertive' }) do |f| + = gitlab_ui_form_for(current_user, + url: users_sign_up_welcome_path(glm_tracking_params), + html: { class: 'card gl-w-full! gl-p-5 js-users-signup-welcome', 'aria-live' => 'assertive' }) do |f| .devise-errors = render 'devise/shared/error_messages', resource: current_user .row diff --git a/db/post_migrate/20220928225711_schedule_update_ci_pipeline_artifacts_locked_status.rb b/db/post_migrate/20220928225711_schedule_update_ci_pipeline_artifacts_locked_status.rb new file mode 100644 index 00000000000..0d7a5dc4bec --- /dev/null +++ b/db/post_migrate/20220928225711_schedule_update_ci_pipeline_artifacts_locked_status.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class ScheduleUpdateCiPipelineArtifactsLockedStatus < Gitlab::Database::Migration[2.0] + disable_ddl_transaction! + + MIGRATION = 'UpdateCiPipelineArtifactsUnknownLockedStatus' + DELAY_INTERVAL = 2.minutes + BATCH_SIZE = 1_000 + SUB_BATCH_SIZE = 500 + + restrict_gitlab_migration gitlab_schema: :gitlab_ci + + def up + queue_batched_background_migration( + MIGRATION, + :ci_pipeline_artifacts, + :id, + job_interval: DELAY_INTERVAL, + batch_size: BATCH_SIZE, + sub_batch_size: SUB_BATCH_SIZE + ) + end + + def down + delete_batched_background_migration(MIGRATION, :ci_pipeline_artifacts, :id, []) + end +end diff --git a/db/schema_migrations/20220928225711 b/db/schema_migrations/20220928225711 new file mode 100644 index 00000000000..fd7a8b303d1 --- /dev/null +++ b/db/schema_migrations/20220928225711 @@ -0,0 +1 @@ +5ec9b3f36a986cbb86c8005a4425307f0f4399a4a4030460e715370630cb9490
\ No newline at end of file diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md index 5773b9be5f8..ad345461776 100644 --- a/doc/administration/compliance.md +++ b/doc/administration/compliance.md @@ -48,9 +48,9 @@ settings and automation to ensure that whatever a compliance team has configured stays configured and working correctly. These features can help you automate compliance: -- [**Compliance frameworks**](../user/project/settings/index.md#compliance-frameworks) (for groups): Create a custom +- [**Compliance frameworks**](../user/group/manage.md#compliance-frameworks) (for groups): Create a custom compliance framework at the group level to describe the type of compliance requirements any child project needs to follow. -- [**Compliance pipelines**](../user/project/settings/index.md#compliance-pipeline-configuration) (for groups): Define a +- [**Compliance pipelines**](../user/group/manage.md#configure-a-compliance-pipeline) (for groups): Define a pipeline configuration to run for any projects with a given compliance framework. ## Audit management diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e8318aed0d5..a1de8e1caba 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -10258,6 +10258,7 @@ List of branch rules for a project, grouped by branch name. | ---- | ---- | ----------- | | <a id="branchrulebranchprotection"></a>`branchProtection` | [`BranchProtection!`](#branchprotection) | Branch protections configured for this branch rule. | | <a id="branchrulecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the branch rule was created. | +| <a id="branchruleisdefault"></a>`isDefault` | [`Boolean!`](#boolean) | Check if this branch rule protects the project's default branch. | | <a id="branchrulename"></a>`name` | [`String!`](#string) | Branch name, with wildcards, for the branch rules. | | <a id="branchruleupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the branch rule was last updated. | diff --git a/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md b/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md index e95e05ec5ea..f7f11d27155 100644 --- a/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md +++ b/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md @@ -359,14 +359,25 @@ We first create a unique index including the `(id, partition_id)`. Then, we drop the primary key constraint and use the new index created to set the new primary key constraint. -We must set the primary key explicitly as `ActiveRecord` does not support composite primary keys. +`ActiveRecord` [does not support](https://github.com/rails/rails/blob/6-1-stable/activerecord/lib/active_record/attribute_methods/primary_key.rb#L126) +composite primary keys, so we must force it to treat the `id` column as a primary key: ```ruby -class Model +class Model < ApplicationRecord self.primary_key = 'id' end ``` +The application layer is now ignorant of the database structure and all of the +existing queries from `ActiveRecord` continue to use the `id` column to access +the data. There is some risk to this approach because it is possible to +construct application code that results in duplicate models with the same `id` +value, but on a different `partition_id`. To mitigate this risk we must ensure +that all inserts use the database sequence to populate the `id` since they are +[guaranteed](https://www.postgresql.org/docs/12/sql-createsequence.html#id-1.9.3.81.7) +to allocate distinct values and rewrite the access patterns to include the +`partition_id` value. Manually assigning the ids during inserts must be avoided. + ### Foreign keys Foreign keys must reference columns that either are a primary key or form a diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 6075a646532..054a100574b 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -379,7 +379,7 @@ start. Jobs in the current stage are not stopped and continue to run. - If a job does not specify a [`stage`](#stage), the job is assigned the `test` stage. - If a stage is defined but no jobs use it, the stage is not visible in the pipeline, - which can help [compliance pipeline configurations](../../user/project/settings/index.md#compliance-pipeline-configuration): + which can help [compliance pipeline configurations](../../user/group/manage.md#configure-a-compliance-pipeline): - Stages can be defined in the compliance configuration but remain hidden if not used. - The defined stages become visible when developers use them in job definitions. diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md index 2afd9f66804..4e552411d54 100644 --- a/doc/gitlab-basics/start-using-git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -147,10 +147,14 @@ between your computer and GitLab. git clone https://gitlab.com/gitlab-tests/sample-project.git ``` -1. GitLab requests your username and password: - - If you have 2FA enabled for your account, you must [clone using a token](#clone-using-a-token) - with `read_repository` or `write_repository` permissions instead of your account's password. - - If you don't have 2FA enabled, use your account's password. +1. GitLab requests your username and password. + + If you have enabled two-factor authentication (2FA) on your account, you cannot use your account password. Instead, you can do one of the following: + + - [Clone using a token](#clone-using-a-token) with `read_repository` or `write_repository` permissions. + - Install [Git Credential Manager](../user/profile/account/two_factor_authentication.md#git-credential-manager). + + If you have not enabled 2FA, use your account password. 1. To view the files, go to the new directory: diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index 403a2564da3..95e40d4acb6 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -244,7 +244,7 @@ To enable or disable the banner: > [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/352316) from GitLab Premium to GitLab Ultimate in 15.0. NOTE: -An alternative [compliance solution](../../project/settings/index.md#compliance-pipeline-configuration) +An alternative [compliance solution](../../group/manage.md#configure-a-compliance-pipeline) is available. We recommend this alternative solution because it provides greater flexibility, allowing required pipelines to be assigned to specific compliance framework labels. diff --git a/doc/user/application_security/configuration/index.md b/doc/user/application_security/configuration/index.md index 26805859ae8..7e39f1086b0 100644 --- a/doc/user/application_security/configuration/index.md +++ b/doc/user/application_security/configuration/index.md @@ -19,10 +19,17 @@ The Security Configuration page lists the following for the security testing and - Whether or not it is available. - A configuration button or a link to its configuration guide. -The status of each security control is determined by the project's latest default branch -[CI pipeline](../../../ci/pipelines/index.md). -If a job with the expected security report artifact exists in the pipeline, the feature's status is -_enabled_. +The status of each security control is determined by the following process: + +1. Check for a [CI pipeline](../../../ci/pipelines/index.md) in the most recent commit on the default branch. +1. If no CI pipelines exist, then consider all security scanners disabled. Show the **Not enabled** status. +1. If a pipeline is found, then inspect the CI YAML for each job in the CI/CD pipeline. If a + job in the pipeline defines an [`artifacts:reports` keyword](../../../ci/yaml/artifacts_reports.md) + for a security scanner, then consider the security scanner enabled. Show the **Enabled** status. + +Failed pipelines and jobs are included in this process. If a scanner is configured but the job fails, +that scanner is still considered enabled. This process also determines the scanners and statuses +returned through [our API](../../../api/graphql/reference/index.md#securityscanners). If the latest pipeline used [Auto DevOps](../../../topics/autodevops/index.md), all security features are configured by default. diff --git a/doc/user/application_security/get-started-security.md b/doc/user/application_security/get-started-security.md index d8abc290b72..41dc35dd8ce 100644 --- a/doc/user/application_security/get-started-security.md +++ b/doc/user/application_security/get-started-security.md @@ -36,7 +36,7 @@ The following steps will help you get the most from GitLab application security remediating existing vulnerabilities and preventing the introduction of new ones. 1. Enable other scan types such as [SAST](sast/index.md), [DAST](dast/index.md), [Fuzz testing](coverage_fuzzing/index.md), or [Container Scanning](container_scanning/index.md). -1. Use [Compliance Pipelines](../../user/project/settings/index.md#compliance-pipeline-configuration) +1. Use [Compliance Pipelines](../group/manage.md#configure-a-compliance-pipeline) or [Scan Execution Policies](policies/scan-execution-policies.md) to enforce required scan types and ensure separation of duties between security and engineering. 1. Consider enabling [Review Apps](../../development/testing_guide/review_apps.md) to allow for DAST diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md index 84540c570e5..ffd9bb1a783 100644 --- a/doc/user/application_security/index.md +++ b/doc/user/application_security/index.md @@ -446,7 +446,7 @@ Security and compliance teams must ensure that security scans: GitLab provides two methods of accomplishing this, each with advantages and disadvantages. -- [Compliance framework pipelines](../project/settings/index.md#compliance-pipeline-configuration) +- [Compliance framework pipelines](../group/manage.md#configure-a-compliance-pipeline) are recommended when: - Scan execution enforcement is required for any scanner that uses a GitLab template, such as SAST IaC, DAST, Dependency Scanning, diff --git a/doc/user/application_security/policies/scan-execution-policies.md b/doc/user/application_security/policies/scan-execution-policies.md index 7b95848f978..ce0900aaf4e 100644 --- a/doc/user/application_security/policies/scan-execution-policies.md +++ b/doc/user/application_security/policies/scan-execution-policies.md @@ -15,7 +15,7 @@ with a long, random job name. In the unlikely event of a job name collision, the any pre-existing job in the pipeline. If a policy is created at the group-level, it will apply to every child project or sub-group. A group-level policy cannot be edited from a child project or sub-group. -This feature has some overlap with [compliance framework pipelines](../../project/settings/index.md#compliance-pipeline-configuration), +This feature has some overlap with [compliance framework pipelines](../../group/manage.md#configure-a-compliance-pipeline), as we have not [unified the user experience for these two features](https://gitlab.com/groups/gitlab-org/-/epics/7312). For details on the similarities and differences between these features, see [Enforce scan execution](../index.md#enforce-scan-execution). diff --git a/doc/user/compliance/compliance_report/index.md b/doc/user/compliance/compliance_report/index.md index 621fe8a4bc5..ac4b20b5166 100644 --- a/doc/user/compliance/compliance_report/index.md +++ b/doc/user/compliance/compliance_report/index.md @@ -72,7 +72,7 @@ The following is a list of violations that are either: When you select a row, a drawer is shown that provides further details about the merge request: -- Project name and [compliance framework label](../../project/settings/index.md#compliance-frameworks), +- Project name and [compliance framework label](../../project/settings/index.md#add-a-compliance-framework-to-a-project), if the project has one assigned. - Link to the merge request. - The merge request's branch path in the format `[source] into [target]`. diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md index 27c1d3e459d..8a6cb42f247 100644 --- a/doc/user/group/manage.md +++ b/doc/user/group/manage.md @@ -376,6 +376,186 @@ To enable delayed deletion of projects in a group: NOTE: In GitLab 13.11 and above the group setting for delayed project deletion is inherited by subgroups. As discussed in [Cascading settings](../../development/cascading_settings.md) inheritance can be overridden, unless enforced by an ancestor. +## Compliance frameworks **(PREMIUM)** + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276221) in GitLab 13.9. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/287779) in GitLab 13.12. + +You can create a compliance framework that is a label to identify that your project has certain compliance +requirements or needs additional oversight. The label can optionally enforce +[compliance pipeline configuration](#configure-a-compliance-pipeline) to the projects on which it is +[applied](../project/settings/index.md#add-a-compliance-framework-to-a-project). + +Group owners can create, edit, and delete compliance frameworks: + +1. On the top bar, select **Main menu > Groups > View all groups** and find your group. +1. On the left sidebar, select **Settings** > **General**. +1. Expand the **Compliance frameworks** section. +1. Create, edit, or delete compliance frameworks. + +### Configure a compliance pipeline **(ULTIMATE)** + +> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3156) in GitLab 13.9, disabled behind `ff_evaluate_group_level_compliance_pipeline` [feature flag](../../administration/feature_flags.md). +> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/300324) in GitLab 13.11. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/331231) in GitLab 14.2. + +Group owners can configure a compliance pipeline in a project separate to other projects. By default, the compliance +pipeline configuration (`.gitlab-ci.yml` file) is run instead of the pipeline configuration of labeled projects. + +However, the compliance pipeline configuration can reference the `.gitlab-ci.yml` file of the labeled projects so that: + +- The compliance pipeline can also run jobs of labeled project pipelines. This allows for centralized control of + pipeline configuration. +- Jobs and variables defined in the compliance pipeline can't be changed by variables in the labeled project's + `.gitlab-ci.yml` file. + +See [example configuration](#example-configuration) for help configuring a compliance pipeline that runs jobs from +labeled project pipeline configuration. + +To configure a compliance pipeline: + +1. On the top bar, select **Main menu > Groups > View all groups** and find your group. +1. On the left sidebar, select **Settings** > **General**. +1. Expand the **Compliance frameworks** section. +1. In **Compliance pipeline configuration (optional)**, add the path to the compliance framework configuration. Use the + `path/file.y[a]ml@group-name/project-name` format. For example: + + - `.compliance-ci.yml@gitlab-org/gitlab`. + - `.compliance-ci.yaml@gitlab-org/gitlab`. + +This configuration is inherited by projects where the compliance framework label is +[applied](../project/settings/index.md#add-a-compliance-framework-to-a-project). In projects with the applied compliance +framework label, the compliance pipeline configuration is run instead of the labeled project's own pipeline configuration. + +The user running the pipeline in the labeled project must at least have the Reporter role on the compliance project. + +When used to enforce scan execution, this feature has some overlap with +[scan execution policies](../application_security/policies/scan-execution-policies.md). We have not +[unified the user experience for these two features](https://gitlab.com/groups/gitlab-org/-/epics/7312). For details on +the similarities and differences between these features, see [Enforce scan execution](../application_security/index.md#enforce-scan-execution). + +#### Example configuration + +The following example `.compliance-gitlab-ci.yml` includes the `include` keyword to ensure labeled project pipeline +configuration is also executed. + +```yaml +# Allows compliance team to control the ordering and interweaving of stages/jobs. +# Stages without jobs defined will remain hidden. +stages: + - pre-compliance + - build + - test + - pre-deploy-compliance + - deploy + - post-compliance + +variables: # Can be overridden by setting a job-specific variable in project's local .gitlab-ci.yml + FOO: sast + +sast: # None of these attributes can be overridden by a project's local .gitlab-ci.yml + variables: + FOO: sast + image: ruby:2.6 + stage: pre-compliance + rules: + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" + when: never + - when: always # or when: on_success + allow_failure: false + before_script: + - "# No before scripts." + script: + - echo "running $FOO" + after_script: + - "# No after scripts." + +sanity check: + image: ruby:2.6 + stage: pre-deploy-compliance + rules: + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" + when: never + - when: always # or when: on_success + allow_failure: false + before_script: + - "# No before scripts." + script: + - echo "running $FOO" + after_script: + - "# No after scripts." + +audit trail: + image: ruby:2.7 + stage: post-compliance + rules: + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" + when: never + - when: always # or when: on_success + allow_failure: false + before_script: + - "# No before scripts." + script: + - echo "running $FOO" + after_script: + - "# No after scripts." + +include: # Execute individual project's configuration (if project contains .gitlab-ci.yml) + project: '$CI_PROJECT_PATH' + file: '$CI_CONFIG_PATH' + ref: '$CI_COMMIT_REF_NAME' # Must be defined or MR pipelines always use the use default branch +``` + +### Ensure compliance jobs are always run + +Compliance pipelines [use GitLab CI/CD](../../ci/index.md) to give you an incredible amount of flexibility +for defining any sort of compliance jobs you like. Depending on your goals, these jobs +can be configured to be: + +- Modified by users. +- Non-modifiable. + +Generally, if a value in a compliance job: + +- Is set, it cannot be changed or overridden by project-level configurations. +- Is not set, a project-level configuration may set. + +Either might be wanted or not depending on your use case. + +There are a few best practices for ensuring that these jobs are always run exactly +as you define them and that downstream, project-level pipeline configurations +cannot change them: + +- Add [a `rules:when:always` block](../../ci/yaml/index.md#when) to each of your compliance jobs. This ensures they are + non-modifiable and are always run. +- Explicitly set any [variables](../../ci/yaml/index.md#variables) the job references. This: + - Ensures that project-level pipeline configurations do not set them and alter their + behavior. + - Includes any jobs that drive the logic of your job. +- Explicitly set the [container image](../../ci/yaml/index.md#image) to run the job in. This ensures that your script + steps execute in the correct environment. +- Explicitly set any relevant GitLab pre-defined [job keywords](../../ci/yaml/index.md#job-keywords). + This ensures that your job uses the settings you intend and that they are not overridden by + project-level pipelines. + +### Avoid parent and child pipelines in GitLab 14.7 and earlier + +NOTE: +This advice does not apply to GitLab 14.8 and later because [a fix](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78878) added +compatibility for combining compliance pipelines, and parent and child pipelines. + +Compliance pipelines start on the run of _every_ pipeline in a labeled project. This means that if a pipeline in the labeled project +triggers a child pipeline, the compliance pipeline runs first. This can trigger the parent pipeline, instead of the child pipeline. + +Therefore, in projects with compliance frameworks, we recommend replacing +[parent-child pipelines](../../ci/pipelines/downstream_pipelines.md#parent-child-pipelines) with the following: + +- Direct [`include`](../../ci/yaml/index.md#include) statements that provide the parent pipeline with child pipeline configuration. +- Child pipelines placed in another project that are run using the [trigger API](../../ci/triggers/index.md) rather than the parent-child + pipeline feature. + +This alternative ensures the compliance pipeline does not re-start the parent pipeline. + ## Disable email notifications > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23585) in GitLab 12.2. @@ -560,9 +740,7 @@ Changes to [group wikis](../project/wiki/group.md) do not appear in group activi You can view the most recent actions taken in a group, either in your browser or in an RSS feed: -1. On the top bar, select **Main menu > Groups > View all groups**. -1. Select **Your Groups**. -1. Find the group and select it. +1. On the top bar, select **Main menu > Groups > View all groups** and find your group. 1. On the left sidebar, select **Group information > Activity**. To view the activity feed in Atom format, select the diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 9d02d9a7672..102abf2b427 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -166,7 +166,7 @@ The following table lists project permissions available for each role: | [Projects](project/index.md):<br>Rename project | | | | ✓ | ✓ | | [Projects](project/index.md):<br>Share (invite) projects with groups | | | | ✓ (*7*) | ✓ (*7*) | | [Projects](project/index.md):<br>View 2FA status of members | | | | ✓ | ✓ | -| [Projects](project/index.md):<br>Assign project to a [compliance framework](project/settings/index.md#compliance-frameworks) | | | | | ✓ | +| [Projects](project/index.md):<br>Assign project to a [compliance framework](project/settings/index.md#add-a-compliance-framework-to-a-project) | | | | | ✓ | | [Projects](project/index.md):<br>Archive project | | | | | ✓ | | [Projects](project/index.md):<br>Change project visibility level | | | | | ✓ | | [Projects](project/index.md):<br>Delete project | | | | | ✓ | diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index 7442268fc2c..3dc768f6606 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -452,11 +452,10 @@ This error occurs in the following scenarios: [enforce 2FA for all users](../../../security/two_factor_authentication.md#enforce-2fa-for-all-users) setting. - You do not have 2FA enabled, but an administrator has disabled the [password authentication enabled for Git over HTTP(S)](../../admin_area/settings/sign_in_restrictions.md#password-authentication-enabled) - setting. If LDAP is: - - Configured, an [LDAP password](../../../administration/auth/ldap/index.md) - or a [personal access token](../personal_access_tokens.md) - must be used to authenticate Git requests over HTTP(S). - - Not configured, you must use a [personal access token](../personal_access_tokens.md). + setting. You can authenticate Git requests: + - Over HTTP(S) using a [personal access token](../personal_access_tokens.md). + - In your browser using [Git Credential Manager](#git-credential-manager). + - If you have configured LDAP, over HTTP(S) using an [LDAP password](../../../administration/auth/ldap/index.md). ### Error: "invalid pin code" diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index d7f6ef8539f..1ee71fe1a7a 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -43,187 +43,21 @@ To assign topics to a project: If you're an instance administrator, you can administer all project topics from the [Admin Area's Topics page](../../admin_area/index.md#administering-topics). -## Compliance frameworks **(PREMIUM)** +## Add a compliance framework to a project **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276221) in GitLab 13.9. -> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/287779) in GitLab 13.12. +[Compliance frameworks](../../group/manage.md#compliance-frameworks) can be assigned to projects within group that has a +compliance framework using either: -You can create a compliance framework label to identify that your project has certain compliance -requirements or needs additional oversight. The label can optionally apply -[compliance pipeline configuration](#compliance-pipeline-configuration). - -Group owners can create, edit, and delete compliance frameworks: - -1. On the top bar, select **Main menu > Groups** and find your group. -1. On the left sidebar, select **Settings** > **General**. -1. Expand the **Compliance frameworks** section. - -Compliance frameworks created can then be assigned to projects within the group using: - -- The GitLab UI, using the project settings page. +- The GitLab UI: + 1. On the top bar, select **Main menu > Projects > View all projects** and find your project. + 1. On the left sidebar, select **Settings** > **General**. + 1. Expand the **Compliance frameworks** section. + 1. Select a compliance framework. + 1. Select **Save changes**. - In [GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/issues/333249) and later, using the - [GraphQL API](../../../api/graphql/reference/index.md#mutationprojectsetcomplianceframework). - -NOTE: -Creating compliance frameworks on subgroups with GraphQL causes the framework to be -created on the root ancestor if the user has the correct permissions. The GitLab UI presents a -read-only view to discourage this behavior. - -### Compliance pipeline configuration **(ULTIMATE)** - -> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3156) in GitLab 13.9, disabled behind `ff_evaluate_group_level_compliance_pipeline` [feature flag](../../../administration/feature_flags.md). -> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/300324) in GitLab 13.11. -> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/331231) in GitLab 14.2. - -Compliance framework pipelines allow group owners to define -a compliance pipeline in a separate repository that gets -executed in place of the local project's `.gitlab-ci.yml` file. As part of this pipeline, an -`include` statement can reference the local project's `.gitlab-ci.yml` file. This way, the compliance -pipeline jobs can run alongside the project-specific jobs any time the pipeline runs. -Jobs and variables defined in the compliance -pipeline can't be changed by variables in the local project's `.gitlab-ci.yml` file. - -When you set up the compliance framework, use the **Compliance pipeline configuration** box to link -the compliance framework to specific CI/CD configuration. Use the -`path/file.y[a]ml@group-name/project-name` format. For example: - -- `.compliance-ci.yml@gitlab-org/gitlab`. -- `.compliance-ci.yaml@gitlab-org/gitlab`. - -This configuration is inherited by projects where the compliance framework label is applied. The -result forces projects with the label to run the compliance CI/CD configuration in addition to -the project's own CI/CD configuration. When a project with a compliance framework label executes a -pipeline, it evaluates configuration in the following order: - -1. Compliance pipeline configuration. -1. Project-specific pipeline configuration. - -The user running the pipeline in the project must at least have the Reporter role on the compliance -project. - -Example `.compliance-gitlab-ci.yml`: - -```yaml -# Allows compliance team to control the ordering and interweaving of stages/jobs. -# Stages without jobs defined will remain hidden. -stages: - - pre-compliance - - build - - test - - pre-deploy-compliance - - deploy - - post-compliance - -variables: # Can be overridden by setting a job-specific variable in project's local .gitlab-ci.yml - FOO: sast - -sast: # None of these attributes can be overridden by a project's local .gitlab-ci.yml - variables: - FOO: sast - image: ruby:2.6 - stage: pre-compliance - rules: - - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" - when: never - - when: always # or when: on_success - allow_failure: false - before_script: - - "# No before scripts." - script: - - echo "running $FOO" - after_script: - - "# No after scripts." - -sanity check: - image: ruby:2.6 - stage: pre-deploy-compliance - rules: - - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" - when: never - - when: always # or when: on_success - allow_failure: false - before_script: - - "# No before scripts." - script: - - echo "running $FOO" - after_script: - - "# No after scripts." - -audit trail: - image: ruby:2.6 - stage: post-compliance - rules: - - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" - when: never - - when: always # or when: on_success - allow_failure: false - before_script: - - "# No before scripts." - script: - - echo "running $FOO" - after_script: - - "# No after scripts." - -include: # Execute individual project's configuration (if project contains .gitlab-ci.yml) - project: '$CI_PROJECT_PATH' - file: '$CI_CONFIG_PATH' - ref: '$CI_COMMIT_REF_NAME' # Must be defined or MR pipelines always use the use default branch -``` - -When used to enforce scan execution, this feature has some overlap with [scan execution policies](../../application_security/policies/scan-execution-policies.md), -as we have not [unified the user experience for these two features](https://gitlab.com/groups/gitlab-org/-/epics/7312). -For details on the similarities and differences between these features, see -[Enforce scan execution](../../application_security/index.md#enforce-scan-execution). - -### Ensure compliance jobs are always run - -Compliance pipelines use GitLab CI/CD to give you an incredible amount of flexibility -for defining any sort of compliance jobs you like. Depending on your goals, these jobs -can be configured to be: - -- Modified by users. -- Non-modifiable. - -At a high-level, if a value in a compliance job: - -- Is set, it cannot be changed or overridden by project-level configurations. -- Is not set, a project-level configuration may set. - -Either might be wanted or not depending on your use case. - -There are a few best practices for ensuring that these jobs are always run exactly -as you define them and that downstream, project-level pipeline configurations -cannot change them: - -- Add a `rules:when:always` block to each of your compliance jobs. This ensures they are - non-modifiable and are always run. -- Explicitly set any variables the job references. This: - - Ensures that project-level pipeline configurations do not set them and alter their - behavior. - - Includes any jobs that drive the logic of your job. -- Explicitly set the container image file to run the job in. This ensures that your script - steps execute in the correct environment. -- Explicitly set any relevant GitLab pre-defined [job keywords](../../../ci/yaml/index.md#job-keywords). - This ensures that your job uses the settings you intend and that they are not overridden by - project-level pipelines. - -### Avoid parent and child pipelines in GitLab 14.7 and earlier - -NOTE: -This advice does not apply to GitLab 14.8 and later because [a fix](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78878) added -compatibility for combining compliance pipelines, and parent and child pipelines. - -Compliance pipelines start on the run of _every_ pipeline in a relevant project. This means that if a pipeline in the relevant project -triggers a child pipeline, the compliance pipeline runs first. This can trigger the parent pipeline, instead of the child pipeline. - -Therefore, in projects with compliance frameworks, we recommend replacing -[parent-child pipelines](../../../ci/pipelines/downstream_pipelines.md#parent-child-pipelines) with the following: - -- Direct [`include`](../../../ci/yaml/index.md#include) statements that provide the parent pipeline with child pipeline configuration. -- Child pipelines placed in another project that are run using the [trigger API](../../../ci/triggers/index.md) rather than the parent-child - pipeline feature. - -This alternative ensures the compliance pipeline does not re-start the parent pipeline. + [GraphQL API](../../../api/graphql/reference/index.md#mutationprojectsetcomplianceframework). If you create + compliance frameworks on subgroups with GraphQL, the framework is created on the root ancestor if the user has the + correct permissions. The GitLab UI presents a read-only view to discourage this behavior. ## Configure project visibility, features, and permissions diff --git a/doc/user/usage_quotas.md b/doc/user/usage_quotas.md index 7f4bb84f0aa..2060a029833 100644 --- a/doc/user/usage_quotas.md +++ b/doc/user/usage_quotas.md @@ -26,30 +26,32 @@ Storage types that add to the total namespace storage are: - Wiki - Snippets -If your total namespace storage exceeds the available namespace storage quota, all projects under the namespace are locked. A locked project will not be able to push to the repository, run pipelines and jobs, or build and push packages. +If your total namespace storage exceeds the available namespace storage quota, all projects under the namespace are locked. +A locked project cannot push to the repository, run pipelines and jobs, or build and push packages. To prevent exceeding the namespace storage quota, you can: -1. Reduce storage consumption by following the suggestions in the [Manage Your Storage Usage](#manage-your-storage-usage) section of this page. -1. Apply for [GitLab for Education](https://about.gitlab.com/solutions/education/join/), [GitLab for Open Source](https://about.gitlab.com/solutions/open-source/join/), or [GitLab for Startups](https://about.gitlab.com/solutions/startups/) if you meet the eligibility requirements. -1. Consider using a [self-managed instance](../subscriptions/self_managed/index.md) of GitLab which does not have these limits on the free tier. -1. [Purchase additional storage](../subscriptions/gitlab_com/index.md#purchase-more-storage-and-transfer) units at $60/year for 10GB of storage. -1. [Start a trial](https://about.gitlab.com/free-trial/) or [upgrade to GitLab Premium or Ultimate](https://about.gitlab.com/pricing) which include higher limits and features that enable growing teams to ship faster without sacrificing on quality. -1. [Talk to an expert](https://page.gitlab.com/usage_limits_help.html) to learn more about your options and ask questions. +- Reduce storage consumption by following the suggestions in the [Manage Your Storage Usage](#manage-your-storage-usage) section of this page. +- Apply for [GitLab for Education](https://about.gitlab.com/solutions/education/join/), [GitLab for Open Source](https://about.gitlab.com/solutions/open-source/join/), or [GitLab for Startups](https://about.gitlab.com/solutions/startups/) if you meet the eligibility requirements. +- Consider using a [self-managed instance](../subscriptions/self_managed/index.md) of GitLab which does not have these limits on the free tier. +- [Purchase additional storage](../subscriptions/gitlab_com/index.md#purchase-more-storage-and-transfer) units at $60/year for 10GB of storage. +- [Start a trial](https://about.gitlab.com/free-trial/) or [upgrade to GitLab Premium or Ultimate](https://about.gitlab.com/pricing) which include higher limits and features that enable growing teams to ship faster without sacrificing on quality. +- [Talk to an expert](https://page.gitlab.com/usage_limits_help.html) to learn more about your options and ask questions. ### Namespace storage limit enforcement schedule Storage limits for GitLab SaaS Free tier namespaces will not be enforced prior to 2022-10-19. Storage limits for GitLab SaaS Paid tier namespaces will not be enforced for prior to 2023-02-15. Enforcement will not occur until all storage types are accurately measured, including deduplication of forks for [Git](https://gitlab.com/gitlab-org/gitlab/-/issues/371671) and [LFS](https://gitlab.com/gitlab-org/gitlab/-/issues/370242). -Impacted users are notified via email and in-app notifications at least 60 days prior to enforcement. +Impacted users are notified by email and through in-app notifications at least 60 days prior to enforcement. ### Project storage limit Projects on GitLab SaaS have a 10GB storage limit on their Git repository and LFS storage. -Once namespace-level storage limits are enforced, the project limit will be removed. A namespace has either a namespace-level storage limit or a project-level storage limit, but not both. +After namespace-level storage limits are enforced, the project limit is removed. A namespace has either a namespace-level storage limit or a project-level storage limit, but not both. -When a project's repository and LFS reaches the quota, the project is locked. You cannot push changes to a locked project. To monitor the size of each -repository in a namespace, including a breakdown for each project, you can +When a project's repository and LFS reaches the quota, the project is locked. +You cannot push changes to a locked project. To monitor the size of each +repository in a namespace, including a breakdown for each project, [view storage usage](#view-storage-usage). To allow a project's repository and LFS to exceed the free quota you must purchase additional storage. For more details, see [Excess storage usage](#excess-storage-usage). @@ -66,7 +68,7 @@ Prerequisites: 1. From the left sidebar, select **Settings > Usage Quotas**. 1. Select the **Storage** tab. -The statistics are displayed. Select any title to view details. The information on this page +Select any title to view details. The information on this page is updated every 90 minutes. If your namespace shows `'Not applicable.'`, push a commit to any project in the diff --git a/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status.rb b/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status.rb new file mode 100644 index 00000000000..84183753158 --- /dev/null +++ b/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # The `ci_pipeline_artifacts.locked` column was added in + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97194 to + # speed up the finding of expired, pipeline artifacts. By default, + # the value is "unknown" (2), but the correct value should be the + # value of the associated `ci_pipelines.locked` value. This class + # does an UPDATE join to make the values match. + class UpdateCiPipelineArtifactsUnknownLockedStatus < BatchedMigrationJob + def perform + connection.exec_query(<<~SQL) + UPDATE ci_pipeline_artifacts + SET locked = ci_pipelines.locked + FROM ci_pipelines + WHERE ci_pipeline_artifacts.id BETWEEN #{start_id} AND #{end_id} + AND ci_pipeline_artifacts.locked = 2 + AND ci_pipelines.id = ci_pipeline_artifacts.pipeline_id; + SQL + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fb3abbd6557..46084e22952 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9847,7 +9847,7 @@ msgstr "" msgid "ComplianceFrameworks|No compliance frameworks are set up yet" msgstr "" -msgid "ComplianceFrameworks|Required format: %{codeStart}path/file.y[a]ml@group-name/project-name%{codeEnd}. %{linkStart}Learn more.%{linkEnd}" +msgid "ComplianceFrameworks|Required format: %{codeStart}path/file.y[a]ml@group-name/project-name%{codeEnd}. %{linkStart}What is a compliance pipeline configuration?%{linkEnd}" msgstr "" msgid "ComplianceFrameworks|Unable to save this compliance framework. Please try again" @@ -19041,7 +19041,7 @@ msgstr "" msgid "GroupSettings|Compliance frameworks" msgstr "" -msgid "GroupSettings|Configure compliance frameworks to make them available to projects in this group. %{linkStart}Learn more.%{linkEnd}" +msgid "GroupSettings|Configure compliance frameworks to make them available to projects in this group. %{linkStart}What are compliance frameworks?%{linkEnd}" msgstr "" msgid "GroupSettings|Configure limits on the number of repositories users can download in a given time." diff --git a/qa/qa/tools/ci/qa_changes.rb b/qa/qa/tools/ci/qa_changes.rb index 846f4dc4769..784923714d6 100644 --- a/qa/qa/tools/ci/qa_changes.rb +++ b/qa/qa/tools/ci/qa_changes.rb @@ -18,9 +18,10 @@ module QA /Dockerfile\.assets/ ) - def initialize(mr_diff, mr_labels) + def initialize(mr_diff, mr_labels, additional_group_spec_list) @mr_diff = mr_diff @mr_labels = mr_labels + @additional_group_spec_list = additional_group_spec_list end # Specific specs to run @@ -80,6 +81,9 @@ module QA # @return [Array] attr_reader :mr_labels + # @return [Hash<String, Array<String>>] + attr_reader :additional_group_spec_list + # Are the changed files only qa specs? # # @return [Boolean] whether the changes files are only qa specs @@ -101,6 +105,13 @@ module QA mr_labels.find { |label| label =~ /^devops::/ }&.delete_prefix('devops::') end + # Extract group name from MR labels + # + # @return [String] a group name + def group_name_from_mr_labels + mr_labels.find { |label| label =~ /^group::/ }&.delete_prefix('group::') + end + # Get qa spec directories for devops stage # # @return [Array] qa spec directories @@ -108,7 +119,15 @@ module QA devops_stage = devops_stage_from_mr_labels return unless devops_stage - Dir.glob("qa/specs/**/*/").select { |dir| dir =~ %r{\d+_#{devops_stage}/$} } + spec_dirs = stage_specs(devops_stage) + + grp_name = group_name_from_mr_labels + return spec_dirs if grp_name.nil? + + additional_grp_specs = additional_group_spec_list[grp_name] + return spec_dirs if additional_grp_specs.nil? + + spec_dirs + stage_specs(*additional_grp_specs) end # Changes to gitlab dependencies @@ -122,7 +141,15 @@ module QA # # @return [Array<String>] def changed_files - @changed_files ||= mr_diff.map { |change| change[:path] } # rubocop:disable Rails/Pluck + @changed_files ||= mr_diff.map { |change| change[:path] } + end + + # Devops stage specs + # + # @param [Array<String>] devops_stages + # @return [Array] + def stage_specs(*devops_stages) + Dir.glob("qa/specs/**/*/").select { |dir| dir =~ %r{\d+_(#{devops_stages.join('|')})/$} } end end end diff --git a/qa/spec/tools/ci/qa_changes_spec.rb b/qa/spec/tools/ci/qa_changes_spec.rb index a083ed79bdf..d93d3cd9258 100644 --- a/qa/spec/tools/ci/qa_changes_spec.rb +++ b/qa/spec/tools/ci/qa_changes_spec.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true RSpec.describe QA::Tools::Ci::QaChanges do - subject(:qa_changes) { described_class.new(mr_diff, mr_labels) } + subject(:qa_changes) { described_class.new(mr_diff, mr_labels, additional_group_spec_list) } let(:mr_labels) { [] } + let(:additional_group_spec_list) { [] } before do allow(File).to receive(:directory?).and_return(false) @@ -75,6 +76,43 @@ RSpec.describe QA::Tools::Ci::QaChanges do ) end end + + context "when configured to run tests from other stages" do + let(:additional_group_spec_list) do + { + 'foo' => %w[create], + 'bar' => %w[monitor verify] + } + end + + context "with a single extra stage configured for the group name" do + let(:mr_labels) { %w[devops::manage group::foo] } + + it ".qa_tests return specs for both devops stage and create stage" do + expect(qa_changes.qa_tests.split(" ")).to include( + "qa/specs/features/browser_ui/1_manage/", + "qa/specs/features/api/1_manage/", + "qa/specs/features/browser_ui/3_create/", + "qa/specs/features/api/3_create/" + ) + end + end + + context "with a multiple extra stages configured for the group name" do + let(:mr_labels) { %w[devops::manage group::bar] } + + it ".qa_tests return specs for both devops stage and multiple other stages" do + expect(qa_changes.qa_tests.split(" ")).to include( + "qa/specs/features/browser_ui/1_manage/", + "qa/specs/features/api/1_manage/", + "qa/specs/features/browser_ui/8_monitor/", + "qa/specs/features/api/8_monitor/", + "qa/specs/features/browser_ui/4_verify/", + "qa/specs/features/api/4_verify/" + ) + end + end + end end context "with quarantine changes" do diff --git a/qa/tasks/ci.rake b/qa/tasks/ci.rake index 44a794d9f94..435fe8ebb77 100644 --- a/qa/tasks/ci.rake +++ b/qa/tasks/ci.rake @@ -13,8 +13,10 @@ namespace :ci do diff = mr_diff labels = mr_labels + # Assign mapping of groups to tests in stages other than the groups defined stage + additional_group_spec_list = { 'gitaly' => %w[create] } - qa_changes = QA::Tools::Ci::QaChanges.new(diff, labels) + qa_changes = QA::Tools::Ci::QaChanges.new(diff, labels, additional_group_spec_list) logger = qa_changes.logger logger.info("Analyzing merge request changes") diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb index 77c62c0d930..1da868c6c4b 100644 --- a/spec/controllers/groups/runners_controller_spec.rb +++ b/spec/controllers/groups/runners_controller_spec.rb @@ -8,9 +8,11 @@ RSpec.describe Groups::RunnersController do let_it_be(:project) { create(:project, group: group) } let!(:runner) { create(:ci_runner, :group, groups: [group]) } - let!(:runner_project) { create(:ci_runner, :project, projects: [project]) } + let!(:project_runner) { create(:ci_runner, :project, projects: [project]) } + let!(:instance_runner) { create(:ci_runner, :instance) } - let(:params_runner_project) { { group_id: group, id: runner_project } } + let(:params_runner_project) { { group_id: group, id: project_runner } } + let(:params_runner_instance) { { group_id: group, id: instance_runner } } let(:params) { { group_id: group, id: runner } } before do @@ -70,8 +72,33 @@ RSpec.describe Groups::RunnersController do expect(response).to render_template(:show) end + context 'when runners_finder_all_available is enabled' do + before do + stub_feature_flags(runners_finder_all_available: true) + end + + it 'renders show with 200 status code instance runner' do + get :show, params: { group_id: group, id: instance_runner } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:show) + end + end + + context 'when runners_finder_all_available is disabled' do + before do + stub_feature_flags(runners_finder_all_available: false) + end + + it 'renders show with a 404 instance runner' do + get :show, params: { group_id: group, id: instance_runner } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + it 'renders show with 200 status code project runner' do - get :show, params: { group_id: group, id: runner_project } + get :show, params: { group_id: group, id: project_runner } expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template(:show) @@ -89,8 +116,14 @@ RSpec.describe Groups::RunnersController do expect(response).to have_gitlab_http_status(:not_found) end + it 'renders a 404 instance runner' do + get :show, params: { group_id: group, id: instance_runner } + + expect(response).to have_gitlab_http_status(:not_found) + end + it 'renders a 404 project runner' do - get :show, params: { group_id: group, id: runner_project } + get :show, params: { group_id: group, id: project_runner } expect(response).to have_gitlab_http_status(:not_found) end @@ -103,15 +136,21 @@ RSpec.describe Groups::RunnersController do group.add_owner(user) end - it 'renders show with 200 status code' do + it 'renders edit with 200 status code' do get :edit, params: { group_id: group, id: runner } expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template(:edit) end - it 'renders show with 200 status code project runner' do - get :edit, params: { group_id: group, id: runner_project } + it 'renders a 404 instance runner' do + get :edit, params: { group_id: group, id: instance_runner } + + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'renders edit with 200 status code project runner' do + get :edit, params: { group_id: group, id: project_runner } expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template(:edit) @@ -130,7 +169,7 @@ RSpec.describe Groups::RunnersController do end it 'renders a 404 project runner' do - get :edit, params: { group_id: group, id: runner_project } + get :edit, params: { group_id: group, id: project_runner } expect(response).to have_gitlab_http_status(:not_found) end @@ -154,15 +193,26 @@ RSpec.describe Groups::RunnersController do expect(runner.reload.description).to eq(new_desc) end + it 'does not update the instance runner' do + new_desc = instance_runner.description.swapcase + + expect do + post :update, params: params_runner_instance.merge(runner: { description: new_desc } ) + end.to not_change { instance_runner.ensure_runner_queue_value } + .and not_change { instance_runner.description } + + expect(response).to have_gitlab_http_status(:not_found) + end + it 'updates the project runner, ticks the queue, and redirects project runner' do - new_desc = runner_project.description.swapcase + new_desc = project_runner.description.swapcase expect do post :update, params: params_runner_project.merge(runner: { description: new_desc } ) - end.to change { runner_project.ensure_runner_queue_value } + end.to change { project_runner.ensure_runner_queue_value } expect(response).to have_gitlab_http_status(:found) - expect(runner_project.reload.description).to eq(new_desc) + expect(project_runner.reload.description).to eq(new_desc) end end @@ -182,15 +232,26 @@ RSpec.describe Groups::RunnersController do expect(runner.reload.description).to eq(old_desc) end + it 'rejects the update and responds 404 instance runner' do + old_desc = instance_runner.description + + expect do + post :update, params: params_runner_instance.merge(runner: { description: old_desc.swapcase } ) + end.not_to change { instance_runner.ensure_runner_queue_value } + + expect(response).to have_gitlab_http_status(:not_found) + expect(instance_runner.reload.description).to eq(old_desc) + end + it 'rejects the update and responds 404 project runner' do - old_desc = runner_project.description + old_desc = project_runner.description expect do post :update, params: params_runner_project.merge(runner: { description: old_desc.swapcase } ) - end.not_to change { runner_project.ensure_runner_queue_value } + end.not_to change { project_runner.ensure_runner_queue_value } expect(response).to have_gitlab_http_status(:not_found) - expect(runner_project.reload.description).to eq(old_desc) + expect(project_runner.reload.description).to eq(old_desc) end end end diff --git a/spec/factories/ci/build_metadata.rb b/spec/factories/ci/build_metadata.rb index cfc86c4ef4b..a0a5305ef39 100644 --- a/spec/factories/ci/build_metadata.rb +++ b/spec/factories/ci/build_metadata.rb @@ -3,5 +3,9 @@ FactoryBot.define do factory :ci_build_metadata, class: 'Ci::BuildMetadata' do build { association(:ci_build, strategy: :build, metadata: instance) } + + after(:build) do |metadata| + metadata.build&.valid? + end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 8c2edc8cd9f..9a3b2837ab8 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -424,79 +424,79 @@ FactoryBot.define do trait :codequality_report do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :codequality, job: build) + build.job_artifacts << build(:ci_job_artifact, :codequality, job: build) end end trait :sast_report do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :sast, job: build) + build.job_artifacts << build(:ci_job_artifact, :sast, job: build) end end trait :secret_detection_report do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :secret_detection, job: build) + build.job_artifacts << build(:ci_job_artifact, :secret_detection, job: build) end end trait :test_reports do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :junit, job: build) + build.job_artifacts << build(:ci_job_artifact, :junit, job: build) end end trait :test_reports_with_attachment do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :junit_with_attachment, job: build) + build.job_artifacts << build(:ci_job_artifact, :junit_with_attachment, job: build) end end trait :broken_test_reports do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :junit_with_corrupted_data, job: build) + build.job_artifacts << build(:ci_job_artifact, :junit_with_corrupted_data, job: build) end end trait :test_reports_with_duplicate_failed_test_names do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :junit_with_duplicate_failed_test_names, job: build) + build.job_artifacts << build(:ci_job_artifact, :junit_with_duplicate_failed_test_names, job: build) end end trait :test_reports_with_three_failures do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :junit_with_three_failures, job: build) + build.job_artifacts << build(:ci_job_artifact, :junit_with_three_failures, job: build) end end trait :accessibility_reports do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :accessibility, job: build) + build.job_artifacts << build(:ci_job_artifact, :accessibility, job: build) end end trait :coverage_reports do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :cobertura, job: build) + build.job_artifacts << build(:ci_job_artifact, :cobertura, job: build) end end trait :codequality_reports do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :codequality, job: build) + build.job_artifacts << build(:ci_job_artifact, :codequality, job: build) end end trait :codequality_reports_without_degradation do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :codequality_without_errors, job: build) + build.job_artifacts << build(:ci_job_artifact, :codequality_without_errors, job: build) end end trait :terraform_reports do after(:build) do |build| - build.job_artifacts << create(:ci_job_artifact, :terraform, job: build) + build.job_artifacts << build(:ci_job_artifact, :terraform, job: build) end end diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index 5b20010ef7e..650b8647237 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -8,6 +8,7 @@ FactoryBot.define do sha { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' } status { 'pending' } add_attribute(:protected) { false } + partition_id { 1234 } project @@ -53,6 +54,7 @@ FactoryBot.define do end factory :ci_pipeline do + partition_id { 1234 } transient { ci_ref_presence { true } } before(:create) do |pipeline, evaluator| diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb index dd377aa4a26..1ab231632fb 100644 --- a/spec/features/milestones/user_creates_milestone_spec.rb +++ b/spec/features/milestones/user_creates_milestone_spec.rb @@ -3,29 +3,100 @@ require 'spec_helper' RSpec.describe "User creates milestone", :js do - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:inherited_guest) { create(:user) } + let_it_be(:inherited_developer) { create(:user) } + let_it_be(:group) { create(:group, :public) } + + shared_examples 'creates milestone' do + specify do + title = "v2.3" + + fill_in("Title", with: title) + fill_in("Description", with: "# Description header") + click_button("Create milestone") + + expect(page).to have_content(title) + .and have_content("Issues") + .and have_header_with_correct_id_and_link(1, "Description header", "description-header") + + visit(activity_project_path(project)) + + expect(page).to have_content("#{user.name} #{user.to_reference} opened milestone") + end + end + + shared_examples 'renders not found' do + specify do + expect(page).to have_title('Not Found') + expect(page).to have_content('Page Not Found') + end + end + + before_all do + group.add_guest(inherited_guest) + group.add_developer(inherited_developer) + end before do - project.add_developer(user) sign_in(user) - visit(new_project_milestone_path(project)) end - it "creates milestone" do - title = "v2.3" + context 'when project is public' do + let_it_be(:project) { create(:project, :public, group: group) } + + context 'and issues and merge requests are private' do + before_all do + project.project_feature.update!( + issues_access_level: ProjectFeature::PRIVATE, + merge_requests_access_level: ProjectFeature::PRIVATE + ) + end + + context 'when user is an inherited member from the group' do + context 'and user is a guest' do + let(:user) { inherited_guest } + + it_behaves_like 'renders not found' + end + + context 'and user is a developer' do + let(:user) { inherited_developer } + + it_behaves_like 'creates milestone' + end + end + end + end + + context 'when project is private' do + let_it_be(:project) { create(:project, :private, group: group) } + + context 'and user is a direct project member' do + before_all do + project.add_developer(developer) + end + + context 'when user is a developer' do + let(:user) { developer } + + it_behaves_like 'creates milestone' + end + end - fill_in("Title", with: title) - fill_in("Description", with: "# Description header") - click_button("Create milestone") + context 'and user is an inherited member from the group' do + context 'when user is a guest' do + let(:user) { inherited_guest } - expect(page).to have_content(title) - .and have_content("Issues") - .and have_header_with_correct_id_and_link(1, "Description header", "description-header") + it_behaves_like 'renders not found' + end - visit(activity_project_path(project)) + context 'when user is a developer' do + let(:user) { inherited_developer } - expect(page).to have_content("#{user.name} #{user.to_reference} opened milestone") + it_behaves_like 'creates milestone' + end + end end end diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js index e26c52f0bf7..a809bf248bf 100644 --- a/spec/frontend/flash_spec.js +++ b/spec/frontend/flash_spec.js @@ -285,6 +285,13 @@ describe('Flash', () => { expect(document.querySelector('.gl-alert')).toBeNull(); }); + it('does not crash if calling .dismiss() twice', () => { + alert = createAlert({ message: mockMessage }); + + alert.dismiss(); + expect(() => alert.dismiss()).not.toThrow(); + }); + it('calls onDismiss when dismissed', () => { const dismissHandler = jest.fn(); diff --git a/spec/graphql/types/branch_rule_type_spec.rb b/spec/graphql/types/branch_rule_type_spec.rb index 277901f00bf..12a2c8dfe12 100644 --- a/spec/graphql/types/branch_rule_type_spec.rb +++ b/spec/graphql/types/branch_rule_type_spec.rb @@ -10,6 +10,7 @@ RSpec.describe GitlabSchema.types['BranchRule'] do let(:fields) do %i[ name + isDefault branch_protection created_at updated_at diff --git a/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb b/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb new file mode 100644 index 00000000000..98939e15952 --- /dev/null +++ b/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::UpdateCiPipelineArtifactsUnknownLockedStatus do + describe '#perform' do + let(:batch_table) { :ci_pipeline_artifacts } + let(:batch_column) { :id } + + let(:sub_batch_size) { 1 } + let(:pause_ms) { 0 } + let(:connection) { Ci::ApplicationRecord.connection } + + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:pipelines) { table(:ci_pipelines, database: :ci) } + let(:pipeline_artifacts) { table(:ci_pipeline_artifacts, database: :ci) } + + let(:namespace) { namespaces.create!(name: 'name', path: 'path') } + let(:project) do + projects + .create!(name: "project", path: "project", namespace_id: namespace.id, project_namespace_id: namespace.id) + end + + let(:unlocked) { 0 } + let(:locked) { 1 } + let(:unknown) { 2 } + + let(:unlocked_pipeline) { pipelines.create!(locked: unlocked) } + let(:locked_pipeline) { pipelines.create!(locked: locked) } + + # rubocop:disable Layout/LineLength + let!(:locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 1024, file_type: 0, file_format: 'gzip', file: 'a.gz', locked: unknown) } + let!(:unlocked_artifact_1) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 2048, file_type: 1, file_format: 'raw', file: 'b', locked: unknown) } + let!(:unlocked_artifact_2) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 4096, file_type: 2, file_format: 'gzip', file: 'c.gz', locked: unknown) } + let!(:already_unlocked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: unlocked) } + let!(:already_locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: locked) } + # rubocop:enable Layout/LineLength + + subject do + described_class.new( + start_id: locked_artifact.id, + end_id: already_locked_artifact.id, + batch_table: batch_table, + batch_column: batch_column, + sub_batch_size: sub_batch_size, + pause_ms: pause_ms, + connection: connection + ).perform + end + + it 'updates ci_pipeline_artifacts with unknown lock status' do + subject + + expect(locked_artifact.reload.locked).to eq(locked) + expect(unlocked_artifact_1.reload.locked).to eq(unlocked) + expect(unlocked_artifact_2.reload.locked).to eq(unlocked) + expect(already_unlocked_artifact.reload.locked).to eq(unlocked) + expect(already_locked_artifact.reload.locked).to eq(locked) + end + end +end diff --git a/spec/migrations/20220928225711_schedule_update_ci_pipeline_artifacts_locked_status_spec.rb b/spec/migrations/20220928225711_schedule_update_ci_pipeline_artifacts_locked_status_spec.rb new file mode 100644 index 00000000000..7e3f8caa966 --- /dev/null +++ b/spec/migrations/20220928225711_schedule_update_ci_pipeline_artifacts_locked_status_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe ScheduleUpdateCiPipelineArtifactsLockedStatus, migration: :gitlab_ci do + let_it_be(:migration) { described_class::MIGRATION } + + describe '#up' do + it 'schedules background jobs for each batch of ci_pipeline_artifacts' do + migrate! + + expect(migration).to have_scheduled_batched_migration( + gitlab_schema: :gitlab_ci, + table_name: :ci_pipeline_artifacts, + column_name: :id, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + end + end + + describe '#down' do + it 'deletes all batched migration records' do + migrate! + schema_migrate_down! + + expect(migration).not_to have_scheduled_batched_migration + end + end +end diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index e904463a5ca..16cff72db64 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -14,8 +14,8 @@ RSpec.describe Ci::BuildMetadata do status: 'success') end - let(:build) { create(:ci_build, pipeline: pipeline) } - let(:metadata) { build.metadata } + let(:job) { create(:ci_build, pipeline: pipeline) } + let(:metadata) { job.metadata } it_behaves_like 'having unique enum values' @@ -35,7 +35,7 @@ RSpec.describe Ci::BuildMetadata do context 'when project timeout is set' do context 'when runner is assigned to the job' do before do - build.update!(runner: runner) + job.update!(runner: runner) end context 'when runner timeout is not set' do @@ -59,13 +59,13 @@ RSpec.describe Ci::BuildMetadata do context 'when job timeout is set' do context 'when job timeout is higher than project timeout' do - let(:build) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } + let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } it_behaves_like 'sets timeout', 'job_timeout_source', 3000 end context 'when job timeout is lower than project timeout' do - let(:build) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1000 }) } + let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1000 }) } it_behaves_like 'sets timeout', 'job_timeout_source', 1000 end @@ -73,18 +73,18 @@ RSpec.describe Ci::BuildMetadata do context 'when both runner and job timeouts are set' do before do - build.update!(runner: runner) + job.update!(runner: runner) end context 'when job timeout is higher than runner timeout' do - let(:build) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } + let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } let(:runner) { create(:ci_runner, maximum_timeout: 2100) } it_behaves_like 'sets timeout', 'runner_timeout_source', 2100 end context 'when job timeout is lower than runner timeout' do - let(:build) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1900 }) } + let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1900 }) } let(:runner) { create(:ci_runner, maximum_timeout: 2100) } it_behaves_like 'sets timeout', 'job_timeout_source', 1900 @@ -135,20 +135,51 @@ RSpec.describe Ci::BuildMetadata do describe 'set_cancel_gracefully' do it 'sets cancel_gracefully' do - build.set_cancel_gracefully + job.set_cancel_gracefully - expect(build.cancel_gracefully?).to be true + expect(job.cancel_gracefully?).to be true end it 'returns false' do - expect(build.cancel_gracefully?).to be false + expect(job.cancel_gracefully?).to be false end end context 'loose foreign key on ci_builds_metadata.project_id' do it_behaves_like 'cleanup by a loose foreign key' do - let!(:parent) { create(:project) } - let!(:model) { create(:ci_build_metadata, project: parent) } + let!(:parent) { project } + let!(:model) { metadata } + end + end + + describe 'partitioning' do + context 'with job' do + let(:status) { build(:commit_status, partition_id: 123) } + let(:metadata) { build(:ci_build_metadata, build: status) } + + it 'copies the partition_id from job' do + expect { metadata.valid? }.to change(metadata, :partition_id).to(123) + end + + context 'when it is already set' do + let(:metadata) { build(:ci_build_metadata, build: status, partition_id: 125) } + + it 'does not change the partition_id value' do + expect { metadata.valid? }.not_to change(metadata, :partition_id) + end + end + end + + context 'without job' do + subject(:metadata) do + build(:ci_build_metadata, build: nil) + end + + it { is_expected.to validate_presence_of(:partition_id) } + + it 'does not change the partition_id value' do + expect { metadata.valid? }.not_to change(metadata, :partition_id) + end end end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 41b817f231b..52142278c15 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -320,10 +320,10 @@ RSpec.describe Ci::Build do let(:artifact_scope) { Ci::JobArtifact.where(file_type: 'archive') } - let!(:build_1) { create(:ci_build, :artifacts) } - let!(:build_2) { create(:ci_build, :codequality_reports) } - let!(:build_3) { create(:ci_build, :test_reports) } - let!(:build_4) { create(:ci_build, :artifacts) } + let!(:build_1) { create(:ci_build, :artifacts, pipeline: pipeline) } + let!(:build_2) { create(:ci_build, :codequality_reports, pipeline: pipeline) } + let!(:build_3) { create(:ci_build, :test_reports, pipeline: pipeline) } + let!(:build_4) { create(:ci_build, :artifacts, pipeline: pipeline) } it 'returns artifacts matching the given scope' do expect(builds).to contain_exactly(build_1, build_4) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 3fc1883c1e6..8bf32527342 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -5488,7 +5488,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'partitioning' do - let(:pipeline) { build(:ci_pipeline) } + let(:pipeline) { build(:ci_pipeline, partition_id: nil) } before do allow(described_class).to receive(:current_partition_value) { 123 } diff --git a/spec/models/concerns/ci/partitionable_spec.rb b/spec/models/concerns/ci/partitionable_spec.rb new file mode 100644 index 00000000000..d53501ccc3d --- /dev/null +++ b/spec/models/concerns/ci/partitionable_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::Partitionable do + describe 'partitionable models inclusion' do + let(:ci_model) { Class.new(Ci::ApplicationRecord) } + + subject { ci_model.include(described_class) } + + it 'raises an exception' do + expect { subject } + .to raise_error(/must be included in PARTITIONABLE_MODELS/) + end + + context 'when is included in the models list' do + before do + stub_const("#{described_class}::Testing::PARTITIONABLE_MODELS", [ci_model.name]) + end + + it 'does not raise exceptions' do + expect { subject }.not_to raise_error + end + end + end +end diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index 54a90ca6049..b88367b9ca2 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -435,4 +435,28 @@ RSpec.describe ProtectedBranch do expect(described_class.downcase_humanized_name).to eq 'protected branch' end end + + describe '.default_branch?' do + before do + allow(subject.project).to receive(:default_branch).and_return(branch) + end + + context 'when the name matches the default branch' do + let(:branch) { subject.name } + + it { is_expected.to be_default_branch } + end + + context 'when the name does not match the default branch' do + let(:branch) { "#{subject.name}qwerty" } + + it { is_expected.not_to be_default_branch } + end + + context 'when a wildcard name matches the default branch' do + let(:branch) { "#{subject.name}*" } + + it { is_expected.not_to be_default_branch } + end + end end diff --git a/spec/requests/api/graphql/project/branch_rules_spec.rb b/spec/requests/api/graphql/project/branch_rules_spec.rb index 70fb37941e2..1aaf0e9edc7 100644 --- a/spec/requests/api/graphql/project/branch_rules_spec.rb +++ b/spec/requests/api/graphql/project/branch_rules_spec.rb @@ -21,27 +21,24 @@ RSpec.describe 'getting list of branch rules for a project' do let(:branch_rules_data) { graphql_data_at('project', 'branchRules', 'edges') } let(:variables) { { path: project.full_path } } - - let(:fields) do - <<~QUERY - pageInfo { - hasNextPage - hasPreviousPage - } - edges { - cursor - node { - #{all_graphql_fields_for('branch_rules'.classify)} - } - } - QUERY - end - + # fields must use let as the all_graphql_fields_for also configures some spies + let(:fields) { all_graphql_fields_for('BranchRule') } let(:query) do <<~GQL query($path: ID!, $n: Int, $cursor: String) { project(fullPath: $path) { - branchRules(first: $n, after: $cursor) { #{fields} } + branchRules(first: $n, after: $cursor) { + pageInfo { + hasNextPage + hasPreviousPage + } + edges { + cursor + node { + #{fields} + } + } + } } } GQL @@ -55,7 +52,9 @@ RSpec.describe 'getting list of branch rules for a project' do it_behaves_like 'a working graphql query' - it { expect(branch_rules_data).to be_empty } + it 'hides branch rules data' do + expect(branch_rules_data).to be_empty + end end context 'when the user does have read_protected_branch abilities' do @@ -66,12 +65,17 @@ RSpec.describe 'getting list of branch rules for a project' do it_behaves_like 'a working graphql query' - it 'includes a name' do + it 'returns branch rules data' do expect(branch_rules_data.dig(0, 'node', 'name')).to be_present - end - - it 'includes created_at and updated_at' do + expect(branch_rules_data.dig(0, 'node', 'isDefault')).to be(true).or be(false) + expect(branch_rules_data.dig(0, 'node', 'branchProtection')).to be_present expect(branch_rules_data.dig(0, 'node', 'createdAt')).to be_present + expect(branch_rules_data.dig(0, 'node', 'updatedAt')).to be_present + + expect(branch_rules_data.dig(1, 'node', 'name')).to be_present + expect(branch_rules_data.dig(1, 'node', 'isDefault')).to be(true).or be(false) + expect(branch_rules_data.dig(1, 'node', 'branchProtection')).to be_present + expect(branch_rules_data.dig(1, 'node', 'createdAt')).to be_present expect(branch_rules_data.dig(1, 'node', 'updatedAt')).to be_present end @@ -82,16 +86,16 @@ RSpec.describe 'getting list of branch rules for a project' do { path: project.full_path, n: branch_rule_limit, cursor: last_cursor } end - it_behaves_like 'a working graphql query' do - it 'only returns N branch_rules' do - expect(branch_rules_data.size).to eq(branch_rule_limit) - expect(has_next_page).to be_truthy - expect(has_prev_page).to be_falsey - post_graphql(query, current_user: current_user, variables: next_variables) - expect(branch_rules_data.size).to eq(branch_rule_limit) - expect(has_next_page).to be_falsey - expect(has_prev_page).to be_truthy - end + it_behaves_like 'a working graphql query' + + it 'returns pagination information' do + expect(branch_rules_data.size).to eq(branch_rule_limit) + expect(has_next_page).to be_truthy + expect(has_prev_page).to be_falsey + post_graphql(query, current_user: current_user, variables: next_variables) + expect(branch_rules_data.size).to eq(branch_rule_limit) + expect(has_next_page).to be_falsey + expect(has_prev_page).to be_truthy end context 'when no limit is provided' do diff --git a/spec/services/ci/job_artifacts/create_service_spec.rb b/spec/services/ci/job_artifacts/create_service_spec.rb index a2259f9813b..030ba84951e 100644 --- a/spec/services/ci/job_artifacts/create_service_spec.rb +++ b/spec/services/ci/job_artifacts/create_service_spec.rb @@ -182,7 +182,8 @@ RSpec.describe Ci::JobArtifacts::CreateService do end context 'with job partitioning' do - let(:job) { create(:ci_build, project: project, partition_id: 123) } + let(:pipeline) { create(:ci_pipeline, project: project, partition_id: 123) } + let(:job) { create(:ci_build, pipeline: pipeline) } it 'sets partition_id on artifacts' do expect { subject }.to change { Ci::JobArtifact.count } diff --git a/spec/services/packages/rpm/repository_metadata/build_repomd_xml_spec.rb b/spec/services/packages/rpm/repository_metadata/build_repomd_xml_spec.rb index 29b0f73e3c1..0843a983b7e 100644 --- a/spec/services/packages/rpm/repository_metadata/build_repomd_xml_spec.rb +++ b/spec/services/packages/rpm/repository_metadata/build_repomd_xml_spec.rb @@ -62,5 +62,25 @@ RSpec.describe Packages::Rpm::RepositoryMetadata::BuildRepomdXml do end end end + + context 'when data values has unexpected keys' do + let(:data) do + { + filelists: described_class::ALLOWED_DATA_VALUE_KEYS.each_with_object({}) do |key, result| + result[:"#{key}-wrong"] = { value: 'value' } + end + } + end + + it 'ignores wrong keys' do + result = Nokogiri::XML::Document.parse(subject).remove_namespaces! + + data.each do |tag_name, tag_attributes| + tag_attributes.each_key do |key| + expect(result.at("//repomd/data[@type=\"#{tag_name}\"]/#{key}")).to be_nil + end + end + end + end end end diff --git a/spec/support/models/partitionable_check.rb b/spec/support/models/partitionable_check.rb new file mode 100644 index 00000000000..2c09c1b3408 --- /dev/null +++ b/spec/support/models/partitionable_check.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module PartitioningTesting + module CascadeCheck + extend ActiveSupport::Concern + + included do + after_create :check_partition_cascade_value + end + + def check_partition_cascade_value + raise 'Partition value not found' unless partition_scope_value + raise 'Default value detected' if partition_id == 100 + + return if partition_id == partition_scope_value + + raise "partition_id was expected to equal #{partition_scope_value} but it was #{partition_id}." + end + end + + module DefaultPartitionValue + extend ActiveSupport::Concern + + class_methods do + def current_partition_value + current = super + + if current == 100 + 54321 + else + current + end + end + end + end +end + +Ci::Partitionable::Testing::PARTITIONABLE_MODELS.each do |klass| + model = klass.safe_constantize + + if klass == 'Ci::Pipeline' + model.prepend(PartitioningTesting::DefaultPartitionValue) + else + model.include(PartitioningTesting::CascadeCheck) + end +end diff --git a/spec/views/registrations/welcome/show.html.haml_spec.rb b/spec/views/registrations/welcome/show.html.haml_spec.rb index d9c5d348e15..99d87ac449b 100644 --- a/spec/views/registrations/welcome/show.html.haml_spec.rb +++ b/spec/views/registrations/welcome/show.html.haml_spec.rb @@ -11,6 +11,7 @@ RSpec.describe 'registrations/welcome/show' do allow(view).to receive(:in_trial_flow?).and_return(false) allow(view).to receive(:user_has_memberships?).and_return(false) allow(view).to receive(:in_oauth_flow?).and_return(false) + allow(view).to receive(:glm_tracking_params).and_return({}) render end |