Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
76ed6cf575
commit
ba00dde24c
|
|
@ -231,7 +231,7 @@ e2e:test-on-gdk:
|
|||
SKIP_MESSAGE: Skipping test-on-gdk due to mr containing only quarantine changes!
|
||||
GDK_IMAGE: "${CI_REGISTRY_IMAGE}/gitlab-qa-gdk:${CI_COMMIT_SHA}"
|
||||
|
||||
e2e:test-product-analytics:
|
||||
.e2e:test-product-analytics:
|
||||
extends:
|
||||
- .qa:rules:e2e:test-on-gdk
|
||||
stage: qa
|
||||
|
|
|
|||
|
|
@ -513,6 +513,21 @@ ai-gateway-no-license:
|
|||
- if: $QA_SUITES =~ /Test::Integration::AiGatewayNoLicense/
|
||||
- !reference [.rules:test:manual, rules]
|
||||
|
||||
# ========== continuous-vulnerability-scanning ===========
|
||||
|
||||
continuous-vulnerabiity-scanning:
|
||||
extends:
|
||||
- .qa
|
||||
- .failure-videos
|
||||
variables:
|
||||
QA_SCENARIO: Test::Integration::ContinuousVulnerabilityScanning
|
||||
QA_MOCK_GITHUB: "true"
|
||||
rules:
|
||||
- !reference [.rules:test:ee-only, rules]
|
||||
- !reference [.rules:test:qa, rules]
|
||||
- if: $QA_SUITES =~ /Test::Integration::ContinuousVulnerabilityScanning/
|
||||
- !reference [.rules:test:manual, rules]
|
||||
|
||||
# ------------------------------------------
|
||||
# Update jobs
|
||||
# ------------------------------------------
|
||||
|
|
|
|||
|
|
@ -20,24 +20,12 @@ Layout/LineEndStringConcatenationIndentation:
|
|||
- 'app/graphql/mutations/issues/update.rb'
|
||||
- 'app/graphql/mutations/members/projects/bulk_update.rb'
|
||||
- 'app/graphql/mutations/merge_requests/update.rb'
|
||||
- 'app/graphql/mutations/packages/protection/rule/create.rb'
|
||||
- 'app/graphql/mutations/packages/protection/rule/delete.rb'
|
||||
- 'app/graphql/mutations/packages/protection/rule/update.rb'
|
||||
- 'app/graphql/mutations/todos/mark_all_done.rb'
|
||||
- 'app/graphql/resolvers/analytics/cycle_analytics/value_streams/stage_metrics_resolver.rb'
|
||||
- 'app/graphql/resolvers/ci/all_jobs_resolver.rb'
|
||||
- 'app/graphql/resolvers/ci/config_resolver.rb'
|
||||
- 'app/graphql/resolvers/ci/runners_resolver.rb'
|
||||
- 'app/graphql/resolvers/ci/template_resolver.rb'
|
||||
- 'app/graphql/resolvers/concerns/project_search_arguments.rb'
|
||||
- 'app/graphql/resolvers/design_management/designs_resolver.rb'
|
||||
- 'app/graphql/resolvers/groups_resolver.rb'
|
||||
- 'app/graphql/resolvers/issues/base_resolver.rb'
|
||||
- 'app/graphql/resolvers/work_items/linked_items_resolver.rb'
|
||||
- 'app/graphql/types/analytics/cycle_analytics/value_streams/stage_metrics_type.rb'
|
||||
- 'app/graphql/types/audit_events/definition_type.rb'
|
||||
- 'app/graphql/types/ci/catalog/resource_type.rb'
|
||||
- 'app/graphql/types/ci/config/include_type.rb'
|
||||
- 'app/graphql/types/ci/detailed_status_type.rb'
|
||||
- 'app/graphql/types/ci/job_token_scope/direction_enum.rb'
|
||||
- 'app/graphql/types/ci/job_token_scope_type.rb'
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
|
|||
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
|
||||
import { __ } from '~/locale';
|
||||
import ListSelector from '~/vue_shared/components/list_selector/index.vue';
|
||||
import { GROUPS_TYPE, PROJECTS_TYPE } from '~/vue_shared/components/list_selector/constants';
|
||||
|
||||
export default {
|
||||
DRAWER_Z_INDEX,
|
||||
GROUPS_TYPE,
|
||||
PROJECTS_TYPE,
|
||||
components: {
|
||||
GlDrawer,
|
||||
GlButton,
|
||||
|
|
@ -69,7 +72,7 @@ export default {
|
|||
|
||||
<template #default>
|
||||
<list-selector
|
||||
type="groups"
|
||||
:type="$options.GROUPS_TYPE"
|
||||
class="gl-m-5 !gl-p-0"
|
||||
autofocus
|
||||
disable-namespace-dropdown
|
||||
|
|
@ -79,7 +82,7 @@ export default {
|
|||
/>
|
||||
|
||||
<list-selector
|
||||
type="projects"
|
||||
:type="$options.PROJECTS_TYPE"
|
||||
class="gl-m-5 !gl-p-0"
|
||||
:selected-items="projectExclusions"
|
||||
@select="handleSelectExclusion"
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
|
||||
&.content-component-block {
|
||||
background-color: $body-bg;
|
||||
@apply gl-bg-default;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ $diff-file-header: 41px;
|
|||
left: -11px;
|
||||
width: 10px;
|
||||
height: calc(100% + 1px);
|
||||
background: $body-bg;
|
||||
@apply gl-bg-default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
.top-bar-fixed {
|
||||
@apply gl-shadow-inner-b-1-gray-100;
|
||||
background-color: $body-bg;
|
||||
@apply gl-bg-default;
|
||||
position: fixed;
|
||||
left: var(--application-bar-left);
|
||||
right: var(--application-bar-right);
|
||||
|
|
|
|||
|
|
@ -767,13 +767,6 @@ pre {
|
|||
}
|
||||
}
|
||||
|
||||
code {
|
||||
&.key-fingerprint {
|
||||
background: $body-bg;
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.monospace {
|
||||
@include gl-font-monospace;
|
||||
}
|
||||
|
|
@ -895,10 +888,10 @@ wbr {
|
|||
margin-block-end: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// prevent backref character from rendering as emoji
|
||||
.footnote-backref gl-emoji {
|
||||
font-family: inherit;
|
||||
font-family: inherit;
|
||||
font-size: 0.875em;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
* custom merge request message
|
||||
*/
|
||||
.commit-message-container {
|
||||
background-color: $body-bg;
|
||||
@apply gl-bg-default;
|
||||
position: relative;
|
||||
font-family: $monospace-font;
|
||||
$left: 12px;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ $sticky-header-z-index: 98;
|
|||
.settings-sticky-footer {
|
||||
position: sticky;
|
||||
z-index: $sticky-header-z-index;
|
||||
background: $body-bg;
|
||||
@apply gl-bg-default;
|
||||
}
|
||||
|
||||
.settings-sticky-header {
|
||||
|
|
@ -84,7 +84,7 @@ $sticky-header-z-index: 98;
|
|||
height: $gl-padding-8;
|
||||
position: sticky;
|
||||
top: calc(#{$calc-application-header-height} + 36px);
|
||||
box-shadow: 0 1px 0 $gray-100;
|
||||
box-shadow: 0 1px 0 var(--gl-border-color-default);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,13 +92,13 @@ $sticky-header-z-index: 98;
|
|||
position: sticky;
|
||||
padding: $gl-padding-12 $gl-padding $gl-padding-8;
|
||||
margin: #{-$gl-padding} #{-$gl-padding} 0;
|
||||
background: $body-bg;
|
||||
@apply gl-bg-default;
|
||||
}
|
||||
|
||||
.settings-sticky-footer {
|
||||
bottom: 0;
|
||||
padding: $gl-padding-8 0;
|
||||
box-shadow: 0 -1px 0 $gray-100;
|
||||
box-shadow: 0 -1px 0 var(--gl-border-color-default);
|
||||
}
|
||||
|
||||
// Header shouldn't be sticky if only one section on page
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Groups
|
||||
class RedirectController < ::ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
feature_category :groups_and_projects
|
||||
|
||||
def redirect_from_id
|
||||
group = Group.find(group_params[:id])
|
||||
|
||||
if can?(current_user, :read_group, group)
|
||||
redirect_to group
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_params
|
||||
params.permit(:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -12,8 +12,9 @@ module BatchLoaders
|
|||
|
||||
def self.load_votes_for(object, vote_type, awardable_class: nil)
|
||||
awardable_class ||= object.class.name
|
||||
batch_key = "#{object.class.base_class.name}-#{vote_type}"
|
||||
|
||||
BatchLoader::GraphQL.for(object.id).batch(key: "#{object.issuing_parent_id}-#{vote_type}") do |ids, loader, _args|
|
||||
BatchLoader::GraphQL.for(object.id).batch(key: batch_key) do |ids, loader, _args|
|
||||
counts = AwardEmoji.votes_for_collection(ids, awardable_class).named(vote_type).index_by(&:awardable_id)
|
||||
|
||||
ids.each do |id|
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ module Mutations
|
|||
class Create < ::Mutations::BaseMutation
|
||||
graphql_name 'CreatePackagesProtectionRule'
|
||||
description 'Creates a protection rule to restrict access to project packages. ' \
|
||||
'Available only when feature flag `packages_protected_packages` is enabled.'
|
||||
'Available only when feature flag `packages_protected_packages` is enabled.'
|
||||
|
||||
include FindsProject
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ module Mutations
|
|||
class Delete < ::Mutations::BaseMutation
|
||||
graphql_name 'DeletePackagesProtectionRule'
|
||||
description 'Deletes a protection rule for packages. ' \
|
||||
'Available only when feature flag `packages_protected_packages` is enabled.'
|
||||
'Available only when feature flag `packages_protected_packages` is enabled.'
|
||||
|
||||
authorize :admin_package
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ module Mutations
|
|||
class Update < ::Mutations::BaseMutation
|
||||
graphql_name 'UpdatePackagesProtectionRule'
|
||||
description 'Updates a package protection rule to restrict access to project packages. ' \
|
||||
'You can prevent users without certain permissions from altering packages. ' \
|
||||
'Available only when feature flag `packages_protected_packages` is enabled.'
|
||||
'You can prevent users without certain permissions from altering packages. ' \
|
||||
'Available only when feature flag `packages_protected_packages` is enabled.'
|
||||
|
||||
authorize :admin_package
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ module Mutations
|
|||
alpha: { milestone: '16.6' },
|
||||
description:
|
||||
'Package name protected by the protection rule. For example, `@my-scope/my-package-*`. ' \
|
||||
'Wildcard character `*` allowed.'
|
||||
'Wildcard character `*` allowed.'
|
||||
|
||||
argument :package_type,
|
||||
Types::Packages::Protection::RulePackageTypeEnum,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ module Mutations
|
|||
TodoableID,
|
||||
required: false,
|
||||
description: "Global ID of the to-do item's parent. Issues, merge requests, designs, and epics are supported. " \
|
||||
"If argument is omitted, all pending to-do items of the current user are marked as done."
|
||||
"If argument is omitted, all pending to-do items of the current user are marked as done."
|
||||
|
||||
field :todos, [::Types::TodoType],
|
||||
null: false,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module Resolvers
|
|||
argument :timeframe, Types::TimeframeInputType,
|
||||
required: true,
|
||||
description: 'Aggregation timeframe. Filters the issue or the merge request creation time for FOSS ' \
|
||||
'projects, and the end event timestamp for licensed projects or groups.'
|
||||
'projects, and the end event timestamp for licensed projects or groups.'
|
||||
|
||||
argument :assignee_usernames, [GraphQL::Types::String],
|
||||
required: false,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ module Resolvers
|
|||
required: false,
|
||||
alpha: { milestone: '16.4' },
|
||||
description: 'Filter jobs by runner type if ' \
|
||||
'feature flag `:admin_jobs_filter_runner_type` is enabled.'
|
||||
'feature flag `:admin_jobs_filter_runner_type` is enabled.'
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
jobs = ::Ci::JobsFinder.new(current_user: current_user, params: params_data(args)).execute
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ module Resolvers
|
|||
GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: 'Filter for confidential issues. If "false", excludes confidential issues. ' \
|
||||
'If "true", returns only confidential issues.'
|
||||
'If "true", returns only confidential issues.'
|
||||
argument :created_after, Types::TimeType,
|
||||
required: false,
|
||||
description: 'Issues created after the date.'
|
||||
|
|
@ -69,7 +69,7 @@ module Resolvers
|
|||
argument :my_reaction_emoji, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Filter by reaction emoji applied by the current user. ' \
|
||||
'Wildcard values "NONE" and "ANY" are supported.'
|
||||
'Wildcard values "NONE" and "ANY" are supported.'
|
||||
argument :not, Types::Issues::NegatedIssueFilterInputType,
|
||||
description: 'Negated arguments.',
|
||||
required: false
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ module Resolvers
|
|||
Gitlab::IssuablesCountForState.new(
|
||||
finder(args),
|
||||
resource_parent,
|
||||
fast_fail: true,
|
||||
store_in_redis_cache: true
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ module Resolvers
|
|||
argument :filter, Types::WorkItems::RelatedLinkTypeEnum,
|
||||
required: false,
|
||||
description: "Filter by link type. " \
|
||||
"Supported values: #{Types::WorkItems::RelatedLinkTypeEnum.values.keys.to_sentence}. " \
|
||||
'Returns all types if omitted.'
|
||||
"Supported values: #{Types::WorkItems::RelatedLinkTypeEnum.values.keys.to_sentence}. " \
|
||||
'Returns all types if omitted.'
|
||||
|
||||
type Types::WorkItems::LinkedItemType.connection_type, null: true
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ module Types
|
|||
field :count,
|
||||
::Types::Analytics::CycleAnalytics::MetricType,
|
||||
description: 'Limited item count. The backend counts maximum 1000 items, ' \
|
||||
'for free projects, and maximum 10,000 items for licensed ' \
|
||||
'projects or licensed groups.'
|
||||
'for free projects, and maximum 10,000 items for licensed ' \
|
||||
'projects or licensed groups.'
|
||||
|
||||
field :median,
|
||||
::Types::Analytics::CycleAnalytics::MetricType,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ module Types
|
|||
class DefinitionType < ::Types::BaseObject
|
||||
graphql_name 'AuditEventDefinition'
|
||||
description 'Represents the YAML definitions for audit events defined ' \
|
||||
'in `ee/config/audit_events/types/<event-type-name>.yml` ' \
|
||||
'and `config/audit_events/types/<event-type-name>.yml`.'
|
||||
'in `ee/config/audit_events/types/<event-type-name>.yml` ' \
|
||||
'and `config/audit_events/types/<event-type-name>.yml`.'
|
||||
|
||||
authorize :audit_event_definitions
|
||||
|
||||
|
|
@ -21,14 +21,14 @@ module Types
|
|||
field :introduced_by_issue, GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'Link to the issue introducing the event. For older' \
|
||||
'audit events, it can be a commit URL rather than a' \
|
||||
'merge request URL.'
|
||||
'audit events, it can be a commit URL rather than a' \
|
||||
'merge request URL.'
|
||||
|
||||
field :introduced_by_mr, GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'Link to the merge request introducing the event. For' \
|
||||
'older audit events, it can be a commit URL rather than' \
|
||||
'a merge request URL.'
|
||||
'older audit events, it can be a commit URL rather than' \
|
||||
'a merge request URL.'
|
||||
|
||||
field :feature_category, GraphQL::Types::String,
|
||||
null: false,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module Types
|
|||
|
||||
field :versions, Types::Ci::Catalog::Resources::VersionType.connection_type, null: true,
|
||||
description: 'Versions of the catalog resource. This field can only be ' \
|
||||
'resolved for one catalog resource in any single request.',
|
||||
'resolved for one catalog resource in any single request.',
|
||||
resolver: Resolvers::Ci::Catalog::Resources::VersionsResolver
|
||||
|
||||
field :verification_level, Types::Ci::Catalog::Resources::VerificationLevelEnum, null: true,
|
||||
|
|
@ -44,7 +44,7 @@ module Types
|
|||
|
||||
field :last_30_day_usage_count, GraphQL::Types::Int, null: false,
|
||||
description: 'Number of projects that used a component from this catalog resource in a pipeline, by using ' \
|
||||
'`include:component`, in the last 30 days.',
|
||||
'`include:component`, in the last 30 days.',
|
||||
alpha: { milestone: '17.0' }
|
||||
|
||||
def web_path
|
||||
|
|
|
|||
|
|
@ -16,27 +16,27 @@ module Types
|
|||
GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'File location. It can be masked if it contains masked variables. For example, ' \
|
||||
'`".gitlab/ci/build-images.gitlab-ci.yml"`.'
|
||||
'`".gitlab/ci/build-images.gitlab-ci.yml"`.'
|
||||
|
||||
field :blob,
|
||||
GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'File blob location. It can be masked if it contains masked variables. For example, ' \
|
||||
'`"https://gitlab.com/gitlab-org/gitlab/-/blob/e52d6d0246d7375291850e61f0abc101fbda9dc2' \
|
||||
'/.gitlab/ci/build-images.gitlab-ci.yml"`.'
|
||||
'`"https://gitlab.com/gitlab-org/gitlab/-/blob/e52d6d0246d7375291850e61f0abc101fbda9dc2' \
|
||||
'/.gitlab/ci/build-images.gitlab-ci.yml"`.'
|
||||
|
||||
field :raw,
|
||||
GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'File raw location. It can be masked if it contains masked variables. For example, ' \
|
||||
'`"https://gitlab.com/gitlab-org/gitlab/-/raw/e52d6d0246d7375291850e61f0abc101fbda9dc2' \
|
||||
'/.gitlab/ci/build-images.gitlab-ci.yml"`.'
|
||||
'`"https://gitlab.com/gitlab-org/gitlab/-/raw/e52d6d0246d7375291850e61f0abc101fbda9dc2' \
|
||||
'/.gitlab/ci/build-images.gitlab-ci.yml"`.'
|
||||
|
||||
field :extra, # rubocop:disable Graphql/JSONType
|
||||
GraphQL::Types::JSON,
|
||||
null: true,
|
||||
description: 'Extra information for the `include`, which can contain `job_name`, `project`, and `ref`. ' \
|
||||
'Values can be masked if they contain masked variables.'
|
||||
'Values can be masked if they contain masked variables.'
|
||||
|
||||
field :context_project,
|
||||
GraphQL::Types::String,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ module Ml
|
|||
delegate :name, to: :model
|
||||
|
||||
scope :order_by_model_id_id_desc, -> { order('model_id, id DESC') }
|
||||
scope :latest_by_model, -> { order_by_model_id_id_desc.select('DISTINCT ON (model_id) *') }
|
||||
scope :latest_by_model, -> {
|
||||
order(model_id: :desc, semver_major: :desc, semver_minor: :desc, semver_patch: :desc)
|
||||
.select('DISTINCT ON (model_id) *')
|
||||
}
|
||||
scope :by_version, ->(version) { where("version LIKE ?", "#{sanitize_sql_like(version)}%") } # rubocop:disable GitlabSecurity/SqlInjection -- we are sanitizing
|
||||
scope :for_model, ->(model) { where(project: model.project, model: model) }
|
||||
scope :including_relations, -> { includes(:project, :model, :candidate) }
|
||||
|
|
|
|||
|
|
@ -613,6 +613,9 @@ class User < ApplicationRecord
|
|||
end
|
||||
scope :by_user_email, ->(emails) { iwhere(email: Array(emails)) }
|
||||
scope :by_emails, ->(emails) { joins(:emails).where(emails: { email: Array(emails).map(&:downcase) }) }
|
||||
scope :by_detumbled_emails, ->(detumbled_emails) do
|
||||
joins(:emails).where(emails: { detumbled_email: Array(detumbled_emails) })
|
||||
end
|
||||
scope :for_todos, ->(todos) { where(id: todos.select(:user_id).distinct) }
|
||||
scope :with_emails, -> { preload(:emails) }
|
||||
scope :with_dashboard, ->(dashboard) { where(dashboard: dashboard) }
|
||||
|
|
@ -1735,6 +1738,10 @@ class User < ApplicationRecord
|
|||
verified_emails.uniq
|
||||
end
|
||||
|
||||
def verified_detumbled_emails
|
||||
emails.distinct.confirmed.pluck(:detumbled_email).compact
|
||||
end
|
||||
|
||||
def public_verified_emails
|
||||
strong_memoize(:public_verified_emails) do
|
||||
emails = verified_emails(include_private_email: false)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ class UserCustomAttribute < ApplicationRecord
|
|||
upsert_custom_attribute(user_id: spam_log.user_id, key: AUTO_BANNED_BY_SPAM_LOG_ID, value: spam_log.id)
|
||||
end
|
||||
|
||||
def set_auto_banned_by(user:, reason:)
|
||||
upsert_custom_attribute(user_id: user.id, key: AUTO_BANNED_BY, value: reason)
|
||||
end
|
||||
|
||||
def set_trusted_by(user:, trusted_by:)
|
||||
return unless user && trusted_by
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ module Import
|
|||
groups_awaiting_placeholder_assignment.collect do |namespace|
|
||||
placeholders << "%{group_#{namespace.id}_link_start}#{namespace.name}%{group_#{namespace.id}_link_end}"
|
||||
tag_pairs << tag_pair(
|
||||
tag.a(href: group_group_members_path(namespace)),
|
||||
tag.a(href: group_group_members_path(namespace, tab: 'placeholders')),
|
||||
:"group_#{namespace.id}_link_start",
|
||||
:"group_#{namespace.id}_link_end"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ module Users
|
|||
def execute
|
||||
if user.ban
|
||||
record_custom_attribute
|
||||
ban_duplicate_users
|
||||
success
|
||||
else
|
||||
messages = user.errors.full_messages
|
||||
|
|
@ -20,6 +21,7 @@ module Users
|
|||
def execute!
|
||||
user.ban!
|
||||
record_custom_attribute
|
||||
ban_duplicate_users
|
||||
success
|
||||
end
|
||||
|
||||
|
|
@ -27,13 +29,12 @@ module Users
|
|||
|
||||
attr_reader :user, :reason
|
||||
|
||||
def ban_duplicate_users
|
||||
AntiAbuse::BanDuplicateUsersWorker.perform_async(user.id)
|
||||
end
|
||||
|
||||
def record_custom_attribute
|
||||
custom_attribute = {
|
||||
user_id: user.id,
|
||||
key: UserCustomAttribute::AUTO_BANNED_BY,
|
||||
value: reason
|
||||
}
|
||||
UserCustomAttribute.upsert_custom_attributes([custom_attribute])
|
||||
UserCustomAttribute.set_auto_banned_by(user: user, reason: reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ module Users
|
|||
private
|
||||
|
||||
def update_user(user)
|
||||
user.ban
|
||||
if user.ban
|
||||
ban_duplicate_users(user)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def valid_state?(user)
|
||||
|
|
@ -17,6 +22,10 @@ module Users
|
|||
def action
|
||||
:ban
|
||||
end
|
||||
|
||||
def ban_duplicate_users(user)
|
||||
AntiAbuse::BanDuplicateUsersWorker.perform_async(user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@
|
|||
%tbody
|
||||
%tr
|
||||
%th= _('MD5')
|
||||
%td.gl-font-monospace.key-fingerprint= @key.fingerprint
|
||||
%td.gl-font-monospace= @key.fingerprint
|
||||
- if @key.fingerprint_sha256.present?
|
||||
%tr
|
||||
%th= _('SHA256')
|
||||
%td.gl-font-monospace.key-fingerprint= @key.fingerprint_sha256
|
||||
%td.gl-font-monospace= @key.fingerprint_sha256
|
||||
|
|
|
|||
|
|
@ -2424,6 +2424,15 @@
|
|||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: anti_abuse_ban_duplicate_users
|
||||
:worker_name: AntiAbuse::BanDuplicateUsersWorker
|
||||
:feature_category: :instance_resiliency
|
||||
:has_external_dependencies: false
|
||||
:urgency: :low
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: anti_abuse_spam_abuse_events
|
||||
:worker_name: AntiAbuse::SpamAbuseEventsWorker
|
||||
:feature_category: :instance_resiliency
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AntiAbuse
|
||||
class BanDuplicateUsersWorker
|
||||
include ApplicationWorker
|
||||
|
||||
data_consistency :delayed
|
||||
|
||||
idempotent!
|
||||
feature_category :instance_resiliency
|
||||
urgency :low
|
||||
|
||||
def perform(banned_user_id)
|
||||
@banned_user = User.find_by_id(banned_user_id)
|
||||
return unless banned_user&.banned?
|
||||
|
||||
ban_users_with_the_same_detumbled_email!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :banned_user
|
||||
|
||||
def ban_users_with_the_same_detumbled_email!
|
||||
return unless Feature.enabled?(:auto_ban_via_detumbled_email, banned_user, type: :gitlab_com_derisk)
|
||||
|
||||
reason = "User #{banned_user.id} was banned with the same detumbled email address"
|
||||
|
||||
User.active.by_detumbled_emails(banned_user.verified_detumbled_emails).each do |user|
|
||||
user.with_lock do
|
||||
# Check the user state again in case it changed before acquiring the lock
|
||||
ban_user!(user, reason) if user.active?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def ban_user!(user, reason)
|
||||
user.ban!
|
||||
UserCustomAttribute.set_auto_banned_by(user: user, reason: reason)
|
||||
log_event(user, reason)
|
||||
end
|
||||
|
||||
def log_event(user, reason)
|
||||
Gitlab::AppLogger.info(
|
||||
message: "Duplicate user auto-ban",
|
||||
reason: reason,
|
||||
username: user.username.to_s,
|
||||
user_id: user.id,
|
||||
email: user.email.to_s,
|
||||
triggered_by_banned_user_id: banned_user.id,
|
||||
triggered_by_banned_username: banned_user.username
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
AntiAbuse::BanDuplicateUsersWorker.prepend_mod
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks an event when a user cancels the deletion of a specific package version after initiating the delete process.
|
||||
action: cancel_delete_package_version
|
||||
product_group: package_registry
|
||||
milestone: '16.0'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117497
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks an event when a user cancels the deletion process for multiple package versions after initiating it.
|
||||
action: cancel_delete_package_versions
|
||||
product_group: package_registry
|
||||
milestone: '15.9'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110721
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks an event when a user cancels the deletion process for multiple packages after initiating it.
|
||||
action: cancel_delete_packages
|
||||
product_group: package_registry
|
||||
milestone: '15.8'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104402
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
description: tracks when a user selects or deselects a checkbox in the filter.
|
||||
action: checkbox
|
||||
product_group: global_search
|
||||
milestone: '16.1'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121037
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
additional_properties:
|
||||
label:
|
||||
description: label on the checkbox
|
||||
property:
|
||||
description: value of checkbox (checked/unchecked)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks when a user deletes the work item.
|
||||
action: click_delete_work_item
|
||||
product_group: project_management
|
||||
milestone: '15.0'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86128
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks when a user toggles the confidentiality status of a work item.
|
||||
action: click_toggle_work_item_confidentiality
|
||||
product_group: product_planning
|
||||
milestone: '15.1'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94059
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks an event when a user confirms the deletion of a specific package version.
|
||||
action: delete_package_version
|
||||
product_group: package_registry
|
||||
milestone: '16.0'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117497
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks an event when a user confirms the deletion of multiple package versions in bulk.
|
||||
action: delete_package_versions
|
||||
product_group: package_registry
|
||||
milestone: '15.9'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110721
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks an event when a user confirms the deletion of multiple packages in bulk.
|
||||
action: delete_packages
|
||||
product_group: package_registry
|
||||
milestone: '15.8'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104402
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
description: tracks when a user collapses the sidebar navigation.
|
||||
action: nav_hide
|
||||
product_group: personal_productivity
|
||||
milestone: '16.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124194
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
additional_properties:
|
||||
label:
|
||||
description: Label on how sidebar is hidden with browser resize, toggle button etc.
|
||||
property:
|
||||
description: name of the sidebar(nav_sidebar).
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
description: tracks when a user hovers over or partially opens the sidebar to preview its contents without fully expanding it.
|
||||
action: nav_hover_peek
|
||||
product_group: personal_productivity
|
||||
milestone: '16.4'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131028
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
additional_properties:
|
||||
label:
|
||||
description: nav_sidebar_toggle
|
||||
property:
|
||||
description: name of the sidebar(nav_sidebar).
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
description: tracks when a user hovers over or partially opens the sidebar to preview its contents without fully expanding it.
|
||||
action: nav_peek
|
||||
product_group: personal_productivity
|
||||
milestone: '16.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124194
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
additional_properties:
|
||||
label:
|
||||
description: nav_hover
|
||||
property:
|
||||
description: name of the sidebar(nav_sidebar).
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
description: tracks when a user sees the sidebar navigation.
|
||||
action: nav_show
|
||||
product_group: personal_productivity
|
||||
milestone: '16.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124194
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
additional_properties:
|
||||
label:
|
||||
description: Label on how sidebar is shown with browser resize, toggle button etc.
|
||||
property:
|
||||
description: name of the sidebar(nav_sidebar).
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks an event when a user initiates the request to delete a specific package version.
|
||||
action: request_delete_package_version
|
||||
product_group: package_registry
|
||||
milestone: '16.0'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117497
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks an event when a user initiates the request to delete multiple package versions in bulk.
|
||||
action: request_delete_package_versions
|
||||
product_group: package_registry
|
||||
milestone: '15.9'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110721
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks an event when a user initiates the request to delete multiple packages in bulk.
|
||||
action: request_delete_packages
|
||||
product_group: package_registry
|
||||
milestone: '15.8'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104402
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks when a user initiates the deletion of selected package files in the package registry.
|
||||
action: request_delete_selected_package_file
|
||||
product_group: package_registry
|
||||
milestone: '15.3'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93169
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks when a user selects "Incident" as the issue type from the dropdown.
|
||||
action: select_issue_type_incident
|
||||
product_group: project_management
|
||||
milestone: '15.11'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115364
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks when a user updates the status of an alert.
|
||||
action: update_alert_status
|
||||
product_group: respond
|
||||
milestone: '13.1'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32846
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks when a user submits the form to update the package cleanup policy settings for a project.
|
||||
action: submit_packages_cleanup_form
|
||||
product_group: package_registry
|
||||
milestone: '15.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90783
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
description: tracks when a user updates the status of an alert.
|
||||
action: update_alert_status
|
||||
product_group: respond
|
||||
milestone: '13.1'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32846
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
additional_properties:
|
||||
label:
|
||||
description: status of the alert
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks when a user updates the selected group during the subscription process.
|
||||
action: update_group
|
||||
product_group: acquisition
|
||||
milestone: '14.8'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74828
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks when a user updates the number of seats (users) during the subscription process.
|
||||
action: update_seat_count
|
||||
product_group: acquisition
|
||||
milestone: '14.8'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74828
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
description: tracks when a user views the details of an alert
|
||||
action: view_alert_details
|
||||
product_group: respond
|
||||
milestone: '13.1'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32846
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: auto_ban_via_detumbled_email
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/modelops/anti-abuse/team-tasks/-/issues/814
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/166673
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/491796
|
||||
milestone: '17.5'
|
||||
group: group::anti-abuse
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -5,4 +5,4 @@ rollout_issue_url:
|
|||
milestone: '16.1'
|
||||
type: ops
|
||||
group: group::tenant scale
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -453,6 +453,10 @@ project_security_exclusions:
|
|||
- table: projects
|
||||
column: project_id
|
||||
on_delete: async_delete
|
||||
project_security_statistics:
|
||||
- table: projects
|
||||
column: project_id
|
||||
on_delete: async_delete
|
||||
projects:
|
||||
- table: organizations
|
||||
column: organization_id
|
||||
|
|
|
|||
|
|
@ -726,6 +726,9 @@ Settings.cron_jobs['ci_click_house_finished_pipelines_sync_worker']['job_class']
|
|||
Settings.cron_jobs['deactivate_expired_deployments_cron_worker'] ||= {}
|
||||
Settings.cron_jobs['deactivate_expired_deployments_cron_worker']['cron'] ||= '*/10 * * * *'
|
||||
Settings.cron_jobs['deactivate_expired_deployments_cron_worker']['job_class'] ||= 'Pages::DeactivateExpiredDeploymentsCronWorker'
|
||||
Settings.cron_jobs['database_monitor_locked_tables_cron_worker'] ||= {}
|
||||
Settings.cron_jobs['database_monitor_locked_tables_cron_worker']['cron'] ||= '30 7 */3 * *'
|
||||
Settings.cron_jobs['database_monitor_locked_tables_cron_worker']['job_class'] = 'Database::MonitorLockedTablesWorker'
|
||||
|
||||
Gitlab.ee do
|
||||
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= {}
|
||||
|
|
@ -752,9 +755,6 @@ Gitlab.ee do
|
|||
Settings.cron_jobs['adjourned_projects_deletion_cron_worker'] ||= {}
|
||||
Settings.cron_jobs['adjourned_projects_deletion_cron_worker']['cron'] ||= '0 7 * * *'
|
||||
Settings.cron_jobs['adjourned_projects_deletion_cron_worker']['job_class'] = 'AdjournedProjectsDeletionCronWorker'
|
||||
Settings.cron_jobs['database_monitor_locked_tables_cron_worker'] ||= {}
|
||||
Settings.cron_jobs['database_monitor_locked_tables_cron_worker']['cron'] ||= '30 7 */3 * *'
|
||||
Settings.cron_jobs['database_monitor_locked_tables_cron_worker']['job_class'] = 'Database::MonitorLockedTablesWorker'
|
||||
Settings.cron_jobs['geo_verification_cron_worker'] ||= {}
|
||||
Settings.cron_jobs['geo_verification_cron_worker']['cron'] ||= '* * * * *'
|
||||
Settings.cron_jobs['geo_verification_cron_worker']['job_class'] ||= 'Geo::VerificationCronWorker'
|
||||
|
|
|
|||
|
|
@ -270,11 +270,14 @@ InitializerConnections.raise_if_new_database_connection do
|
|||
|
||||
resources :groups, only: [:index, :new, :create]
|
||||
|
||||
get '/-/g/:id' => 'groups/redirect#redirect_from_id'
|
||||
|
||||
draw :group
|
||||
|
||||
resources :projects, only: [:index, :new, :create]
|
||||
|
||||
get '/projects/:id' => 'projects/redirect#redirect_from_id'
|
||||
get '/-/p/:id' => 'projects/redirect#redirect_from_id'
|
||||
|
||||
draw :git_http
|
||||
draw :api
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@
|
|||
- 1
|
||||
- - analytics_usage_trends_counter_job
|
||||
- 1
|
||||
- - anti_abuse_ban_duplicate_users
|
||||
- 1
|
||||
- - anti_abuse_new_abuse_report
|
||||
- 1
|
||||
- - anti_abuse_spam_abuse_events
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
table_name: project_security_statistics
|
||||
classes:
|
||||
- Security::ProjectStatistics
|
||||
feature_categories:
|
||||
- vulnerability_management
|
||||
description: Stores security-related statistics
|
||||
introduced_by_url:
|
||||
milestone: '17.5'
|
||||
gitlab_schema: gitlab_sec
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateProjectSecurityStatisticsTable < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.5'
|
||||
|
||||
def change
|
||||
create_table :project_security_statistics, id: false do |t| # rubocop:disable Migration/EnsureFactoryForTable -- False positive
|
||||
t.bigint :project_id, primary_key: true, default: nil
|
||||
t.integer :vulnerability_count, default: 0, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
2baf9eab2929a27d4dfca9d7a811a44e39102e0ac642dc769a008a06c18940a9
|
||||
|
|
@ -16955,6 +16955,11 @@ CREATE SEQUENCE project_security_settings_project_id_seq
|
|||
|
||||
ALTER SEQUENCE project_security_settings_project_id_seq OWNED BY project_security_settings.project_id;
|
||||
|
||||
CREATE TABLE project_security_statistics (
|
||||
project_id bigint NOT NULL,
|
||||
vulnerability_count integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE project_settings (
|
||||
project_id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
|
@ -25057,6 +25062,9 @@ ALTER TABLE ONLY project_security_exclusions
|
|||
ALTER TABLE ONLY project_security_settings
|
||||
ADD CONSTRAINT project_security_settings_pkey PRIMARY KEY (project_id);
|
||||
|
||||
ALTER TABLE ONLY project_security_statistics
|
||||
ADD CONSTRAINT project_security_statistics_pkey PRIMARY KEY (project_id);
|
||||
|
||||
ALTER TABLE ONLY project_settings
|
||||
ADD CONSTRAINT project_settings_pkey PRIMARY KEY (project_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -473,7 +473,7 @@ you must do so in an `attributes` hash.
|
|||
|
||||
| Setting | Description | Examples |
|
||||
|--------------|-------------|----------|
|
||||
| `username` | Used in paths for the user's own projects (for example, `gitlab.example.com/username/project`) and when mentioning them in issues, merge request and comments (for example, `@username`). If the attribute specified for `username` contains an email address, the GitLab username is part of the email address before the `@`. Defaults to the LDAP attribute [specified as `uid`](#basic-configuration-settings). | `['uid', 'userid', 'sAMAccountName']` |
|
||||
| `username` | The `@username` that the GitLab account will be provisioned with. If the value contains an email address, the GitLab username is the part of the email address before the `@`. Defaults to the LDAP attribute [specified as `uid`](#basic-configuration-settings). | `['uid', 'userid', 'sAMAccountName']` |
|
||||
| `email` | LDAP attribute for user email. Defaults to `['mail', 'email', 'userPrincipalName']` | `['mail', 'email', 'userPrincipalName']` |
|
||||
| `name` | LDAP attribute for user display name. If `name` is blank, the full name is taken from the `first_name` and `last_name`. Defaults to `'cn'`. | Attributes `'cn'`, or `'displayName'` commonly carry full names. Alternatively, you can force the use of `first_name` and `last_name` by specifying an absent attribute such as `'somethingNonExistent'`. |
|
||||
| `first_name` | LDAP attribute for user first name. Used when the attribute configured for `name` does not exist. Defaults to `'givenName'`. | `'givenName'` |
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# Configure GitLab to access self-hosted models
|
||||
|
||||
DETAILS:
|
||||
**Tier:** For a limited time, Premium and Ultimate. On October 17, 2024, [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
|
||||
**Tier:** For a limited time, Ultimate. On October 17, 2024, Ultimate with [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
|
||||
**Offering:** Self-managed
|
||||
**Status:** Beta
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# Deploy a self-hosted large language model
|
||||
|
||||
DETAILS:
|
||||
**Tier:** For a limited time, Premium and Ultimate. On October 17, 2024, [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
|
||||
**Tier:** For a limited time, Ultimate. On October 17, 2024, Ultimate with [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
|
||||
**Offering:** Self-managed
|
||||
**Status:** Beta
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# Set up your self-hosted model infrastructure
|
||||
|
||||
DETAILS:
|
||||
**Tier:** For a limited time, Premium and Ultimate. On October 17, 2024, [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
|
||||
**Tier:** For a limited time, Ultimate. On October 17, 2024, Ultimate with [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
|
||||
**Offering:** Self-managed
|
||||
**Status:** Beta
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ group: Custom Models
|
|||
description: Self Hosted model setup with an OpenAI Proxy Server (LiteLLM)
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Set up a self-hosted large language model with LiteLLM
|
||||
|
||||
[LiteLLM](https://www.litellm.ai/) is an OpenAI proxy server. You can use LiteLLM to simplify the integration with different large language models (LLMs) by leveraging the OpenAI API spec. Use LiteLLM to easily switch between different LLMs.
|
||||
|
|
|
|||
|
|
@ -8,13 +8,20 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
DETAILS:
|
||||
**Tier:** Ultimate
|
||||
**Offering:** GitLab.com
|
||||
**Offering:** GitLab.com, Self-managed
|
||||
**Status:** Beta
|
||||
|
||||
> - Introduced in GitLab 15.4 [with a flag](../administration/feature_flags.md) named `cube_api_proxy`. Disabled by default.
|
||||
> - `cube_api_proxy` removed and replaced with `product_analytics_internal_preview` in GitLab 15.10.
|
||||
> - `product_analytics_internal_preview` replaced with `product_analytics_dashboards` in GitLab 15.11.
|
||||
> - `product_analytics_dashboards` [enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/398653) by default in GitLab 16.11.
|
||||
> - Feature flag `product_analytics_dashboards` [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/454059) in GitLab 17.1.
|
||||
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167296) to beta and feature flag `product_analytics_features` added in GitLab 17.5.
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
This feature is not ready for production use.
|
||||
|
||||
NOTE:
|
||||
Make sure to define the `cube_api_base_url` and `cube_api_key` application settings first using [the API](settings.md).
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ You can also enable and disable the setting with the [GraphQL](../../api/graphql
|
|||
### Git push to your project repository
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/389060) in GitLab 17.2. [with a flag](../../administration/feature_flags.md) named `allow_push_repository_for_job_token`. Disabled by default.
|
||||
> - **Token Access** setting [renamed to **Job token permissions**](https://gitlab.com/gitlab-org/gitlab/-/issues/415519) in GitLab 17.2.
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
|
|
@ -208,7 +209,7 @@ To grant permission to job tokens generated in your project to push to the proje
|
|||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > CI/CD**.
|
||||
1. Expand **Token Access**.
|
||||
1. Expand **Job token permissions**.
|
||||
1. In the **Permissions** section, select **Allow Git push requests to the repository**.
|
||||
|
||||
The job token has the same access permissions as the user that started the job.
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ configure the settings that are common for all providers.
|
|||
|
||||
| Linux package, Docker, and self-compiled | Helm chart | Description | Default value |
|
||||
| ----------------------------|------------|-------------|-----------|
|
||||
| `enable` | `enable` | Allows the use of OmniAuth providers. | `false`, which means that signing in using your OmniAuth providers is not allowed, and the OmniAuth provider buttons are not visible in the user interface. |
|
||||
| `enabled` | `enabled` | Allows the use of OmniAuth providers. | `false`, which means that signing in using your OmniAuth providers is not allowed, and the OmniAuth provider buttons are not visible in the user interface. |
|
||||
| `allow_single_sign_on` | `allowSingleSignOn` | List of providers that automatically create a GitLab account. The provider names are available in the **OmniAuth provider name** column in the [supported providers table](#supported-providers). | `false`, which means that signing in using your OmniAuth provider account without a pre-existing GitLab account is not allowed. You must create a GitLab account first, and then connect it to your OmniAuth provider account through your profile settings. |
|
||||
| `auto_link_ldap_user` | `autoLinkLdapUser` | Creates an LDAP identity in GitLab for users that are created through an OmniAuth provider. You can enable this setting if you have [LDAP integration](../administration/auth/ldap/index.md) enabled. Requires the `uid` of the user to be the same in both LDAP and the OmniAuth provider. | `false` |
|
||||
| `block_auto_created_users` | `blockAutoCreatedUsers` | Places automatically-created users in a [Pending approval](../administration/moderate_users.md#users-pending-approval) state (unable to sign in) until they are approved by an administrator. | `true`. If you set the value to `false`, make sure you define providers that you can control, like SAML or Google. Otherwise, any user on the internet can sign in to GitLab without an administrator's approval. |
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ DETAILS:
|
|||
### Self-Hosted Models
|
||||
|
||||
DETAILS:
|
||||
**Tier:** For a limited time, Premium and Ultimate. On October 17, 2024, [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
|
||||
**Tier:** For a limited time, Ultimate. On October 17, 2024, Ultimate with [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
|
||||
**Offering:** Self-managed
|
||||
**Status:** Beta
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
DETAILS:
|
||||
**Tier:** Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
**Offering:** GitLab.com, Self-managed
|
||||
**Status:** Beta
|
||||
|
||||
> - Introduced in GitLab 15.4 as an [experiment](../../policy/experiment-beta-support.md#experiment) feature [with a flag](../../administration/feature_flags.md) named `cube_api_proxy`. Disabled by default.
|
||||
|
|
@ -23,6 +23,7 @@ DETAILS:
|
|||
> - [Enabled on self-managed and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/issues/444345) in GitLab 16.11.
|
||||
> - Feature flag `product_analytics_dashboards` [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/454059) in GitLab 17.1.
|
||||
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167192) to beta and feature flag `product_analytics_admin_settings` added in GitLab 17.5.
|
||||
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167296) to beta and feature flag `product_analytics_features` added in GitLab 17.5.
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ client.get_latest_versions(model_name)
|
|||
**Notes**
|
||||
|
||||
- Argument `stages` is ignored.
|
||||
- Versions are ordered by last created.
|
||||
- Versions are ordered by highest semantic version.
|
||||
|
||||
#### Loading a model version
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ The Repository X-Ray searches a maximum of two directory levels from the reposit
|
|||
| Java | Gradle | `build.gradle` | 17.4 or later |
|
||||
| Java | Maven | `pom.xml` | 17.4 or later |
|
||||
| Kotlin | Gradle | `build.gradle.kts` | 17.5 or later |
|
||||
| PHP | Composer | `composer.lock` | 17.5 or later |
|
||||
| Python | Conda | `environment.yml` | 17.5 or later |
|
||||
| Python | Pip | `requirements.txt` | 17.5 or later |
|
||||
| Python | Poetry | `poetry.lock`, `pyproject.toml` | 17.5 or later |
|
||||
|
|
|
|||
|
|
@ -143,8 +143,8 @@ module Gitlab
|
|||
|
||||
def params_include_filters?
|
||||
non_filtering_params = %i[
|
||||
scope state sort group_id include_subgroups namespace_id
|
||||
attempt_group_search_optimizations non_archived issue_types lookahead
|
||||
scope state sort group_id include_subgroups include_descendants namespace_id
|
||||
attempt_group_search_optimizations non_archived issue_types lookahead exclude_projects
|
||||
]
|
||||
|
||||
finder.params.except(*non_filtering_params).values.any?
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
reports/
|
||||
no_of_examples/
|
||||
.ruby-version
|
||||
.tool-versions
|
||||
.ruby-gemset
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
--color
|
||||
--format documentation
|
||||
--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
|
||||
--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
|
||||
--require spec_helper
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'gitlab-qa', '~> 14', '>= 14.15.0', require: 'gitlab/qa'
|
||||
gem 'gitlab-qa', '~> 14', '>= 14.16.0', require: 'gitlab/qa'
|
||||
gem 'gitlab_quality-test_tooling', '~> 1.38.1', require: false
|
||||
gem 'gitlab-utils', path: '../gems/gitlab-utils'
|
||||
gem 'activesupport', '~> 7.0.8.4' # This should stay in sync with the root's Gemfile
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ GEM
|
|||
gitlab (4.19.0)
|
||||
httparty (~> 0.20)
|
||||
terminal-table (>= 1.5.1)
|
||||
gitlab-qa (14.15.0)
|
||||
gitlab-qa (14.16.0)
|
||||
activesupport (>= 6.1, < 7.2)
|
||||
gitlab (~> 4.19)
|
||||
http (~> 5.0)
|
||||
|
|
@ -369,7 +369,7 @@ DEPENDENCIES
|
|||
fog-core (= 2.1.0)
|
||||
fog-google (~> 1.24, >= 1.24.1)
|
||||
gitlab-cng!
|
||||
gitlab-qa (~> 14, >= 14.15.0)
|
||||
gitlab-qa (~> 14, >= 14.16.0)
|
||||
gitlab-utils!
|
||||
gitlab_quality-test_tooling (~> 1.38.1)
|
||||
googleauth (~> 1.9.0)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ARG GDK_SHA=25dd408d7727657811bf9fb59b1cbc15d4f1d160
|
||||
ARG GDK_SHA=a5a1c84f9a0625231b09b03c8dadcc2322a3e2bb
|
||||
# Use tag prefix when running on 'stable' branch to make sure 'protected' image is used which is not deleted by registry cleanup
|
||||
ARG GDK_BASE_TAG_PREFIX
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ module QA
|
|||
return false
|
||||
end
|
||||
|
||||
QA::Support::Retrier.retry_on_exception do
|
||||
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
|
||||
end
|
||||
# Perform app readiness check before continuing with the whole test suite
|
||||
Tools::ReadinessCheck.perform(wait: 60)
|
||||
|
||||
# Reset admin password if admin token is present but can't be used due to expired password
|
||||
reset_admin_password!
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require 'active_support/deprecation'
|
||||
require 'uri'
|
||||
require 'etc'
|
||||
|
||||
module QA
|
||||
module Runtime
|
||||
|
|
@ -734,6 +735,16 @@ module QA
|
|||
enabled?(ENV['QA_RSPEC_RETRIED'], default: false)
|
||||
end
|
||||
|
||||
def parallel_processes
|
||||
ENV.fetch('QA_PARALLEL_PROCESSES') do
|
||||
[Etc.nprocessors / 2, 1].max
|
||||
end.to_i
|
||||
end
|
||||
|
||||
def parallel_run?
|
||||
enabled?(ENV["QA_PARALLEL_RUN"], default: false)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remote_grid_credentials
|
||||
|
|
|
|||
|
|
@ -10,12 +10,26 @@ module QA
|
|||
def logger
|
||||
@logger ||= Gitlab::QA::TestLogger.logger(
|
||||
level: Gitlab::QA::Runtime::Env.log_level,
|
||||
source: 'QA Tests',
|
||||
path: File.expand_path('../../tmp', __dir__)
|
||||
source: logger_source,
|
||||
path: log_path
|
||||
)
|
||||
end
|
||||
|
||||
delegate :debug, :info, :warn, :error, :fatal, :unknown, to: :logger
|
||||
|
||||
private
|
||||
|
||||
def logger_source
|
||||
if ENV['TEST_ENV_NUMBER']
|
||||
"QA Tests ENV-#{ENV['TEST_ENV_NUMBER']}"
|
||||
else
|
||||
"QA Tests"
|
||||
end
|
||||
end
|
||||
|
||||
def log_path
|
||||
File.expand_path('../../tmp', __dir__)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module QA
|
||||
module Specs
|
||||
class KnapsackRunner
|
||||
def self.run(args)
|
||||
def self.run(args, parallel: false)
|
||||
QA::Support::KnapsackReport.configure!
|
||||
|
||||
allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
|
||||
|
|
@ -14,6 +14,12 @@ module QA
|
|||
Knapsack.logger.info 'Leftover specs:'
|
||||
Knapsack.logger.info allocator.leftover_node_tests
|
||||
|
||||
if parallel
|
||||
rspec_args = args.reject { |arg| arg == "--" || arg.start_with?("qa/specs/features") }
|
||||
run_args = [*rspec_args, '--', *allocator.node_tests]
|
||||
return ParallelRunner.run(run_args)
|
||||
end
|
||||
|
||||
status = RSpec::Core::Runner.run([*args, '--', *allocator.node_tests])
|
||||
yield status if block_given?
|
||||
status
|
||||
|
|
|
|||
|
|
@ -1,31 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'open3'
|
||||
require "parallel_tests"
|
||||
require "etc"
|
||||
|
||||
module QA
|
||||
module Specs
|
||||
module ParallelRunner
|
||||
module_function
|
||||
class ParallelRunner
|
||||
class << self
|
||||
def run(rspec_args)
|
||||
used_processes = Runtime::Env.parallel_processes
|
||||
|
||||
def run(args)
|
||||
unless args.include?('--')
|
||||
index = args.index { |opt| opt.include?('features') }
|
||||
args = [
|
||||
"--type", "rspec",
|
||||
"-n", used_processes.to_s,
|
||||
"--serialize-stdout",
|
||||
'--first-is-1',
|
||||
"--combine-stderr"
|
||||
]
|
||||
|
||||
args.insert(index, '--') if index
|
||||
unless rspec_args.include?('--')
|
||||
index = rspec_args.index { |opt| opt.include?("qa/specs/features") }
|
||||
|
||||
rspec_args.insert(index, '--') if index
|
||||
end
|
||||
|
||||
args.push("--", *rspec_args) unless rspec_args.empty?
|
||||
|
||||
set_environment!
|
||||
perform_global_setup!
|
||||
|
||||
ParallelTests::CLI.new.run(args)
|
||||
end
|
||||
|
||||
env = {}
|
||||
Runtime::Env::ENV_VARIABLES.each_key do |key|
|
||||
env[key] = ENV[key] if ENV[key]
|
||||
private
|
||||
|
||||
def perform_global_setup!
|
||||
Runtime::Browser.configure!
|
||||
Runtime::Release.perform_before_hooks
|
||||
end
|
||||
env['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = Runtime::Scenario.attributes.to_json
|
||||
env['GITLAB_QA_ACCESS_TOKEN'] = Runtime::API::Client.new(:gitlab).personal_access_token unless env['GITLAB_QA_ACCESS_TOKEN']
|
||||
|
||||
cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{args.flatten.join(' ')}"
|
||||
::Open3.popen2e(env, cmd) do |_, out, wait|
|
||||
out.each { |line| puts line }
|
||||
def set_environment!
|
||||
ENV.store("NO_KNAPSACK", "true")
|
||||
ENV.store("QA_PARALLEL_RUN", "true")
|
||||
|
||||
exit wait.value.exitstatus
|
||||
return if ENV["QA_GITLAB_URL"].present?
|
||||
|
||||
Support::GitlabAddress.define_gitlab_address_attribute!
|
||||
ENV.store("QA_GITLAB_URL", Support::GitlabAddress.address_with_port(with_default_port: false))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require 'rspec/core'
|
||||
require 'rspec/expectations'
|
||||
require 'tempfile'
|
||||
|
||||
module QA
|
||||
module Specs
|
||||
|
|
@ -47,51 +48,57 @@ module QA
|
|||
end
|
||||
|
||||
def perform
|
||||
args = []
|
||||
args.push('--tty') if tty
|
||||
args.push(rspec_tags)
|
||||
args.push(options)
|
||||
args = build_initial_args
|
||||
# use options from default .rspec file for metadata only runs
|
||||
configure_default_formatters!(args) unless metadata_run?
|
||||
args.push(DEFAULT_TEST_PATH_ARGS) unless custom_test_paths?
|
||||
|
||||
unless Runtime::Env.knapsack? || options.any? { |opt| opt.include?('features') }
|
||||
args.push(DEFAULT_TEST_PATH_ARGS)
|
||||
run_rspec(args)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
delegate :rspec_retried?, :parallel_run?, to: Runtime::Env
|
||||
|
||||
def build_initial_args
|
||||
[].tap do |args|
|
||||
args.push('--tty') if tty
|
||||
args.push(rspec_tags)
|
||||
args.push(options)
|
||||
end
|
||||
end
|
||||
|
||||
if Runtime::Env.knapsack?
|
||||
KnapsackRunner.run(args.flatten) { |status| abort if status.nonzero? }
|
||||
elsif Runtime::Scenario.attributes[:parallel]
|
||||
ParallelRunner.run(args.flatten)
|
||||
elsif Runtime::Scenario.attributes[:loop]
|
||||
LoopRunner.run(args.flatten)
|
||||
elsif Runtime::Scenario.attributes[:count_examples_only]
|
||||
def run_rspec(args)
|
||||
if Runtime::Scenario.attributes[:count_examples_only]
|
||||
count_examples_only(args)
|
||||
elsif Runtime::Scenario.attributes[:test_metadata_only]
|
||||
test_metadata_only(args)
|
||||
elsif Runtime::Env.knapsack?
|
||||
KnapsackRunner.run(args.flatten, parallel: parallel_run?) { |status| abort if status.nonzero? }
|
||||
elsif !rspec_retried? && parallel_run?
|
||||
ParallelRunner.run(args.flatten)
|
||||
elsif Runtime::Scenario.attributes[:loop]
|
||||
LoopRunner.run(args.flatten)
|
||||
else
|
||||
RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap { |status| abort if status.nonzero? }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def count_examples_only(args)
|
||||
args.unshift('--dry-run')
|
||||
out = StringIO.new
|
||||
err = StringIO.new
|
||||
|
||||
RSpec::Core::Runner.run(args.flatten, $stderr, out).tap do |status|
|
||||
abort if status.nonzero?
|
||||
total_examples = Tempfile.open('test-metadata.json') do |file|
|
||||
RSpec.configure { |config| config.add_formatter(QA::Support::JsonFormatter, file.path) }
|
||||
RSpec::Core::Runner.run(args.flatten, err, out).tap { |status| abort if status.nonzero? }
|
||||
|
||||
JSON.load_file(file, symbolize_names: true).dig(:summary, :example_count)
|
||||
end
|
||||
|
||||
begin
|
||||
total_examples = out.string.match(/(\d+) examples?,/)[1]
|
||||
rescue StandardError
|
||||
raise RegexMismatchError, 'Rspec output did not match regex'
|
||||
end
|
||||
|
||||
filename = build_filename
|
||||
|
||||
File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0
|
||||
|
||||
$stdout.puts total_examples
|
||||
puts total_examples
|
||||
rescue StandardError => e
|
||||
raise e, "Failed to detect example count, error: '#{e}'.\nOut: '#{out.string}'\nErr: #{err.string}"
|
||||
end
|
||||
|
||||
def test_metadata_only(args)
|
||||
|
|
@ -111,23 +118,28 @@ module QA
|
|||
$stdout.puts "Saved to file: #{output_file}"
|
||||
end
|
||||
|
||||
def build_filename
|
||||
filename = Runtime::Scenario.klass.split('::').last(3).join('_').downcase
|
||||
def configure_default_formatters!(args)
|
||||
default_formatter_file_name = "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-#{rspec_retried?}"
|
||||
filename = if parallel_run?
|
||||
rspec_retried? ? default_formatter_file_name : "#{default_formatter_file_name}-$TEST_ENV_NUMBER"
|
||||
else
|
||||
default_formatter_file_name
|
||||
end
|
||||
|
||||
tags = []
|
||||
tag_opts = %w[--tag -t]
|
||||
options.reduce do |before, after|
|
||||
tags << after if tag_opts.include?(before)
|
||||
after
|
||||
args.push("--format", "documentation") unless args.flatten.include?("documentation")
|
||||
{ "QA::Support::JsonFormatter" => "json", "RspecJunitFormatter" => "xml" }.each do |formatter, extension|
|
||||
next if args.flatten.include?(formatter)
|
||||
|
||||
args.push("--format", formatter, "--out", "#{filename}.#{extension}")
|
||||
end
|
||||
tags = tags.compact.join('_')
|
||||
end
|
||||
|
||||
filename.concat("_#{tags}") unless tags.empty?
|
||||
def custom_test_paths?
|
||||
Runtime::Env.knapsack? || options.any? { |opt| opt.include?('features') }
|
||||
end
|
||||
|
||||
filename.concat('.txt')
|
||||
|
||||
FileUtils.mkdir_p('no_of_examples')
|
||||
File.join('no_of_examples', filename)
|
||||
def metadata_run?
|
||||
Runtime::Scenario.attributes[:count_examples_only] || Runtime::Scenario.attributes[:test_metadata_only]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,7 +42,12 @@ RSpec.configure do |config|
|
|||
config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', 'tmp/examples.txt')
|
||||
|
||||
config.prepend_before do |example|
|
||||
QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}")
|
||||
if QA::Runtime::Env.parallel_run?
|
||||
QA::Runtime::Logger.info("Starting test - PID #{Process.pid}: #{Rainbow(example.full_description).bright}")
|
||||
else
|
||||
QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}")
|
||||
end
|
||||
|
||||
QA::Runtime::Example.current = example
|
||||
|
||||
visit(QA::Runtime::Scenario.gitlab_address) if QA::Runtime::Env.mobile_layout?
|
||||
|
|
@ -53,8 +58,8 @@ RSpec.configure do |config|
|
|||
end
|
||||
|
||||
config.prepend_before(:suite) do
|
||||
# Perform before hooks at the very start of the test run
|
||||
QA::Runtime::Release.perform_before_hooks unless QA::Runtime::Env.dry_run
|
||||
# Perform before hooks at the very start of the test run, perform once for parallel runs
|
||||
QA::Runtime::Release.perform_before_hooks unless QA::Runtime::Env.dry_run || QA::Runtime::Env.parallel_run?
|
||||
end
|
||||
|
||||
config.before(:suite) do
|
||||
|
|
|
|||
|
|
@ -97,9 +97,12 @@ module QA
|
|||
def push_test_metrics_to_gcs
|
||||
init_gcs_client! # init client and exit early if mandatory configuration is missing
|
||||
retry_on_exception(sleep_interval: 30, message: 'Failed to push test metrics to GCS') do
|
||||
gcs_client.put_object(gcs_bucket, metrics_file_name(prefix: 'test',
|
||||
postfix: "-#{env('CI_PIPELINE_ID') || 'local'}"), execution_data.to_json,
|
||||
force: true, content_type: 'application/json')
|
||||
gcs_client.put_object(
|
||||
gcs_bucket,
|
||||
metrics_file_name(prefix: 'test', postfix: metrics_filename_postfix),
|
||||
execution_data.to_json,
|
||||
force: true, content_type: 'application/json'
|
||||
)
|
||||
|
||||
log(:info, "Pushed #{execution_data.length} test execution entries to GCS")
|
||||
end
|
||||
|
|
@ -140,10 +143,12 @@ module QA
|
|||
def push_fabrication_metrics_gcs(data)
|
||||
init_gcs_client! # init client and exit early if mandatory configuration is missing
|
||||
retry_on_exception(sleep_interval: 30, message: 'Failed to push resource fabrication metrics to GCS') do
|
||||
gcs_client.put_object(gcs_bucket,
|
||||
metrics_file_name(prefix: 'fabrication',
|
||||
postfix: "-#{env('CI_PIPELINE_ID') || 'local'}"),
|
||||
data.to_json, force: true, content_type: 'application/json')
|
||||
gcs_client.put_object(
|
||||
gcs_bucket,
|
||||
metrics_file_name(prefix: 'fabrication', postfix: metrics_filename_postfix),
|
||||
data.to_json, force: true,
|
||||
content_type: 'application/json'
|
||||
)
|
||||
|
||||
log(:info, "Pushed #{data.length} resource fabrication entries to GCS")
|
||||
end
|
||||
|
|
@ -186,6 +191,17 @@ module QA
|
|||
"#{retry_failed_specs? ? "-retry-#{rspec_retried?}" : ''}#{postfix}.json"
|
||||
end
|
||||
|
||||
# Postfix for metrics filenames
|
||||
#
|
||||
# @return [String]
|
||||
def metrics_filename_postfix
|
||||
@metrics_filename_postfix ||= if QA::Runtime::Env.parallel_run?
|
||||
"-#{Process.pid}-#{env('CI_PIPELINE_ID') || 'local'}"
|
||||
else
|
||||
"-#{env('CI_PIPELINE_ID') || 'local'}"
|
||||
end
|
||||
end
|
||||
|
||||
# Transform example to influxdb compatible metrics data
|
||||
# https://github.com/influxdata/influxdb-client-ruby#data-format
|
||||
#
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "nokogiri"
|
||||
|
||||
module QA
|
||||
module Tools
|
||||
# Helper to assert GitLab instance readiness without starting a web browser
|
||||
#
|
||||
class ReadinessCheck
|
||||
include Support::API
|
||||
|
||||
def self.perform(wait: 60)
|
||||
new(wait: wait).perform
|
||||
end
|
||||
|
||||
def initialize(wait:)
|
||||
@wait = wait
|
||||
end
|
||||
|
||||
# Validate gitlab readiness via check for presence of sign-in-form element
|
||||
#
|
||||
# @return [void]
|
||||
def perform
|
||||
error = nil
|
||||
|
||||
info("Waiting for Gitlab to become ready!")
|
||||
Support::Retrier.retry_until(max_duration: wait, sleep_interval: 1, raise_on_failure: false, log: false) do
|
||||
result = !required_elements_missing?
|
||||
error = nil
|
||||
|
||||
result
|
||||
rescue StandardError => e
|
||||
error = "#{error_base} #{e.message}"
|
||||
|
||||
false
|
||||
end
|
||||
raise error if error
|
||||
|
||||
info("Gitlab is ready!")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
delegate :debug, :info, to: QA::Runtime::Logger
|
||||
|
||||
attr_reader :wait
|
||||
|
||||
# Sign in page url
|
||||
#
|
||||
# @return [String]
|
||||
def url
|
||||
@url ||= "#{Support::GitlabAddress.address_with_port(with_default_port: false)}/users/sign_in"
|
||||
end
|
||||
|
||||
# Error message base
|
||||
#
|
||||
# @return [String]
|
||||
def error_base
|
||||
@error_base ||= "Gitlab readiness check failed, valid sign_in page did not appear within #{wait} seconds!"
|
||||
end
|
||||
|
||||
# Required elements css selectors
|
||||
#
|
||||
# @return [Array<String>]
|
||||
def elements_css
|
||||
@element_css ||= QA::Page::Main::Login.elements.select(&:required?).map(&:selector_css)
|
||||
end
|
||||
|
||||
# Check for missing required elements on sign-in page
|
||||
#
|
||||
# @return [Boolean]
|
||||
def required_elements_missing?
|
||||
debug("Checking for required element presence on '#{url}'")
|
||||
# Do not perform headless request on .com due to cloudfare
|
||||
return rendered_elements_missing? if Runtime::Env.running_on_dot_com?
|
||||
|
||||
response = get(url)
|
||||
|
||||
unless ok_response?(response)
|
||||
msg = "Got unsucessfull response code: #{response.code}"
|
||||
debug(msg) && raise(msg)
|
||||
end
|
||||
|
||||
unless required_elements_present?(response)
|
||||
msg = "Sign in page missing required elements: '#{elements_css}'"
|
||||
debug(msg) && raise(msg)
|
||||
end
|
||||
|
||||
debug("Required elements are present!")
|
||||
false
|
||||
end
|
||||
|
||||
# Perform check for present elements via web browser
|
||||
#
|
||||
# @return [Boolean]
|
||||
def rendered_elements_missing?
|
||||
debug("Checking for required elements via web browser")
|
||||
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
||||
false
|
||||
rescue StandardError => e
|
||||
debug("Sign in page did not render fully")
|
||||
raise(e)
|
||||
end
|
||||
|
||||
# Validate response code is 200
|
||||
#
|
||||
# @param [RestClient::Response] response
|
||||
# @return [Boolean]
|
||||
def ok_response?(response)
|
||||
response.code == Support::API::HTTP_STATUS_OK
|
||||
end
|
||||
|
||||
# Check required elements are present on sign-in page
|
||||
#
|
||||
# @param [RestClient::Response] response
|
||||
# @return [Boolean]
|
||||
def required_elements_present?(response)
|
||||
doc = Nokogiri::HTML.parse(response.body)
|
||||
|
||||
elements_css.all? { |sel| doc.css(sel).any? }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,58 +1,93 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'etc'
|
||||
|
||||
RSpec.describe QA::Specs::ParallelRunner do
|
||||
include QA::Support::Helpers::StubEnv
|
||||
|
||||
subject(:runner) { described_class }
|
||||
|
||||
let(:parallel_tests) { instance_double(ParallelTests::CLI, run: nil) }
|
||||
|
||||
before do
|
||||
allow(QA::Runtime::Scenario).to receive(:attributes).and_return(parallel: true)
|
||||
stub_env('GITLAB_QA_ACCESS_TOKEN', 'skip_token_creation')
|
||||
allow(ParallelTests::CLI).to receive(:new).and_return(parallel_tests)
|
||||
allow(Etc).to receive(:nprocessors).and_return(8)
|
||||
allow(ENV).to receive(:store)
|
||||
|
||||
allow(QA::Runtime::Browser).to receive(:configure!)
|
||||
allow(QA::Runtime::Release).to receive(:perform_before_hooks)
|
||||
|
||||
stub_env("QA_GITLAB_URL", "http://127.0.0.1:3000")
|
||||
stub_env("QA_PARALLEL_PROCESSES", "8")
|
||||
end
|
||||
|
||||
it 'passes args to parallel_tests' do
|
||||
expect_cli_arguments(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS])
|
||||
it "runs cli without additional rspec args" do
|
||||
runner.run([])
|
||||
|
||||
subject.run(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS])
|
||||
expect(parallel_tests).to have_received(:run).with([
|
||||
"--type", "rspec",
|
||||
"-n", "8",
|
||||
"--serialize-stdout",
|
||||
"--first-is-1",
|
||||
"--combine-stderr"
|
||||
])
|
||||
end
|
||||
|
||||
it 'passes a given test path to parallel_tests and adds a separator' do
|
||||
expect_cli_arguments(%w[-- qa/specs/features/foo])
|
||||
it "runs cli with additional rspec args" do
|
||||
runner.run(["--force-color", "qa/specs/features/api"])
|
||||
|
||||
subject.run(%w[qa/specs/features/foo])
|
||||
expect(parallel_tests).to have_received(:run).with([
|
||||
"--type", "rspec",
|
||||
"-n", "8",
|
||||
"--serialize-stdout",
|
||||
"--first-is-1",
|
||||
"--combine-stderr",
|
||||
"--", "--force-color",
|
||||
"--", "qa/specs/features/api"
|
||||
])
|
||||
end
|
||||
|
||||
it 'passes tags and test paths to parallel_tests and adds a separator' do
|
||||
expect_cli_arguments(%w[--tag smoke -- qa/specs/features/foo qa/specs/features/bar])
|
||||
context "with QA_GITLAB_URL not set" do
|
||||
before do
|
||||
stub_env("QA_GITLAB_URL", nil)
|
||||
|
||||
subject.run(%w[--tag smoke qa/specs/features/foo qa/specs/features/bar])
|
||||
end
|
||||
|
||||
it 'passes tags and test paths with separators to parallel_tests' do
|
||||
expect_cli_arguments(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar])
|
||||
|
||||
subject.run(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar])
|
||||
end
|
||||
|
||||
it 'passes supported environment variables' do
|
||||
# Test only env vars starting with GITLAB because some of the others
|
||||
# affect how the runner behaves, and we're not concerned with those
|
||||
# behaviors in this test
|
||||
gitlab_env_vars = QA::Runtime::Env::ENV_VARIABLES.reject { |v| !v.start_with?('GITLAB') }
|
||||
|
||||
gitlab_env_vars.each do |k, v|
|
||||
stub_env(k, v)
|
||||
QA::Support::GitlabAddress.instance_variable_set(:@initialized, nil)
|
||||
end
|
||||
|
||||
gitlab_env_vars['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = '{"parallel":true}'
|
||||
after do
|
||||
QA::Support::GitlabAddress.instance_variable_set(:@initialized, nil)
|
||||
end
|
||||
|
||||
expect_cli_arguments([], gitlab_env_vars)
|
||||
it "sets QA_GITLAB_URL variable for subprocess" do
|
||||
runner.run([])
|
||||
|
||||
subject.run([])
|
||||
expect(ENV).to have_received(:store).with("QA_GITLAB_URL", "http://127.0.0.1:3000")
|
||||
end
|
||||
end
|
||||
|
||||
def expect_cli_arguments(arguments, env = { 'QA_RUNTIME_SCENARIO_ATTRIBUTES' => '{"parallel":true}' })
|
||||
cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{arguments.join(' ')}"
|
||||
expect(Open3).to receive(:popen2e)
|
||||
.with(hash_including(env), cmd)
|
||||
.and_return(0)
|
||||
context "with QA_PARALLEL_PROCESSES not set" do
|
||||
before do
|
||||
stub_env("QA_PARALLEL_PROCESSES", nil)
|
||||
allow(Etc).to receive(:nprocessors).and_return(8)
|
||||
end
|
||||
|
||||
it "sets number of processes to half of available processors" do
|
||||
allow(QA::Runtime::Env).to receive(:parallel_processes).and_call_original
|
||||
|
||||
runner.run([])
|
||||
|
||||
expect(QA::Runtime::Env).to have_received(:parallel_processes)
|
||||
actual_processes = QA::Runtime::Env.parallel_processes
|
||||
|
||||
expect(parallel_tests).to have_received(:run) do |args|
|
||||
expect(args).to eq([
|
||||
"--type", "rspec",
|
||||
"-n", actual_processes.to_s,
|
||||
"--serialize-stdout",
|
||||
"--first-is-1",
|
||||
"--combine-stderr"
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
RSpec.describe QA::Specs::Runner do
|
||||
shared_examples 'excludes default skipped, and geo' do
|
||||
it 'excludes the default skipped and geo tags, and includes default args' do
|
||||
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo',
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
|
@ -28,7 +31,10 @@ RSpec.describe QA::Specs::Runner do
|
|||
|
||||
it 'sets the `--tty` flag' do
|
||||
expect_rspec_runner_arguments(
|
||||
['--tty'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS]
|
||||
['--tty'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo',
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]
|
||||
)
|
||||
|
||||
subject.perform
|
||||
|
|
@ -37,63 +43,37 @@ RSpec.describe QA::Specs::Runner do
|
|||
|
||||
context 'when count_examples_only is set as an option' do
|
||||
let(:out) { StringIO.new }
|
||||
let(:err) { StringIO.new }
|
||||
|
||||
before do
|
||||
QA::Runtime::Scenario.define(:count_examples_only, true)
|
||||
out.string = '22 examples,'
|
||||
allow(StringIO).to receive(:new).and_return(out)
|
||||
allow(StringIO).to receive(:new).and_return(out, err)
|
||||
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
|
||||
end
|
||||
|
||||
after do
|
||||
QA::Runtime::Scenario.attributes.delete(:count_examples_only)
|
||||
end
|
||||
|
||||
it 'sets the `--dry-run` flag' do
|
||||
it 'sets the `--dry-run` flag and minimal arguments' do
|
||||
expect_rspec_runner_arguments(
|
||||
['--dry-run'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS],
|
||||
[$stderr, anything]
|
||||
['--dry-run', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS],
|
||||
[err, out]
|
||||
)
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'writes to file when examples are more than zero' do
|
||||
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
|
||||
|
||||
expect(File).to receive(:open).with('no_of_examples/test_instance_all.txt', 'w') { '22' }
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'does not write to file when zero examples' do
|
||||
out.string = '0 examples,'
|
||||
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
|
||||
|
||||
expect(File).not_to receive(:open)
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'raises error when Rspec output does not match regex' do
|
||||
out.string = '0'
|
||||
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
|
||||
|
||||
expect { subject.perform }
|
||||
.to raise_error(QA::Specs::Runner::RegexMismatchError, 'Rspec output did not match regex')
|
||||
expect { subject.perform }.to raise_error(JSON::ParserError, /Failed to detect example count/)
|
||||
end
|
||||
|
||||
context 'when --tag is specified as an option' do
|
||||
subject { described_class.new.tap { |runner| runner.options = %w[--tag actioncable] } }
|
||||
|
||||
it 'includes the option value in the file name' do
|
||||
it 'includes the tag in the RSpec arguments' do
|
||||
expect_rspec_runner_arguments(
|
||||
['--dry-run', '--tag', '~geo', '--tag', 'actioncable', *described_class::DEFAULT_TEST_PATH_ARGS],
|
||||
[$stderr, anything]
|
||||
[err, out]
|
||||
)
|
||||
|
||||
expect(File).to receive(:open).with('no_of_examples/test_instance_all_actioncable.txt', 'w') { '22' }
|
||||
|
||||
subject.perform
|
||||
expect { subject.perform }.to raise_error(JSON::ParserError, /Failed to detect example count/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -116,24 +96,11 @@ RSpec.describe QA::Specs::Runner do
|
|||
it 'sets the `--dry-run` flag' do
|
||||
expect_rspec_runner_arguments(
|
||||
['--dry-run', *described_class::DEFAULT_TEST_PATH_ARGS],
|
||||
[$stderr, anything]
|
||||
[$stderr, $stdout]
|
||||
)
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'configures json formatted output to file' do
|
||||
allow(QA::Runtime::Path).to receive(:qa_root).and_return('/root')
|
||||
|
||||
expect(rspec_config).to receive(:add_formatter)
|
||||
.with(QA::Support::JsonFormatter, output_file)
|
||||
expect(rspec_config).to receive(:fail_if_no_examples=)
|
||||
.with(true)
|
||||
|
||||
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
|
||||
|
||||
subject.perform
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tags are set' do
|
||||
|
|
@ -141,7 +108,10 @@ RSpec.describe QA::Specs::Runner do
|
|||
|
||||
it 'focuses on the given tags' do
|
||||
expect_rspec_runner_arguments(
|
||||
['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS]
|
||||
['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo',
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]
|
||||
)
|
||||
|
||||
subject.perform
|
||||
|
|
@ -152,7 +122,10 @@ RSpec.describe QA::Specs::Runner do
|
|||
subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke] } }
|
||||
|
||||
it 'focuses on the given tag without excluded tags' do
|
||||
expect_rspec_runner_arguments(['--tag', '~geo', '--tag', 'smoke', *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
expect_rspec_runner_arguments(['--tag', '~geo', '--tag', 'smoke',
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
|
@ -162,7 +135,13 @@ RSpec.describe QA::Specs::Runner do
|
|||
subject { described_class.new.tap { |runner| runner.options = %w[qa/specs/features/foo] } }
|
||||
|
||||
it 'passes the given tests path and excludes the default skipped, and geo tags' do
|
||||
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', 'qa/specs/features/foo'])
|
||||
expect_rspec_runner_arguments(
|
||||
['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo',
|
||||
'qa/specs/features/foo',
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml"]
|
||||
)
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
|
@ -172,7 +151,13 @@ RSpec.describe QA::Specs::Runner do
|
|||
subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke qa/specs/features/foo] } }
|
||||
|
||||
it 'focuses on the given tag and includes the path without excluding the orchestrated or transient tags' do
|
||||
expect_rspec_runner_arguments(['--tag', '~geo', '--tag', 'smoke', 'qa/specs/features/foo'])
|
||||
expect_rspec_runner_arguments(
|
||||
['--tag', '~geo', '--tag', 'smoke',
|
||||
'qa/specs/features/foo',
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml"]
|
||||
)
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
|
@ -184,7 +169,10 @@ RSpec.describe QA::Specs::Runner do
|
|||
end
|
||||
|
||||
it 'includes default args and excludes the skip_signup_disabled tag' do
|
||||
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_signup_disabled',
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
|
@ -196,7 +184,10 @@ RSpec.describe QA::Specs::Runner do
|
|||
end
|
||||
|
||||
it 'includes default args and excludes the skip_live_env tag' do
|
||||
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_live_env', *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_live_env',
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
subject.perform
|
||||
end
|
||||
end
|
||||
|
|
@ -213,7 +204,10 @@ RSpec.describe QA::Specs::Runner do
|
|||
subject { described_class.new.tap { |runner| runner.tags = %i[geo] } }
|
||||
|
||||
it 'includes the geo tag' do
|
||||
expect_rspec_runner_arguments(['--tag', 'geo', *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
expect_rspec_runner_arguments(['--tag', 'geo',
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
|
||||
subject.perform
|
||||
end
|
||||
end
|
||||
|
|
@ -231,7 +225,9 @@ RSpec.describe QA::Specs::Runner do
|
|||
it 'includes default args and excludes all unsupported tags' do
|
||||
expect_rspec_runner_arguments(
|
||||
DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *excluded_feature_tags_except(feature),
|
||||
*described_class::DEFAULT_TEST_PATH_ARGS]
|
||||
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
|
||||
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
|
||||
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]
|
||||
)
|
||||
|
||||
subject.perform
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe QA::Tools::ReadinessCheck do
|
||||
subject(:readiness_check) { described_class.new(wait: wait) }
|
||||
|
||||
let(:url) { "example.com" }
|
||||
let(:wait) { 1 }
|
||||
let(:msg_base) { "Gitlab readiness check failed, valid sign_in page did not appear within #{wait} seconds!" }
|
||||
let(:dot_com) { false }
|
||||
|
||||
let(:response) { instance_double(RestClient::Response, code: code, body: body) }
|
||||
let(:code) { 200 }
|
||||
let(:body) { "" }
|
||||
|
||||
before do
|
||||
allow(QA::Runtime::Env).to receive(:running_on_dot_com?).and_return(dot_com)
|
||||
allow(QA::Support::GitlabAddress).to receive(:address_with_port).with(with_default_port: false).and_return(url)
|
||||
allow(readiness_check).to receive(:get).with("#{url}/users/sign_in").and_return(response)
|
||||
end
|
||||
|
||||
context "with successfull response" do
|
||||
let(:body) do
|
||||
<<~HTML
|
||||
<!DOCTYPE html>
|
||||
<body data-testid="login-page">
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
end
|
||||
|
||||
it "validates readiness" do
|
||||
expect { readiness_check.perform }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context "with missing sign in form" do
|
||||
let(:body) do
|
||||
<<~HTML
|
||||
<!DOCTYPE html>
|
||||
</html>
|
||||
HTML
|
||||
end
|
||||
|
||||
it "raises an error on validation" do
|
||||
expect { readiness_check.perform }.to raise_error(/#{msg_base} Sign in page missing required elements/)
|
||||
end
|
||||
end
|
||||
|
||||
context "with unsuccessfull response code" do
|
||||
let(:code) { 500 }
|
||||
|
||||
it "raises an error on validation" do
|
||||
expect { readiness_check.perform }.to raise_error(
|
||||
"#{msg_base} Got unsucessfull response code: #{code}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "with request timeout" do
|
||||
before do
|
||||
allow(readiness_check).to receive(:get).and_raise(RestClient::Exceptions::OpenTimeout)
|
||||
end
|
||||
|
||||
it "raises an error on validation" do
|
||||
expect { readiness_check.perform }.to raise_error("#{msg_base} Timed out connecting to server")
|
||||
end
|
||||
end
|
||||
|
||||
context "when running on dot com" do
|
||||
let(:dot_com) { true }
|
||||
|
||||
context "with successfull check" do
|
||||
before do
|
||||
allow(QA::Runtime::Browser).to receive(:visit).with(:gitlab, QA::Page::Main::Login)
|
||||
end
|
||||
|
||||
it "validates readiness" do
|
||||
expect { readiness_check.perform }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context "with unsuccessfull check" do
|
||||
before do
|
||||
allow(QA::Runtime::Browser).to receive(:visit).with(:gitlab, QA::Page::Main::Login).and_raise("not loaded")
|
||||
end
|
||||
|
||||
it "raises an error on validation" do
|
||||
expect { readiness_check.perform }.to raise_error("#{msg_base} not loaded")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -895,6 +895,7 @@ project:
|
|||
- observability_traces
|
||||
- observability_logs
|
||||
- security_exclusions
|
||||
- security_statistics
|
||||
award_emoji:
|
||||
- awardable
|
||||
- user
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ RSpec.describe Ml::ModelVersion, feature_category: :mlops do
|
|||
subject { described_class.latest_by_model }
|
||||
|
||||
it 'returns only the latest model version per model id' do
|
||||
is_expected.to match_array([model_version4, model_version2])
|
||||
is_expected.to match_array([model_version3, model_version2])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue