diff options
107 files changed, 931 insertions, 1249 deletions
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 5992affe96a..b88ccbabdea 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -1588,6 +1588,7 @@ Gitlab/NamespacedClass: - 'app/models/list_user_preference.rb' - 'app/models/member.rb' - 'app/models/members/group_member.rb' + - 'app/models/members/last_group_owner_assigner.rb' - 'app/models/members/project_member.rb' - 'app/models/members_preloader.rb' - 'app/models/merge_request.rb' diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 303d6d1dba9..48abc072675 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -323,7 +323,7 @@ export function isAbsolute(url) { * @param {String} url */ export function isRootRelative(url) { - return /^\//.test(url); + return /^\/(?!\/)/.test(url); } /** diff --git a/app/assets/javascripts/packages/details/components/file_sha.vue b/app/assets/javascripts/packages/details/components/file_sha.vue new file mode 100644 index 00000000000..a25839be7e1 --- /dev/null +++ b/app/assets/javascripts/packages/details/components/file_sha.vue @@ -0,0 +1,41 @@ +<script> +import { s__ } from '~/locale'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; + +export default { + name: 'FileSha', + components: { + DetailsRow, + ClipboardButton, + }, + props: { + sha: { + type: String, + required: true, + }, + title: { + type: String, + required: true, + }, + }, + i18n: { + copyButtonTitle: s__('PackageRegistry|Copy SHA'), + }, +}; +</script> + +<template> + <details-row dashed> + <div class="gl-px-4"> + {{ title }}: + {{ sha }} + <clipboard-button + :text="sha" + :title="$options.i18n.copyButtonTitle" + category="tertiary" + size="small" + /> + </div> + </details-row> +</template> diff --git a/app/assets/javascripts/packages/details/components/package_files.vue b/app/assets/javascripts/packages/details/components/package_files.vue index 735f5c2e5cd..3c71ac5137e 100644 --- a/app/assets/javascripts/packages/details/components/package_files.vue +++ b/app/assets/javascripts/packages/details/components/package_files.vue @@ -1,8 +1,9 @@ <script> -import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon } from '@gitlab/ui'; +import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon, GlButton } from '@gitlab/ui'; import { last } from 'lodash'; import { numberToHumanSize } from '~/lib/utils/number_utils'; import { __ } from '~/locale'; +import FileSha from '~/packages/details/components/file_sha.vue'; import Tracking from '~/tracking'; import FileIcon from '~/vue_shared/components/file_icon.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -15,8 +16,10 @@ export default { GlIcon, GlDropdown, GlDropdownItem, + GlButton, FileIcon, TimeAgoTooltip, + FileSha, }, mixins: [Tracking.mixin()], props: { @@ -76,6 +79,9 @@ export default { formatSize(size) { return numberToHumanSize(size); }, + hasDetails(item) { + return item.file_sha256 || item.file_md5 || item.file_sha1; + }, }, i18n: { deleteFile: __('Delete file'), @@ -91,7 +97,15 @@ export default { :items="filesTableRows" :tbody-tr-attr="{ 'data-testid': 'file-row' }" > - <template #cell(name)="{ item }"> + <template #cell(name)="{ item, toggleDetails, detailsShowing }"> + <gl-button + v-if="hasDetails(item)" + :icon="detailsShowing ? 'angle-up' : 'angle-down'" + :aria-label="detailsShowing ? __('Collapse') : __('Expand')" + category="tertiary" + size="small" + @click="toggleDetails" + /> <gl-link :href="item.download_path" class="gl-text-gray-500" @@ -131,6 +145,21 @@ export default { </gl-dropdown-item> </gl-dropdown> </template> + + <template #row-details="{ item }"> + <div + class="gl-display-flex gl-flex-direction-column gl-flex-fill-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100" + > + <file-sha + v-if="item.file_sha256" + data-testid="sha-256" + title="SHA-256" + :sha="item.file_sha256" + /> + <file-sha v-if="item.file_md5" data-testid="md5" title="MD5" :sha="item.file_md5" /> + <file-sha v-if="item.file_sha1" data-testid="sha-1" title="SHA-1" :sha="item.file_sha1" /> + </div> + </template> </gl-table> </div> </template> diff --git a/app/assets/javascripts/pages/projects/forks/new/components/app.vue b/app/assets/javascripts/pages/projects/forks/new/components/app.vue index 02b357d389b..7fb41c6e7b7 100644 --- a/app/assets/javascripts/pages/projects/forks/new/components/app.vue +++ b/app/assets/javascripts/pages/projects/forks/new/components/app.vue @@ -38,6 +38,10 @@ export default { type: String, required: true, }, + restrictedVisibilityLevels: { + type: Array, + required: true, + }, }, }; </script> @@ -66,6 +70,7 @@ export default { :project-path="projectPath" :project-description="projectDescription" :project-visibility="projectVisibility" + :restricted-visibility-levels="restrictedVisibilityLevels" /> </div> </div> diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue index 96bd38ea9e2..75c3b6d564c 100644 --- a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue +++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue @@ -95,6 +95,10 @@ export default { type: String, required: true, }, + restrictedVisibilityLevels: { + type: Array, + required: true, + }, }, data() { const form = { @@ -111,7 +115,7 @@ export default { required: false, skipValidation: true, }), - visibility: initFormField({ value: this.projectVisibility }), + visibility: initFormField({ value: this.getInitialVisibilityValue() }), }, }; return { @@ -134,13 +138,28 @@ export default { visibilityLevelCap() { return Math.min(this.projectVisibilityLevel, this.namespaceVisibilityLevel); }, + restrictedVisibilityLevelsSet() { + return new Set(this.restrictedVisibilityLevels); + }, allowedVisibilityLevels() { - return Object.entries(VISIBILITY_LEVEL).reduce((levels, [levelName, levelValue]) => { - if (levelValue <= this.visibilityLevelCap) { - levels.push(levelName); - } - return levels; - }, []); + const allowedLevels = Object.entries(VISIBILITY_LEVEL).reduce( + (levels, [levelName, levelValue]) => { + if ( + !this.restrictedVisibilityLevelsSet.has(levelValue) && + levelValue <= this.visibilityLevelCap + ) { + levels.push(levelName); + } + return levels; + }, + [], + ); + + if (!allowedLevels.length) { + return [PRIVATE_VISIBILITY]; + } + + return allowedLevels; }, visibilityLevels() { return [ @@ -173,7 +192,8 @@ export default { watch: { // eslint-disable-next-line func-names 'form.fields.namespace.value': function () { - this.form.fields.visibility.value = PRIVATE_VISIBILITY; + this.form.fields.visibility.value = + this.restrictedVisibilityLevels.length !== 0 ? null : PRIVATE_VISIBILITY; }, // eslint-disable-next-line func-names 'form.fields.name.value': function (newVal) { @@ -191,6 +211,9 @@ export default { isVisibilityLevelDisabled(visibility) { return !this.allowedVisibilityLevels.includes(visibility); }, + getInitialVisibilityValue() { + return this.restrictedVisibilityLevels.length !== 0 ? null : this.projectVisibility; + }, async onSubmit() { this.form.showValidation = true; @@ -340,6 +363,7 @@ export default { v-model="form.fields.visibility.value" data-testid="fork-visibility-radio-group" name="visibility" + :aria-label="__('visibility')" required > <gl-form-radio diff --git a/app/assets/javascripts/pages/projects/forks/new/index.js b/app/assets/javascripts/pages/projects/forks/new/index.js index 372967c8a1e..1a171252048 100644 --- a/app/assets/javascripts/pages/projects/forks/new/index.js +++ b/app/assets/javascripts/pages/projects/forks/new/index.js @@ -16,6 +16,7 @@ if (gon.features.forkProjectForm) { projectPath, projectDescription, projectVisibility, + restrictedVisibilityLevels, } = mountElement.dataset; // eslint-disable-next-line no-new @@ -38,6 +39,7 @@ if (gon.features.forkProjectForm) { projectPath, projectDescription, projectVisibility, + restrictedVisibilityLevels: JSON.parse(restrictedVisibilityLevels), }, }); }, diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js index 7d9d962b6f4..ac02392d60f 100644 --- a/app/assets/javascripts/repository/log_tree.js +++ b/app/assets/javascripts/repository/log_tree.js @@ -8,6 +8,12 @@ import refQuery from './queries/ref.query.graphql'; const fetchpromises = {}; const resolvers = {}; let maxOffset; +let nextOffset; +let currentPath; + +function setNextOffset(offset) { + nextOffset = offset || null; +} export function resolveCommit(commits, path, { resolve, entry }) { const commit = commits.find( @@ -24,7 +30,17 @@ export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset maxOffset = _maxOffset; } - if (Number(offset) > maxOffset) { + if (!currentPath || currentPath !== path) { + // ensures the nextOffset is reset if the user changed directories + setNextOffset(null); + } + + currentPath = path; + + const offsetNumber = Number(offset); + + if (!nextOffset && offsetNumber > maxOffset) { + setNextOffset(offsetNumber - 25); // ensures commit data is fetched for newly added rows that need data from the previous request (requests are made in batches of 25). return Promise.resolve(); } @@ -47,7 +63,7 @@ export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset path.replace(/^\//, ''), )}`, { - params: { format: 'json', offset }, + params: { format: 'json', offset: nextOffset || offset }, }, ) .then(({ data: newData, headers }) => { @@ -66,10 +82,12 @@ export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset delete fetchpromises[path]; if (headerLogsOffset) { + setNextOffset(null); fetchLogsTree(client, path, headerLogsOffset); } else { delete resolvers[path]; maxOffset = null; + setNextOffset(null); } }); diff --git a/app/assets/javascripts/vue_shared/components/registry/details_row.vue b/app/assets/javascripts/vue_shared/components/registry/details_row.vue index 2e245fadead..72e06b45561 100644 --- a/app/assets/javascripts/vue_shared/components/registry/details_row.vue +++ b/app/assets/javascripts/vue_shared/components/registry/details_row.vue @@ -8,7 +8,8 @@ export default { props: { icon: { type: String, - required: true, + required: false, + default: null, }, padding: { type: String, @@ -34,7 +35,7 @@ export default { class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-word-break-all" :class="[padding, borderClass]" > - <gl-icon :name="icon" class="gl-mr-4" /> + <gl-icon v-if="icon" :name="icon" class="gl-mr-4" /> <span> <slot></slot> </span> diff --git a/app/controllers/clusters/applications_controller.rb b/app/controllers/clusters/applications_controller.rb index c533fe007d7..5c1d85f4374 100644 --- a/app/controllers/clusters/applications_controller.rb +++ b/app/controllers/clusters/applications_controller.rb @@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController end def cluster_application_params - params.permit(:application, :hostname, :pages_domain_id, :email, :stack, :modsecurity_enabled, :modsecurity_mode, :host, :port, :protocol, :waf_log_enabled, :cilium_log_enabled) + params.permit(:application, :hostname, :pages_domain_id, :email, :stack, :host, :port, :protocol, :cilium_log_enabled) end def cluster_application_destroy_params diff --git a/app/controllers/projects/analytics/cycle_analytics/summary_controller.rb b/app/controllers/projects/analytics/cycle_analytics/summary_controller.rb new file mode 100644 index 00000000000..c51a5ac7b88 --- /dev/null +++ b/app/controllers/projects/analytics/cycle_analytics/summary_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class Projects::Analytics::CycleAnalytics::SummaryController < Projects::ApplicationController + include CycleAnalyticsParams + + respond_to :json + + feature_category :planning_analytics + + before_action :authorize_read_cycle_analytics! + + def show + render json: project_level.summary + end + + private + + def project_level + @project_level ||= Analytics::CycleAnalytics::ProjectLevel.new(project: @project, options: options(allowed_params)) + end + + def allowed_params + params.permit(:created_after, :created_before) + end +end + +Projects::Analytics::CycleAnalytics::SummaryController.prepend_mod_with('Projects::Analytics::CycleAnalytics::SummaryController') diff --git a/app/controllers/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb index 3a5dd23047c..a1da8d4e91f 100644 --- a/app/controllers/projects/cycle_analytics/events_controller.rb +++ b/app/controllers/projects/cycle_analytics/events_controller.rb @@ -53,7 +53,7 @@ module Projects end def cycle_analytics - @cycle_analytics ||= ::CycleAnalytics::ProjectLevel.new(project, options: options(cycle_analytics_project_params)) + @cycle_analytics ||= ::Analytics::CycleAnalytics::ProjectLevel.new(project: project, options: options(cycle_analytics_project_params)) end end end diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index 5c15a5d246c..d1d27286c68 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -14,7 +14,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController feature_category :planning_analytics def show - @cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_project_params)) + @cycle_analytics = Analytics::CycleAnalytics::ProjectLevel.new(project: @project, options: options(cycle_analytics_project_params)) respond_to do |format| format.html do diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index f0e4cd10366..116e7970bbf 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -176,11 +176,7 @@ class Projects::PipelinesController < Projects::ApplicationController end def retry - if Gitlab::Ci::Features.background_pipeline_retry_endpoint?(@project) - ::Ci::RetryPipelineWorker.perform_async(pipeline.id, current_user.id) # rubocop:disable CodeReuse/Worker - else - pipeline.retry_failed(current_user) - end + ::Ci::RetryPipelineWorker.perform_async(pipeline.id, current_user.id) # rubocop:disable CodeReuse/Worker respond_to do |format| format.html do diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb index 60dd977ff94..7ad51361efd 100644 --- a/app/finders/ci/runners_finder.rb +++ b/app/finders/ci/runners_finder.rb @@ -4,6 +4,9 @@ module Ci class RunnersFinder < UnionFinder include Gitlab::Allowable + ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze + DEFAULT_SORT = 'created_at_desc' + def initialize(current_user:, group: nil, params:) @params = params @group = group @@ -24,11 +27,7 @@ module Ci end def sort_key - if @params[:sort] == 'contacted_asc' - 'contacted_asc' - else - 'created_date' - end + ALLOWED_SORTS.include?(@params[:sort]) ? @params[:sort] : DEFAULT_SORT end private diff --git a/app/graphql/mutations/ci/ci_cd_settings_update.rb b/app/graphql/mutations/ci/ci_cd_settings_update.rb index a484c2438a4..0973e9beae3 100644 --- a/app/graphql/mutations/ci/ci_cd_settings_update.rb +++ b/app/graphql/mutations/ci/ci_cd_settings_update.rb @@ -17,6 +17,10 @@ module Mutations required: false, description: 'Indicates if the latest artifact should be kept for this project.' + argument :job_token_scope_enabled, GraphQL::BOOLEAN_TYPE, + required: false, + description: 'Indicates CI job tokens generated in this project have restricted access to resources.' + field :ci_cd_settings, Types::Ci::CiCdSettingType, null: false, diff --git a/app/graphql/types/ci/ci_cd_setting_type.rb b/app/graphql/types/ci/ci_cd_setting_type.rb index b34a91446a2..f90c75454ba 100644 --- a/app/graphql/types/ci/ci_cd_setting_type.rb +++ b/app/graphql/types/ci/ci_cd_setting_type.rb @@ -16,6 +16,9 @@ module Types field :keep_latest_artifact, GraphQL::BOOLEAN_TYPE, null: true, description: 'Whether to keep the latest builds artifacts.', method: :keep_latest_artifacts_available? + field :job_token_scope_enabled, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Indicates CI job tokens generated in this project have restricted access to resources.', + method: :job_token_scope_enabled? field :project, Types::ProjectType, null: true, description: 'Project the CI/CD settings belong to.' end diff --git a/app/graphql/types/ci/runner_sort_enum.rb b/app/graphql/types/ci/runner_sort_enum.rb index 550e870316a..95ec1867fea 100644 --- a/app/graphql/types/ci/runner_sort_enum.rb +++ b/app/graphql/types/ci/runner_sort_enum.rb @@ -7,7 +7,9 @@ module Types description 'Values for sorting runners' value 'CONTACTED_ASC', 'Ordered by contacted_at in ascending order.', value: :contacted_asc - value 'CREATED_DESC', 'Ordered by created_date in descending order.', value: :created_date + value 'CONTACTED_DESC', 'Ordered by contacted_at in descending order.', value: :contacted_desc + value 'CREATED_ASC', 'Ordered by created_at in ascending order.', value: :created_at_asc + value 'CREATED_DESC', 'Ordered by created_at in descending order.', value: :created_at_desc end end end diff --git a/app/models/analytics/cycle_analytics/project_level.rb b/app/models/analytics/cycle_analytics/project_level.rb new file mode 100644 index 00000000000..7a73bc75ed6 --- /dev/null +++ b/app/models/analytics/cycle_analytics/project_level.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Analytics + module CycleAnalytics + class ProjectLevel + attr_reader :project, :options + + def initialize(project:, options:) + @project = project + @options = options.merge(project: project) + end + + def summary + @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project, + options: options, + current_user: options[:current_user]).data + end + + def permissions(user:) + Gitlab::CycleAnalytics::Permissions.get(user: user, project: project) + end + + def stats + @stats ||= default_stage_names.map do |stage_name| + self[stage_name].as_json + end + end + + def [](stage_name) + ::CycleAnalytics::ProjectLevelStageAdapter.new(build_stage(stage_name), options) + end + + private + + def build_stage(stage_name) + stage_params = stage_params_by_name(stage_name).merge(project: project) + Analytics::CycleAnalytics::ProjectStage.new(stage_params) + end + + def stage_params_by_name(name) + Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(name) + end + + def default_stage_names + Gitlab::Analytics::CycleAnalytics::DefaultStages.symbolized_stage_names + end + end + end +end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 14ec3a6838c..71110ef0696 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -134,6 +134,8 @@ module Ci end scope :order_contacted_at_asc, -> { order(contacted_at: :asc) } + scope :order_contacted_at_desc, -> { order(contacted_at: :desc) } + scope :order_created_at_asc, -> { order(created_at: :asc) } scope :order_created_at_desc, -> { order(created_at: :desc) } scope :with_tags, -> { preload(:tags) } @@ -190,8 +192,13 @@ module Ci end def self.order_by(order) - if order == 'contacted_asc' + case order + when 'contacted_asc' order_contacted_at_asc + when 'contacted_desc' + order_contacted_at_desc + when 'created_at_asc' + order_created_at_asc else order_created_at_desc end diff --git a/app/models/clusters/applications/fluentd.rb b/app/models/clusters/applications/fluentd.rb index 8d0bf7b6321..c5d674c1908 100644 --- a/app/models/clusters/applications/fluentd.rb +++ b/app/models/clusters/applications/fluentd.rb @@ -12,11 +12,13 @@ module Clusters include ::Clusters::Concerns::ApplicationStatus include ::Clusters::Concerns::ApplicationVersion include ::Clusters::Concerns::ApplicationData + include IgnorableColumns default_value_for :version, VERSION default_value_for :port, 514 default_value_for :protocol, :tcp - default_value_for :waf_log_enabled, false + + ignore_column :waf_log_enabled, remove_with: '14.2', remove_after: '2021-07-22' enum protocol: { tcp: 0, udp: 1 } @@ -48,9 +50,7 @@ module Clusters private def has_at_least_one_log_enabled? - if !waf_log_enabled && !cilium_log_enabled - errors.add(:base, _("At least one logging option is required to be enabled")) - end + errors.add(:base, _("At least one logging option is required to be enabled")) unless cilium_log_enabled end def content_values @@ -113,7 +113,6 @@ module Clusters def path_to_logs path = [] - path << "/var/log/containers/*#{Ingress::MODSECURITY_LOG_CONTAINER_NAME}*.log" if waf_log_enabled path << "/var/log/containers/*#{CILIUM_CONTAINER_NAME}*.log" if cilium_log_enabled path.join(',') end diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index e7d4d737b8e..3a8c314efe4 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -7,10 +7,6 @@ module Clusters class Ingress < ApplicationRecord VERSION = '1.40.2' INGRESS_CONTAINER_NAME = 'nginx-ingress-controller' - MODSECURITY_LOG_CONTAINER_NAME = 'modsecurity-log' - MODSECURITY_MODE_LOGGING = "DetectionOnly" - MODSECURITY_MODE_BLOCKING = "On" - MODSECURITY_OWASP_RULES_FILE = "/etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf" self.table_name = 'clusters_applications_ingress' @@ -20,22 +16,18 @@ module Clusters include ::Clusters::Concerns::ApplicationData include AfterCommitQueue include UsageStatistics + include IgnorableColumns default_value_for :ingress_type, :nginx - default_value_for :modsecurity_enabled, true default_value_for :version, VERSION - default_value_for :modsecurity_mode, :logging + + ignore_column :modsecurity_enabled, remove_with: '14.2', remove_after: '2021-07-22' + ignore_column :modsecurity_mode, remove_with: '14.2', remove_after: '2021-07-22' enum ingress_type: { nginx: 1 } - enum modsecurity_mode: { logging: 0, blocking: 1 } - - scope :modsecurity_not_installed, -> { where(modsecurity_enabled: nil) } - scope :modsecurity_enabled, -> { where(modsecurity_enabled: true) } - scope :modsecurity_disabled, -> { where(modsecurity_enabled: false) } - FETCH_IP_ADDRESS_DELAY = 30.seconds state_machine :status do @@ -92,96 +84,13 @@ module Clusters private - def specification - return {} unless modsecurity_enabled - - { - "controller" => { - "config" => { - "enable-modsecurity" => "true", - "enable-owasp-modsecurity-crs" => "false", - "modsecurity-snippet" => modsecurity_snippet_content, - "modsecurity.conf" => modsecurity_config_content - }, - "extraContainers" => [ - { - "name" => MODSECURITY_LOG_CONTAINER_NAME, - "image" => "busybox", - "args" => [ - "/bin/sh", - "-c", - "tail -F /var/log/modsec/audit.log" - ], - "volumeMounts" => [ - { - "name" => "modsecurity-log-volume", - "mountPath" => "/var/log/modsec", - "readOnly" => true - } - ], - "livenessProbe" => { - "exec" => { - "command" => [ - "ls", - "/var/log/modsec/audit.log" - ] - } - } - } - ], - "extraVolumeMounts" => [ - { - "name" => "modsecurity-template-volume", - "mountPath" => "/etc/nginx/modsecurity/modsecurity.conf", - "subPath" => "modsecurity.conf" - }, - { - "name" => "modsecurity-log-volume", - "mountPath" => "/var/log/modsec" - } - ], - "extraVolumes" => [ - { - "name" => "modsecurity-template-volume", - "configMap" => { - "name" => "ingress-#{INGRESS_CONTAINER_NAME}", - "items" => [ - { - "key" => "modsecurity.conf", - "path" => "modsecurity.conf" - } - ] - } - }, - { - "name" => "modsecurity-log-volume", - "emptyDir" => {} - } - ] - } - } - end - - def modsecurity_config_content - File.read(modsecurity_config_file_path) - end - - def modsecurity_config_file_path - Rails.root.join('vendor', 'ingress', 'modsecurity.conf') - end - def content_values - YAML.load_file(chart_values_file).deep_merge!(specification) + YAML.load_file(chart_values_file) end def application_jupyter_installed? cluster.application_jupyter&.installed? end - - def modsecurity_snippet_content - sec_rule_engine = logging? ? MODSECURITY_MODE_LOGGING : MODSECURITY_MODE_BLOCKING - "SecRuleEngine #{sec_rule_engine}\nInclude #{MODSECURITY_OWASP_RULES_FILE}" - end end end end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 257a7043ce2..aeebd2b368e 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -138,7 +138,6 @@ module Clusters scope :gcp_installed, -> { gcp_provided.joins(:provider_gcp).merge(Clusters::Providers::Gcp.with_status(:created)) } scope :aws_installed, -> { aws_provided.joins(:provider_aws).merge(Clusters::Providers::Aws.with_status(:created)) } - scope :with_enabled_modsecurity, -> { joins(:application_ingress).merge(::Clusters::Applications::Ingress.modsecurity_enabled) } scope :with_available_elasticstack, -> { joins(:application_elastic_stack).merge(::Clusters::Applications::ElasticStack.available) } scope :with_available_cilium, -> { joins(:application_cilium).merge(::Clusters::Applications::Cilium.available) } scope :distinct_with_deployed_environments, -> { joins(:environments).merge(::Deployment.success).distinct } diff --git a/app/models/cycle_analytics/project_level.rb b/app/models/cycle_analytics/project_level.rb deleted file mode 100644 index 5bd07b3f6c3..00000000000 --- a/app/models/cycle_analytics/project_level.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -module CycleAnalytics - class ProjectLevel - attr_reader :project, :options - - def initialize(project, options:) - @project = project - @options = options.merge(project: project) - end - - def summary - @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project, - from: options[:from], - to: options[:to], - current_user: options[:current_user]).data - end - - def permissions(user:) - Gitlab::CycleAnalytics::Permissions.get(user: user, project: project) - end - - def stats - @stats ||= default_stage_names.map do |stage_name| - self[stage_name].as_json - end - end - - def [](stage_name) - CycleAnalytics::ProjectLevelStageAdapter.new(build_stage(stage_name), options) - end - - private - - def build_stage(stage_name) - stage_params = stage_params_by_name(stage_name).merge(project: project) - Analytics::CycleAnalytics::ProjectStage.new(stage_params) - end - - def stage_params_by_name(name) - Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(name) - end - - def default_stage_names - Gitlab::Analytics::CycleAnalytics::DefaultStages.symbolized_stage_names - end - end -end diff --git a/app/models/members/last_group_owner_assigner.rb b/app/models/members/last_group_owner_assigner.rb index 64decb1df36..dcf0a2d0ad3 100644 --- a/app/models/members/last_group_owner_assigner.rb +++ b/app/models/members/last_group_owner_assigner.rb @@ -1,46 +1,44 @@ # frozen_string_literal: true -module Members - class LastGroupOwnerAssigner - def initialize(group, members) - @group = group - @members = members - end +class LastGroupOwnerAssigner + def initialize(group, members) + @group = group + @members = members + end - def execute - @last_blocked_owner = no_owners_in_heirarchy? && group.single_blocked_owner? - @group_single_owner = owners.size == 1 + def execute + @last_blocked_owner = no_owners_in_heirarchy? && group.single_blocked_owner? + @group_single_owner = owners.size == 1 - members.each { |member| set_last_owner(member) } - end + members.each { |member| set_last_owner(member) } + end - private + private - attr_reader :group, :members, :last_blocked_owner, :group_single_owner + attr_reader :group, :members, :last_blocked_owner, :group_single_owner - def no_owners_in_heirarchy? - owners.empty? - end + def no_owners_in_heirarchy? + owners.empty? + end - def set_last_owner(member) - member.last_owner = member.id.in?(owner_ids) && group_single_owner - member.last_blocked_owner = member.id.in?(blocked_owner_ids) && last_blocked_owner - end + def set_last_owner(member) + member.last_owner = member.id.in?(owner_ids) && group_single_owner + member.last_blocked_owner = member.id.in?(blocked_owner_ids) && last_blocked_owner + end - def owner_ids - @owner_ids ||= owners.where(id: member_ids).ids - end + def owner_ids + @owner_ids ||= owners.where(id: member_ids).ids + end - def blocked_owner_ids - @blocked_owner_ids ||= group.blocked_owners.where(id: member_ids).ids - end + def blocked_owner_ids + @blocked_owner_ids ||= group.blocked_owners.where(id: member_ids).ids + end - def member_ids - @members_ids ||= members.pluck(:id) - end + def member_ids + @members_ids ||= members.pluck(:id) + end - def owners - @owners ||= group.members_with_parents.owners.load - end + def owners + @owners ||= group.members_with_parents.owners.load end end diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb index 6b9a3ce114b..fab590dbe09 100644 --- a/app/serializers/cluster_application_entity.rb +++ b/app/serializers/cluster_application_entity.rb @@ -10,15 +10,12 @@ class ClusterApplicationEntity < Grape::Entity expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) } expose :email, if: -> (e, _) { e.respond_to?(:email) } expose :stack, if: -> (e, _) { e.respond_to?(:stack) } - expose :modsecurity_enabled, if: -> (e, _) { e.respond_to?(:modsecurity_enabled) } expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) } expose :can_uninstall?, as: :can_uninstall expose :available_domains, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:available_domains) } expose :pages_domain, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:pages_domain) } - expose :modsecurity_mode, if: -> (e, _) { e.respond_to?(:modsecurity_mode) } expose :host, if: -> (e, _) { e.respond_to?(:host) } expose :port, if: -> (e, _) { e.respond_to?(:port) } expose :protocol, if: -> (e, _) { e.respond_to?(:protocol) } - expose :waf_log_enabled, if: -> (e, _) { e.respond_to?(:waf_log_enabled) } expose :cilium_log_enabled, if: -> (e, _) { e.respond_to?(:cilium_log_enabled) } end diff --git a/app/serializers/member_serializer.rb b/app/serializers/member_serializer.rb index 462f6be5d04..ad258b0ef1e 100644 --- a/app/serializers/member_serializer.rb +++ b/app/serializers/member_serializer.rb @@ -4,7 +4,7 @@ class MemberSerializer < BaseSerializer entity MemberEntity def represent(members, opts = {}) - Members::LastGroupOwnerAssigner.new(opts[:group], members).execute unless opts[:source].is_a?(Project) + LastGroupOwnerAssigner.new(opts[:group], members).execute unless opts[:source].is_a?(Project) super(members, opts) end diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb index 489360f9070..47d6fbbeda2 100644 --- a/app/services/clusters/applications/base_service.rb +++ b/app/services/clusters/applications/base_service.rb @@ -29,14 +29,6 @@ module Clusters application.stack = params[:stack] end - if application.has_attribute?(:modsecurity_enabled) - application.modsecurity_enabled = params[:modsecurity_enabled] || false - end - - if application.has_attribute?(:modsecurity_mode) - application.modsecurity_mode = params[:modsecurity_mode] || 0 - end - apply_fluentd_related_attributes(application) if application.respond_to?(:oauth_application) diff --git a/app/views/admin/application_settings/_diff_limits.html.haml b/app/views/admin/application_settings/_diff_limits.html.haml index 7286fffcaf6..ff22f6181b3 100644 --- a/app/views/admin/application_settings/_diff_limits.html.haml +++ b/app/views/admin/application_settings/_diff_limits.html.haml @@ -3,13 +3,11 @@ %fieldset .form-group - = f.label :diff_max_patch_bytes, 'Maximum diff patch size (Bytes)', class: 'label-light' + = f.label :diff_max_patch_bytes, 'Maximum diff patch size in bytes', class: 'label-light' = f.number_field :diff_max_patch_bytes, class: 'form-control gl-form-input' %span.form-text.text-muted - Diff files surpassing this limit will be presented as 'too large' - and won't be expandable. + Collapse diffs larger than this size, and show a 'too large' message instead. = link_to sprite_icon('question-o'), - help_page_path('user/admin_area/diff_limits', - anchor: 'maximum-diff-patch-size') + help_page_path('user/admin_area/diff_limits') = f.submit _('Save changes'), class: 'gl-button btn btn-confirm' diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml index 3f1c363bcb9..0fbbef02613 100644 --- a/app/views/admin/application_settings/general.html.haml +++ b/app/views/admin/application_settings/general.html.haml @@ -31,7 +31,7 @@ %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded_by_default? ? _('Collapse') : _('Expand') %p - = _('Diff content limits') + = _('Set size limits for displaying diffs in the browser.') .settings-content = render 'diff_limits' diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml index 267fc3ae986..0716eda79a8 100644 --- a/app/views/projects/forks/new.html.haml +++ b/app/views/projects/forks/new.html.haml @@ -10,7 +10,8 @@ project_name: @project.name, project_path: @project.path, project_description: @project.description, - project_visibility: @project.visibility } } + project_visibility: @project.visibility, + restricted_visibility_levels: Gitlab::CurrentSettings.restricted_visibility_levels.to_json } } - else .row.gl-mt-3 .col-lg-3 diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml index 70e931ac164..1f391e8a321 100644 --- a/app/views/shared/issuable/form/_branch_chooser.html.haml +++ b/app/views/shared/issuable/form/_branch_chooser.html.haml @@ -37,10 +37,12 @@ data: { placeholder: _('Select branch'), endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }}) - if source_level < target_level - .gl-alert.gl-alert-warning.gl-mt-4 - = sprite_icon('warning', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') - .gl-alert-body - = visibilityMismatchString - %br - = _('Review the target project before submitting to avoid exposing %{source} changes.') % { source: source_visibility } + .gl-alert.gl-alert-warning.gl-alert-not-dismissible.gl-max-content.gl-mt-4 + .gl-alert-container + .gl-alert-content{ role: 'alert' } + = sprite_icon('warning', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') + .gl-alert-body + = visibilityMismatchString + %br + = _('Review the target project before submitting to avoid exposing %{source} changes.') % { source: source_visibility } %hr diff --git a/app/workers/ssh_keys/expired_notification_worker.rb b/app/workers/ssh_keys/expired_notification_worker.rb index e8baf0c28dd..b67849942b0 100644 --- a/app/workers/ssh_keys/expired_notification_worker.rb +++ b/app/workers/ssh_keys/expired_notification_worker.rb @@ -15,8 +15,6 @@ module SshKeys # rubocop: disable CodeReuse/ActiveRecord def perform - return unless ::Feature.enabled?(:ssh_key_expiration_email_notification, default_enabled: :yaml) - order = Gitlab::Pagination::Keyset::Order.build([ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new( attribute_name: 'expires_at_utc', diff --git a/app/workers/ssh_keys/expiring_soon_notification_worker.rb b/app/workers/ssh_keys/expiring_soon_notification_worker.rb index 1ec655b5cf5..d87e31c36a5 100644 --- a/app/workers/ssh_keys/expiring_soon_notification_worker.rb +++ b/app/workers/ssh_keys/expiring_soon_notification_worker.rb @@ -12,8 +12,6 @@ module SshKeys idempotent! def perform - return unless ::Feature.enabled?(:ssh_key_expiration_email_notification, default_enabled: :yaml) - # rubocop:disable CodeReuse/ActiveRecord User.with_ssh_key_expiring_soon.find_each(batch_size: 10_000) do |user| with_context(user: user) do diff --git a/config/feature_flags/development/background_pipeline_retry_endpoint.yml b/config/feature_flags/development/background_pipeline_retry_endpoint.yml deleted file mode 100644 index 57f90d01e2c..00000000000 --- a/config/feature_flags/development/background_pipeline_retry_endpoint.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: background_pipeline_retry_endpoint -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61270 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330915 -milestone: '13.12' -type: development -group: group::pipeline authoring -default_enabled: false diff --git a/config/feature_flags/development/ci_dynamic_child_pipeline.yml b/config/feature_flags/development/ci_dynamic_child_pipeline.yml deleted file mode 100644 index ad4b7bcadb2..00000000000 --- a/config/feature_flags/development/ci_dynamic_child_pipeline.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_dynamic_child_pipeline -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23790 -rollout_issue_url: -milestone: '12.9' -type: development -group: group::pipeline execution -default_enabled: true diff --git a/config/feature_flags/development/remove_release_notes_from_tags_api.yml b/config/feature_flags/development/sidekiq_load_balancing_rotate_up_to_date_replica.yml index 6fb26d9bea5..4532cc0a59d 100644 --- a/config/feature_flags/development/remove_release_notes_from_tags_api.yml +++ b/config/feature_flags/development/sidekiq_load_balancing_rotate_up_to_date_replica.yml @@ -1,8 +1,8 @@ --- -name: remove_release_notes_from_tags_api -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63392 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290311 +name: sidekiq_load_balancing_rotate_up_to_date_replica +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63413/ +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/333153 milestone: '14.0' type: development -group: group::release +group: group::memory default_enabled: false diff --git a/config/feature_flags/development/ssh_key_expiration_email_notification.yml b/config/feature_flags/development/ssh_key_expiration_email_notification.yml deleted file mode 100644 index f1d081dad18..00000000000 --- a/config/feature_flags/development/ssh_key_expiration_email_notification.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ssh_key_expiration_email_notification -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56888 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326386 -milestone: '13.11' -type: development -group: group::compliance -default_enabled: true diff --git a/config/metrics/counts_28d/20210216183828_dependency_scanning_scans.yml b/config/metrics/counts_28d/20210216183828_dependency_scanning_scans.yml deleted file mode 100644 index f4eb1e39947..00000000000 --- a/config/metrics/counts_28d/20210216183828_dependency_scanning_scans.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -key_path: usage_activity_by_stage_monthly.secure.dependency_scanning_scans -description: '' -product_section: '' -product_stage: '' -product_group: '' -product_category: '' -value_type: number -status: data_available -time_frame: 28d -data_source: -distribution: -- ce -tier: -- free -skip_validation: true diff --git a/config/metrics/license/20210204124854_license_management_jobs.yml b/config/metrics/license/20210204124854_license_management_jobs.yml deleted file mode 100644 index 81e3e17e24b..00000000000 --- a/config/metrics/license/20210204124854_license_management_jobs.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -key_path: counts.license_management_jobs -description: Name on the GitLab license -product_section: growth -product_stage: growth -product_group: group::product intelligence -product_category: collection -value_type: number -status: data_available -time_frame: none -data_source: database -distribution: -- ce -tier: -- premium -- ultimate -skip_validation: true diff --git a/config/routes/project.rb b/config/routes/project.rb index 22db62c9e07..641ca399547 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -273,6 +273,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do resources :value_streams, only: [:index] do resources :stages, only: [:index] end + resource :summary, controller: :summary, only: :show end end diff --git a/db/migrate/20210601125410_add_runners_created_at_index.rb b/db/migrate/20210601125410_add_runners_created_at_index.rb new file mode 100644 index 00000000000..6a6be6d8480 --- /dev/null +++ b/db/migrate/20210601125410_add_runners_created_at_index.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# See https://docs.gitlab.com/ee/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddRunnersCreatedAtIndex < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + def up + add_concurrent_index :ci_runners, [:created_at, :id], order: { id: :desc }, name: 'index_ci_runners_on_created_at_and_id_desc' + add_concurrent_index :ci_runners, [:created_at, :id], order: { created_at: :desc, id: :desc }, name: 'index_ci_runners_on_created_at_desc_and_id_desc' + end + + def down + remove_concurrent_index :ci_runners, [:created_at, :id], order: { id: :desc }, name: 'index_ci_runners_on_created_at_and_id_desc' + remove_concurrent_index :ci_runners, [:created_at, :id], order: { created_at: :desc, id: :desc }, name: 'index_ci_runners_on_created_at_desc_and_id_desc' + end +end diff --git a/db/migrate/20210601133459_replace_runners_contacted_at_index.rb b/db/migrate/20210601133459_replace_runners_contacted_at_index.rb new file mode 100644 index 00000000000..a0a933721f0 --- /dev/null +++ b/db/migrate/20210601133459_replace_runners_contacted_at_index.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# See https://docs.gitlab.com/ee/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ReplaceRunnersContactedAtIndex < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + OLD_INDEX_NAME = 'index_ci_runners_on_contacted_at' + + def up + add_concurrent_index :ci_runners, [:contacted_at, :id], order: { id: :desc }, name: 'index_ci_runners_on_contacted_at_and_id_desc', using: 'btree' + add_concurrent_index :ci_runners, [:contacted_at, :id], order: { contacted_at: :desc, id: :desc }, name: 'index_ci_runners_on_contacted_at_desc_and_id_desc', using: 'btree' + + remove_concurrent_index_by_name :ci_runners, OLD_INDEX_NAME + end + + def down + remove_concurrent_index_by_name :ci_runners, 'index_ci_runners_on_contacted_at_and_id_desc' + remove_concurrent_index_by_name :ci_runners, 'index_ci_runners_on_contacted_at_desc_and_id_desc' + + add_concurrent_index :ci_runners, :contacted_at, name: OLD_INDEX_NAME, using: 'btree' + end +end diff --git a/db/schema_migrations/20210601125410 b/db/schema_migrations/20210601125410 new file mode 100644 index 00000000000..241a60e966a --- /dev/null +++ b/db/schema_migrations/20210601125410 @@ -0,0 +1 @@ +fc500e4dd555a6baad91ad3c9fb8a2f8541e1613dd64afdbdd28b19447a28caf
\ No newline at end of file diff --git a/db/schema_migrations/20210601133459 b/db/schema_migrations/20210601133459 new file mode 100644 index 00000000000..2f40fffa40f --- /dev/null +++ b/db/schema_migrations/20210601133459 @@ -0,0 +1 @@ +4dcf6277439e8abe52534540100fa621fedcecb3eaf71ad5685ac0230cd2e5bb
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 05cbb357513..3c96987bb5c 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -22904,7 +22904,13 @@ CREATE INDEX index_ci_runner_projects_on_project_id ON ci_runner_projects USING CREATE INDEX index_ci_runner_projects_on_runner_id ON ci_runner_projects USING btree (runner_id); -CREATE INDEX index_ci_runners_on_contacted_at ON ci_runners USING btree (contacted_at); +CREATE INDEX index_ci_runners_on_contacted_at_and_id_desc ON ci_runners USING btree (contacted_at, id DESC); + +CREATE INDEX index_ci_runners_on_contacted_at_desc_and_id_desc ON ci_runners USING btree (contacted_at DESC, id DESC); + +CREATE INDEX index_ci_runners_on_created_at_and_id_desc ON ci_runners USING btree (created_at, id DESC); + +CREATE INDEX index_ci_runners_on_created_at_desc_and_id_desc ON ci_runners USING btree (created_at DESC, id DESC); CREATE INDEX index_ci_runners_on_description_trigram ON ci_runners USING gin (description gin_trgm_ops); diff --git a/doc/administration/file_hooks.md b/doc/administration/file_hooks.md index 159f46a1146..f73c961f541 100644 --- a/doc/administration/file_hooks.md +++ b/doc/administration/file_hooks.md @@ -63,8 +63,11 @@ need to restart GitLab to apply a new file hook. If a file hook executes with non-zero exit code or GitLab fails to execute it, a message is logged to: -- `gitlab-rails/plugin.log` in an Omnibus installation. -- `log/plugin.log` in a source installation. +- `gitlab-rails/file_hook.log` in an Omnibus installation. +- `log/file_hook.log` in a source installation. + +NOTE: +Before 14.0 release, the file name was `plugin.log` ## Creating file hooks diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index be544331e8b..843f5c2d413 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -766,6 +766,7 @@ Input type: `CiCdSettingsUpdateInput` | ---- | ---- | ----------- | | <a id="mutationcicdsettingsupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | <a id="mutationcicdsettingsupdatefullpath"></a>`fullPath` | [`ID!`](#id) | Full Path of the project the settings belong to. | +| <a id="mutationcicdsettingsupdatejobtokenscopeenabled"></a>`jobTokenScopeEnabled` | [`Boolean`](#boolean) | Indicates CI job tokens generated in this project have restricted access to resources. | | <a id="mutationcicdsettingsupdatekeeplatestartifact"></a>`keepLatestArtifact` | [`Boolean`](#boolean) | Indicates if the latest artifact should be kept for this project. | | <a id="mutationcicdsettingsupdatemergepipelinesenabled"></a>`mergePipelinesEnabled` | [`Boolean`](#boolean) | Indicates if merge pipelines are enabled for the project. | | <a id="mutationcicdsettingsupdatemergetrainsenabled"></a>`mergeTrainsEnabled` | [`Boolean`](#boolean) | Indicates if merge trains are enabled for the project. | @@ -11853,6 +11854,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount). | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="projectcicdsettingjobtokenscopeenabled"></a>`jobTokenScopeEnabled` | [`Boolean`](#boolean) | Indicates CI job tokens generated in this project have restricted access to resources. | | <a id="projectcicdsettingkeeplatestartifact"></a>`keepLatestArtifact` | [`Boolean`](#boolean) | Whether to keep the latest builds artifacts. | | <a id="projectcicdsettingmergepipelinesenabled"></a>`mergePipelinesEnabled` | [`Boolean`](#boolean) | Whether merge pipelines are enabled. | | <a id="projectcicdsettingmergetrainsenabled"></a>`mergeTrainsEnabled` | [`Boolean`](#boolean) | Whether merge trains are enabled. | @@ -13841,7 +13843,9 @@ Values for sorting runners. | Value | Description | | ----- | ----------- | | <a id="cirunnersortcontacted_asc"></a>`CONTACTED_ASC` | Ordered by contacted_at in ascending order. | -| <a id="cirunnersortcreated_desc"></a>`CREATED_DESC` | Ordered by created_date in descending order. | +| <a id="cirunnersortcontacted_desc"></a>`CONTACTED_DESC` | Ordered by contacted_at in descending order. | +| <a id="cirunnersortcreated_asc"></a>`CREATED_ASC` | Ordered by created_at in ascending order. | +| <a id="cirunnersortcreated_desc"></a>`CREATED_DESC` | Ordered by created_at in descending order. | ### `CiRunnerStatus` diff --git a/doc/api/tags.md b/doc/api/tags.md index 3ac4e8bb6ab..53a981256aa 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -123,7 +123,6 @@ Parameters: | `tag_name` | string | yes | The name of a tag | | `ref` | string | yes | Create tag using commit SHA, another tag name, or branch name | | `message` | string | no | Creates annotated tag | -| `release_description` | string | no | This parameter is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311) in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead. | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/tags?tag_name=test&ref=master" @@ -149,10 +148,7 @@ Example response: "committer_email": "jack@example.com", "committed_date": "2012-05-28T04:42:42-07:00" }, - "release": { - "tag_name": "1.0.0", - "description": "Amazing release. Wow" - }, + "release": null, "name": "v1.0.0", "target": "2695effb5807a22ff3d138d593fd856244e155e7", "message": null, @@ -182,82 +178,3 @@ Parameters: | ---------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `tag_name` | string | yes | The name of a tag | - -## Create a new release - -WARNING: -This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) -for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311) -in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead. - -Add release notes to the existing Git tag. If there -already exists a release for the given tag, status code `409` is returned. - -```plaintext -POST /projects/:id/repository/tags/:tag_name/release -``` - -Parameters: - -| Attribute | Type | Required | Description | -| ---------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `tag_name` | string | yes | The name of a tag | - -Request body: - -- `description` (required) - Release notes with Markdown support - -```json -{ - "description": "Amazing release. Wow" -} -``` - -Response: - -```json -{ - "tag_name": "1.0.0", - "description": "Amazing release. Wow" -} -``` - -## Update a release - -WARNING: -This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) -for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311) -in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead. - -Updates the release notes of a given release. - -```plaintext -PUT /projects/:id/repository/tags/:tag_name/release -``` - -Parameters: - -| Attribute | Type | Required | Description | -| ---------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `tag_name` | string | yes | The name of a tag | - -Request body: - -- `description` (required) - Release notes with Markdown support - -```json -{ - "description": "Amazing release. Wow" -} -``` - -Response: - -```json -{ - "tag_name": "1.0.0", - "description": "Amazing release. Wow" -} -``` diff --git a/doc/development/appsec/index.md b/doc/development/appsec/index.md index e8ce885e75d..2ece3fdf4bf 100644 --- a/doc/development/appsec/index.md +++ b/doc/development/appsec/index.md @@ -20,7 +20,7 @@ the feature categories in the [Secure](https://about.gitlab.com/stages-devops-li - `AppSec::ContainerScanning`: Container Scanning code. - `AppSec::Dast`: DAST code. - `AppSec::DependencyScanning`: Dependency Scanning code. - - `AppSec::Fuzzing::Api`: API Fuzzing code. + - `AppSec::Fuzzing::API`: API Fuzzing code. - `AppSec::Fuzzing::Coverage`: Coverage Fuzzing code. - `AppSec::Fuzzing`: Shared fuzzing code. - `AppSec::LicenseCompliance`: License Compliance code. diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index aae62801b62..92a4a13b27d 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -3288,15 +3288,15 @@ Tiers: `free` ### `counts.license_management_jobs` -Name on the GitLab license +Count of License Scanning jobs run -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/license/20210204124854_license_management_jobs.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210204124854_license_management_jobs.yml) -Group: `group::product intelligence` +Group: `group::composition analysis` Status: `data_available` -Tiers: `premium`, `ultimate` +Tiers: `ultimate` ### `counts.licenses_list_views` @@ -17320,7 +17320,7 @@ Tiers: ### `usage_activity_by_stage.secure.dependency_scanning_scans` -Counts dependency scanning jobs +Total number of users running Dependency Scanning Scans [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175220_dependency_scanning_scans.yml) @@ -17416,7 +17416,7 @@ Tiers: `free` ### `usage_activity_by_stage.secure.user_dependency_scanning_jobs` -no idea, Count of Dependency Scanning jobs run, it implies user but AFAIK we don't track per user +Total number of users running Dependency Scanning jobs [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175216_user_dependency_scanning_jobs.yml) @@ -17428,9 +17428,9 @@ Tiers: `ultimate` ### `usage_activity_by_stage.secure.user_license_management_jobs` -no idea, Count of License Scanning jobs run, it implies user but AFAIK we don't track per user +Total number of users running License Scanning jobs -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/license/20210216175218_user_license_management_jobs.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175218_user_license_management_jobs.yml) Group: `group::composition analysis` @@ -19324,7 +19324,7 @@ Tiers: `free` ### `usage_activity_by_stage_monthly.secure.dependency_scanning_pipeline` -no idea, what is this when did it get added? guess pipelines containing a DS job +Count of pipelines with successful Dependency Scanning jobs [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175226_dependency_scanning_pipeline.yml) @@ -19336,15 +19336,15 @@ Tiers: `ultimate` ### `usage_activity_by_stage_monthly.secure.dependency_scanning_scans` -Missing description +Monthly number of users running Dependency Scanning Scans -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183828_dependency_scanning_scans.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216183828_dependency_scanning_scans.yml) -Group: `` +Group: `group::composition analysis` Status: `data_available` -Tiers: `free` +Tiers: `ultimate` ### `usage_activity_by_stage_monthly.secure.sast_pipeline` @@ -19456,7 +19456,7 @@ Tiers: `free` ### `usage_activity_by_stage_monthly.secure.user_dependency_scanning_jobs` -no idea, Count of Dependency Scanning jobs run, it implies user and monthly, but AFAIK we don't track per user +Monthly number of users creating Dependency Scanning jobs [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175222_user_dependency_scanning_jobs.yml) @@ -19468,9 +19468,9 @@ Tiers: `ultimate` ### `usage_activity_by_stage_monthly.secure.user_license_management_jobs` -no idea, Count of License Scanning jobs run, it implies user and monthly, but AFAIK we don't track per user +Monthly number of users running License Scanning jobs -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/license/20210216175224_user_license_management_jobs.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175224_user_license_management_jobs.yml) Group: `group::composition analysis` diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md index c0d232ba491..b9410be791e 100644 --- a/doc/user/profile/notifications.md +++ b/doc/user/profile/notifications.md @@ -176,6 +176,7 @@ Users are notified of the following events: | Event | Sent to | Settings level | |------------------------------|---------------------|------------------------------| | New SSH key added | User | Security email, always sent. | +| SSH key has expired | User | Security email, always sent. | | New email added | User | Security email, always sent. | | Email changed | User | Security email, always sent. | | Password changed | User | Security email, always sent when user changes their own password | diff --git a/lib/api/entities/issue_basic.rb b/lib/api/entities/issue_basic.rb index d27cc5498bd..6c332870228 100644 --- a/lib/api/entities/issue_basic.rb +++ b/lib/api/entities/issue_basic.rb @@ -23,7 +23,7 @@ module API expose :issue_type, as: :type, format_with: :upcase, - documentation: { type: "String", desc: "One of #{Issue.issue_types.keys.map(&:upcase)}" } + documentation: { type: "String", desc: "One of #{::Issue.issue_types.keys.map(&:upcase)}" } expose :assignee, using: ::API::Entities::UserBasic do |issue| issue.assignees.first diff --git a/lib/api/tags.rb b/lib/api/tags.rb index dc9f4cda3d7..6c8e2c69a6d 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -51,14 +51,12 @@ module API end desc 'Create a new repository tag' do - detail 'This optional release_description parameter was deprecated in GitLab 11.7.' success Entities::Tag end params do requires :tag_name, type: String, desc: 'The name of the tag' requires :ref, type: String, desc: 'The commit sha or branch name' optional :message, type: String, desc: 'Specifying a message creates an annotated tag' - optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database (deprecated in GitLab 11.7)' end post ':id/repository/tags', :release_orchestration do deprecate_release_notes unless params[:release_description].blank? @@ -69,19 +67,6 @@ module API .execute(params[:tag_name], params[:ref], params[:message]) if result[:status] == :success - # Release creation with Tags API was deprecated in GitLab 11.7 - if params[:release_description].present? - release_create_params = { - tag: params[:tag_name], - name: params[:tag_name], # Name can be specified in new API - description: params[:release_description] - } - - ::Releases::CreateService - .new(user_project, current_user, release_create_params) - .execute - end - present result[:tag], with: Entities::Tag, project: user_project @@ -111,82 +96,6 @@ module API end end end - - desc 'Add a release note to a tag' do - detail 'This feature was deprecated in GitLab 11.7.' - success Entities::TagRelease - end - params do - requires :tag_name, type: String, desc: 'The name of the tag', as: :tag - requires :description, type: String, desc: 'Release notes with markdown support' - end - post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do - deprecate_release_notes - authorize_create_release! - - ## - # Legacy API does not support tag auto creation. - not_found!('Tag') unless user_project.repository.find_tag(params[:tag]) - - release_create_params = { - tag: params[:tag], - name: params[:tag], # Name can be specified in new API - description: params[:description] - } - - result = ::Releases::CreateService - .new(user_project, current_user, release_create_params) - .execute - - if result[:status] == :success - present result[:release], with: Entities::TagRelease - else - render_api_error!(result[:message], result[:http_status]) - end - end - - desc "Update a tag's release note" do - detail 'This feature was deprecated in GitLab 11.7.' - success Entities::TagRelease - end - params do - requires :tag_name, type: String, desc: 'The name of the tag', as: :tag - requires :description, type: String, desc: 'Release notes with markdown support' - end - put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do - deprecate_release_notes - authorize_update_release! - - result = ::Releases::UpdateService - .new(user_project, current_user, declared_params(include_missing: false)) - .execute - - if result[:status] == :success - present result[:release], with: Entities::TagRelease - else - render_api_error!(result[:message], result[:http_status]) - end - end - end - - helpers do - def authorize_create_release! - authorize! :create_release, user_project - end - - def authorize_update_release! - authorize! :update_release, release - end - - def release - @release ||= user_project.releases.find_by_tag(params[:tag]) - end - - def deprecate_release_notes - return unless Feature.enabled?(:remove_release_notes_from_tags_api, user_project, default_enabled: :yaml) - - render_api_error!("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311", 400) - end end end end diff --git a/lib/gitlab/ci/config/external/file/artifact.rb b/lib/gitlab/ci/config/external/file/artifact.rb index a8f78b62d8d..e6ff33d6f79 100644 --- a/lib/gitlab/ci/config/external/file/artifact.rb +++ b/lib/gitlab/ci/config/external/file/artifact.rb @@ -28,11 +28,6 @@ module Gitlab end end - def matching? - super && - Feature.enabled?(:ci_dynamic_child_pipeline, project, default_enabled: true) - end - private def project diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index d772024542c..fe69a170404 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -41,10 +41,6 @@ module Gitlab def self.gldropdown_tags_enabled? ::Feature.enabled?(:gldropdown_tags, default_enabled: :yaml) end - - def self.background_pipeline_retry_endpoint?(project) - ::Feature.enabled?(:background_pipeline_retry_endpoint, project) - end end end end diff --git a/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml b/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml index 4dc88418671..07d0de5f9e5 100644 --- a/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml @@ -16,7 +16,7 @@ build-job: # This job runs in the build stage, which runs first. stage: build script: - echo "Compiling the code..." - - echo "Compile complete. + - echo "Compile complete." unit-test-job: # This job runs in the test stage. stage: test # It only starts when the job in the build stage completes successfully. diff --git a/lib/gitlab/cycle_analytics/stage_summary.rb b/lib/gitlab/cycle_analytics/stage_summary.rb index 7559cd376bf..b309802f296 100644 --- a/lib/gitlab/cycle_analytics/stage_summary.rb +++ b/lib/gitlab/cycle_analytics/stage_summary.rb @@ -3,10 +3,9 @@ module Gitlab module CycleAnalytics class StageSummary - def initialize(project, from:, to: nil, current_user:) + def initialize(project, options:, current_user:) @project = project - @from = from - @to = to + @options = options @current_user = current_user end @@ -20,15 +19,15 @@ module Gitlab private def issue_stats - serialize(Summary::Issue.new(project: @project, from: @from, to: @to, current_user: @current_user)) + serialize(Summary::Issue.new(project: @project, options: @options, current_user: @current_user)) end def commit_stats - serialize(Summary::Commit.new(project: @project, from: @from, to: @to)) + serialize(Summary::Commit.new(project: @project, options: @options)) end def deployments_summary - @deployments_summary ||= Summary::Deploy.new(project: @project, from: @from, to: @to) + @deployments_summary ||= Summary::Deploy.new(project: @project, options: @options) end def deploy_stats @@ -39,8 +38,7 @@ module Gitlab serialize( Summary::DeploymentFrequency.new( deployments: deployments_summary.value.raw_value, - from: @from, - to: @to), + options: @options), with_unit: true ) end @@ -50,8 +48,7 @@ module Gitlab end def serialize(summary_object, with_unit: false) - AnalyticsSummarySerializer.new.represent( - summary_object, with_unit: with_unit) + AnalyticsSummarySerializer.new.represent(summary_object, with_unit: with_unit) end end end diff --git a/lib/gitlab/cycle_analytics/summary/base.rb b/lib/gitlab/cycle_analytics/summary/base.rb index 67ad75652b0..50a8f189df0 100644 --- a/lib/gitlab/cycle_analytics/summary/base.rb +++ b/lib/gitlab/cycle_analytics/summary/base.rb @@ -4,10 +4,9 @@ module Gitlab module CycleAnalytics module Summary class Base - def initialize(project:, from:, to: nil) + def initialize(project:, options:) @project = project - @from = from - @to = to + @options = options end def title diff --git a/lib/gitlab/cycle_analytics/summary/commit.rb b/lib/gitlab/cycle_analytics/summary/commit.rb index 1dc9d5de966..fb55c3df869 100644 --- a/lib/gitlab/cycle_analytics/summary/commit.rb +++ b/lib/gitlab/cycle_analytics/summary/commit.rb @@ -21,7 +21,7 @@ module Gitlab def commits_count return unless ref - @commits_count ||= gitaly_commit_client.commit_count(ref, after: @from, before: @to) + @commits_count ||= gitaly_commit_client.commit_count(ref, after: @options[:from], before: @options[:to]) end def gitaly_commit_client diff --git a/lib/gitlab/cycle_analytics/summary/deploy.rb b/lib/gitlab/cycle_analytics/summary/deploy.rb index e5bf6ef616f..ea16226a865 100644 --- a/lib/gitlab/cycle_analytics/summary/deploy.rb +++ b/lib/gitlab/cycle_analytics/summary/deploy.rb @@ -16,7 +16,7 @@ module Gitlab def deployments_count DeploymentsFinder - .new(project: @project, finished_after: @from, finished_before: @to, status: :success, order_by: :finished_at) + .new(project: @project, finished_after: @options[:from], finished_before: @options[:to], status: :success, order_by: :finished_at) .execute .count end diff --git a/lib/gitlab/cycle_analytics/summary/deployment_frequency.rb b/lib/gitlab/cycle_analytics/summary/deployment_frequency.rb index 00676a02a6f..1947866d772 100644 --- a/lib/gitlab/cycle_analytics/summary/deployment_frequency.rb +++ b/lib/gitlab/cycle_analytics/summary/deployment_frequency.rb @@ -6,10 +6,10 @@ module Gitlab class DeploymentFrequency < Base include SummaryHelper - def initialize(deployments:, from:, to: nil, project: nil) + def initialize(deployments:, options:, project: nil) @deployments = deployments - super(project: project, from: from, to: to) + super(project: project, options: options) end def title @@ -17,7 +17,7 @@ module Gitlab end def value - @value ||= frequency(@deployments, @from, @to || Time.now) + @value ||= frequency(@deployments, @options[:from], @options[:to] || Time.current) end def unit diff --git a/lib/gitlab/cycle_analytics/summary/issue.rb b/lib/gitlab/cycle_analytics/summary/issue.rb index 462fd4c2d3d..34e0d34b960 100644 --- a/lib/gitlab/cycle_analytics/summary/issue.rb +++ b/lib/gitlab/cycle_analytics/summary/issue.rb @@ -4,10 +4,9 @@ module Gitlab module CycleAnalytics module Summary class Issue < Base - def initialize(project:, from:, to: nil, current_user:) + def initialize(project:, options:, current_user:) @project = project - @from = from - @to = to + @options = options @current_user = current_user end @@ -23,10 +22,18 @@ module Gitlab def issues_count IssuesFinder - .new(@current_user, project_id: @project.id, created_after: @from, created_before: @to) + .new(@current_user, finder_params) .execute .count end + + def finder_params + @options.dup.tap do |hash| + hash[:created_after] = hash.delete(:from) + hash[:created_before] = hash.delete(:to) + hash[:project_id] = @project.id + end + end end end end diff --git a/lib/gitlab/database/load_balancing/load_balancer.rb b/lib/gitlab/database/load_balancing/load_balancer.rb index f655bb46023..a833bb8491f 100644 --- a/lib/gitlab/database/load_balancing/load_balancer.rb +++ b/lib/gitlab/database/load_balancing/load_balancer.rb @@ -176,6 +176,20 @@ module Gitlab true end + # Returns true if there was at least one host that has caught up with the given transaction. + # Similar to `#select_caught_up_hosts`, picks a random host, to rotate replicas we use. + # Unlike `#select_caught_up_hosts`, does not iterate over all hosts if finds any. + def select_up_to_date_host(location) + all_hosts = @host_list.hosts.shuffle + host = all_hosts.find { |host| host.caught_up?(location) } + + return false unless host + + RequestStore[CACHE_KEY] = host + + true + end + def set_consistent_hosts_for_request(hosts) RequestStore[VALID_HOSTS_CACHE_KEY] = hosts end diff --git a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb index 3f0d39b811f..9bd0adf8dbd 100644 --- a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb +++ b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb @@ -59,7 +59,11 @@ module Gitlab end def replica_caught_up?(location) - load_balancer.host.caught_up?(location) + if Feature.enabled?(:sidekiq_load_balancing_rotate_up_to_date_replica) + load_balancer.select_up_to_date_host(location) + else + load_balancer.host.caught_up?(location) + end end end end diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 3a94e109d2a..d6cc4310438 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -5,7 +5,7 @@ module Gitlab module MigrationHelpers include Migrations::BackgroundMigrationHelpers include DynamicModelHelpers - include Migrations::RenameTableHelpers + include RenameTableHelpers # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS MAX_IDENTIFIER_NAME_LENGTH = 63 diff --git a/lib/gitlab/file_hook_logger.rb b/lib/gitlab/file_hook_logger.rb index c5e69172016..4d6a650161f 100644 --- a/lib/gitlab/file_hook_logger.rb +++ b/lib/gitlab/file_hook_logger.rb @@ -3,7 +3,7 @@ module Gitlab class FileHookLogger < Gitlab::Logger def self.file_name_noext - 'plugin' + 'file_hook' end end end diff --git a/lib/gitlab/graphql.rb b/lib/gitlab/graphql.rb deleted file mode 100644 index 74c04e5380e..00000000000 --- a/lib/gitlab/graphql.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Graphql - StandardGraphqlError = Class.new(StandardError) - end -end diff --git a/lib/gitlab/graphql/standard_graphql_error.rb b/lib/gitlab/graphql/standard_graphql_error.rb new file mode 100644 index 00000000000..8364c232af2 --- /dev/null +++ b/lib/gitlab/graphql/standard_graphql_error.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# rubocop:disable Cop/CustomErrorClass + +module Gitlab + module Graphql + class StandardGraphqlError < StandardError + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 12b52556a72..a320412f475 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -11402,9 +11402,6 @@ msgstr "" msgid "Didn't receive unlock instructions?" msgstr "" -msgid "Diff content limits" -msgstr "" - msgid "Diff limits" msgstr "" @@ -23339,6 +23336,9 @@ msgstr "" msgid "PackageRegistry|Copy Pip command" msgstr "" +msgid "PackageRegistry|Copy SHA" +msgstr "" + msgid "PackageRegistry|Copy add Gradle Groovy DSL repository command" msgstr "" @@ -29645,6 +29645,9 @@ msgstr "" msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication." msgstr "" +msgid "Set size limits for displaying diffs in the browser." +msgstr "" + msgid "Set target branch" msgstr "" @@ -39511,6 +39514,9 @@ msgstr "" msgid "view the source" msgstr "" +msgid "visibility" +msgstr "" + msgid "vulnerability" msgid_plural "vulnerabilities" msgstr[0] "" diff --git a/scripts/gitaly-test-build b/scripts/gitaly-test-build index 26f84cd81d3..e6afadccc7e 100755 --- a/scripts/gitaly-test-build +++ b/scripts/gitaly-test-build @@ -18,6 +18,8 @@ class GitalyTestBuild # If we have the binaries from the cache, we can skip building them again if File.exist?(tmp_tests_gitaly_bin_dir) GitalySetup::LOGGER.debug "Gitaly binary already built. Skip building...\n" + # We still need to install the gems in that case + install_gitaly_gems else abort 'gitaly build failed' unless build_gitaly end diff --git a/spec/controllers/projects/analytics/cycle_analytics/summary_controller_spec.rb b/spec/controllers/projects/analytics/cycle_analytics/summary_controller_spec.rb new file mode 100644 index 00000000000..1832b84ab6e --- /dev/null +++ b/spec/controllers/projects/analytics/cycle_analytics/summary_controller_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + + let(:params) { { namespace_id: project.namespace.to_param, project_id: project.to_param, created_after: '2010-01-01', created_before: '2010-01-02' } } + + before do + sign_in(user) + end + + describe 'GET "show"' do + subject { get :show, params: params } + + it 'succeeds' do + project.add_reporter(user) + + subject + + expect(response).to be_successful + expect(response).to match_response_schema('analytics/cycle_analytics/summary') + end + + context 'when analytics_disabled features are disabled' do + it 'renders 404' do + project.add_reporter(user) + project.project_feature.update!(analytics_access_level: ProjectFeature::DISABLED) + + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user is not part of the project' do + it 'renders 404' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end +end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 19f9e8b780b..050d5e00670 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -878,19 +878,6 @@ RSpec.describe Projects::PipelinesController do expect(::Ci::RetryPipelineWorker).to have_received(:perform_async).with(pipeline.id, user.id) end - context 'when feature flag is disabled' do - before do - stub_feature_flags(background_pipeline_retry_endpoint: false) - end - - it 'retries the pipeline without returning any content' do - post_retry - - expect(response).to have_gitlab_http_status(:no_content) - expect(build.reload).to be_retried - end - end - context 'when builds are disabled' do let(:feature) { ProjectFeature::DISABLED } diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index 1ff1292c36e..73103956ee8 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -96,26 +96,7 @@ FactoryBot.define do end factory :clusters_applications_ingress, class: 'Clusters::Applications::Ingress' do - modsecurity_enabled { false } cluster factory: %i(cluster with_installed_helm provided_by_gcp) - - trait :modsecurity_blocking do - modsecurity_enabled { true } - modsecurity_mode { :blocking } - end - - trait :modsecurity_logging do - modsecurity_enabled { true } - modsecurity_mode { :logging } - end - - trait :modsecurity_disabled do - modsecurity_enabled { false } - end - - trait :modsecurity_not_installed do - modsecurity_enabled { nil } - end end factory :clusters_applications_cert_manager, class: 'Clusters::Applications::CertManager' do @@ -153,7 +134,6 @@ FactoryBot.define do factory :clusters_applications_fluentd, class: 'Clusters::Applications::Fluentd' do host { 'example.com' } - waf_log_enabled { true } cilium_log_enabled { true } cluster factory: %i(cluster with_installed_helm provided_by_gcp) end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 3cb18f21a47..cc561ef65a2 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -47,6 +47,7 @@ FactoryBot.define do import_last_error { nil } forward_deployment_enabled { nil } restrict_user_defined_variables { nil } + ci_job_token_scope_enabled { nil } end before(:create) do |project, evaluator| @@ -88,6 +89,7 @@ FactoryBot.define do project.merge_trains_enabled = evaluator.merge_trains_enabled unless evaluator.merge_trains_enabled.nil? project.keep_latest_artifact = evaluator.keep_latest_artifact unless evaluator.keep_latest_artifact.nil? project.restrict_user_defined_variables = evaluator.restrict_user_defined_variables unless evaluator.restrict_user_defined_variables.nil? + project.ci_job_token_scope_enabled = evaluator.ci_job_token_scope_enabled unless evaluator.ci_job_token_scope_enabled.nil? if evaluator.import_status import_state = project.import_state || project.build_import_state diff --git a/spec/finders/ci/runners_finder_spec.rb b/spec/finders/ci/runners_finder_spec.rb index 4df026f2f5f..250a85dde30 100644 --- a/spec/finders/ci/runners_finder_spec.rb +++ b/spec/finders/ci/runners_finder_spec.rb @@ -51,23 +51,55 @@ RSpec.describe Ci::RunnersFinder do end context 'sort' do + let_it_be(:runner1) { create :ci_runner, created_at: '2018-07-12 07:00', contacted_at: 1.minute.ago } + let_it_be(:runner2) { create :ci_runner, created_at: '2018-07-12 08:00', contacted_at: 3.minutes.ago } + let_it_be(:runner3) { create :ci_runner, created_at: '2018-07-12 09:00', contacted_at: 2.minutes.ago } + + subject do + described_class.new(current_user: admin, params: params).execute + end + + shared_examples 'sorts by created_at descending' do + it 'sorts by created_at descending' do + is_expected.to eq [runner3, runner2, runner1] + end + end + context 'without sort param' do - it 'sorts by created_at' do - runner1 = create :ci_runner, created_at: '2018-07-12 07:00' - runner2 = create :ci_runner, created_at: '2018-07-12 08:00' - runner3 = create :ci_runner, created_at: '2018-07-12 09:00' + let(:params) { {} } + + it_behaves_like 'sorts by created_at descending' + end + + %w(created_date created_at_desc).each do |sort| + context "with sort param equal to #{sort}" do + let(:params) { { sort: sort } } + + it_behaves_like 'sorts by created_at descending' + end + end + + context 'with sort param equal to created_at_asc' do + let(:params) { { sort: 'created_at_asc' } } + + it 'sorts by created_at ascending' do + is_expected.to eq [runner1, runner2, runner3] + end + end + + context 'with sort param equal to contacted_asc' do + let(:params) { { sort: 'contacted_asc' } } - expect(described_class.new(current_user: admin, params: {}).execute).to eq [runner3, runner2, runner1] + it 'sorts by contacted_at ascending' do + is_expected.to eq [runner2, runner3, runner1] end end - context 'with sort param' do - it 'sorts by specified attribute' do - runner1 = create :ci_runner, contacted_at: 1.minute.ago - runner2 = create :ci_runner, contacted_at: 3.minutes.ago - runner3 = create :ci_runner, contacted_at: 2.minutes.ago + context 'with sort param equal to contacted_desc' do + let(:params) { { sort: 'contacted_desc' } } - expect(described_class.new(current_user: admin, params: { sort: 'contacted_asc' }).execute).to eq [runner2, runner3, runner1] + it 'sorts by contacted_at descending' do + is_expected.to eq [runner1, runner3, runner2] end end end @@ -246,8 +278,8 @@ RSpec.describe Ci::RunnersFinder do subject { described_class.new(current_user: user, group: group, params: params).sort_key } context 'no params' do - it 'returns created_date' do - expect(subject).to eq('created_date') + it 'returns created_at_desc' do + expect(subject).to eq('created_at_desc') end end diff --git a/spec/fixtures/api/schemas/analytics/cycle_analytics/summary.json b/spec/fixtures/api/schemas/analytics/cycle_analytics/summary.json new file mode 100644 index 00000000000..73904438ede --- /dev/null +++ b/spec/fixtures/api/schemas/analytics/cycle_analytics/summary.json @@ -0,0 +1,21 @@ +{ + "type": "array", + "items": { + "minItems": 2, + "maxItems": 3, + "type": "object", + "required": ["value", "title"], + "properties": { + "value": { + "type": "string" + }, + "title": { + "type": "string" + }, + "unit": { + "type": "string" + } + }, + "additionalProperties": false + } +} diff --git a/spec/fixtures/api/schemas/cluster_status.json b/spec/fixtures/api/schemas/cluster_status.json index f6db336fe65..c919cd54a28 100644 --- a/spec/fixtures/api/schemas/cluster_status.json +++ b/spec/fixtures/api/schemas/cluster_status.json @@ -42,7 +42,6 @@ "host": {"type": ["string", "null"]}, "port": {"type": ["integer", "514"]}, "protocol": {"type": ["integer", "0"]}, - "waf_log_enabled": {"type": ["boolean", "true"]}, "cilium_log_enabled": {"type": ["boolean", "true"]}, "update_available": { "type": ["boolean", "null"] }, "can_uninstall": { "type": "boolean" }, diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index bffe108896a..305d3de3c53 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -471,6 +471,7 @@ describe('URL utility', () => { ${'notaurl'} | ${false} ${'../relative_url'} | ${false} ${'<a></a>'} | ${false} + ${'//other-host.test'} | ${false} `('returns $valid for $url', ({ url, valid }) => { expect(urlUtils.isRootRelative(url)).toBe(valid); }); diff --git a/spec/frontend/packages/details/components/__snapshots__/file_sha_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/file_sha_spec.js.snap new file mode 100644 index 00000000000..881d441e116 --- /dev/null +++ b/spec/frontend/packages/details/components/__snapshots__/file_sha_spec.js.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FileSha renders 1`] = ` +<div + class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-word-break-all gl-py-2 gl-border-b-solid gl-border-gray-100 gl-border-b-1" +> + <!----> + + <span> + <div + class="gl-px-4" + > + + bar: + foo + + <gl-button-stub + aria-label="Copy this value" + buttontextclasses="" + category="tertiary" + data-clipboard-text="foo" + icon="copy-to-clipboard" + size="small" + title="Copy SHA" + variant="default" + /> + </div> + </span> +</div> +`; diff --git a/spec/frontend/packages/details/components/file_sha_spec.js b/spec/frontend/packages/details/components/file_sha_spec.js new file mode 100644 index 00000000000..7bfcf78baab --- /dev/null +++ b/spec/frontend/packages/details/components/file_sha_spec.js @@ -0,0 +1,33 @@ +import { shallowMount } from '@vue/test-utils'; + +import FileSha from '~/packages/details/components/file_sha.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; + +describe('FileSha', () => { + let wrapper; + + const defaultProps = { sha: 'foo', title: 'bar' }; + + function createComponent() { + wrapper = shallowMount(FileSha, { + propsData: { + ...defaultProps, + }, + stubs: { + ClipboardButton, + DetailsRow, + }, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders', () => { + createComponent(); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/spec/frontend/packages/details/components/package_files_spec.js b/spec/frontend/packages/details/components/package_files_spec.js index 494aa631d9d..e8e5a24d3a3 100644 --- a/spec/frontend/packages/details/components/package_files_spec.js +++ b/spec/frontend/packages/details/components/package_files_spec.js @@ -1,5 +1,6 @@ -import { GlDropdown } from '@gitlab/ui'; +import { GlDropdown, GlButton } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue/'; import stubChildren from 'helpers/stub_children'; import component from '~/packages/details/components/package_files.vue'; import FileIcon from '~/vue_shared/components/file_icon.vue'; @@ -20,6 +21,8 @@ describe('Package Files', () => { const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip); const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown); const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]'); + const findFirstToggleDetailsButton = () => findFirstRow().findComponent(GlButton); + const findFirstRowShaComponent = (id) => wrapper.find(`[data-testid="${id}"]`); const createComponent = ({ packageFiles = npmFiles, canDelete = true } = {}) => { wrapper = mount(component, { @@ -181,4 +184,76 @@ describe('Package Files', () => { }); }); }); + + describe('additional details', () => { + describe('details toggle button', () => { + it('exists', () => { + createComponent(); + + expect(findFirstToggleDetailsButton().exists()).toBe(true); + }); + + it('is hidden when no details is present', () => { + const [{ ...noShaFile }] = npmFiles; + noShaFile.file_sha256 = null; + noShaFile.file_md5 = null; + noShaFile.file_sha1 = null; + createComponent({ packageFiles: [noShaFile] }); + + expect(findFirstToggleDetailsButton().exists()).toBe(false); + }); + + it('toggles the details row', async () => { + createComponent(); + + expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down'); + + findFirstToggleDetailsButton().vm.$emit('click'); + await nextTick(); + + expect(findFirstRowShaComponent('sha-256').exists()).toBe(true); + expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-up'); + + findFirstToggleDetailsButton().vm.$emit('click'); + await nextTick(); + + expect(findFirstRowShaComponent('sha-256').exists()).toBe(false); + expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down'); + }); + }); + + describe('file shas', () => { + const showShaFiles = () => { + findFirstToggleDetailsButton().vm.$emit('click'); + return nextTick(); + }; + + it.each` + selector | title | sha + ${'sha-256'} | ${'SHA-256'} | ${'file_sha256'} + ${'md5'} | ${'MD5'} | ${'file_md5'} + ${'sha-1'} | ${'SHA-1'} | ${'file_sha1'} + `('has a $title row', async ({ selector, title, sha }) => { + createComponent(); + + await showShaFiles(); + + expect(findFirstRowShaComponent(selector).props()).toMatchObject({ + title, + sha, + }); + }); + + it('does not display a row when the data is missing', async () => { + const [{ ...missingMd5 }] = npmFiles; + missingMd5.file_md5 = null; + + createComponent({ packageFiles: [missingMd5] }); + + await showShaFiles(); + + expect(findFirstRowShaComponent('md5').exists()).toBe(false); + }); + }); + }); }); diff --git a/spec/frontend/packages/mock_data.js b/spec/frontend/packages/mock_data.js index 06009daba54..46ccf5994b9 100644 --- a/spec/frontend/packages/mock_data.js +++ b/spec/frontend/packages/mock_data.js @@ -79,6 +79,9 @@ export const npmFiles = [ pipelines: [ { id: 1, project: { commit_url: 'http://foo.bar' }, git_commit_message: 'foo bar baz?' }, ], + file_sha256: 'file_sha256', + file_md5: 'file_md5', + file_sha1: 'file_sha1', }, ]; diff --git a/spec/frontend/pages/projects/forks/new/components/app_spec.js b/spec/frontend/pages/projects/forks/new/components/app_spec.js index e1820606704..a7b4b9c42bd 100644 --- a/spec/frontend/pages/projects/forks/new/components/app_spec.js +++ b/spec/frontend/pages/projects/forks/new/components/app_spec.js @@ -13,6 +13,7 @@ describe('App component', () => { projectPath: 'project-name', projectDescription: 'some project description', projectVisibility: 'private', + restrictedVisibilityLevels: [], }; const createComponent = (props = {}) => { diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js index 03338b1930c..c80ccfa8256 100644 --- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js +++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js @@ -1,4 +1,5 @@ -import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadio, GlFormSelect } from '@gitlab/ui'; +import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadioGroup, GlFormRadio } from '@gitlab/ui'; +import { getByRole, getAllByRole } from '@testing-library/dom'; import { mount, shallowMount } from '@vue/test-utils'; import axios from 'axios'; import AxiosMockAdapter from 'axios-mock-adapter'; @@ -44,6 +45,7 @@ describe('ForkForm component', () => { projectPath: 'project-name', projectDescription: 'some project description', projectVisibility: 'private', + restrictedVisibilityLevels: [], }; const mockGetRequest = (data = {}, statusCode = httpStatus.OK) => { @@ -68,6 +70,7 @@ describe('ForkForm component', () => { stubs: { GlFormInputGroup, GlFormInput, + GlFormRadioGroup, GlFormRadio, }, }); @@ -89,7 +92,7 @@ describe('ForkForm component', () => { axiosMock.restore(); }); - const findFormSelect = () => wrapper.find(GlFormSelect); + const findFormSelectOptions = () => wrapper.find('select[name="namespace"]').findAll('option'); const findPrivateRadio = () => wrapper.find('[data-testid="radio-private"]'); const findInternalRadio = () => wrapper.find('[data-testid="radio-internal"]'); const findPublicRadio = () => wrapper.find('[data-testid="radio-public"]'); @@ -230,7 +233,7 @@ describe('ForkForm component', () => { expect(wrapper.findAll(GlFormRadio)).toHaveLength(3); }); - it('resets the visibility to default "private" when the namespace is changed', async () => { + describe('when the namespace is changed', () => { const namespaces = [ { visibility: 'private', @@ -243,42 +246,114 @@ describe('ForkForm component', () => { }, ]; - mockGetRequest(); - createComponent( - { - projectVisibility: 'public', - }, - { - namespaces, - }, - ); + beforeEach(() => { + mockGetRequest(); + }); - expect(wrapper.vm.form.fields.visibility.value).toBe('public'); - findFormSelect().vm.$emit('input', namespaces[1]); + it('resets the visibility to default "private"', async () => { + createFullComponent({ projectVisibility: 'public' }, { namespaces }); - await wrapper.vm.$nextTick(); + expect(wrapper.vm.form.fields.visibility.value).toBe('public'); + await findFormSelectOptions().at(1).setSelected(); + + await wrapper.vm.$nextTick(); + + expect(getByRole(wrapper.element, 'radio', { name: /private/i }).checked).toBe(true); + }); + + it('sets the visibility to be null when restrictedVisibilityLevels is set', async () => { + createFullComponent({ restrictedVisibilityLevels: [10] }, { namespaces }); + + await findFormSelectOptions().at(1).setSelected(); + + await wrapper.vm.$nextTick(); + + const container = getByRole(wrapper.element, 'radiogroup', { name: /visibility/i }); + const visibilityRadios = getAllByRole(container, 'radio'); + expect(visibilityRadios.filter((e) => e.checked)).toHaveLength(0); + }); + }); + + it.each` + project | restrictedVisibilityLevels + ${'private'} | ${[]} + ${'internal'} | ${[]} + ${'public'} | ${[]} + ${'private'} | ${[0]} + ${'private'} | ${[10]} + ${'private'} | ${[20]} + ${'private'} | ${[0, 10]} + ${'private'} | ${[0, 20]} + ${'private'} | ${[10, 20]} + ${'private'} | ${[0, 10, 20]} + ${'internal'} | ${[0]} + ${'internal'} | ${[10]} + ${'internal'} | ${[20]} + ${'internal'} | ${[0, 10]} + ${'internal'} | ${[0, 20]} + ${'internal'} | ${[10, 20]} + ${'internal'} | ${[0, 10, 20]} + ${'public'} | ${[0]} + ${'public'} | ${[10]} + ${'public'} | ${[0, 10]} + ${'public'} | ${[0, 20]} + ${'public'} | ${[10, 20]} + ${'public'} | ${[0, 10, 20]} + `('checks the correct radio button', async ({ project, restrictedVisibilityLevels }) => { + mockGetRequest(); + createFullComponent({ + projectVisibility: project, + restrictedVisibilityLevels, + }); - expect(wrapper.vm.form.fields.visibility.value).toBe('private'); + if (restrictedVisibilityLevels.length === 0) { + expect(wrapper.find('[name="visibility"]:checked').attributes('value')).toBe(project); + } else { + expect(wrapper.find('[name="visibility"]:checked').exists()).toBe(false); + } }); it.each` - project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled - ${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} - ${'private'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} - ${'private'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} - ${'internal'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} - ${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} - ${'internal'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} - ${'public'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} - ${'public'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} - ${'public'} | ${'public'} | ${undefined} | ${undefined} | ${undefined} + project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled | restrictedVisibilityLevels + ${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]} + ${'private'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[]} + ${'private'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[]} + ${'internal'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]} + ${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[]} + ${'internal'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} | ${[]} + ${'public'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]} + ${'public'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[]} + ${'public'} | ${'public'} | ${undefined} | ${undefined} | ${undefined} | ${[]} + ${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[0]} + ${'internal'} | ${'internal'} | ${'true'} | ${undefined} | ${'true'} | ${[0]} + ${'public'} | ${'public'} | ${'true'} | ${undefined} | ${undefined} | ${[0]} + ${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[10]} + ${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[10]} + ${'public'} | ${'public'} | ${undefined} | ${'true'} | ${undefined} | ${[10]} + ${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[20]} + ${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[20]} + ${'public'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} | ${[20]} + ${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]} + ${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]} + ${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]} + ${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]} + ${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]} + ${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]} `( 'sets appropriate radio button disabled state', - async ({ project, namespace, privateIsDisabled, internalIsDisabled, publicIsDisabled }) => { + async ({ + project, + namespace, + privateIsDisabled, + internalIsDisabled, + publicIsDisabled, + restrictedVisibilityLevels, + }) => { mockGetRequest(); createComponent( { projectVisibility: project, + restrictedVisibilityLevels, }, { form: { fields: { namespace: { value: { visibility: namespace } } } }, diff --git a/spec/frontend/repository/log_tree_spec.js b/spec/frontend/repository/log_tree_spec.js index d338af88ce3..8cabf902a4f 100644 --- a/spec/frontend/repository/log_tree_spec.js +++ b/spec/frontend/repository/log_tree_spec.js @@ -69,6 +69,16 @@ describe('fetchLogsTree', () => { mock.restore(); }); + it('persists the offset for a given page if offset is larger than maximum offset', async () => { + await fetchLogsTree(client, 'path', '1000', resolver, 900).then(() => {}); + + await fetchLogsTree(client, 'path', '1100', resolver, 1200).then(() => { + expect(axios.get).toHaveBeenCalledWith('/gitlab-org/gitlab-foss/-/refs/main/logs_tree/path', { + params: { format: 'json', offset: 975 }, + }); + }); + }); + it('does not call axios get if offset is larger than the maximum offset', () => fetchLogsTree(client, '', '1000', resolver, 900).then(() => { expect(axios.get).not.toHaveBeenCalled(); diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb index d89beb183f4..5ac15d5729f 100644 --- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb @@ -50,13 +50,29 @@ RSpec.describe Resolvers::Ci::RunnersResolver do it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner]) } end - context "set to :created_date" do + context "set to :contacted_desc" do let(:args) do - { sort: :created_date } + { sort: :contacted_desc } + end + + it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner].reverse) } + end + + context "set to :created_at_desc" do + let(:args) do + { sort: :created_at_desc } end it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner]) } end + + context "set to :created_at_asc" do + let(:args) do + { sort: :created_at_asc } + end + + it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner].reverse) } + end end context 'when type is filtered' do diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb index 2cdf95ea101..b9e0132badb 100644 --- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb @@ -4,14 +4,15 @@ require 'spec_helper' RSpec.describe Gitlab::CycleAnalytics::StageSummary do let(:project) { create(:project, :repository) } - let(:options) { { from: 1.day.ago, current_user: user } } + let(:options) { { from: 1.day.ago } } + let(:args) { { options: options, current_user: user } } let(:user) { create(:user, :admin) } before do project.add_maintainer(user) end - let(:stage_summary) { described_class.new(project, **options).data } + let(:stage_summary) { described_class.new(project, **args).data } describe "#new_issues" do subject { stage_summary.first } @@ -117,11 +118,11 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do before do project.add_guest(guest_user) - options.merge!({ current_user: guest_user }) + args.merge!({ current_user: guest_user }) end it 'does not include commit stats' do - data = described_class.new(project, **options).data + data = described_class.new(project, **args).data expect(includes_commits?(data)).to be_falsy end diff --git a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb index 28d78c182ad..4705bb23885 100644 --- a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb @@ -459,7 +459,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do expect(hosts).to all(receive(:caught_up?).with(location).and_return(false)) end - it 'returns true and has does not set the valid hosts' do + it 'returns false and does not set the valid hosts' do expect(subject).to be false expect(valid_host_list).to be_nil end @@ -487,4 +487,36 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do end end end + + describe '#select_caught_up_hosts' do + let(:location) { 'AB/12345'} + let(:hosts) { lb.host_list.hosts } + let(:set_host) { RequestStore[described_class::CACHE_KEY] } + + subject { lb.select_up_to_date_host(location) } + + context 'when none of the replicas are caught up' do + before do + expect(hosts).to all(receive(:caught_up?).with(location).and_return(false)) + end + + it 'returns false and does not update the host thread-local variable' do + expect(subject).to be false + expect(set_host).to be_nil + end + end + + context 'when any of the replicas is caught up' do + before do + # `allow` for non-caught up host, because we may not even check it, if will find the caught up one earlier + allow(hosts[0]).to receive(:caught_up?).with(location).and_return(false) + expect(hosts[1]).to receive(:caught_up?).with(location).and_return(true) + end + + it 'returns true and sets host thread-local variable' do + expect(subject).to be true + expect(set_host).to eq(hosts[1]) + end + end + end end diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb index f1cd84b63c1..b7cd0caa922 100644 --- a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb @@ -126,7 +126,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do context 'when replica is not up to date' do before do allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :release_host) - allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :host, :caught_up?).and_return(false) + allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :select_up_to_date_host).and_return(false) end around do |example| @@ -157,6 +157,20 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do expect(job[:database_chosen]).to eq('primary') end end + + context 'replica selection mechanism feature flag rollout' do + before do + stub_feature_flags(sidekiq_load_balancing_rotate_up_to_date_replica: false) + end + + it 'uses different implmentation' do + expect(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :host, :caught_up?).and_return(false) + + expect do + process_job(job) + end.to raise_error(Sidekiq::JobRetry::Skip) + end + end end end diff --git a/spec/lib/gitlab/database/load_balancing_spec.rb b/spec/lib/gitlab/database/load_balancing_spec.rb index f8aeafbf61a..e7de7f2b43b 100644 --- a/spec/lib/gitlab/database/load_balancing_spec.rb +++ b/spec/lib/gitlab/database/load_balancing_spec.rb @@ -394,7 +394,7 @@ RSpec.describe Gitlab::Database::LoadBalancing do end shared_context 'LoadBalancing setup' do - let(:development_db_config) { ActiveRecord::Base.configurations.default_hash("development").with_indifferent_access } + let(:development_db_config) { ActiveRecord::Base.configurations.configs_for(env_name: 'development').first.configuration_hash } let(:hosts) { [development_db_config[:host]] } let(:model) do Class.new(ApplicationRecord) do diff --git a/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb b/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb index 2da0e7df72f..6955fbcaf5a 100644 --- a/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb +++ b/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb @@ -38,22 +38,6 @@ RSpec.describe Gitlab::Usage::Metrics::NameSuggestion do end context 'joined relations' do - context 'counted attribute comes from joined relation' do - it_behaves_like 'name suggestion' do - let(:operation) { :distinct_count } - let(:column) { ::Deployment.arel_table[:environment_id] } - let(:relation) do - ::Clusters::Applications::Ingress.modsecurity_enabled.logging - .joins(cluster: :deployments) - .merge(::Clusters::Cluster.enabled) - .merge(Deployment.success) - end - - let(:constraints) { /'\(clusters_applications_ingress\.modsecurity_enabled = TRUE AND clusters_applications_ingress\.modsecurity_mode = \d+ AND clusters.enabled = TRUE AND deployments.status = \d+\)'/ } - let(:name_suggestion) { /count_distinct_environment_id_from_<adjective describing\: #{constraints}>_deployments_<with>_<adjective describing\: #{constraints}>_clusters_<having>_<adjective describing\: #{constraints}>_clusters_applications_ingress/ } - end - end - context 'counted attribute comes from source relation' do it_behaves_like 'name suggestion' do # corresponding metric is collected with count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id) diff --git a/spec/models/clusters/applications/fluentd_spec.rb b/spec/models/clusters/applications/fluentd_spec.rb index ccdf6b0e40d..a4df44225f3 100644 --- a/spec/models/clusters/applications/fluentd_spec.rb +++ b/spec/models/clusters/applications/fluentd_spec.rb @@ -3,9 +3,8 @@ require 'spec_helper' RSpec.describe Clusters::Applications::Fluentd do - let(:waf_log_enabled) { true } let(:cilium_log_enabled) { true } - let(:fluentd) { create(:clusters_applications_fluentd, waf_log_enabled: waf_log_enabled, cilium_log_enabled: cilium_log_enabled) } + let(:fluentd) { create(:clusters_applications_fluentd, cilium_log_enabled: cilium_log_enabled) } include_examples 'cluster application core specs', :clusters_applications_fluentd include_examples 'cluster application status specs', :clusters_applications_fluentd @@ -51,13 +50,11 @@ RSpec.describe Clusters::Applications::Fluentd do end describe '#values' do - let(:modsecurity_log_path) { "/var/log/containers/*#{Clusters::Applications::Ingress::MODSECURITY_LOG_CONTAINER_NAME}*.log" } let(:cilium_log_path) { "/var/log/containers/*#{described_class::CILIUM_CONTAINER_NAME}*.log" } subject { fluentd.values } - context 'with both logs variables set to false' do - let(:waf_log_enabled) { false } + context 'with cilium_log_enabled set to false' do let(:cilium_log_enabled) { false } it "raises ActiveRecord::RecordInvalid" do @@ -65,18 +62,8 @@ RSpec.describe Clusters::Applications::Fluentd do end end - context 'with both logs variables set to true' do - it { is_expected.to include("#{modsecurity_log_path},#{cilium_log_path}") } - end - - context 'with waf_log_enabled set to true' do - let(:cilium_log_enabled) { false } - - it { is_expected.to include(modsecurity_log_path) } - end - context 'with cilium_log_enabled set to true' do - let(:waf_log_enabled) { false } + let(:cilium_log_enabled) { true } it { is_expected.to include(cilium_log_path) } end diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index 1bc1a4343aa..e16d97c42d9 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -172,94 +172,4 @@ RSpec.describe Clusters::Applications::Ingress do expect(values).to include('clusterIP') end end - - describe '#values' do - subject { ingress } - - context 'when modsecurity_enabled is enabled' do - before do - allow(subject).to receive(:modsecurity_enabled).and_return(true) - end - - it 'includes modsecurity module enablement' do - expect(subject.values).to include("enable-modsecurity: 'true'") - end - - it 'includes modsecurity core ruleset enablement set to false' do - expect(subject.values).to include("enable-owasp-modsecurity-crs: 'false'") - end - - it 'includes modsecurity snippet with information related to security rules' do - expect(subject.values).to include("SecRuleEngine DetectionOnly") - expect(subject.values).to include("Include #{described_class::MODSECURITY_OWASP_RULES_FILE}") - end - - context 'when modsecurity_mode is set to :blocking' do - before do - subject.blocking! - end - - it 'includes modsecurity snippet with information related to security rules' do - expect(subject.values).to include("SecRuleEngine On") - expect(subject.values).to include("Include #{described_class::MODSECURITY_OWASP_RULES_FILE}") - end - end - - it 'includes modsecurity.conf content' do - expect(subject.values).to include('modsecurity.conf') - # Includes file content from Ingress#modsecurity_config_content - expect(subject.values).to include('SecAuditLog') - - expect(subject.values).to include('extraVolumes') - expect(subject.values).to include('extraVolumeMounts') - end - - it 'includes modsecurity sidecar container' do - expect(subject.values).to include('modsecurity-log-volume') - - expect(subject.values).to include('extraContainers') - end - - it 'executes command to tail modsecurity logs with -F option' do - args = YAML.safe_load(subject.values).dig('controller', 'extraContainers', 0, 'args') - - expect(args).to eq(['/bin/sh', '-c', 'tail -F /var/log/modsec/audit.log']) - end - - it 'includes livenessProbe for modsecurity sidecar container' do - probe_config = YAML.safe_load(subject.values).dig('controller', 'extraContainers', 0, 'livenessProbe') - - expect(probe_config).to eq('exec' => { 'command' => ['ls', '/var/log/modsec/audit.log'] }) - end - end - - context 'when modsecurity_enabled is disabled' do - before do - allow(subject).to receive(:modsecurity_enabled).and_return(false) - end - - it 'excludes modsecurity module enablement' do - expect(subject.values).not_to include('enable-modsecurity') - end - - it 'excludes modsecurity core ruleset enablement' do - expect(subject.values).not_to include('enable-owasp-modsecurity-crs') - end - - it 'excludes modsecurity.conf content' do - expect(subject.values).not_to include('modsecurity.conf') - # Excludes file content from Ingress#modsecurity_config_content - expect(subject.values).not_to include('SecAuditLog') - - expect(subject.values).not_to include('extraVolumes') - expect(subject.values).not_to include('extraVolumeMounts') - end - - it 'excludes modsecurity sidecar container' do - expect(subject.values).not_to include('modsecurity-log-volume') - - expect(subject.values).not_to include('extraContainers') - end - end - end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 28dba3e7efa..8a7ffc315db 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -196,28 +196,6 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do end end - describe '.with_enabled_modsecurity' do - subject { described_class.with_enabled_modsecurity } - - let_it_be(:cluster) { create(:cluster) } - - context 'cluster has ingress application with enabled modsecurity' do - let!(:application) { create(:clusters_applications_ingress, :installed, :modsecurity_logging, cluster: cluster) } - - it { is_expected.to include(cluster) } - end - - context 'cluster has ingress application with disabled modsecurity' do - let!(:application) { create(:clusters_applications_ingress, :installed, :modsecurity_disabled, cluster: cluster) } - - it { is_expected.not_to include(cluster) } - end - - context 'cluster does not have ingress application' do - it { is_expected.not_to include(cluster) } - end - end - describe '.with_available_elasticstack' do subject { described_class.with_available_elasticstack } diff --git a/spec/models/members/last_group_owner_assigner_spec.rb b/spec/models/members/last_group_owner_assigner_spec.rb index 3c9a7a11555..bb0f751e7d5 100644 --- a/spec/models/members/last_group_owner_assigner_spec.rb +++ b/spec/models/members/last_group_owner_assigner_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::LastGroupOwnerAssigner do +RSpec.describe LastGroupOwnerAssigner do describe "#execute" do let_it_be(:user, reload: true) { create(:user) } let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb index 99647d0fa3a..578a71a7272 100644 --- a/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb +++ b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb @@ -47,6 +47,7 @@ RSpec.describe 'Getting Ci Cd Setting' do expect(settings_data['mergePipelinesEnabled']).to eql project.ci_cd_settings.merge_pipelines_enabled? expect(settings_data['mergeTrainsEnabled']).to eql project.ci_cd_settings.merge_trains_enabled? expect(settings_data['keepLatestArtifact']).to eql project.keep_latest_artifacts_available? + expect(settings_data['jobTokenScopeEnabled']).to eql project.ci_cd_settings.job_token_scope_enabled? end end end diff --git a/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb b/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb index 0dcae28ac5d..0d7571d91ca 100644 --- a/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb @@ -5,8 +5,16 @@ require 'spec_helper' RSpec.describe 'CiCdSettingsUpdate' do include GraphqlHelpers - let_it_be(:project) { create(:project, keep_latest_artifact: true) } - let(:variables) { { full_path: project.full_path, keep_latest_artifact: false } } + let_it_be(:project) { create(:project, keep_latest_artifact: true, ci_job_token_scope_enabled: true) } + + let(:variables) do + { + full_path: project.full_path, + keep_latest_artifact: false, + job_token_scope_enabled: false + } + end + let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) } context 'when unauthorized' do @@ -45,6 +53,26 @@ RSpec.describe 'CiCdSettingsUpdate' do expect(project.keep_latest_artifact).to eq(false) end + it 'updates job_token_scope_enabled' do + post_graphql_mutation(mutation, current_user: user) + + project.reload + + expect(response).to have_gitlab_http_status(:success) + expect(project.ci_job_token_scope_enabled).to eq(false) + end + + it 'does not update job_token_scope_enabled if not specified' do + variables.except!(:job_token_scope_enabled) + + post_graphql_mutation(mutation, current_user: user) + + project.reload + + expect(response).to have_gitlab_http_status(:success) + expect(project.ci_job_token_scope_enabled).to eq(true) + end + context 'when bad arguments are provided' do let(:variables) { { full_path: '', keep_latest_artifact: false } } diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index ce7405201ab..1aa1ad87be9 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -358,26 +358,6 @@ RSpec.describe API::Tags do expect(json_response['message']).to eq('Target foo is invalid') end - context 'when release_description is passed' do - it 'returns error' do - post api(route, current_user), params: { tag_name: tag_name, ref: 'master', release_description: 'Wow' } - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response["message"]).to eq("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311") - end - - it 'creates a new tag with release if feature is enabled' do - stub_feature_flags(remove_release_notes_from_tags_api: false) - - post api(route, current_user), params: { tag_name: tag_name, ref: 'master', release_description: 'Wow' } - - expect(response).to have_gitlab_http_status(:created) - expect(response).to match_response_schema('public_api/v4/tag') - expect(json_response['name']).to eq(tag_name) - expect(json_response['release']['description']).to eq('Wow') - end - end - context 'annotated tag' do it 'creates a new annotated tag' do # Identity must be set in .gitconfig to create annotated tag. @@ -449,148 +429,4 @@ RSpec.describe API::Tags do end end end - - describe 'POST /projects/:id/repository/tags/:tag_name/release' do - let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" } - let(:description) { 'Awesome release!' } - - before do - stub_feature_flags(remove_release_notes_from_tags_api: false) - end - - shared_examples_for 'repository new release' do - it 'creates description for existing git tag' do - post api(route, user), params: { description: description } - - expect(response).to have_gitlab_http_status(:created) - expect(response).to match_response_schema('public_api/v4/release/tag_release') - expect(json_response['tag_name']).to eq(tag_name) - expect(json_response['description']).to eq(description) - end - - it 'returns error if feature is removed' do - stub_feature_flags(remove_release_notes_from_tags_api: true) - - post api(route, user), params: { description: description } - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response["message"]).to eq("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311") - end - - context 'when tag does not exist' do - let(:tag_name) { 'unknown' } - - it_behaves_like '404 response' do - let(:request) { post api(route, current_user), params: { description: description } } - let(:message) { '404 Tag Not Found' } - end - end - - context 'when repository is disabled' do - include_context 'disabled repository' - - it_behaves_like '403 response' do - let(:request) { post api(route, current_user), params: { description: description } } - end - end - end - - context 'when authenticated', 'as a maintainer' do - let(:current_user) { user } - - it_behaves_like 'repository new release' - - context 'requesting with the escaped project full path' do - let(:project_id) { CGI.escape(project.full_path) } - - it_behaves_like 'repository new release' - end - - context 'on tag with existing release' do - let!(:release) { create(:release, :legacy, project: project, tag: tag_name, description: description) } - - it 'returns 409 if there is already a release' do - post api(route, user), params: { description: description } - - expect(response).to have_gitlab_http_status(:conflict) - expect(json_response['message']).to eq('Release already exists') - end - end - end - end - - describe 'PUT id/repository/tags/:tag_name/release' do - let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" } - let(:description) { 'Awesome release!' } - let(:new_description) { 'The best release!' } - - before do - stub_feature_flags(remove_release_notes_from_tags_api: false) - end - - shared_examples_for 'repository update release' do - context 'on tag with existing release' do - let!(:release) do - create(:release, - :legacy, - project: project, - tag: tag_name, - description: description) - end - - it 'updates the release description' do - put api(route, current_user), params: { description: new_description } - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['tag_name']).to eq(tag_name) - expect(json_response['description']).to eq(new_description) - end - - it 'returns error if feature is removed' do - stub_feature_flags(remove_release_notes_from_tags_api: true) - - put api(route, current_user), params: { description: new_description } - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response["message"]).to eq("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311") - end - end - - context 'when tag does not exist' do - let(:tag_name) { 'unknown' } - - it_behaves_like '403 response' do - let(:request) { put api(route, current_user), params: { description: new_description } } - let(:message) { '403 Forbidden' } - end - end - - context 'when repository is disabled' do - include_context 'disabled repository' - - it_behaves_like '403 response' do - let(:request) { put api(route, current_user), params: { description: new_description } } - end - end - end - - context 'when authenticated', 'as a maintainer' do - let(:current_user) { user } - - it_behaves_like 'repository update release' - - context 'requesting with the escaped project full path' do - let(:project_id) { CGI.escape(project.full_path) } - - it_behaves_like 'repository update release' - end - - context 'when release does not exist' do - it_behaves_like '403 response' do - let(:request) { put api(route, current_user), params: { description: new_description } } - let(:message) { '403 Forbidden' } - end - end - end - end end diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb index cd8be07827d..9429c9d571a 100644 --- a/spec/serializers/analytics_summary_serializer_spec.rb +++ b/spec/serializers/analytics_summary_serializer_spec.rb @@ -12,7 +12,7 @@ RSpec.describe AnalyticsSummarySerializer do let(:resource) do Gitlab::CycleAnalytics::Summary::Issue - .new(project: double, from: 1.day.ago, current_user: user) + .new(project: double, options: { from: 1.day.ago }, current_user: user) end before do @@ -36,7 +36,7 @@ RSpec.describe AnalyticsSummarySerializer do context 'when representing with unit' do let(:resource) do Gitlab::CycleAnalytics::Summary::DeploymentFrequency - .new(deployments: 10, from: 1.day.ago) + .new(deployments: 10, options: { from: 1.day.ago }) end subject { described_class.new.represent(resource, with_unit: true) } diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb index aa2bb25b17c..3941aad540a 100644 --- a/spec/serializers/cluster_application_entity_spec.rb +++ b/spec/serializers/cluster_application_entity_spec.rb @@ -85,7 +85,6 @@ RSpec.describe ClusterApplicationEntity do expect(subject[:port]).to eq(514) expect(subject[:host]).to eq("example.com") expect(subject[:protocol]).to eq("tcp") - expect(subject[:waf_log_enabled]).to be true expect(subject[:cilium_log_enabled]).to be true end end diff --git a/spec/serializers/member_serializer_spec.rb b/spec/serializers/member_serializer_spec.rb index 687d69f86ea..bc256432c46 100644 --- a/spec/serializers/member_serializer_spec.rb +++ b/spec/serializers/member_serializer_spec.rb @@ -42,7 +42,7 @@ RSpec.describe MemberSerializer do it_behaves_like 'members.json' it 'does not invoke group owner assignment' do - expect(Members::LastGroupOwnerAssigner).not_to receive(:new) + expect(LastGroupOwnerAssigner).not_to receive(:new) representation end diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb index f3b420510a6..eb907377ca8 100644 --- a/spec/services/clusters/applications/create_service_spec.rb +++ b/spec/services/clusters/applications/create_service_spec.rb @@ -46,8 +46,7 @@ RSpec.describe Clusters::Applications::CreateService do context 'ingress application' do let(:params) do { - application: 'ingress', - modsecurity_enabled: true + application: 'ingress' } end @@ -64,10 +63,6 @@ RSpec.describe Clusters::Applications::CreateService do cluster.reload end.to change(cluster, :application_ingress) end - - it 'sets modsecurity_enabled' do - expect(subject.modsecurity_enabled).to eq(true) - end end context 'cert manager application' do diff --git a/spec/workers/ssh_keys/expired_notification_worker_spec.rb b/spec/workers/ssh_keys/expired_notification_worker_spec.rb index 84f8f8d7b83..109d24f03ab 100644 --- a/spec/workers/ssh_keys/expired_notification_worker_spec.rb +++ b/spec/workers/ssh_keys/expired_notification_worker_spec.rb @@ -49,16 +49,6 @@ RSpec.describe SshKeys::ExpiredNotificationWorker, type: :worker do perform_multiple(worker: worker) end end - - context 'when feature is not enabled' do - before do - stub_feature_flags(ssh_key_expiration_email_notification: false) - end - - it 'does not update notified column' do - expect { worker.perform }.not_to change { expired_today.reload.expiry_notification_delivered_at } - end - end end context 'when key has expired in the past' do diff --git a/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb b/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb index f9276c86cdf..0a1d4a14ad0 100644 --- a/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb +++ b/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb @@ -35,16 +35,6 @@ RSpec.describe SshKeys::ExpiringSoonNotificationWorker, type: :worker do perform_multiple(worker: worker) end end - - context 'when feature is not enabled' do - before do - stub_feature_flags(ssh_key_expiration_email_notification: false) - end - - it 'does not update notified column' do - expect { worker.perform }.not_to change { expiring_soon.reload.before_expiry_notification_delivered_at } - end - end end context 'when key has expired in the past' do diff --git a/vendor/elastic_stack/values.yaml b/vendor/elastic_stack/values.yaml index 65e9c4b683f..59a6037efeb 100644 --- a/vendor/elastic_stack/values.yaml +++ b/vendor/elastic_stack/values.yaml @@ -61,12 +61,6 @@ filebeat: target_field: tie_breaker_id - add_cloud_metadata: ~ - add_kubernetes_metadata: ~ - - decode_json_fields: - fields: ["message"] - when: - equals: - kubernetes.container.namespace: "gitlab-managed-apps" - kubernetes.container.name: "modsecurity-log" kibana: enabled: false elasticsearchHosts: "http://elastic-stack-elasticsearch-master:9200" diff --git a/vendor/ingress/modsecurity.conf b/vendor/ingress/modsecurity.conf deleted file mode 100644 index 3a6b5cee2e5..00000000000 --- a/vendor/ingress/modsecurity.conf +++ /dev/null @@ -1,274 +0,0 @@ -# -- GitLab Customization ---------------------------------------------- -# Based on https://github.com/SpiderLabs/ModSecurity/blob/v3.0.3/modsecurity.conf-recommended -# Our base modsecurity.conf includes some minor customization: -# - `SecRuleEngine` is disabled, defaulting to `DetectionOnly`. Overridable at project-level -# - `SecAuditLogType` is disabled, defaulting to `Serial`. Overridable at project-level -# - `SecStatusEngine` is disabled, to disallow usage reporting -# -# ---------------------------------------------------------------------------- - -# -- Rule engine initialization ---------------------------------------------- - -# Enable ModSecurity, attaching it to every transaction. Use detection -# only to start with, because that minimises the chances of post-installation -# disruption. -# -# SecRuleEngine DetectionOnly - - -# -- Request body handling --------------------------------------------------- - -# Allow ModSecurity to access request bodies. If you don't, ModSecurity -# won't be able to see any POST parameters, which opens a large security -# hole for attackers to exploit. -# -SecRequestBodyAccess On - - -# Enable XML request body parser. -# Initiate XML Processor in case of xml content-type -# -SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\+|/)|text/)xml" \ - "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" - -# Enable JSON request body parser. -# Initiate JSON Processor in case of JSON content-type; change accordingly -# if your application does not use 'application/json' -# -SecRule REQUEST_HEADERS:Content-Type "application/json" \ - "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" - -# Maximum request body size we will accept for buffering. If you support -# file uploads then the value given on the first line has to be as large -# as the largest file you are willing to accept. The second value refers -# to the size of data, with files excluded. You want to keep that value as -# low as practical. -# -SecRequestBodyLimit 13107200 -SecRequestBodyNoFilesLimit 131072 - -# What do do if the request body size is above our configured limit. -# Keep in mind that this setting will automatically be set to ProcessPartial -# when SecRuleEngine is set to DetectionOnly mode in order to minimize -# disruptions when initially deploying ModSecurity. -# -SecRequestBodyLimitAction Reject - -# Verify that we've correctly processed the request body. -# As a rule of thumb, when failing to process a request body -# you should reject the request (when deployed in blocking mode) -# or log a high-severity alert (when deployed in detection-only mode). -# -SecRule REQBODY_ERROR "!@eq 0" \ -"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" - -# By default be strict with what we accept in the multipart/form-data -# request body. If the rule below proves to be too strict for your -# environment consider changing it to detection-only. You are encouraged -# _not_ to remove it altogether. -# -SecRule MULTIPART_STRICT_ERROR "!@eq 0" \ -"id:'200003',phase:2,t:none,log,deny,status:400, \ -msg:'Multipart request body failed strict validation: \ -PE %{REQBODY_PROCESSOR_ERROR}, \ -BQ %{MULTIPART_BOUNDARY_QUOTED}, \ -BW %{MULTIPART_BOUNDARY_WHITESPACE}, \ -DB %{MULTIPART_DATA_BEFORE}, \ -DA %{MULTIPART_DATA_AFTER}, \ -HF %{MULTIPART_HEADER_FOLDING}, \ -LF %{MULTIPART_LF_LINE}, \ -SM %{MULTIPART_MISSING_SEMICOLON}, \ -IQ %{MULTIPART_INVALID_QUOTING}, \ -IP %{MULTIPART_INVALID_PART}, \ -IH %{MULTIPART_INVALID_HEADER_FOLDING}, \ -FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'" - -# Did we see anything that might be a boundary? -# -# Here is a short description about the ModSecurity Multipart parser: the -# parser returns with value 0, if all "boundary-like" line matches with -# the boundary string which given in MIME header. In any other cases it returns -# with different value, eg. 1 or 2. -# -# The RFC 1341 descript the multipart content-type and its syntax must contains -# only three mandatory lines (above the content): -# * Content-Type: multipart/mixed; boundary=BOUNDARY_STRING -# * --BOUNDARY_STRING -# * --BOUNDARY_STRING-- -# -# First line indicates, that this is a multipart content, second shows that -# here starts a part of the multipart content, third shows the end of content. -# -# If there are any other lines, which starts with "--", then it should be -# another boundary id - or not. -# -# After 3.0.3, there are two kinds of types of boundary errors: strict and permissive. -# -# If multipart content contains the three necessary lines with correct order, but -# there are one or more lines with "--", then parser returns with value 2 (non-zero). -# -# If some of the necessary lines (usually the start or end) misses, or the order -# is wrong, then parser returns with value 1 (also a non-zero). -# -# You can choose, which one is what you need. The example below contains the -# 'strict' mode, which means if there are any lines with start of "--", then -# ModSecurity blocked the content. But the next, commented example contains -# the 'permissive' mode, then you check only if the necessary lines exists in -# correct order. Whit this, you can enable to upload PEM files (eg "----BEGIN.."), -# or other text files, which contains eg. HTTP headers. -# -# The difference is only the operator - in strict mode (first) the content blocked -# in case of any non-zero value. In permissive mode (second, commented) the -# content blocked only if the value is explicit 1. If it 0 or 2, the content will -# allowed. -# - -# -# See #1747 and #1924 for further information on the possible values for -# MULTIPART_UNMATCHED_BOUNDARY. -# -SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \ - "id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" - - -# PCRE Tuning -# We want to avoid a potential RegEx DoS condition -# -SecPcreMatchLimit 1000 -SecPcreMatchLimitRecursion 1000 - -# Some internal errors will set flags in TX and we will need to look for these. -# All of these are prefixed with "MSC_". The following flags currently exist: -# -# MSC_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded. -# -SecRule TX:/^MSC_/ "!@streq 0" \ - "id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'" - - -# -- Response body handling -------------------------------------------------- - -# Allow ModSecurity to access response bodies. -# You should have this directive enabled in order to identify errors -# and data leakage issues. -# -# Do keep in mind that enabling this directive does increases both -# memory consumption and response latency. -# -SecResponseBodyAccess On - -# Which response MIME types do you want to inspect? You should adjust the -# configuration below to catch documents but avoid static files -# (e.g., images and archives). -# -SecResponseBodyMimeType text/plain text/html text/xml - -# Buffer response bodies of up to 512 KB in length. -SecResponseBodyLimit 524288 - -# What happens when we encounter a response body larger than the configured -# limit? By default, we process what we have and let the rest through. -# That's somewhat less secure, but does not break any legitimate pages. -# -SecResponseBodyLimitAction ProcessPartial - - -# -- Filesystem configuration ------------------------------------------------ - -# The location where ModSecurity stores temporary files (for example, when -# it needs to handle a file upload that is larger than the configured limit). -# -# This default setting is chosen due to all systems have /tmp available however, -# this is less than ideal. It is recommended that you specify a location that's private. -# -SecTmpDir /tmp/ - -# The location where ModSecurity will keep its persistent data. This default setting -# is chosen due to all systems have /tmp available however, it -# too should be updated to a place that other users can't access. -# -SecDataDir /tmp/ - - -# -- File uploads handling configuration ------------------------------------- - -# The location where ModSecurity stores intercepted uploaded files. This -# location must be private to ModSecurity. You don't want other users on -# the server to access the files, do you? -# -#SecUploadDir /opt/modsecurity/var/upload/ - -# By default, only keep the files that were determined to be unusual -# in some way (by an external inspection script). For this to work you -# will also need at least one file inspection rule. -# -#SecUploadKeepFiles RelevantOnly - -# Uploaded files are by default created with permissions that do not allow -# any other user to access them. You may need to relax that if you want to -# interface ModSecurity to an external program (e.g., an anti-virus). -# -#SecUploadFileMode 0600 - - -# -- Debug log configuration ------------------------------------------------- - -# The default debug log configuration is to duplicate the error, warning -# and notice messages from the error log. -# -#SecDebugLog /opt/modsecurity/var/log/debug.log -#SecDebugLogLevel 3 - - -# -- Audit log configuration ------------------------------------------------- - -# Log the transactions that are marked by a rule, as well as those that -# trigger a server error (determined by a 5xx or 4xx, excluding 404, -# level response status codes). -# -SecAuditEngine RelevantOnly -SecAuditLogRelevantStatus "^(?:5|4(?!04))" - -# Log everything we know about a transaction. -SecAuditLogParts ABIJDEFHZ - -# Use a single file for logging. This is much easier to look at, but -# assumes that you will use the audit log only ocassionally. -# -# SecAuditLogType Serial -SecAuditLogFormat JSON -SecAuditLog /var/log/modsec/audit.log - -# Specify the path for concurrent audit logging. -#SecAuditLogStorageDir /opt/modsecurity/var/audit/ - - -# -- Miscellaneous ----------------------------------------------------------- - -# Use the most commonly used application/x-www-form-urlencoded parameter -# separator. There's probably only one application somewhere that uses -# something else so don't expect to change this value. -# -SecArgumentSeparator & - -# Settle on version 0 (zero) cookies, as that is what most applications -# use. Using an incorrect cookie version may open your installation to -# evasion attacks (against the rules that examine named cookies). -# -SecCookieFormat 0 - -# Specify your Unicode Code Point. -# This mapping is used by the t:urlDecodeUni transformation function -# to properly map encoded data to your language. Properly setting -# these directives helps to reduce false positives and negatives. -# -SecUnicodeMapFile unicode.mapping 20127 - -# Improve the quality of ModSecurity by sharing information about your -# current ModSecurity version and dependencies versions. -# The following information will be shared: ModSecurity version, -# Web Server version, APR version, PCRE version, Lua version, Libxml2 -# version, Anonymous unique id for host. -# SecStatusEngine On - - |