Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-01 18:12:02 +00:00
parent faa19db44a
commit fb7b3d71b1
35 changed files with 641 additions and 218 deletions

View File

@ -233,58 +233,8 @@ Layout/HashAlignment:
- 'ee/app/graphql/mutations/projects/set_locked.rb'
- 'ee/app/graphql/resolvers/iterations/cadences_resolver.rb'
- 'ee/app/graphql/resolvers/vulnerabilities_count_per_day_resolver.rb'
- 'ee/app/graphql/types/admin/cloud_licenses/current_license_type.rb'
- 'ee/app/graphql/types/admin/cloud_licenses/license_type.rb'
- 'ee/app/graphql/types/admin/cloud_licenses/subscription_future_entry_type.rb'
- 'ee/app/graphql/types/analytics/devops_adoption/enabled_namespace_type.rb'
- 'ee/app/graphql/types/analytics/devops_adoption/snapshot_type.rb'
- 'ee/app/graphql/types/app_sec/fuzzing/api/ci_configuration_type.rb'
- 'ee/app/graphql/types/app_sec/fuzzing/api/scan_profile_type.rb'
- 'ee/app/graphql/types/app_sec/fuzzing/coverage/corpus_type.rb'
- 'ee/app/graphql/types/boards/board_epic_type.rb'
- 'ee/app/graphql/types/boards/epic_board_type.rb'
- 'ee/app/graphql/types/boards/epic_list_metadata_type.rb'
- 'ee/app/graphql/types/boards/epic_list_type.rb'
- 'ee/app/graphql/types/boards/epic_user_preferences_type.rb'
- 'ee/app/graphql/types/burnup_chart_daily_totals_type.rb'
- 'ee/app/graphql/types/ci/code_coverage_activity_type.rb'
- 'ee/app/graphql/types/ci/code_coverage_summary_type.rb'
- 'ee/app/graphql/types/ci/code_quality_degradation_type.rb'
- 'ee/app/graphql/types/ci/minutes/namespace_monthly_usage_type.rb'
- 'ee/app/graphql/types/ci/minutes/project_monthly_usage_type.rb'
- 'ee/app/graphql/types/compliance_management/merge_requests/compliance_violation_type.rb'
- 'ee/app/graphql/types/dast/profile_branch_type.rb'
- 'ee/app/graphql/types/dast/profile_schedule_type.rb'
- 'ee/app/graphql/types/dast/profile_type.rb'
- 'ee/app/graphql/types/dast_scanner_profile_type.rb'
- 'ee/app/graphql/types/dast_site_profile_type.rb'
- 'ee/app/graphql/types/dast_site_validation_type.rb'
- 'ee/app/graphql/types/dora_metric_type.rb'
- 'ee/app/graphql/types/dora_type.rb'
- 'ee/app/graphql/types/epic_descendant_weight_sum_type.rb'
- 'ee/app/graphql/types/epic_issue_type.rb'
- 'ee/app/graphql/types/epic_type.rb'
- 'ee/app/graphql/types/external_issue_type.rb'
- 'ee/app/graphql/types/group_release_stats_type.rb'
- 'ee/app/graphql/types/instance_security_dashboard_type.rb'
- 'ee/app/graphql/types/iteration_type.rb'
- 'ee/app/graphql/types/iterations/cadence_type.rb'
- 'ee/app/graphql/types/merge_requests/approval_state_type.rb'
- 'ee/app/graphql/types/metric_image_type.rb'
- 'ee/app/graphql/types/path_lock_type.rb'
- 'ee/app/graphql/types/requirements_management/requirement_type.rb'
- 'ee/app/graphql/types/requirements_management/test_report_type.rb'
- 'ee/app/graphql/types/security/training_type.rb'
- 'ee/app/graphql/types/security/training_url_type.rb'
- 'ee/app/graphql/types/security_report_summary_type.rb'
- 'ee/app/graphql/types/security_scanners.rb'
- 'ee/app/graphql/types/time_report_stats_type.rb'
- 'ee/app/graphql/types/timebox_metrics_type.rb'
- 'ee/app/graphql/types/timebox_report_interface.rb'
- 'ee/app/graphql/types/timebox_report_type.rb'
- 'ee/app/graphql/types/vulnerabilities/asset_type.rb'
- 'ee/app/graphql/types/vulnerabilities/link_type.rb'
- 'ee/app/graphql/types/vulnerabilities_count_by_day_type.rb'
- 'ee/app/graphql/types/vulnerability/external_issue_link_type.rb'
- 'ee/app/graphql/types/vulnerability/issue_link_type.rb'
- 'ee/app/graphql/types/vulnerability_details/base_type.rb'
@ -300,10 +250,6 @@ Layout/HashAlignment:
- 'ee/app/graphql/types/vulnerability_details/table_type.rb'
- 'ee/app/graphql/types/vulnerability_details/text_type.rb'
- 'ee/app/graphql/types/vulnerability_details/url_type.rb'
- 'ee/app/graphql/types/vulnerability_evidence_source_type.rb'
- 'ee/app/graphql/types/vulnerability_evidence_supporting_message_type.rb'
- 'ee/app/graphql/types/vulnerability_evidence_type.rb'
- 'ee/app/graphql/types/vulnerability_identifier_type.rb'
- 'ee/app/graphql/types/vulnerability_location/cluster_image_scanning_type.rb'
- 'ee/app/graphql/types/vulnerability_location/container_scanning_type.rb'
- 'ee/app/graphql/types/vulnerability_location/coverage_fuzzing_type.rb'
@ -313,15 +259,6 @@ Layout/HashAlignment:
- 'ee/app/graphql/types/vulnerability_location/sast_type.rb'
- 'ee/app/graphql/types/vulnerability_location/secret_detection_type.rb'
- 'ee/app/graphql/types/vulnerability_request_response_header_type.rb'
- 'ee/app/graphql/types/vulnerability_request_type.rb'
- 'ee/app/graphql/types/vulnerability_response_type.rb'
- 'ee/app/graphql/types/vulnerability_scanner_type.rb'
- 'ee/app/graphql/types/vulnerability_severities_count_type.rb'
- 'ee/app/graphql/types/vulnerability_type.rb'
- 'ee/app/graphql/types/vulnerable_dependency_type.rb'
- 'ee/app/graphql/types/vulnerable_kubernetes_resource_type.rb'
- 'ee/app/graphql/types/vulnerable_package_type.rb'
- 'ee/app/graphql/types/vulnerable_projects_by_grade_type.rb'
- 'ee/app/helpers/ee/feature_flags_helper.rb'
- 'ee/app/helpers/ee/sorting_helper.rb'
- 'ee/app/models/allowed_email_domain.rb'

View File

@ -262,9 +262,9 @@ export default {
<div
v-if="showSearchDropdown"
data-testid="header-search-dropdown-menu"
class="header-search-dropdown-menu gl-absolute gl-w-full gl-bg-white gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-200 gl-shadow-x0-y2-b4-s0"
class="header-search-dropdown-menu gl-overflow-y-auto gl-absolute gl-w-full gl-bg-white gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-200 gl-shadow-x0-y2-b4-s0 gl-mt-3"
>
<div class="header-search-dropdown-content gl-overflow-y-auto gl-py-2">
<div class="header-search-dropdown-content gl-py-2">
<dropdown-keyboard-navigation
v-model="currentFocusIndex"
:max="searchOptions.length - 1"

View File

@ -3,12 +3,17 @@ import { trackNewRegistrations } from '~/google_tag_manager';
import NoEmojiValidator from '~/emoji/no_emoji_validator';
import LengthValidator from '~/pages/sessions/new/length_validator';
import UsernameValidator from '~/pages/sessions/new/username_validator';
import EmailFormatValidator from '~/pages/sessions/new/email_format_validator';
import Tracking from '~/tracking';
new UsernameValidator(); // eslint-disable-line no-new
new LengthValidator(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
if (gon.features.trialEmailValidation) {
new EmailFormatValidator(); // eslint-disable-line no-new
}
trackNewRegistrations();
Tracking.enableFormTracking({

View File

@ -0,0 +1,46 @@
import InputValidator from '~/validators/input_validator';
// It checks if email contains at least one character, number or whatever except
// another "@" or whitespace before "@", at least two characters except
// another "@" or whitespace after "@" and one dot in between
const emailRegexPattern = /[^@\s]+@[^@\s]+\.[^@\s]+/;
const hintMessageSelector = '.validation-hint';
const warningMessageSelector = '.validation-warning';
export default class EmailFormatValidator extends InputValidator {
constructor(opts = {}) {
super();
const container = opts.container || '';
document
.querySelectorAll(`${container} .js-validate-email`)
.forEach((element) =>
element.addEventListener('keyup', EmailFormatValidator.eventHandler.bind(this)),
);
}
static eventHandler(event) {
const inputDomElement = event.target;
EmailFormatValidator.setMessageVisibility(inputDomElement, hintMessageSelector);
EmailFormatValidator.setMessageVisibility(inputDomElement, warningMessageSelector);
EmailFormatValidator.validateEmailInput(inputDomElement);
}
static validateEmailInput(inputDomElement) {
const validEmail = inputDomElement.checkValidity();
const validPattern = inputDomElement.value.match(emailRegexPattern);
EmailFormatValidator.setMessageVisibility(
inputDomElement,
warningMessageSelector,
validEmail && !validPattern,
);
}
static setMessageVisibility(inputDomElement, messageSelector, isVisible = false) {
const messageElement = inputDomElement.parentElement.querySelector(messageSelector);
messageElement.classList.toggle('hide', !isVisible);
}
}

View File

@ -121,11 +121,7 @@ input[type='checkbox']:hover {
.header-search-dropdown-menu {
max-height: $dropdown-max-height;
top: $header-height;
}
.header-search-dropdown-content {
max-height: $dropdown-max-height;
top: 100%;
}
.search {

View File

@ -21,6 +21,7 @@ class RegistrationsController < Devise::RegistrationsController
before_action only: [:new] do
push_frontend_feature_flag(:gitlab_gtm_datalayer, type: :ops)
push_frontend_feature_flag(:trial_email_validation, type: :development)
end
feature_category :authentication_and_authorization

View File

@ -4,6 +4,6 @@ module CommitSignatures
class SshSignature < ApplicationRecord
include CommitSignature
belongs_to :key, optional: false
belongs_to :key, optional: true
end
end

View File

@ -39,12 +39,6 @@ module AlertManagement
SystemNoteService.change_alert_status(alert, User.alert_bot)
close_issue(alert.issue_id) if auto_close_incident?
else
logger.warn(
message: 'Unable to update AlertManagement::Alert status to resolved',
project_id: project.id,
alert_id: alert.id
)
end
end
@ -66,9 +60,10 @@ module AlertManagement
SystemNoteService.create_new_alert(alert, alert_source)
else
logger.warn(
message: "Unable to create AlertManagement::Alert from #{alert_source}",
message: "Unable to create AlertManagement::Alert",
project_id: project.id,
alert_errors: alert.errors.messages
alert_errors: alert.errors.messages,
alert_source: alert_source
)
end
end

View File

@ -49,7 +49,8 @@
data: { qa_selector: 'new_user_email_field' },
required: true,
title: _('Please provide a valid email address.')
%p.gl-field-hint.text-secondary= _('We recommend a work email address.')
%p.validation-hint.gl-field-hint.text-secondary= _('We recommend a work email address.')
%p.validation-warning.gl-field-error-ignore.text-secondary.hide= _('This email address does not look right, are you sure you typed it correctly?')
-# This is used for providing entry to Jihu on email verification
= render_if_exists 'devise/shared/signup_email_additional_info'
.form-group.gl-mb-5#password-strength

View File

@ -1,6 +1,6 @@
- if can?(current_user, :update_pages, @project)
.card
.card-header
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5'}, body_options: { class: 'gl-text-center nothing-here-block' }) do |c|
- c.header do
= s_('GitLabPages|Domains')
.nothing-here-block
- c.body do
= s_("GitLabPages|Support for domains and certificates is disabled. Ask your system's administrator to enable it.")

View File

@ -1,7 +1,7 @@
- if @group.admin_note.present?
- text = @group.admin_note.note
.card.border-info
.card-header.bg-info.gl-text-white
= render Pajamas::CardComponent.new(card_options: { class: 'gl-border-blue-500 gl-mb-5' }, header_options: { class: 'gl-bg-blue-500 gl-text-white' }) do |c|
- c.header do
= s_('Admin|Admin notes')
.card-body
- c.body do
%p= text

View File

@ -0,0 +1,8 @@
---
name: trial_email_validation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92762
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368999
milestone: '15.3'
type: development
group: group::acquisition
default_enabled: false

View File

@ -13,7 +13,8 @@ Gitlab::Cluster::LifecycleEvents.on_worker_start do
Gitlab::Memory::Watchdog::NullHandler.instance
end
Gitlab::Memory::Watchdog.new(
watchdog = Gitlab::Memory::Watchdog.new(
handler: handler, logger: Gitlab::AppLogger
).start
)
Gitlab::BackgroundTask.new(watchdog).start
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class MakeSshSignatureKeyNullable < Gitlab::Database::Migration[2.0]
enable_lock_retries!
def change
change_column_null :ssh_signatures, :key_id, true
end
end

View File

@ -0,0 +1 @@
eb0a6cff006f54f3b5fe12ab566dabfbefa1af46fafbfadde1b292b46e9d17c9

View File

@ -21133,7 +21133,7 @@ CREATE TABLE ssh_signatures (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
project_id bigint NOT NULL,
key_id bigint NOT NULL,
key_id bigint,
verification_status smallint DEFAULT 0 NOT NULL,
commit_sha bytea NOT NULL
);

View File

@ -731,4 +731,4 @@ You can do the following **only by using quick actions**:
- [Publish an issue](#publish-an-issue) (`/publish`).
- Clone an issue to the same or another project (`/clone`).
- Close an issue and mark as a duplicate of another issue (`/duplicate`).
- Copy labels and milestone from another merge request in the project (`/copy_metadata`).
- Copy labels and milestone from another merge request or issue in the project (`/copy_metadata`).

View File

@ -15,7 +15,7 @@ module Gitlab
#
# The duration for which a process may be above a given fragmentation
# threshold is computed as `max_strikes * sleep_time_seconds`.
class Watchdog < Daemon
class Watchdog
DEFAULT_SLEEP_TIME_SECONDS = 60
DEFAULT_HEAP_FRAG_THRESHOLD = 0.5
DEFAULT_MAX_STRIKES = 5
@ -91,7 +91,7 @@ module Gitlab
attr_reader :strikes, :max_heap_fragmentation, :max_strikes, :sleep_time_seconds
def run_thread
def call
@logger.info(log_labels.merge(message: 'started'))
while @alive
@ -103,6 +103,10 @@ module Gitlab
@logger.info(log_labels.merge(message: 'stopped'))
end
def stop
@alive = false
end
private
def monitor_heap_fragmentation
@ -141,10 +145,6 @@ module Gitlab
@handler
end
def stop_working
@alive = false
end
def log_labels
{
pid: $$,

24
lib/gitlab/ssh/commit.rb Normal file
View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Gitlab
module Ssh
class Commit < Gitlab::SignedCommit
private
def signature_class
CommitSignatures::SshSignature
end
def attributes
signature = ::Gitlab::Ssh::Signature.new(signature_text, signed_text, @commit.committer_email)
{
commit_sha: @commit.sha,
project: @commit.project,
key_id: signature.signed_by_key&.id,
verification_status: signature.verification_status
}
end
end
end
end

View File

@ -26,6 +26,14 @@ module Gitlab
end
end
def signed_by_key
strong_memoize(:signed_by_key) do
next unless key_fingerprint
Key.find_by_fingerprint_sha256(key_fingerprint)
end
end
private
def all_attributes_present?
@ -61,14 +69,6 @@ module Gitlab
def key_fingerprint
strong_memoize(:key_fingerprint) { signature&.public_key&.fingerprint }
end
def signed_by_key
strong_memoize(:signed_by_key) do
next unless key_fingerprint
Key.find_by_fingerprint_sha256(key_fingerprint)
end
end
end
end
end

View File

@ -39861,6 +39861,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
msgid "This email address does not look right, are you sure you typed it correctly?"
msgstr ""
msgid "This email supersedes any previous emails about scheduled deletion you may have received for %{project_link}."
msgstr ""

View File

@ -229,7 +229,6 @@
"jest-junit": "^12.0.0",
"jest-raw-loader": "^1.0.1",
"jest-transform-graphql": "^2.1.0",
"jest-util": "^26.5.2",
"jest-util": "^27.5.1",
"markdownlint-cli": "0.31.0",
"miragejs": "^0.1.40",

View File

@ -283,6 +283,12 @@ RSpec.describe 'Signup' do
expect(page).to have_current_path user_registration_path, ignore_query: true
expect(page.body).not_to match(/#{new_user.password}/)
end
context 'with invalid email', :saas, :js do
it_behaves_like 'user email validation' do
let(:path) { new_user_registration_path }
end
end
end
context 'when terms are enforced' do

View File

@ -0,0 +1,116 @@
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe 'memory watchdog' do
subject(:run_initializer) do
load Rails.root.join('config/initializers/memory_watchdog.rb')
end
context 'when GITLAB_MEMORY_WATCHDOG_ENABLED is truthy' do
let(:env_switch) { 'true' }
before do
stub_env('GITLAB_MEMORY_WATCHDOG_ENABLED', env_switch)
end
context 'when runtime is an application' do
let(:watchdog) { instance_double(Gitlab::Memory::Watchdog) }
let(:background_task) { instance_double(Gitlab::BackgroundTask) }
before do
allow(Gitlab::Runtime).to receive(:application?).and_return(true)
end
it 'registers a life-cycle hook' do
expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start)
run_initializer
end
shared_examples 'starts watchdog with handler' do |handler_class|
it "uses the #{handler_class} and starts the watchdog" do
expect(Gitlab::Memory::Watchdog).to receive(:new).with(
handler: an_instance_of(handler_class),
logger: Gitlab::AppLogger).and_return(watchdog)
expect(Gitlab::BackgroundTask).to receive(:new).with(watchdog).and_return(background_task)
expect(background_task).to receive(:start)
expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start).and_yield
run_initializer
end
end
# In tests, the Puma constant does not exist so we cannot use a verified double.
# rubocop: disable RSpec/VerifiedDoubles
context 'when puma' do
let(:puma) do
Class.new do
def self.cli_config
Struct.new(:options).new
end
end
end
before do
stub_const('Puma', puma)
stub_const('Puma::Cluster::WorkerHandle', double.as_null_object)
allow(Gitlab::Runtime).to receive(:puma?).and_return(true)
end
it_behaves_like 'starts watchdog with handler', Gitlab::Memory::Watchdog::PumaHandler
end
# rubocop: enable RSpec/VerifiedDoubles
context 'when sidekiq' do
before do
allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
end
it_behaves_like 'starts watchdog with handler', Gitlab::Memory::Watchdog::TermProcessHandler
end
context 'when other runtime' do
it_behaves_like 'starts watchdog with handler', Gitlab::Memory::Watchdog::NullHandler
end
end
context 'when runtime is unsupported' do
it 'does not register life-cycle hook' do
expect(Gitlab::Cluster::LifecycleEvents).not_to receive(:on_worker_start)
run_initializer
end
end
end
context 'when GITLAB_MEMORY_WATCHDOG_ENABLED is false' do
let(:env_switch) { 'false' }
before do
stub_env('GITLAB_MEMORY_WATCHDOG_ENABLED', env_switch)
# To rule out we return early due to this being false.
allow(Gitlab::Runtime).to receive(:application?).and_return(true)
end
it 'does not register life-cycle hook' do
expect(Gitlab::Cluster::LifecycleEvents).not_to receive(:on_worker_start)
run_initializer
end
end
context 'when GITLAB_MEMORY_WATCHDOG_ENABLED is not set' do
before do
# To rule out we return early due to this being false.
allow(Gitlab::Runtime).to receive(:application?).and_return(true)
end
it 'does not register life-cycle hook' do
expect(Gitlab::Cluster::LifecycleEvents).not_to receive(:on_worker_start)
run_initializer
end
end
end

View File

@ -30,6 +30,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::RackMiddleware, :redis do
expect(app).to receive(:call).with(env).and_return(10)
allow(ActiveSupport::Notifications).to receive(:instrument).and_call_original
expect(ActiveSupport::Notifications)
.to receive(:instrument)
.with('web_transaction_completed.load_balancing')

View File

@ -77,6 +77,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
let(:last_write_location) { 'foo' }
before do
allow(ActiveSupport::Notifications).to receive(:instrument).and_call_original
allow(sticking)
.to receive(:last_write_location_for)
.with(:user, 42)

View File

@ -14,12 +14,39 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
let(:sleep_time) { 0.1 }
let(:max_heap_fragmentation) { 0.2 }
# Tests should set this to control the number of loop iterations in `call`.
let(:watchdog_iterations) { 1 }
subject(:watchdog) do
described_class.new(handler: handler, logger: logger, sleep_time_seconds: sleep_time,
max_strikes: max_strikes, max_heap_fragmentation: max_heap_fragmentation)
max_strikes: max_strikes, max_heap_fragmentation: max_heap_fragmentation).tap do |instance|
# We need to defuse `sleep` and stop the internal loop after N iterations.
iterations = 0
expect(instance).to receive(:sleep) do
instance.stop if (iterations += 1) >= watchdog_iterations
end.at_most(watchdog_iterations)
end
end
def stub_prometheus_metrics
allow(Gitlab::Metrics).to receive(:gauge)
.with(:gitlab_memwd_heap_frag_limit, anything)
.and_return(heap_frag_limit_gauge)
allow(Gitlab::Metrics).to receive(:counter)
.with(:gitlab_memwd_heap_frag_violations_total, anything, anything)
.and_return(heap_frag_violations_counter)
allow(Gitlab::Metrics).to receive(:counter)
.with(:gitlab_memwd_heap_frag_violations_handled_total, anything, anything)
.and_return(heap_frag_violations_handled_counter)
allow(heap_frag_limit_gauge).to receive(:set)
allow(heap_frag_violations_counter).to receive(:increment)
allow(heap_frag_violations_handled_counter).to receive(:increment)
end
before do
stub_prometheus_metrics
allow(handler).to receive(:on_high_heap_fragmentation).and_return(true)
allow(logger).to receive(:warn)
@ -30,18 +57,14 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
allow(::Prometheus::PidProvider).to receive(:worker_id).and_return('worker_1')
end
after do
watchdog.stop
end
context 'when starting up' do
context 'when created' do
let(:fragmentation) { 0 }
let(:max_strikes) { 0 }
it 'sets the heap fragmentation limit gauge' do
allow(Gitlab::Metrics).to receive(:gauge).with(anything, anything).and_return(heap_frag_limit_gauge)
expect(heap_frag_limit_gauge).to receive(:set).with({}, max_heap_fragmentation)
watchdog
end
context 'when no settings are set in the environment' do
@ -78,72 +101,49 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
it 'does not signal the handler' do
expect(handler).not_to receive(:on_high_heap_fragmentation)
watchdog.start
sleep sleep_time * 3
watchdog.call
end
end
context 'when process exceeds heap fragmentation threshold permanently' do
let(:fragmentation) { max_heap_fragmentation + 0.1 }
before do
expected_labels = { pid: 'worker_1' }
allow(Gitlab::Metrics).to receive(:counter)
.with(:gitlab_memwd_heap_frag_violations_total, anything, expected_labels)
.and_return(heap_frag_violations_counter)
allow(Gitlab::Metrics).to receive(:counter)
.with(:gitlab_memwd_heap_frag_violations_handled_total, anything, expected_labels)
.and_return(heap_frag_violations_handled_counter)
allow(heap_frag_violations_counter).to receive(:increment)
allow(heap_frag_violations_handled_counter).to receive(:increment)
end
let(:max_strikes) { 3 }
context 'when process has not exceeded allowed number of strikes' do
let(:max_strikes) { 10 }
let(:watchdog_iterations) { max_strikes }
it 'does not signal the handler' do
expect(handler).not_to receive(:on_high_heap_fragmentation)
watchdog.start
sleep sleep_time * 3
watchdog.call
end
it 'does not log any events' do
expect(logger).not_to receive(:warn)
watchdog.start
sleep sleep_time * 3
watchdog.call
end
it 'increments the violations counter' do
expect(heap_frag_violations_counter).to receive(:increment)
expect(heap_frag_violations_counter).to receive(:increment).exactly(watchdog_iterations)
watchdog.start
sleep sleep_time * 3
watchdog.call
end
it 'does not increment violations handled counter' do
expect(heap_frag_violations_handled_counter).not_to receive(:increment)
watchdog.start
sleep sleep_time * 3
watchdog.call
end
end
context 'when process exceeds the allowed number of strikes' do
let(:max_strikes) { 1 }
let(:watchdog_iterations) { max_strikes + 1 }
it 'signals the handler and resets strike counter' do
expect(handler).to receive(:on_high_heap_fragmentation).and_return(true)
watchdog.start
sleep sleep_time * 3
watchdog.call
expect(watchdog.strikes).to eq(0)
end
@ -163,18 +163,14 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
memwd_rss_bytes: 1024
})
watchdog.start
sleep sleep_time * 3
watchdog.call
end
it 'increments both the violations and violations handled counters' do
expect(heap_frag_violations_counter).to receive(:increment)
expect(heap_frag_violations_counter).to receive(:increment).exactly(watchdog_iterations)
expect(heap_frag_violations_handled_counter).to receive(:increment)
watchdog.start
sleep sleep_time * 3
watchdog.call
end
context 'when enforce_memory_watchdog ops toggle is off' do
@ -188,35 +184,31 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
receive(:on_high_heap_fragmentation).with(fragmentation).and_return(true)
)
watchdog.start
sleep sleep_time * 3
watchdog.call
end
end
end
context 'when handler result is true' do
let(:max_strikes) { 1 }
context 'when handler result is true' do
it 'considers the event handled and stops itself' do
expect(handler).to receive(:on_high_heap_fragmentation).once.and_return(true)
expect(logger).to receive(:info).with(hash_including(message: 'stopped'))
it 'considers the event handled and stops itself' do
expect(handler).to receive(:on_high_heap_fragmentation).once.and_return(true)
watchdog.start
sleep sleep_time * 3
watchdog.call
end
end
end
context 'when handler result is false' do
let(:max_strikes) { 1 }
context 'when handler result is false' do
let(:max_strikes) { 0 } # to make sure the handler fires each iteration
let(:watchdog_iterations) { 3 }
it 'keeps running' do
# Return true the third time to terminate the daemon.
expect(handler).to receive(:on_high_heap_fragmentation).and_return(false, false, true)
it 'keeps running' do
expect(heap_frag_violations_counter).to receive(:increment).exactly(watchdog_iterations)
expect(heap_frag_violations_handled_counter).to receive(:increment).exactly(watchdog_iterations)
# Return true the third time to terminate the daemon.
expect(handler).to receive(:on_high_heap_fragmentation).and_return(false, false, true)
watchdog.start
sleep sleep_time * 4
watchdog.call
end
end
end
end
@ -224,6 +216,7 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
context 'when process exceeds heap fragmentation threshold temporarily' do
let(:fragmentation) { max_heap_fragmentation }
let(:max_strikes) { 1 }
let(:watchdog_iterations) { 4 }
before do
allow(Gitlab::Metrics::Memory).to receive(:gc_heap_fragmentation).and_return(
@ -237,9 +230,7 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
it 'does not signal the handler' do
expect(handler).not_to receive(:on_high_heap_fragmentation)
watchdog.start
sleep sleep_time * 4
watchdog.call
end
end
@ -254,9 +245,7 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
it 'does not monitor heap fragmentation' do
expect(Gitlab::Metrics::Memory).not_to receive(:gc_heap_fragmentation)
watchdog.start
sleep sleep_time * 3
watchdog.call
end
end
end

View File

@ -0,0 +1,82 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ssh::Commit do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:signed_by_key) { create(:key) }
let(:commit) { create(:commit, project: project) }
let(:signature_text) { 'signature_text' }
let(:signed_text) { 'signed_text' }
let(:signature_data) { [signature_text, signed_text] }
let(:verifier) { instance_double('Gitlab::Ssh::Signature') }
let(:verification_status) { :verified }
subject(:signature) { described_class.new(commit).signature }
before do
allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
.with(Gitlab::Git::Repository, commit.sha)
.and_return(signature_data)
allow(verifier).to receive(:verification_status).and_return(verification_status)
allow(verifier).to receive(:signed_by_key).and_return(signed_by_key)
allow(Gitlab::Ssh::Signature).to receive(:new)
.with(signature_text, signed_text, commit.committer_email)
.and_return(verifier)
end
describe '#signature' do
it 'returns the cached signature on multiple calls' do
ssh_commit = described_class.new(commit)
expect(ssh_commit).to receive(:create_cached_signature!).and_call_original
ssh_commit.signature
expect(ssh_commit).not_to receive(:create_cached_signature!)
ssh_commit.signature
end
context 'when all expected data is present' do
it 'calls signature verifier and uses returned attributes' do
expect(signature).to have_attributes(
commit_sha: commit.sha,
project: project,
key_id: signed_by_key.id,
verification_status: 'verified'
)
end
end
context 'when signed_by_key is nil' do
let_it_be(:signed_by_key) { nil }
let(:verification_status) { :unknown_key }
it 'creates signature without a key_id' do
expect(signature).to have_attributes(
commit_sha: commit.sha,
project: project,
key_id: nil,
verification_status: 'unknown_key'
)
end
end
end
describe '#update_signature!' do
it 'updates verification status' do
allow(verifier).to receive(:verification_status).and_return(:unverified)
signature
stored_signature = CommitSignatures::SshSignature.find_by_commit_sha(commit.sha)
allow(verifier).to receive(:verification_status).and_return(:verified)
expect { described_class.new(commit).update_signature!(stored_signature) }.to(
change { signature.reload.verification_status }.from('unverified').to('verified')
)
end
end
end

View File

@ -22,7 +22,7 @@ RSpec.describe CommitSignatures::SshSignature do
it_behaves_like 'commit signature'
describe 'associations' do
it { is_expected.to belong_to(:key).required }
it { is_expected.to belong_to(:key).optional }
end
describe '.by_commit_sha scope' do

View File

@ -506,3 +506,16 @@ module TouchRackUploadedFile
end
Rack::Test::UploadedFile.prepend(TouchRackUploadedFile)
# Monkey-patch to enable ActiveSupport::Notifications for Redis commands
module RedisCommands
module Instrumentation
def process(commands, &block)
ActiveSupport::Notifications.instrument('redis.process_commands', commands: commands) do
super(commands, &block)
end
end
end
end
Redis::Client.prepend(RedisCommands::Instrumentation)

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
module RedisCommands
class Recorder
def initialize(pattern: nil, &block)
@log = []
@pattern = pattern
record(&block) if block
end
attr_reader :log
def record(&block)
ActiveSupport::Notifications.subscribed(method(:callback), 'redis.process_commands', &block)
end
def by_command(command)
@log.select { |record| record.include?(command) }
end
def count
@count ||= @log.count
end
private
def callback(name, start, finish, message_id, values)
commands = values[:commands]
@log << commands.flatten if @pattern.nil? || commands.to_s.include?(@pattern)
end
end
end

View File

@ -0,0 +1,59 @@
# frozen_string_literal: true
RSpec.shared_examples 'user email validation' do
let(:email_hint_message) { 'We recommend a work email address.' }
let(:email_error_message) { 'Please provide a valid email address.' }
let(:email_warning_message) do
'This email address does not look right, are you sure you typed it correctly?'
end
context 'with trial_email_validation flag enabled' do
it 'shows an error message until a correct email is entered' do
visit path
expect(page).to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
fill_in 'new_user_email', with: 'foo@'
fill_in 'new_user_first_name', with: ''
expect(page).not_to have_content(email_hint_message)
expect(page).to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
fill_in 'new_user_email', with: 'foo@bar'
fill_in 'new_user_first_name', with: ''
expect(page).not_to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).to have_content(email_warning_message)
fill_in 'new_user_email', with: 'foo@gitlab.com'
fill_in 'new_user_first_name', with: ''
expect(page).not_to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
end
end
context 'when trial_email_validation flag disabled' do
before do
stub_feature_flags trial_email_validation: false
end
it 'does not show an error message' do
visit path
expect(page).to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
fill_in 'new_user_email', with: 'foo@'
expect(page).to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
end
end
end

View File

@ -35,9 +35,10 @@ RSpec.shared_examples 'creates an alert management alert or errors' do
it 'writes a warning to the log' do
expect(Gitlab::AppLogger).to receive(:warn).with(
message: "Unable to create AlertManagement::Alert from #{source}",
message: "Unable to create AlertManagement::Alert",
project_id: project.id,
alert_errors: { hosts: ['hosts array is over 255 chars'] }
alert_errors: { hosts: ['hosts array is over 255 chars'] },
alert_source: source
)
subject

View File

@ -4,8 +4,6 @@
# - `alert`, the alert to be resolved
RSpec.shared_examples 'resolves an existing alert management alert' do
it 'sets the end time and status' do
expect(Gitlab::AppLogger).not_to receive(:warn)
expect { subject }
.to change { alert.reload.resolved? }.to(true)
.and change { alert.ended_at.present? }.to(true)
@ -22,36 +20,6 @@ RSpec.shared_examples 'does not change the alert end time' do
end
end
# This shared_example requires the following variables:
# - `project`, expected project for an incoming alert
# - `service`, a service which includes AlertManagement::AlertProcessing
# - `alert` (optional), the alert which should fail to resolve. If not
# included, the log is expected to correspond to a new alert
RSpec.shared_examples 'writes a warning to the log for a failed alert status update' do
before do
allow(service).to receive(:alert).and_call_original
allow(service).to receive_message_chain(:alert, :resolve).and_return(false)
end
specify do
expect(Gitlab::AppLogger).to receive(:warn).with(
message: 'Unable to update AlertManagement::Alert status to resolved',
project_id: project.id,
alert_id: alert ? alert.id : (last_alert_id + 1)
)
# Failure to resolve a recovery alert is not a critical failure
expect(subject).to be_success
end
private
def last_alert_id
AlertManagement::Alert.connection
.select_value("SELECT nextval('#{AlertManagement::Alert.sequence_name}')")
end
end
RSpec.shared_examples 'processes recovery alert' do
context 'seen for the first time' do
let(:alert) { AlertManagement::Alert.last }
@ -69,7 +37,6 @@ RSpec.shared_examples 'processes recovery alert' do
it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert
it_behaves_like 'sends alert notification emails if enabled'
it_behaves_like 'closes related incident if enabled'
it_behaves_like 'writes a warning to the log for a failed alert status update'
it_behaves_like 'does not create an alert management alert'
it_behaves_like 'does not process incident issues'
@ -83,7 +50,6 @@ RSpec.shared_examples 'processes recovery alert' do
it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert
it_behaves_like 'sends alert notification emails if enabled'
it_behaves_like 'closes related incident if enabled'
it_behaves_like 'writes a warning to the log for a failed alert status update'
it_behaves_like 'does not create an alert management alert'
it_behaves_like 'does not process incident issues'
@ -97,7 +63,6 @@ RSpec.shared_examples 'processes recovery alert' do
it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert
it_behaves_like 'sends alert notification emails if enabled'
it_behaves_like 'closes related incident if enabled'
it_behaves_like 'writes a warning to the log for a failed alert status update'
it_behaves_like 'does not create an alert management alert'
it_behaves_like 'does not process incident issues'

View File

@ -0,0 +1,128 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe RedisCommands::Recorder, :use_clean_rails_redis_caching do
subject(:recorder) { described_class.new(pattern: pattern) }
let(:cache) { Rails.cache }
let(:pattern) { nil }
describe '#initialize' do
context 'with a block' do
it 'records Redis commands' do
recorder = described_class.new { cache.read('key1') }
expect(recorder.log).to include([:get, 'cache:gitlab:key1'])
end
end
context 'without block' do
it 'only initializes the recorder' do
recorder = described_class.new
expect(recorder.log).to eq([])
end
end
end
describe '#record' do
it 'records Redis commands' do
recorder.record do
cache.write('key1', '1')
cache.read('key1')
cache.read('key2')
cache.delete('key1')
end
expect(recorder.log).to include([:set, 'cache:gitlab:key1', anything])
expect(recorder.log).to include([:get, 'cache:gitlab:key1'])
expect(recorder.log).to include([:get, 'cache:gitlab:key2'])
expect(recorder.log).to include([:del, 'cache:gitlab:key1'])
end
it 'does not record commands before the call' do
cache.write('key1', 1)
recorder.record do
cache.read('key1')
end
expect(recorder.log).not_to include([:set, anything, anything])
expect(recorder.log).to include([:get, 'cache:gitlab:key1'])
end
it 'refreshes recording after reinitialization' do
cache.read('key1')
recorder1 = described_class.new
recorder1.record do
cache.read('key2')
end
recorder2 = described_class.new
cache.read('key3')
recorder2.record do
cache.read('key4')
end
expect(recorder1.log).to include([:get, 'cache:gitlab:key2'])
expect(recorder1.log).not_to include([:get, 'cache:gitlab:key1'])
expect(recorder1.log).not_to include([:get, 'cache:gitlab:key3'])
expect(recorder1.log).not_to include([:get, 'cache:gitlab:key4'])
expect(recorder2.log).to include([:get, 'cache:gitlab:key4'])
expect(recorder2.log).not_to include([:get, 'cache:gitlab:key1'])
expect(recorder2.log).not_to include([:get, 'cache:gitlab:key2'])
expect(recorder2.log).not_to include([:get, 'cache:gitlab:key3'])
end
end
describe 'Pattern recording' do
let(:pattern) { 'key1' }
it 'records only matching keys' do
recorder.record do
cache.write('key1', '1')
cache.read('key2')
cache.read('key1')
cache.delete('key2')
end
expect(recorder.log).to include([:set, 'cache:gitlab:key1', anything])
expect(recorder.log).to include([:get, 'cache:gitlab:key1'])
expect(recorder.log).not_to include([:get, 'cache:gitlab:key2'])
expect(recorder.log).not_to include([:del, 'cache:gitlab:key2'])
end
end
describe '#by_command' do
it 'returns only matching commands' do
recorder.record do
cache.write('key1', '1')
cache.read('key2')
cache.read('key1')
cache.delete('key2')
end
expect(recorder.by_command(:del)).to match_array([[:del, 'cache:gitlab:key2']])
end
end
describe '#count' do
it 'returns the number of recorded commands' do
cache.read 'warmup'
recorder.record do
cache.write('key1', '1')
cache.read('key2')
cache.read('key1')
cache.delete('key2')
end
expect(recorder.count).to eq(4)
end
end
end