Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-06-07 00:10:26 +00:00
parent b04f912deb
commit 1e19d757e8
39 changed files with 648 additions and 267 deletions

View File

@ -1,3 +1 @@
5f8440d74ba194204935669f6f98fe9c08a21200:doc/architecture/blueprints/runner_tokens/index.md:gitlab-rrt:504
a349496b88d2add528669f5566ef458d90fc7fba:doc/architecture/blueprints/runner_tokens/index.md:generic-api-key:516
afedb913baf4203aa688421873fdb9f94649578e:doc/api/users.md:generic-api-key:2201

View File

@ -154,7 +154,9 @@ export default {
return createJoinedEnvironments(this.variables, this.environments, this.newEnvironments);
},
maskedFeedback() {
return this.displayMaskedError ? __('This variable can not be masked.') : '';
return this.displayMaskedError
? __('This variable value does not meet the masking requirements.')
: '';
},
maskedState() {
if (this.displayMaskedError) {
@ -190,6 +192,11 @@ export default {
variableValidationState() {
return this.variable.value === '' || (this.tokenValidationState && this.maskedState);
},
variableValueHelpText() {
return this.variable.masked
? __('Value must meet regular expression requirements to be masked.')
: '';
},
},
watch: {
variable: {
@ -324,6 +331,7 @@ export default {
:label="__('Value')"
label-for="ci-variable-value"
:state="variableValidationState"
:description="variableValueHelpText"
:invalid-feedback="variableValidationFeedback"
>
<gl-form-textarea
@ -423,17 +431,19 @@ export default {
>
{{ __('Mask variable') }}
<p class="gl-mt-2 text-secondary">
{{ __('Variable will be masked in job logs.') }}
<span
:class="{
'bold text-plain': displayMaskedError,
}"
<gl-sprintf
:message="
__(
'Mask this variable in job logs if it meets %{linkStart}regular expression requirements%{linkEnd}.',
)
"
>
{{ __('Requires values to meet regular expression requirements.') }}</span
>
<gl-link target="_blank" :href="maskedEnvironmentVariablesLink">{{
__('Learn more.')
}}</gl-link>
<template #link="{ content }"
><gl-link target="_blank" :href="maskedEnvironmentVariablesLink">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</p>
</gl-form-checkbox>
<gl-form-checkbox

View File

@ -29,6 +29,7 @@ module Ci
scope :order_by_created_at, -> { order(created_at: :desc) }
scope :project_id_in, ->(ids) { where(project_id: ids) }
scope :with_files_stored_locally, -> { where(file_store: Ci::SecureFileUploader::Store::LOCAL) }
def checksum_algorithm
CHECKSUM_ALGORITHM

View File

@ -317,6 +317,16 @@ class CommitStatus < Ci::ApplicationRecord
ci_stage&.name
end
# For AiAction
def to_ability_name
'build'
end
# For AiAction
def resource_parent
project
end
private
def unrecoverable_failure?

View File

@ -405,6 +405,12 @@ class Namespace < ApplicationRecord
Project.where(namespace: namespace)
end
# Includes projects from this namespace and projects from all subgroups
# that belongs to this namespace, except the ones that are soft deleted
def all_projects_except_soft_deleted
all_projects.not_aimed_for_deletion
end
def has_parent?
parent_id.present? || parent.present?
end

View File

@ -21,7 +21,7 @@ class Namespace::RootStorageStatistics < ApplicationRecord
scope :for_namespace_ids, ->(namespace_ids) { where(namespace_id: namespace_ids) }
delegate :all_projects, to: :namespace
delegate :all_projects_except_soft_deleted, to: :namespace
enum notification_level: {
storage_remaining: 100,
@ -76,7 +76,7 @@ class Namespace::RootStorageStatistics < ApplicationRecord
end
def for_forks_statistics
all_projects
all_projects_except_soft_deleted
.joins([:statistics, :fork_network])
.where('fork_networks.root_project_id != projects.id')
.group('projects.visibility_level')
@ -92,7 +92,7 @@ class Namespace::RootStorageStatistics < ApplicationRecord
end
def from_project_statistics
all_projects
all_projects_except_soft_deleted
.joins('INNER JOIN project_statistics ps ON ps.project_id = projects.id')
.select(
'COALESCE(SUM(ps.storage_size), 0) AS storage_size',

View File

@ -6,6 +6,10 @@ module Ci
storage_location :ci_secure_files
# TODO: Remove this line
# See https://gitlab.com/gitlab-org/gitlab/-/issues/232917
alias_method :upload, :model
# Use Lockbox to encrypt/decrypt the stored file (registers CarrierWave callbacks)
encrypt(key: :key)

View File

@ -1,12 +1,2 @@
= format(s_('CiVariables|Variables store information, like passwords and secret keys, that you can use in job scripts. Each %{entity} can define a maximum of %{limit} variables.'), entity: entity, limit: variable_limit).html_safe
= link_to _('Learn more.'), help_page_path('ci/variables/index'), target: '_blank', rel: 'noopener noreferrer'
%p
= _('Variables can have several attributes.')
= link_to _('Learn more.'), help_page_path('ci/variables/index', anchor: 'define-a-cicd-variable-in-the-ui'), target: '_blank', rel: 'noopener noreferrer'
%ul
%li
= html_escape(_('%{code_open}Protected:%{code_close} Only exposed to protected branches or protected tags.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
%li
= html_escape(_('%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
%li
= html_escape(_('%{code_open}Expanded:%{code_close} Variables with %{code_open}$%{code_close} will be treated as the start of a reference to another variable.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }

View File

@ -2,6 +2,17 @@
- is_group = !@group.nil?
- is_project = !@project.nil?
%p
= _('Variables can have several attributes.')
= link_to _('Learn more.'), help_page_path('ci/variables/index', anchor: 'define-a-cicd-variable-in-the-ui'), target: '_blank', rel: 'noopener noreferrer'
%ul
%li
= html_escape(_('%{code_open}Protected:%{code_close} Only exposed to protected branches or protected tags.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
%li
= html_escape(_('%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
%li
= html_escape(_('%{code_open}Expanded:%{code_close} Variables with %{code_open}$%{code_close} will be treated as the start of a reference to another variable.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
#js-ci-variables{ data: { endpoint: save_endpoint,
is_project: is_project.to_s,
project_id: @project&.id || '',

View File

@ -6,4 +6,4 @@
= s_('UserProfile|User ID: %{id}') % { id: @user.id }
= clipboard_button(title: s_('UserProfile|Copy user ID'), text: @user.id)
= render 'middle_dot_divider', stacking: true do
= s_('Member since %{date}') % { date: @user.created_at.to_date.to_s(:long) }
= s_('Member since %{date}') % { date: l(@user.created_at, format: :long) }

View File

@ -201,8 +201,6 @@ module WorkerAttributes
end
def defer_on_database_health_signal?
return false unless Feature.enabled?(:defer_sidekiq_workers_on_database_health_signal, type: :worker)
database_health_check_attrs.present?
end

View File

@ -1,8 +0,0 @@
---
name: defer_sidekiq_workers_on_database_health_signal
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121261
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/412990
milestone: '16.1'
type: worker
group: group::database
default_enabled: false

View File

@ -18,4 +18,7 @@ path = "/gitleaks.toml"
"glpat--8GMtG8Mf4EnMJzmAWDU",
# doc/development/sec/token_revocation_api.md
"glpat--tG84EGK33nMLLDE70zU",
# doc/ci/runners/new_creation_workflow.md
"GR1348941C6YcZVddc8kjtdU-yWYD",
"glrt-2CR8_eVxiioB1QmzPZwa",
]

View File

@ -440,124 +440,7 @@ scope.
## FAQ
### Will my runner registration workflow break?
If no action is taken before your GitLab instance is upgraded to 16.6, then your runner registration
workflow will break.
Until then, both the new and the old workflow will coexist side-by-side.
To avoid a broken workflow, you need to first create a runner in the GitLab runners admin page.
After that, you'll need to replace the registration token you're using in your runner registration
workflow with the obtained runner authentication token.
### Can I use the old runner registration process after 15.6?
- If you're using GitLab.com, you'll be able to manually re-enable the previous runner registration process in the top-level group settings until GitLab 16.8.
- If you're running GitLab self-managed, you'll be able re-enable the previous runner registration process in admin settings until GitLab 17.0.
### What is the new runner registration process?
When the new runner registration process is introduced, you will:
1. Create a runner directly in the GitLab UI.
1. Receive an authentication token in return.
1. Use the authentication token instead of the registration token, whenever you need to register a runner with this
configuration. Runner managers registered in multiple hosts will appear under the same runner in the GitLab UI,
but with an identifying system ID.
This has added benefits such as preserved ownership records for runners, and minimizes
impact on users.
The addition of a unique system ID ensures that you can reuse the same authentication token across
multiple runners.
For example, in an auto-scaling scenario where a runner manager spawns a runner process with a
fixed authentication token.
This ID generates once at the runner's startup, persists in a sidecar file, and is sent to the
GitLab instance when requesting jobs.
This allows the GitLab instance to display which system executed a given job.
### What is the estimated timeframe for the planned changes?
- In GitLab 15.10, we plan to implement runner creation directly in the runners administration page,
and prepare the runner to follow the new workflow.
- In GitLab 16.6, we plan to disable registration tokens.
- In GitLab 17.0, we plan to completely remove support for runner registration tokens.
### How will the `gitlab-runner register` command syntax change?
The `gitlab-runner register` command will stop accepting registration tokens and instead accept new
authentication tokens generated in the GitLab runners administration page.
These authentication tokens are recognizable by their `glrt-` prefix.
Example command for GitLab 15.9:
```shell
gitlab-runner register
--non-interactive \
--executor "shell" \
--url "https://gitlab.com/" \
--tag-list "shell,mac,gdk,test" \
--run-untagged "false" \
--locked "false" \
--access-level "not_protected" \
--registration-token "GR1348941C6YcZVddc8kjtdU-yWYD"
```
In GitLab 16.0, the runner will be created in the UI where some of its attributes can be
pre-configured by the creator.
Examples are the tag list, locked status, or access level. These are no longer accepted as arguments
to `register`. The following example shows the new command:
```shell
gitlab-runner register
--non-interactive \
--executor "shell" \
--url "https://gitlab.com/" \
--token "glrt-2CR8_eVxiioB1QmzPZwa"
```
### How does this change impact auto-scaling scenarios?
In auto-scaling scenarios such as GitLab Runner Operator or GitLab Runner Helm Chart, the
registration token is replaced with the authentication token generated from the UI.
This means that the same runner configuration is reused across jobs, instead of creating a runner
for each job.
The specific runner can be identified by the unique system ID that is generated when the runner
process is started.
### Will existing runners continue to work?
Yes, existing runners will continue to work as usual. This change only affects registration of new runners.
### Can runners still be created programmatically?
A new [POST /user/runners REST API](../../../api/users.md#create-a-runner) was introduced in
GitLab 15.11, which allows a runner to be created in the context of an authenticated user. This should only be used in
scenarios where the runner configuration is dynamic, or not reusable. If the runner configuration is static, it is
preferable to reuse the authentication token of an existing runner.
The following snippet shows how a group runner could be created and registered with a
[Group Access Token](../../../user/group/settings/group_access_tokens.md) using the new creation flow.
The process is very similar when using [Project Access Tokens](../../../user/project/settings/project_access_tokens.md)
or [Personal Access Tokens](../../../user/profile/personal_access_tokens.md):
```shell
# `GROUP_ID` contains the numerical ID of the group where the runner will be created
# `GITLAB_TOKEN` can be a Personal Access Token for a group owner, or a Group Access Token on the respective group
# created with `owner` access and `api` scope.
#
# The output will be parsed by `jq` to extract the token of the newly created runner
RUNNER_TOKEN=$(curl --silent --method POST "https://gitlab.com/api/v4/user/runners" \
--header "private-token: $GITLAB_TOKEN" \
--header 'content-type: application/json' \
--data "{\"runner_type\":\"group_type\",\"group_id\":\"$GROUP_ID\",\"description\":\"My runner\",\"tag-list\":\"java,linux\"}" \
| jq -r '.token')
gitlab-runner register
--non-interactive \
--executor "shell" \
--url "https://gitlab.com/" \
--token "$RUNNER_TOKEN"
```
Please follow [the user documentation](../../../ci/runners/new_creation_workflow.md).
## Status

View File

@ -0,0 +1,152 @@
---
stage: Verify
group: Runner
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Migrating to the new runner registration workflow **(FREE)**
DISCLAIMER:
This page contains information related to upcoming products, features, and functionality.
It is important to note that the information presented is for informational purposes only.
Please do not rely on this information for purchasing or planning purposes.
As with all projects, the items mentioned on this page are subject to change or delay.
The development, release, and timing of any products, features, or functionality remain at the
sole discretion of GitLab Inc.
In GitLab 16.0 we introduced a new runner creation workflow,
the previous workflow that uses registration tokens is deprecated
and will be removed in GitLab 17.0.
For more information about the implementation for the new workflow, see the:
- [Next GitLab Runner Token Architecture](../../architecture/blueprints/runner_tokens/index.md) for information about the technical design and reasons for the new token architecture.
- [Development epic](https://gitlab.com/groups/gitlab-org/-/epics/7663) for the most accurate information about the current development status.
## Feedback
If you experience problems with the new runner registration workflow,
and the following information is not sufficient,
or if you have concerns about it,
you can reach out to us in the [feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/387993).
## Will my runner registration workflow break?
If no action is taken before your GitLab instance is upgraded to 16.6, then your runner registration
workflow will break.
Until then, both the new and the old workflow will coexist side-by-side.
To avoid a broken workflow, you must:
1. [Create a shared runner](register_runner.md#for-a-shared-runner) and obtain the authentication token.
1. Replace the registration token in your runner registration workflow with the
authentication token.
## Can I use the old runner registration process after 16.6?
- On GitLab.com, you'll be able to manually re-enable the previous runner registration process in the top-level group settings until GitLab 16.8.
- On GitLab self-managed, you'll be able manually re-enable the previous runner registration process in the Admin Area settings until GitLab 17.0.
## What is the new runner registration process?
When the new runner registration process is introduced, you will:
1. Create a runner directly in the GitLab UI.
1. Receive an authentication token in return.
1. Use the authentication token instead of the registration token, whenever you need to register a runner with this
configuration. Runner managers registered in multiple hosts will appear under the same runner in the GitLab UI,
but with an identifying system ID.
This has added benefits such as preserved ownership records for runners, and minimizes
impact on users.
The addition of a unique system ID ensures that you can reuse the same authentication token across
multiple runners.
For example, in an auto-scaling scenario where a runner manager spawns a runner process with a
fixed authentication token.
This ID generates once at the runner's startup, persists in a sidecar file, and is sent to the
GitLab instance when requesting jobs.
This allows the GitLab instance to display which system executed a given job.
## What is the estimated timeframe for the planned changes?
- In GitLab 15.10, we plan to implement runner creation directly in the runners administration page,
and prepare the runner to follow the new workflow.
- In GitLab 16.6, we plan to disable registration tokens.
- In GitLab 17.0, we plan to completely remove support for runner registration tokens.
## How will the `gitlab-runner register` command syntax change?
The `gitlab-runner register` command will stop accepting registration tokens and instead accept new
authentication tokens generated in the GitLab runners administration page.
These authentication tokens are recognizable by their `glrt-` prefix.
Example command for GitLab 15.9:
```shell
gitlab-runner register
--non-interactive \
--executor "shell" \
--url "https://gitlab.com/" \
--tag-list "shell,mac,gdk,test" \
--run-untagged "false" \
--locked "false" \
--access-level "not_protected" \
--registration-token "GR1348941C6YcZVddc8kjtdU-yWYD"
```
In GitLab 16.0, the runner will be created in the UI where some of its attributes can be
pre-configured by the creator.
Examples are the tag list, locked status, or access level. These are no longer accepted as arguments
to `register`. The following example shows the new command:
```shell
gitlab-runner register
--non-interactive \
--executor "shell" \
--url "https://gitlab.com/" \
--token "glrt-2CR8_eVxiioB1QmzPZwa"
```
## How does this change impact auto-scaling scenarios?
In auto-scaling scenarios such as GitLab Runner Operator or GitLab Runner Helm Chart, the
registration token is replaced with the authentication token generated from the UI.
This means that the same runner configuration is reused across jobs, instead of creating a runner
for each job.
The specific runner can be identified by the unique system ID that is generated when the runner
process is started.
## Will existing runners continue to work?
Yes, existing runners will continue to work as usual. This change only affects registration of new runners.
## Can runners still be created programmatically?
A new [POST /user/runners REST API](../../api/users.md#create-a-runner) was introduced in
GitLab 15.11, which allows a runner to be created in the context of an authenticated user. This should only be used in
scenarios where the runner configuration is dynamic, or not reusable. If the runner configuration is static, it is
preferable to reuse the authentication token of an existing runner.
The following snippet shows how a group runner could be created and registered with a
[Group Access Token](../../user/group/settings/group_access_tokens.md) using the new creation flow.
The process is very similar when using [Project Access Tokens](../../user/project/settings/project_access_tokens.md)
or [Personal Access Tokens](../../user/profile/personal_access_tokens.md):
```shell
# `GROUP_ID` contains the numerical ID of the group where the runner will be created
# `GITLAB_TOKEN` can be a Personal Access Token for a group owner, or a Group Access Token on the respective group
# created with `owner` access and `api` scope.
#
# The output will be parsed by `jq` to extract the token of the newly created runner
RUNNER_TOKEN=$(curl --silent --method POST "https://gitlab.com/api/v4/user/runners" \
--header "private-token: $GITLAB_TOKEN" \
--header 'content-type: application/json' \
--data "{\"runner_type\":\"group_type\",\"group_id\":\"$GROUP_ID\",\"description\":\"My runner\",\"tag-list\":\"java,linux\"}" \
| jq -r '.token')
gitlab-runner register
--non-interactive \
--executor "shell" \
--url "https://gitlab.com/" \
--token "$RUNNER_TOKEN"
```

View File

@ -774,3 +774,17 @@ xargs: tail: terminated by signal 6
```
Removing old log files helps fix the error, and ensures a clean startup of the instance.
### ThreadError can't create Thread Operation not permitted
```plaintext
can't create Thread: Operation not permitted
```
This error occurs when running a container built with newer `glibc` versions on a
[host that doesn't have support for the new clone3 function](https://github.com/moby/moby/issues/42680). In GitLab 16.0 and later, the container image includes
the Ubuntu 22.04 GitLab Linux package which is built with this newer `glibc`.
This problem is fixed with newer container runtime tools like [Docker 20.10.10](https://github.com/moby/moby/pull/42836).
To resolve this issue, update Docker to version 20.10.10 or later.

View File

@ -287,6 +287,8 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
- Sidekiq jobs are only routed to `default` and `mailers` queues by default, and as a result,
every Sidekiq process also listens to those queues to ensure all jobs are processed across
all queues. This behavior does not apply if you have configured the [routing rules](../administration/sidekiq/processing_specific_job_classes.md#routing-rules).
- Docker 20.10.10 or later is required to run the GitLab Docker image. Older versions
[throw errors on startup](../install/docker.md#threaderror-cant-create-thread-operation-not-permitted).
### 15.11.1

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
module Gitlab
module Ci
module SecureFiles
class MigrationHelper
class << self
def migrate_to_remote_storage(&block)
migrate_in_batches(
::Ci::SecureFile.with_files_stored_locally,
::Ci::SecureFileUploader::Store::REMOTE,
&block
)
end
private
def batch_size
ENV.fetch('MIGRATION_BATCH_SIZE', 10).to_i
end
def migrate_in_batches(files, store, &block)
files.find_each(batch_size: batch_size) do |file| # rubocop:disable CodeReuse/ActiveRecord
file.file.migrate!(store)
yield file if block
end
end
end
end
end
end
end

View File

@ -7,7 +7,7 @@ module Gitlab
attr_reader :status_checker, :connection, :tables, :gitlab_schema
# status_checker: the caller object which checks for database health status
# eg: batched_migration
# eg: BackgroundMigration::BatchedMigration or DeferJobs::DatabaseHealthStatusChecker
def initialize(status_checker, connection, tables, gitlab_schema)
@status_checker = status_checker
@connection = connection
@ -16,16 +16,11 @@ module Gitlab
end
def status_checker_info
data = {
{
status_checker_id: status_checker.id,
status_checker_type: status_checker.class.name
status_checker_type: status_checker.class.name,
job_class_name: status_checker.job_class_name
}
if status_checker.is_a?(Gitlab::Database::BackgroundMigration::BatchedMigration)
data[:job_class_name] = status_checker.job_class_name
end
data
end
end
end

View File

@ -86,6 +86,8 @@ module Gitlab
payload['message'] = "#{message}: #{job_status}: #{payload['duration_s']} sec"
payload['job_status'] = job_status
payload['job_deferred_by'] = job['deferred_by'] if job['deferred']
Gitlab::ExceptionLogFormatter.format!(job_exception, payload) if job_exception
db_duration = ActiveRecord::LogSubscriber.runtime

View File

@ -36,7 +36,7 @@ module Gitlab
chain.add ::Gitlab::SidekiqVersioning::Middleware
chain.add ::Gitlab::SidekiqStatus::ServerMiddleware
chain.add ::Gitlab::SidekiqMiddleware::WorkerContext::Server
# DuplicateJobs::Server should be placed at the bottom, but before the SidekiqServerMiddleware,
# DuplicateJobs::Server should be placed at the bottom, but before the SidekiqServerMiddleware,
# so we can compare the latest WAL location against replica
chain.add ::Gitlab::SidekiqMiddleware::DuplicateJobs::Server
chain.add ::Gitlab::Database::LoadBalancing::SidekiqServerMiddleware

View File

@ -3,30 +3,72 @@
module Gitlab
module SidekiqMiddleware
class DeferJobs
include Sidekiq::ServerMiddleware
DELAY = ENV.fetch("SIDEKIQ_DEFER_JOBS_DELAY", 5.minutes)
FEATURE_FLAG_PREFIX = "defer_sidekiq_jobs"
# This middleware will defer jobs indefinitely until the `defer_sidekiq_jobs_#{worker_name}` feature flag
# is turned off (or when Feature.enabled? returns false by chance while using `percentage of time` value)
DatabaseHealthStatusChecker = Struct.new(:id, :job_class_name)
# There are 2 scenarios under which this middleware defers a job
# 1. defer_sidekiq_jobs_#{worker_name} FF, jobs are deferred indefinitely until this feature flag
# is turned off or when Feature.enabled? returns false by chance while using `percentage of time` value.
# 2. Gitlab::Database::HealthStatus, on evaluating the db health status if it returns any indicator
# with stop signal, the jobs will be delayed by 'x' seconds (set in worker).
def call(worker, job, _queue)
if defer_job?(worker)
job['deferred'] = true # for logging job_status
worker.class.perform_in(DELAY, *job['args'])
# ActiveJobs have wrapped class stored in 'wrapped' key
resolved_class = job['wrapped']&.safe_constantize || worker.class
defer_job, delay, deferred_by = defer_job_info(resolved_class, job)
if !!defer_job
# Referred in job_logger's 'log_job_done' method to compute proper 'job_status'
job['deferred'] = true
job['deferred_by'] = deferred_by
worker.class.perform_in(delay, *job['args'])
counter.increment({ worker: worker.class.name })
# This breaks the middleware chain and return
return
end
yield
end
def defer_job?(worker)
Feature.enabled?(:"#{FEATURE_FLAG_PREFIX}_#{worker.class.name}", type: :worker,
default_enabled_if_undefined: false)
private
def defer_job_info(worker_class, job)
if defer_job_by_ff?(worker_class)
[true, DELAY, :feature_flag]
elsif defer_job_by_database_health_signal?(job, worker_class)
[true, worker_class.database_health_check_attrs[:delay_by], :database_health_check]
end
end
private
def defer_job_by_ff?(worker_class)
Feature.enabled?(
:"#{FEATURE_FLAG_PREFIX}_#{worker_class.name}",
type: :worker,
default_enabled_if_undefined: false
)
end
def defer_job_by_database_health_signal?(job, worker_class)
unless worker_class.respond_to?(:defer_on_database_health_signal?) &&
worker_class.defer_on_database_health_signal?
return false
end
health_check_attrs = worker_class.database_health_check_attrs
job_base_model = Gitlab::Database.schemas_to_base_models[health_check_attrs[:gitlab_schema]].first
health_context = Gitlab::Database::HealthStatus::Context.new(
DatabaseHealthStatusChecker.new(job['jid'], worker_class.name),
job_base_model.connection,
health_check_attrs[:gitlab_schema],
health_check_attrs[:tables]
)
Gitlab::Database::HealthStatus.evaluate(health_context).any?(&:stop?)
end
def counter
@counter ||= Gitlab::Metrics.counter(:sidekiq_jobs_deferred_total, 'The number of jobs deferred')

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
desc "GitLab | CI Secure Files | Migrate Secure Files to remote storage"
namespace :gitlab do
namespace :ci_secure_files do
task migrate: :environment do
require 'logger'
logger = Logger.new($stdout)
logger.info('Starting transfer of Secure Files to object storage')
begin
Gitlab::Ci::SecureFiles::MigrationHelper.migrate_to_remote_storage do |file|
message = "Transferred Secure File ID #{file.id} (#{file.name}) to object storage"
logger.info(message)
end
rescue StandardError => e
logger.error("Failed to migrate: #{e.message}")
end
end
end
end

View File

@ -27677,6 +27677,9 @@ msgstr ""
msgid "Marks to do as done."
msgstr ""
msgid "Mask this variable in job logs if it meets %{linkStart}regular expression requirements%{linkEnd}."
msgstr ""
msgid "Mask variable"
msgstr ""
@ -38739,9 +38742,6 @@ msgstr[1] ""
msgid "Requires a verified GitLab email address."
msgstr ""
msgid "Requires values to meet regular expression requirements."
msgstr ""
msgid "Requires you to deploy or set up cloud-hosted Sentry."
msgstr ""
@ -46939,7 +46939,7 @@ msgstr ""
msgid "This user is the author of this %{noteable}."
msgstr ""
msgid "This variable can not be masked."
msgid "This variable value does not meet the masking requirements."
msgstr ""
msgid "This vulnerability was automatically resolved because its vulnerability type was disabled in this project or removed from GitLab's default ruleset."
@ -49681,6 +49681,9 @@ msgstr ""
msgid "Value might contain a variable reference"
msgstr ""
msgid "Value must meet regular expression requirements to be masked."
msgstr ""
msgid "Value stream"
msgstr ""
@ -49825,9 +49828,6 @@ msgstr ""
msgid "Variable value will be evaluated as raw string."
msgstr ""
msgid "Variable will be masked in job logs."
msgstr ""
msgid "Variables"
msgstr ""

View File

@ -56,7 +56,7 @@
"@gitlab/cluster-client": "^1.2.0",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.2.0",
"@gitlab/svgs": "3.50.0",
"@gitlab/svgs": "3.51.0",
"@gitlab/ui": "64.2.3",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230524134151",

View File

@ -27,13 +27,6 @@ module QA
end
end
let(:registry_repository) do
Resource::RegistryRepository.fabricate! do |repository|
repository.name = project.path_with_namespace.to_s
repository.project = project
end
end
let!(:runner) do
Resource::ProjectRunner.fabricate! do |runner|
runner.name = "qa-runner-#{Time.now.to_i}"
@ -51,7 +44,6 @@ module QA
end
after do
registry_repository&.remove_via_api!
runner.remove_via_api!
end

View File

@ -53,7 +53,6 @@ module QA
end
after do
project.remove_via_api!
runner.remove_via_api!
end

View File

@ -458,7 +458,8 @@ describe('Ci variable modal', () => {
});
describe('Validations', () => {
const maskError = 'This variable can not be masked.';
const maskError = 'This variable value does not meet the masking requirements.';
const helpText = 'Value must meet regular expression requirements to be masked.';
describe('when the variable is raw', () => {
const [variable] = mockVariables;
@ -488,6 +489,10 @@ describe('Ci variable modal', () => {
expect(findModal().text()).toContain(maskError);
});
it('does not show the masked variable help text', () => {
expect(findModal().text()).not.toContain(helpText);
});
});
describe('when the mask state is invalid', () => {
@ -510,8 +515,9 @@ describe('Ci variable modal', () => {
expect(findAddorUpdateButton().attributes('disabled')).toBeDefined();
});
it('shows the correct error text', () => {
it('shows the correct error text and help text', () => {
expect(findModal().text()).toContain(maskError);
expect(findModal().text()).toContain(helpText);
});
it('sends the correct tracking event', () => {
@ -578,6 +584,10 @@ describe('Ci variable modal', () => {
});
});
it('shows the help text', () => {
expect(findModal().text()).toContain(helpText);
});
it('does not disable the submit button', () => {
expect(findAddorUpdateButton().attributes('disabled')).toBeUndefined();
});

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::SecureFiles::MigrationHelper, feature_category: :mobile_devops do
before do
stub_ci_secure_file_object_storage
end
describe '.migrate_to_remote_storage' do
let!(:local_file) { create(:ci_secure_file) }
subject { described_class.migrate_to_remote_storage }
it 'migrates remote files to remote storage' do
subject
expect(local_file.reload.file_store).to eq(Ci::SecureFileUploader::Store::REMOTE)
end
end
describe '.migrate_in_batches' do
let!(:local_file) { create(:ci_secure_file) }
let!(:storage) { Ci::SecureFileUploader::Store::REMOTE }
subject { described_class.migrate_to_remote_storage }
it 'migrates the given file to the given storage backend' do
expect_next_found_instance_of(Ci::SecureFile) do |instance|
expect(instance).to receive_message_chain(:file, :migrate!).with(storage)
end
described_class.send(:migrate_in_batches, Ci::SecureFile.all, storage)
end
it 'calls the given block for each migrated file' do
expect_next_found_instance_of(Ci::SecureFile) do |instance|
expect(instance).to receive(:metadata)
end
described_class.send(:migrate_in_batches, Ci::SecureFile.all, storage, &:metadata)
end
end
end

View File

@ -12,9 +12,10 @@ RSpec.describe Gitlab::Database::HealthStatus, feature_category: :database do
end
describe '.evaluate' do
subject(:evaluate) { described_class.evaluate(migration.health_context, [autovacuum_indicator_class]) }
subject(:evaluate) { described_class.evaluate(health_context, [autovacuum_indicator_class]) }
let(:migration) { build(:batched_background_migration, :active) }
let(:health_context) { migration.health_context }
let(:health_status) { described_class }
let(:autovacuum_indicator_class) { health_status::Indicators::AutovacuumActiveOnTable }
@ -25,20 +26,20 @@ RSpec.describe Gitlab::Database::HealthStatus, feature_category: :database do
let(:patroni_apdex_indicator) { instance_double(patroni_apdex_indicator_class) }
before do
allow(autovacuum_indicator_class).to receive(:new).with(migration.health_context).and_return(autovacuum_indicator)
allow(autovacuum_indicator_class).to receive(:new).with(health_context).and_return(autovacuum_indicator)
end
context 'with default indicators' do
subject(:evaluate) { described_class.evaluate(migration.health_context) }
subject(:evaluate) { described_class.evaluate(health_context) }
it 'returns a collection of signals' do
normal_signal = instance_double("#{health_status}::Signals::Normal", log_info?: false)
not_available_signal = instance_double("#{health_status}::Signals::NotAvailable", log_info?: false)
expect(autovacuum_indicator).to receive(:evaluate).and_return(normal_signal)
expect(wal_indicator_class).to receive(:new).with(migration.health_context).and_return(wal_indicator)
expect(wal_indicator_class).to receive(:new).with(health_context).and_return(wal_indicator)
expect(wal_indicator).to receive(:evaluate).and_return(not_available_signal)
expect(patroni_apdex_indicator_class).to receive(:new).with(migration.health_context)
expect(patroni_apdex_indicator_class).to receive(:new).with(health_context)
.and_return(patroni_apdex_indicator)
expect(patroni_apdex_indicator).to receive(:evaluate).and_return(not_available_signal)
@ -46,7 +47,7 @@ RSpec.describe Gitlab::Database::HealthStatus, feature_category: :database do
end
end
it 'returns a collection of signals' do
it 'returns the signal of the given indicator' do
signal = instance_double("#{health_status}::Signals::Normal", log_info?: false)
expect(autovacuum_indicator).to receive(:evaluate).and_return(signal)
@ -54,28 +55,78 @@ RSpec.describe Gitlab::Database::HealthStatus, feature_category: :database do
expect(evaluate).to contain_exactly(signal)
end
it 'logs interesting signals' do
signal = instance_double(
"#{health_status}::Signals::Stop",
log_info?: true,
indicator_class: autovacuum_indicator_class,
short_name: 'Stop',
reason: 'Test Exception'
)
context 'with stop signals' do
let(:stop_signal) do
instance_double(
"#{health_status}::Signals::Stop",
log_info?: true,
indicator_class: autovacuum_indicator_class,
short_name: 'Stop',
reason: 'Test Exception'
)
end
expect(autovacuum_indicator).to receive(:evaluate).and_return(signal)
before do
allow(autovacuum_indicator).to receive(:evaluate).and_return(stop_signal)
end
expect(Gitlab::Database::HealthStatus::Logger).to receive(:info).with(
status_checker_id: migration.id,
status_checker_type: 'Gitlab::Database::BackgroundMigration::BatchedMigration',
job_class_name: migration.job_class_name,
health_status_indicator: autovacuum_indicator_class.to_s,
indicator_signal: 'Stop',
signal_reason: 'Test Exception',
message: "#{migration} signaled: #{signal}"
)
context 'with batched migrations as the status checker' do
it 'captures BatchedMigration class name in the log' do
expect(Gitlab::Database::HealthStatus::Logger).to receive(:info).with(
status_checker_id: migration.id,
status_checker_type: 'Gitlab::Database::BackgroundMigration::BatchedMigration',
job_class_name: migration.job_class_name,
health_status_indicator: autovacuum_indicator_class.to_s,
indicator_signal: 'Stop',
signal_reason: 'Test Exception',
message: "#{migration} signaled: #{stop_signal}"
)
evaluate
evaluate
end
end
context 'with sidekiq deferred job as the status checker' do
let(:deferred_worker) do
Class.new do
def self.name
'TestDeferredWorker'
end
include ApplicationWorker
end
end
let(:deferred_worker_health_checker) do
Gitlab::SidekiqMiddleware::DeferJobs::DatabaseHealthStatusChecker.new(
123,
deferred_worker.name
)
end
let(:health_context) do
Gitlab::Database::HealthStatus::Context.new(
deferred_worker_health_checker,
ActiveRecord::Base.connection,
:gitlab_main,
[:users]
)
end
it 'captures sidekiq job class in the log' do
expect(Gitlab::Database::HealthStatus::Logger).to receive(:info).with(
status_checker_id: deferred_worker_health_checker.id,
status_checker_type: 'Gitlab::SidekiqMiddleware::DeferJobs::DatabaseHealthStatusChecker',
job_class_name: deferred_worker_health_checker.job_class_name,
health_status_indicator: autovacuum_indicator_class.to_s,
indicator_signal: 'Stop',
signal_reason: 'Test Exception',
message: "#{deferred_worker_health_checker} signaled: #{stop_signal}"
)
evaluate
end
end
end
it 'does not log signals of no interest' do

View File

@ -435,6 +435,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
call_subject(job, 'test_queue') do
job['deferred'] = true
job['deferred_by'] = :feature_flag
end
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::SidekiqMiddleware::DeferJobs, feature_category: :scalability do
let(:job) { { 'jid' => 123, 'args' => [456] } }
let(:queue) { 'test_queue' }
let(:worker) do
let(:deferred_worker) do
Class.new do
def self.name
'TestDeferredWorker'
@ -14,7 +14,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::DeferJobs, feature_category: :scalabil
end
end
let(:worker2) do
let(:undeferred_worker) do
Class.new do
def self.name
'UndeferredWorker'
@ -26,21 +26,29 @@ RSpec.describe Gitlab::SidekiqMiddleware::DeferJobs, feature_category: :scalabil
subject { described_class.new }
before do
stub_const('TestDeferredWorker', worker)
stub_const('UndeferredWorker', worker2)
stub_const('TestDeferredWorker', deferred_worker)
stub_const('UndeferredWorker', undeferred_worker)
end
describe '#call' do
context 'when sidekiq_defer_jobs feature flag is enabled for a worker' do
before do
stub_feature_flags("defer_sidekiq_jobs_#{TestDeferredWorker.name}": true)
stub_feature_flags("defer_sidekiq_jobs_#{UndeferredWorker.name}": false)
end
context 'with worker not opted for database health check' do
context 'when sidekiq_defer_jobs feature flag is enabled for a worker' do
before do
stub_feature_flags("defer_sidekiq_jobs_#{TestDeferredWorker.name}": true)
stub_feature_flags("defer_sidekiq_jobs_#{UndeferredWorker.name}": false)
end
context 'for the affected worker' do
it 'defers the job' do
expect(TestDeferredWorker).to receive(:perform_in).with(described_class::DELAY, *job['args'])
expect { |b| subject.call(TestDeferredWorker.new, job, queue, &b) }.not_to yield_control
context 'for the affected worker' do
it 'defers the job' do
expect(TestDeferredWorker).to receive(:perform_in).with(described_class::DELAY, *job['args'])
expect { |b| subject.call(TestDeferredWorker.new, job, queue, &b) }.not_to yield_control
end
end
context 'for other workers' do
it 'runs the job normally' do
expect { |b| subject.call(UndeferredWorker.new, job, queue, &b) }.to yield_control
end
end
it 'increments the counter' do
@ -51,22 +59,52 @@ RSpec.describe Gitlab::SidekiqMiddleware::DeferJobs, feature_category: :scalabil
end
end
context 'for other workers' do
context 'when sidekiq_defer_jobs feature flag is disabled' do
before do
stub_feature_flags("defer_sidekiq_jobs_#{TestDeferredWorker.name}": false)
stub_feature_flags("defer_sidekiq_jobs_#{UndeferredWorker.name}": false)
end
it 'runs the job normally' do
expect { |b| subject.call(TestDeferredWorker.new, job, queue, &b) }.to yield_control
expect { |b| subject.call(UndeferredWorker.new, job, queue, &b) }.to yield_control
end
end
end
context 'when sidekiq_defer_jobs feature flag is disabled' do
before do
stub_feature_flags("defer_sidekiq_jobs_#{TestDeferredWorker.name}": false)
stub_feature_flags("defer_sidekiq_jobs_#{UndeferredWorker.name}": false)
context 'with worker opted for database health check' do
let(:health_signal_attrs) { { gitlab_schema: :gitlab_main, delay: 1.minute, tables: [:users] } }
around do |example|
with_sidekiq_server_middleware do |chain|
chain.add described_class
Sidekiq::Testing.inline! { example.run }
end
end
it 'runs the job normally' do
expect { |b| subject.call(TestDeferredWorker.new, job, queue, &b) }.to yield_control
expect { |b| subject.call(UndeferredWorker.new, job, queue, &b) }.to yield_control
before do
stub_feature_flags("defer_sidekiq_jobs_#{TestDeferredWorker.name}": false)
TestDeferredWorker.defer_on_database_health_signal(*health_signal_attrs.values)
end
context 'without any stop signal from database health check' do
it 'runs the job normally' do
expect { |b| subject.call(TestDeferredWorker.new, job, queue, &b) }.to yield_control
end
end
context 'with stop signal from database health check' do
before do
stop_signal = instance_double("Gitlab::Database::HealthStatus::Signals::Stop", stop?: true)
allow(Gitlab::Database::HealthStatus).to receive(:evaluate).and_return([stop_signal])
end
it 'defers the job by set time' do
expect(TestDeferredWorker).to receive(:perform_in).with(health_signal_attrs[:delay], *job['args'])
TestDeferredWorker.perform_async(*job['args'])
end
end
end
end

View File

@ -6,7 +6,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
it { is_expected.to belong_to :namespace }
it { is_expected.to have_one(:route).through(:namespace) }
it { is_expected.to delegate_method(:all_projects).to(:namespace) }
it { is_expected.to delegate_method(:all_projects_except_soft_deleted).to(:namespace) }
context 'scopes' do
describe '.for_namespace_ids' do
@ -28,9 +28,10 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
let(:project1) { create(:project, namespace: namespace) }
let(:project2) { create(:project, namespace: namespace) }
let(:project3) { create(:project, namespace: namespace, marked_for_deletion_at: 1.day.ago, pending_delete: true) }
shared_examples 'project data refresh' do
it 'aggregates project statistics' do
it 'aggregates eligible project statistics' do
root_storage_statistics.recalculate!
root_storage_statistics.reload
@ -97,6 +98,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
context 'with project statistics' do
let!(:project_stat1) { create(:project_statistics, project: project1, with_data: true, size_multiplier: 100) }
let!(:project_stat2) { create(:project_statistics, project: project2, with_data: true, size_multiplier: 200) }
let!(:project_stat3) { create(:project_statistics, project: project3, with_data: true, size_multiplier: 300) }
it_behaves_like 'project data refresh'
it_behaves_like 'does not include personal snippets'

View File

@ -1745,6 +1745,52 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
end
end
describe '#all_projects_except_soft_deleted' do
context 'when namespace is a group' do
let_it_be(:namespace) { create(:group) }
let_it_be(:child) { create(:group, parent: namespace) }
let_it_be(:project1) { create(:project_empty_repo, namespace: namespace) }
let_it_be(:project2) { create(:project_empty_repo, namespace: child) }
let_it_be(:other_project) { create(:project_empty_repo) }
before do
reload_models(namespace, child)
end
it { expect(namespace.all_projects_except_soft_deleted.to_a).to match_array([project2, project1]) }
it { expect(child.all_projects_except_soft_deleted.to_a).to match_array([project2]) }
context 'with soft deleted projects' do
let_it_be(:delayed_deletion_project) { create(:project, namespace: child, marked_for_deletion_at: Date.current) }
it 'skips delayed deletion project' do
expect(namespace.all_projects_except_soft_deleted.to_a).to match_array([project2, project1])
end
end
end
context 'when namespace is a user namespace' do
let_it_be(:user) { create(:user) }
let_it_be(:user_namespace) { create(:namespace, owner: user) }
let_it_be(:project) { create(:project, namespace: user_namespace) }
let_it_be(:other_project) { create(:project_empty_repo) }
before do
reload_models(user_namespace)
end
it { expect(user_namespace.all_projects_except_soft_deleted.to_a).to match_array([project]) }
context 'with soft deleted projects' do
let_it_be(:delayed_deletion_project) { create(:project, namespace: user_namespace, marked_for_deletion_at: Date.current) }
it 'skips delayed deletion project' do
expect(user_namespace.all_projects_except_soft_deleted.to_a).to match_array([project])
end
end
end
end
describe '#all_projects' do
context 'with use_traversal_ids feature flag enabled' do
before do

View File

@ -68,7 +68,8 @@ RSpec.shared_context 'structured_logger' do
let(:deferred_payload) do
end_payload.merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: deferred: 0.0 sec',
'job_status' => 'deferred'
'job_status' => 'deferred',
'job_deferred_by' => :feature_flag
)
end

View File

@ -186,7 +186,7 @@ RSpec.shared_examples 'variable list' do
click_button('Add variable')
fill_variable('empty_mask_key', '???', protected: true, masked: true) do
expect(page).to have_content('This variable can not be masked')
expect(page).to have_content('This variable value does not meet the masking requirements.')
expect(find_button('Add variable', disabled: true)).to be_present
end
end

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
require 'rake_helper'
RSpec.describe 'gitlab:ci_secure_files', feature_category: :mobile_devops do
let_it_be(:local_file) { create(:ci_secure_file) }
let(:logger) { instance_double(Logger) }
let(:helper) { double }
before(:all) do
Rake.application.rake_require 'tasks/gitlab/ci_secure_files/migrate'
end
before do
allow(Logger).to receive(:new).with($stdout).and_return(logger)
end
describe 'gitlab:ci_secure_files:migrate' do
subject { run_rake_task('gitlab:ci_secure_files:migrate') }
it 'invokes the migration helper to move files to object storage' do
expect(Gitlab::Ci::SecureFiles::MigrationHelper).to receive(:migrate_to_remote_storage).and_yield(local_file)
expect(logger).to receive(:info).with('Starting transfer of Secure Files to object storage')
expect(logger).to receive(:info).with(/Transferred Secure File ID #{local_file.id}/)
subject
end
context 'when an error is raised while migrating' do
let(:error_message) { 'Something went wrong' }
before do
allow(Gitlab::Ci::SecureFiles::MigrationHelper).to receive(:migrate_to_remote_storage).and_raise(StandardError,
error_message)
end
it 'logs the error' do
expect(logger).to receive(:info).with('Starting transfer of Secure Files to object storage')
expect(logger).to receive(:error).with("Failed to migrate: #{error_message}")
subject
end
end
end
end

View File

@ -157,23 +157,5 @@ RSpec.describe WorkerAttributes, feature_category: :shared do
context 'when defer_on_database_health_signal is not set' do
it { is_expected.to be(false) }
end
context 'when FF `defer_sidekiq_workers_on_database_health_signal` is disabled' do
before do
stub_feature_flags(defer_sidekiq_workers_on_database_health_signal: false)
end
context 'when defer_on_database_health_signal is set' do
before do
worker.defer_on_database_health_signal(:gitlab_main, 1.minute, [:users])
end
it { is_expected.to be(false) }
end
context 'when defer_on_database_health_signal is not set' do
it { is_expected.to be(false) }
end
end
end
end

View File

@ -1122,10 +1122,10 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.2.0"
"@gitlab/svgs@3.50.0":
version "3.50.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.50.0.tgz#5ef7c0dabcd32ff32b5aaa3f576cf2bba7d5b466"
integrity sha512-Ssw+TXeAJd/LRKovx/P5RHi1ofPTdroFidej9vdyIVhcGZh7lZYt+qCBq4FfCXGefK86jisyVmNuStdpZxGHng==
"@gitlab/svgs@3.51.0":
version "3.51.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.51.0.tgz#c6d63c7e16b71b167662696662efc1b0587acaaf"
integrity sha512-IyMcZsLJ7AYoyHhWimHKsP/Swk0MlOLxHxgP9kp8Nc/xPC8p/JRVzlWU9vehSTcoYzu55alnKYABJe4fhUw2aQ==
"@gitlab/ui@64.2.3":
version "64.2.3"