Commit 411cc779 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 3e36f70b
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 12.7.2
- No changes.
## 12.7.1 ## 12.7.1
### Fixed (6 changes) ### Fixed (6 changes)
......
...@@ -20,6 +20,8 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; ...@@ -20,6 +20,8 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import _ from 'underscore'; import _ from 'underscore';
export const tableDataClass = 'table-col d-flex d-sm-table-cell';
export default { export default {
FIRST_PAGE: 1, FIRST_PAGE: 1,
PREV_PAGE: 1, PREV_PAGE: 1,
...@@ -29,37 +31,37 @@ export default { ...@@ -29,37 +31,37 @@ export default {
key: 'error', key: 'error',
label: __('Error'), label: __('Error'),
thClass: 'w-60p', thClass: 'w-60p',
tdClass: 'table-col d-flex d-sm-table-cell px-3', tdClass: `${tableDataClass} px-3`,
}, },
{ {
key: 'events', key: 'events',
label: __('Events'), label: __('Events'),
thClass: 'text-right', thClass: 'text-right',
tdClass: 'table-col d-flex d-sm-table-cell', tdClass: `${tableDataClass}`,
}, },
{ {
key: 'users', key: 'users',
label: __('Users'), label: __('Users'),
thClass: 'text-right', thClass: 'text-right',
tdClass: 'table-col d-flex d-sm-table-cell', tdClass: `${tableDataClass}`,
}, },
{ {
key: 'lastSeen', key: 'lastSeen',
label: __('Last seen'), label: __('Last seen'),
thClass: '', thClass: '',
tdClass: 'table-col d-flex d-sm-table-cell', tdClass: `${tableDataClass}`,
}, },
{ {
key: 'ignore', key: 'ignore',
label: '', label: '',
thClass: 'w-3rem', thClass: 'w-3rem',
tdClass: 'table-col d-flex pl-0 d-sm-table-cell', tdClass: `${tableDataClass} pl-0`,
}, },
{ {
key: 'resolved', key: 'resolved',
label: '', label: '',
thClass: 'w-3rem', thClass: 'w-3rem',
tdClass: 'table-col d-flex pl-0 d-sm-table-cell', tdClass: `${tableDataClass} pl-0`,
}, },
{ {
key: 'details', key: 'details',
......
...@@ -119,6 +119,7 @@ export default { ...@@ -119,6 +119,7 @@ export default {
:class="retryButtonClass" :class="retryButtonClass"
:href="job.retry_path" :href="job.retry_path"
data-method="post" data-method="post"
data-qa-selector="retry_button"
rel="nofollow" rel="nofollow"
>{{ __('Retry') }}</gl-link >{{ __('Retry') }}</gl-link
> >
......
# frozen_string_literal: true
class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper
include Gitlab::Utils::StrongMemoize
attr_reader :authentication_result, :redirected_path
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
delegate :type, to: :authentication_result, allow_nil: true, prefix: :auth_result
alias_method :user, :actor
alias_method :authenticated_user, :actor
# Git clients will not know what authenticity token to send along
skip_around_action :set_session_storage
skip_before_action :verify_authenticity_token
skip_before_action :repository
before_action :authenticate_user
private
def download_request?
raise NotImplementedError
end
def upload_request?
raise NotImplementedError
end
def authenticate_user
@authentication_result = Gitlab::Auth::Result.new
if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request)
if handle_basic_authentication(login, password)
return # Allow access
end
elsif allow_kerberos_spnego_auth? && spnego_provided?
kerberos_user = find_kerberos_user
if kerberos_user
@authentication_result = Gitlab::Auth::Result.new(
kerberos_user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities)
send_final_spnego_response
return # Allow access
end
elsif http_download_allowed?
@authentication_result = Gitlab::Auth::Result.new(nil, project, :none, [:download_code])
return # Allow access
end
send_challenges
render plain: "HTTP Basic: Access denied\n", status: :unauthorized
rescue Gitlab::Auth::MissingPersonalAccessTokenError
render_missing_personal_access_token
end
def basic_auth_provided?
has_basic_credentials?(request)
end
def send_challenges
challenges = []
challenges << 'Basic realm="GitLab"' if allow_basic_auth?
challenges << spnego_challenge if allow_kerberos_spnego_auth?
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
end
def project
parse_repo_path unless defined?(@project)
@project
end
def parse_repo_path
@project, @repo_type, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
end
def render_missing_personal_access_token
render plain: "HTTP Basic: Access denied\n" \
"You must use a personal access token with 'read_repository' or 'write_repository' scope for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}",
status: :unauthorized
end
def repository
strong_memoize(:repository) do
repo_type.repository_for(project)
end
end
def repo_type
parse_repo_path unless defined?(@repo_type)
@repo_type
end
def handle_basic_authentication(login, password)
@authentication_result = Gitlab::Auth.find_for_git_client(
login, password, project: project, ip: request.ip)
@authentication_result.success?
end
def ci?
authentication_result.ci?(project)
end
def http_download_allowed?
Gitlab::ProtocolAccess.allowed?('http') &&
download_request? &&
project && Guest.can?(:download_code, project)
end
end
Projects::GitHttpClientController.prepend_if_ee('EE::Projects::GitHttpClientController')
# frozen_string_literal: true
class Projects::GitHttpController < Projects::GitHttpClientController
include WorkhorseRequest
before_action :access_check
prepend_before_action :deny_head_requests, only: [:info_refs]
rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403_with_exception
rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404_with_exception
rescue_from Gitlab::GitAccess::ProjectCreationError, with: :render_422_with_exception
rescue_from Gitlab::GitAccess::TimeoutError, with: :render_503_with_exception
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
def info_refs
log_user_activity if upload_pack?
render_ok
end
# POST /foo/bar.git/git-upload-pack (git pull)
def git_upload_pack
enqueue_fetch_statistics_update
render_ok
end
# POST /foo/bar.git/git-receive-pack" (git push)
def git_receive_pack
render_ok
end
private
def deny_head_requests
head :forbidden if request.head?
end
def download_request?
upload_pack?
end
def upload_pack?
git_command == 'git-upload-pack'
end
def git_command
if action_name == 'info_refs'
params[:service]
else
action_name.dasherize
end
end
def render_ok
set_workhorse_internal_api_content_type
render json: Gitlab::Workhorse.git_http_ok(repository, repo_type, user, action_name)
end
def render_403_with_exception(exception)
render plain: exception.message, status: :forbidden
end
def render_404_with_exception(exception)
render plain: exception.message, status: :not_found
end
def render_422_with_exception(exception)
render plain: exception.message, status: :unprocessable_entity
end
def render_503_with_exception(exception)
render plain: exception.message, status: :service_unavailable
end
def enqueue_fetch_statistics_update
return if Gitlab::Database.read_only?
return if repo_type.wiki?
return unless project&.daily_statistics_enabled?
ProjectDailyStatisticsWorker.perform_async(project.id)
end
def access
@access ||= access_klass.new(access_actor, project, 'http',
authentication_abilities: authentication_abilities,
namespace_path: params[:namespace_id],
project_path: project_path,
redirected_path: redirected_path,
auth_result_type: auth_result_type)
end
def access_actor
return user if user
return :ci if ci?
end
def access_check
access.check(git_command, Gitlab::GitAccess::ANY)
@project ||= access.project
end
def access_klass
@access_klass ||= repo_type.access_checker_class
end
def project_path
@project_path ||= params[:project_id].sub(/\.git$/, '')
end
def log_user_activity
Users::ActivityService.new(user).execute
end
end
Projects::GitHttpController.prepend_if_ee('EE::Projects::GitHttpController')
# frozen_string_literal: true
class Projects::LfsApiController < Projects::GitHttpClientController
include LfsRequest
include Gitlab::Utils::StrongMemoize
LFS_TRANSFER_CONTENT_TYPE = 'application/octet-stream'
skip_before_action :lfs_check_access!, only: [:deprecated]
before_action :lfs_check_batch_operation!, only: [:batch]
def batch
unless objects.present?
render_lfs_not_found
return
end
if download_request?
render json: { objects: download_objects! }
elsif upload_request?
render json: { objects: upload_objects! }
else
raise "Never reached"
end
end
def deprecated
render(
json: {
message: _('Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.'),
documentation_url: "#{Gitlab.config.gitlab.url}/help"
},
status: :not_implemented
)
end
private
def download_request?
params[:operation] == 'download'
end
def upload_request?
params[:operation] == 'upload'
end
# rubocop: disable CodeReuse/ActiveRecord
def existing_oids
@existing_oids ||= begin
project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
end
end
# rubocop: enable CodeReuse/ActiveRecord
def download_objects!
objects.each do |object|
if existing_oids.include?(object[:oid])
object[:actions] = download_actions(object)
if Guest.can?(:download_code, project)
object[:authenticated] = true
end
else
object[:error] = {
code: 404,
message: _("Object does not exist on the server or you don't have permissions to access it")
}
end
end
objects
end
def upload_objects!
objects.each do |object|
object[:actions] = upload_actions(object) unless existing_oids.include?(object[:oid])
end
objects
end
def download_actions(object)
{
download: {
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}",
header: {
Authorization: authorization_header
}.compact
}
}
end
def upload_actions(object)
{
upload: {
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}/#{object[:size]}",
header: {
Authorization: authorization_header,
# git-lfs v2.5.0 sets the Content-Type based on the uploaded file. This
# ensures that Workhorse can intercept the request.
'Content-Type': LFS_TRANSFER_CONTENT_TYPE
}.compact
}
}
end
def lfs_check_batch_operation!
if batch_operation_disallowed?
render(
json: {
message: lfs_read_only_message
},
content_type: LfsRequest::CONTENT_TYPE,
status: :forbidden
)
end
end
# Overridden in EE
def batch_operation_disallowed?
upload_request? && Gitlab::Database.read_only?
end
# Overridden in EE
def lfs_read_only_message
_('You cannot write to this read-only GitLab instance.')
end
def authorization_header
strong_memoize(:authorization_header) do
lfs_auth_header || request.headers['Authorization']
end
end
def lfs_auth_header
return unless user.is_a?(User)
Gitlab::LfsToken.new(user).basic_encoding
end
end
Projects::LfsApiController.prepend_if_ee('EE::Projects::LfsApiController')
# frozen_string_literal: true
class Projects::LfsLocksApiController < Projects::GitHttpClientController
include LfsRequest
def create
@result = Lfs::LockFileService.new(project, user, lfs_params).execute
render_json(@result[:lock])
end
def unlock
@result = Lfs::UnlockFileService.new(project, user, lfs_params).execute
render_json(@result[:lock])
end
def index
@result = Lfs::LocksFinderService.new(project, user, lfs_params).execute
render_json(@result[:locks])
end
def verify
@result = Lfs::LocksFinderService.new(project, user, {}).execute
ours, theirs = split_by_owner(@result[:locks])
render_json({ ours: ours, theirs: theirs }, false)
end
private
def render_json(data, process = true)
render json: build_payload(data, process),
content_type: LfsRequest::CONTENT_TYPE,
status: @result[:http_status]
end
def build_payload(data, process)
data = LfsFileLockSerializer.new.represent(data) if process
return data if @result[:status] == :success
# When the locking failed due to an existent Lock, the existent record
# is returned in `@result[:lock]`
error_payload(@result[:message], @result[:lock] ? data : {})
end
def error_payload(message, custom_attrs = {})
custom_attrs.merge({
message: message,
documentation_url: help_url
})
end
def split_by_owner(locks)
groups = locks.partition { |lock| lock.user_id == user.id }
groups.map! do |records|
LfsFileLockSerializer.new.represent(records, root: false)
end
end
def download_request?
params[:action] == 'index'
end
def upload_request?
%w(create unlock verify).include?(params[:action])
end
def lfs_params
params.permit(:id, :path, :force)
end
end
# frozen_string_literal: true
class Projects::LfsStorageController < Projects::GitHttpClientController
include LfsRequest
include WorkhorseRequest
include SendFileUpload
skip_before_action :verify_workhorse_api!, only: :download
def download
lfs_object = LfsObject.find_by_oid(oid)
unless lfs_object && lfs_object.file.exists?
render_lfs_not_found
return
end
send_upload(lfs_object.file, send_params: { content_type: "application/octet-stream" })
end
def upload_authorize
set_workhorse_internal_api_content_type
authorized = LfsObjectUploader.workhorse_authorize(has_length: true)
authorized.merge!(LfsOid: oid, LfsSize: size)
render json: authorized
end
def upload_finalize
if store_file!(oid, size)
head 200
else
render plain: 'Unprocessable entity', status: :unprocessable_entity
end
rescue ActiveRecord::RecordInvalid
render_lfs_forbidden
rescue UploadedFile::InvalidPathError
render_lfs_forbidden
rescue ObjectStorage::RemoteStoreError
render_lfs_forbidden
end
private
def download_request?
action_name == 'download'
end
def upload_request?
%w[upload_authorize upload_finalize].include? action_name
end
def oid
params[:oid].to_s
end
def size
params[:size].to_i
end
# rubocop: disable CodeReuse/ActiveRecord
def store_file!(oid, size)
object = LfsObject.find_by(oid: oid, size: size)
unless object&.file&.exists?
object = create_file!(oid, size)
end
return unless object
link_to_project!(object)
end
# rubocop: enable CodeReuse/ActiveRecord
def create_file!(oid, size)
uploaded_file = UploadedFile.from_params(
params, :file, LfsObjectUploader.workhorse_local_upload_path)
return unless uploaded_file
LfsObject.create!(oid: oid, size: size, file: uploaded_file)
end
# rubocop: disable CodeReuse/ActiveRecord
def link_to_project!(object)
if object && !object.projects.exists?(storage_project.id)
object.lfs_objects_projects.create!(project: storage_project)
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
...@@ -387,6 +387,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -387,6 +387,7 @@ class ProjectsController < Projects::ApplicationController
:merge_method, :merge_method,
:initialize_with_readme, :initialize_with_readme,
:autoclose_referenced_issues, :autoclose_referenced_issues,
:suggestion_commit_message,
project_feature_attributes: %i[ project_feature_attributes: %i[
builds_access_level builds_access_level
......
# frozen_string_literal: true
module Repositories
class ApplicationController < ::ApplicationController
skip_before_action :authenticate_user!
end
end
# frozen_string_literal: true
module Repositories
class GitHttpClientController < Repositories::ApplicationController
include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper
include Gitlab::Utils::StrongMemoize
attr_reader :authentication_result, :redirected_path
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
delegate :type, to: :authentication_result, allow_nil: true, prefix: :auth_result
alias_method :user, :actor
alias_method :authenticated_user, :actor
# Git clients will not know what authenticity token to send along
skip_around_action :set_session_storage
skip_before_action :verify_authenticity_token
before_action :parse_repo_path
before_action :authenticate_user
private
def download_request?
raise NotImplementedError
end
def upload_request?
raise NotImplementedError
end
def authenticate_user
@authentication_result = Gitlab::Auth::Result.new
if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request)
if handle_basic_authentication(login, password)
return # Allow access
end
elsif allow_kerberos_spnego_auth? && spnego_provided?
kerberos_user = find_kerberos_user
if kerberos_user
@authentication_result = Gitlab::Auth::Result.new(
kerberos_user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities)
send_final_spnego_response
return # Allow access
end
elsif http_download_allowed?
@authentication_result = Gitlab::Auth::Result.new(nil, project, :none, [:download_code])
return # Allow access
end
send_challenges
render plain: "HTTP Basic: Access denied\n", status: :unauthorized
rescue Gitlab::Auth::MissingPersonalAccessTokenError
render_missing_personal_access_token
end
def basic_auth_provided?
has_basic_credentials?(request)
end
def send_challenges
challenges = []
challenges << 'Basic realm="GitLab"' if allow_basic_auth?
challenges << spnego_challenge if allow_kerberos_spnego_auth?
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
end
def project
parse_repo_path unless defined?(@project)
@project
end
def parse_repo_path
@project, @repo_type, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:repository_id]}")
end
def render_missing_personal_access_token
render plain: "HTTP Basic: Access denied\n" \
"You must use a personal access token with 'read_repository' or 'write_repository' scope for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}",
status: :unauthorized
end
def repository
strong_memoize(:repository) do
repo_type.repository_for(project)
end
end
def repo_type
parse_repo_path unless defined?(@repo_type)
@repo_type
end
def handle_basic_authentication(login, password)
@authentication_result = Gitlab::Auth.find_for_git_client(
login, password, project: project, ip: request.ip)
@authentication_result.success?
end
def ci?
authentication_result.ci?(project)
end
def http_download_allowed?
Gitlab::ProtocolAccess.allowed?('http') &&
download_request? &&
project && Guest.can?(:download_code, project)
end
end
end
Repositories::GitHttpClientController.prepend_if_ee('EE::Repositories::GitHttpClientController')
# frozen_string_literal: true
module Repositories
class GitHttpController < Repositories::GitHttpClientController
include WorkhorseRequest
before_action :access_check
prepend_before_action :deny_head_requests, only: [:info_refs]
rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403_with_exception
rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404_with_exception
rescue_from Gitlab::GitAccess::ProjectCreationError, with: :render_422_with_exception
rescue_from Gitlab::GitAccess::TimeoutError, with: :render_503_with_exception
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
def info_refs
log_user_activity if upload_pack?
render_ok
end
# POST /foo/bar.git/git-upload-pack (git pull)
def git_upload_pack
enqueue_fetch_statistics_update
render_ok
end
# POST /foo/bar.git/git-receive-pack" (git push)
def git_receive_pack
render_ok
end
private
def deny_head_requests
head :forbidden if request.head?
end
def download_request?
upload_pack?
end
def upload_pack?
git_command == 'git-upload-pack'
end
def git_command
if action_name == 'info_refs'
params[:service]
else
action_name.dasherize
end
end
def render_ok
set_workhorse_internal_api_content_type
render json: Gitlab::Workhorse.git_http_ok(repository, repo_type, user, action_name)
end
def render_403_with_exception(exception)
render plain: exception.message, status: :forbidden
end
def render_404_with_exception(exception)
render plain: exception.message, status: :not_found
end
def render_422_with_exception(exception)
render plain: exception.message, status: :unprocessable_entity
end
def render_503_with_exception(exception)
render plain: exception.message, status: :service_unavailable
end
def enqueue_fetch_statistics_update
return if Gitlab::Database.read_only?
return unless repo_type.project?
return unless project&.daily_statistics_enabled?
ProjectDailyStatisticsWorker.perform_async(project.id)
end
def access
@access ||= access_klass.new(access_actor, project, 'http',
authentication_abilities: authentication_abilities,
namespace_path: params[:namespace_id],
project_path: project_path,
redirected_path: redirected_path,
auth_result_type: auth_result_type)
end
def access_actor
return user if user
return :ci if ci?
end
def access_check
access.check(git_command, Gitlab::GitAccess::ANY)
@project ||= access.project
end
def access_klass
@access_klass ||= repo_type.access_checker_class
end
def project_path
@project_path ||= params[:repository_id].sub(/\.git$/, '')
end
def log_user_activity
Users::ActivityService.new(user).execute
end
end
end
Repositories::GitHttpController.prepend_if_ee('EE::Repositories::GitHttpController')
# frozen_string_literal: true
module Repositories
class LfsApiController < Repositories::GitHttpClientController
include LfsRequest
include Gitlab::Utils::StrongMemoize
LFS_TRANSFER_CONTENT_TYPE = 'application/octet-stream'
skip_before_action :lfs_check_access!, only: [:deprecated]
before_action :lfs_check_batch_operation!, only: [:batch]
def batch
unless objects.present?
render_lfs_not_found
return
end
if download_request?
render json: { objects: download_objects! }
elsif upload_request?
render json: { objects: upload_objects! }
else
raise "Never reached"
end
end
def deprecated
render(
json: {
message: _('Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.'),
documentation_url: "#{Gitlab.config.gitlab.url}/help"
},
status: :not_implemented
)
end
private
def download_request?
params[:operation] == 'download'
end
def upload_request?
params[:operation] == 'upload'
end
# rubocop: disable CodeReuse/ActiveRecord
def existing_oids
@existing_oids ||= begin
project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
end
end
# rubocop: enable CodeReuse/ActiveRecord
def download_objects!
objects.each do |object|
if existing_oids.include?(object[:oid])
object[:actions] = download_actions(object)
if Guest.can?(:download_code, project)
object[:authenticated] = true
end
else
object[:error] = {
code: 404,
message: _("Object does not exist on the server or you don't have permissions to access it")
}
end
end
objects
end
def upload_objects!
objects.each do |object|
object[:actions] = upload_actions(object) unless existing_oids.include?(object[:oid])
end
objects
end
def download_actions(object)
{
download: {
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}",
header: {
Authorization: authorization_header
}.compact
}
}
end
def upload_actions(object)
{
upload: {
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}/#{object[:size]}",
header: {
Authorization: authorization_header,
# git-lfs v2.5.0 sets the Content-Type based on the uploaded file. This
# ensures that Workhorse can intercept the request.
'Content-Type': LFS_TRANSFER_CONTENT_TYPE
}.compact
}
}
end
def lfs_check_batch_operation!
if batch_operation_disallowed?
render(
json: {
message: lfs_read_only_message
},
content_type: LfsRequest::CONTENT_TYPE,
status: :forbidden
)
end
end
# Overridden in EE
def batch_operation_disallowed?
upload_request? && Gitlab::Database.read_only?
end
# Overridden in EE
def lfs_read_only_message
_('You cannot write to this read-only GitLab instance.')
end
def authorization_header
strong_memoize(:authorization_header) do
lfs_auth_header || request.headers['Authorization']
end
end
def lfs_auth_header
return unless user.is_a?(User)
Gitlab::LfsToken.new(user).basic_encoding
end
end
end
Repositories::LfsApiController.prepend_if_ee('EE::Repositories::LfsApiController')
# frozen_string_literal: true
module Repositories
class LfsLocksApiController < Repositories::GitHttpClientController
include LfsRequest
def create
@result = Lfs::LockFileService.new(project, user, lfs_params).execute
render_json(@result[:lock])
end
def unlock
@result = Lfs::UnlockFileService.new(project, user, lfs_params).execute
render_json(@result[:lock])
end
def index
@result = Lfs::LocksFinderService.new(project, user, lfs_params).execute
render_json(@result[:locks])
end
def verify
@result = Lfs::LocksFinderService.new(project, user, {}).execute
ours, theirs = split_by_owner(@result[:locks])
render_json({ ours: ours, theirs: theirs }, false)
end
private
def render_json(data, process = true)
render json: build_payload(data, process),
content_type: LfsRequest::CONTENT_TYPE,
status: @result[:http_status]
end
def build_payload(data, process)
data = LfsFileLockSerializer.new.represent(data) if process
return data if @result[:status] == :success
# When the locking failed due to an existent Lock, the existent record
# is returned in `@result[:lock]`
error_payload(@result[:message], @result[:lock] ? data : {})
end
def error_payload(message, custom_attrs = {})
custom_attrs.merge({
message: message,
documentation_url: help_url
})
end
def split_by_owner(locks)
groups = locks.partition { |lock| lock.user_id == user.id }
groups.map! do |records|
LfsFileLockSerializer.new.represent(records, root: false)
end
end
def download_request?
params[:action] == 'index'
end
def upload_request?
%w(create unlock verify).include?(params[:action])
end
def lfs_params
params.permit(:id, :path, :force)
end
end
end
# frozen_string_literal: true
module Repositories
class LfsStorageController < Repositories::GitHttpClientController
include LfsRequest
include WorkhorseRequest
include SendFileUpload
skip_before_action :verify_workhorse_api!, only: :download
def download
lfs_object = LfsObject.find_by_oid(oid)
unless lfs_object && lfs_object.file.exists?
render_lfs_not_found
return
end
send_upload(lfs_object.file, send_params: { content_type: "application/octet-stream" })
end
def upload_authorize
set_workhorse_internal_api_content_type
authorized = LfsObjectUploader.workhorse_authorize(has_length: true)
authorized.merge!(LfsOid: oid, LfsSize: size)
render json: authorized
end
def upload_finalize
if store_file!(oid, size)
head 200
else
render plain: 'Unprocessable entity', status: :unprocessable_entity
end
rescue ActiveRecord::RecordInvalid
render_lfs_forbidden
rescue UploadedFile::InvalidPathError
render_lfs_forbidden
rescue ObjectStorage::RemoteStoreError
render_lfs_forbidden
end
private
def download_request?
action_name == 'download'
end
def upload_request?
%w[upload_authorize upload_finalize].include? action_name
end
def oid
params[:oid].to_s
end
def size
params[:size].to_i
end
# rubocop: disable CodeReuse/ActiveRecord
def store_file!(oid, size)
object = LfsObject.find_by(oid: oid, size: size)
unless object&.file&.exists?
object = create_file!(oid, size)
end
return unless object
link_to_project!(object)
end
# rubocop: enable CodeReuse/ActiveRecord
def create_file!(oid, size)
uploaded_file = UploadedFile.from_params(
params, :file, LfsObjectUploader.workhorse_local_upload_path)
return unless uploaded_file
LfsObject.create!(oid: oid, size: size, file: uploaded_file)
end
# rubocop: disable CodeReuse/ActiveRecord
def link_to_project!(object)
if object && !object.projects.exists?(storage_project.id)
object.lfs_objects_projects.create!(project: storage_project)
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
...@@ -11,77 +11,87 @@ module Types ...@@ -11,77 +11,87 @@ module Types
field :id, GraphQL::ID_TYPE, field :id, GraphQL::ID_TYPE,
null: false, null: false,
description: "ID (global ID) of the error" description: 'ID (global ID) of the error'
field :sentry_id, GraphQL::STRING_TYPE, field :sentry_id, GraphQL::STRING_TYPE,
method: :id, method: :id,
null: false, null: false,
description: "ID (Sentry ID) of the error" description: 'ID (Sentry ID) of the error'
field :title, GraphQL::STRING_TYPE, field :title, GraphQL::STRING_TYPE,
null: false, null: false,
description: "Title of the error" description: 'Title of the error'
field :type, GraphQL::STRING_TYPE, field :type, GraphQL::STRING_TYPE,
null: false, null: false,
description: "Type of the error" description: 'Type of the error'
field :user_count, GraphQL::INT_TYPE, field :user_count, GraphQL::INT_TYPE,
null: false, null: false,
description: "Count of users affected by the error" description: 'Count of users affected by the error'
field :count, GraphQL::INT_TYPE, field :count, GraphQL::INT_TYPE,
null: false, null: false,
description: "Count of occurrences" description: 'Count of occurrences'
field :first_seen, Types::TimeType, field :first_seen, Types::TimeType,
null: false, null: false,
description: "Timestamp when the error was first seen" description: 'Timestamp when the error was first seen'
field :last_seen, Types::TimeType, field :last_seen, Types::TimeType,
null: false, null: false,
description: "Timestamp when the error was last seen" description: 'Timestamp when the error was last seen'
field :message, GraphQL::STRING_TYPE, field :message, GraphQL::STRING_TYPE,
null: true, null: true,
description: "Sentry metadata message of the error" description: 'Sentry metadata message of the error'
field :culprit, GraphQL::STRING_TYPE, field :culprit, GraphQL::STRING_TYPE,
null: false, null: false,
description: "Culprit of the error" description: 'Culprit of the error'
field :external_base_url, GraphQL::STRING_TYPE,
null: false,
description: 'External Base URL of the Sentry Instance'
field :external_url, GraphQL::STRING_TYPE, field :external_url, GraphQL::STRING_TYPE,
null: false, null: false,
description: "External URL of the error" description: 'External URL of the error'
field :sentry_project_id, GraphQL::ID_TYPE, field :sentry_project_id, GraphQL::ID_TYPE,
method: :project_id, method: :project_id,
null: false, null: false,
description: "ID of the project (Sentry project)" description: 'ID of the project (Sentry project)'
field :sentry_project_name, GraphQL::STRING_TYPE, field :sentry_project_name, GraphQL::STRING_TYPE,
method: :project_name, method: :project_name,
null: false, null: false,
description: "Name of the project affected by the error" description: 'Name of the project affected by the error'
field :sentry_project_slug, GraphQL::STRING_TYPE, field :sentry_project_slug, GraphQL::STRING_TYPE,
method: :project_slug, method: :project_slug,
null: false, null: false,
description: "Slug of the project affected by the error" description: 'Slug of the project affected by the error'
field :short_id, GraphQL::STRING_TYPE, field :short_id, GraphQL::STRING_TYPE,
null: false, null: false,
description: "Short ID (Sentry ID) of the error" description: 'Short ID (Sentry ID) of the error'
field :status, Types::ErrorTracking::SentryErrorStatusEnum, field :status, Types::ErrorTracking::SentryErrorStatusEnum,
null: false, null: false,
description: "Status of the error" description: 'Status of the error'
field :frequency, [Types::ErrorTracking::SentryErrorFrequencyType], field :frequency, [Types::ErrorTracking::SentryErrorFrequencyType],
null: false, null: false,
description: "Last 24hr stats of the error" description: 'Last 24hr stats of the error'
field :first_release_last_commit, GraphQL::STRING_TYPE, field :first_release_last_commit, GraphQL::STRING_TYPE,
null: true, null: true,
description: "Commit the error was first seen" description: 'Commit the error was first seen'
field :last_release_last_commit, GraphQL::STRING_TYPE, field :last_release_last_commit, GraphQL::STRING_TYPE,
null: true, null: true,
description: "Commit the error was last seen" description: 'Commit the error was last seen'
field :first_release_short_version, GraphQL::STRING_TYPE, field :first_release_short_version, GraphQL::STRING_TYPE,
null: true, null: true,
description: "Release version the error was first seen" description: 'Release version the error was first seen'
field :last_release_short_version, GraphQL::STRING_TYPE, field :last_release_short_version, GraphQL::STRING_TYPE,
null: true, null: true,
description: "Release version the error was last seen" description: 'Release version the error was last seen'
field :gitlab_commit, GraphQL::STRING_TYPE, field :gitlab_commit, GraphQL::STRING_TYPE,
null: true, null: true,
description: "GitLab commit SHA attributed to the Error based on the release version" description: 'GitLab commit SHA attributed to the Error based on the release version'
field :gitlab_commit_path, GraphQL::STRING_TYPE, field :gitlab_commit_path, GraphQL::STRING_TYPE,
null: true, null: true,
description: "Path to the GitLab page for the GitLab commit attributed to the error" description: 'Path to the GitLab page for the GitLab commit attributed to the error'
field :gitlab_issue_path, GraphQL::STRING_TYPE,
method: :gitlab_issue,
null: true,
description: 'URL of GitLab Issue'
field :tags, Types::ErrorTracking::SentryErrorTagsType,
null: false,
description: 'Tags associated with the Sentry Error'
def first_seen def first_seen
DateTime.parse(object.first_seen) DateTime.parse(object.first_seen)
......
# frozen_string_literal: true
module Types
module ErrorTracking
# rubocop: disable Graphql/AuthorizeTypes
class SentryErrorTagsType < ::Types::BaseObject
graphql_name 'SentryErrorTags'
description 'State of a Sentry error'
field :level, GraphQL::STRING_TYPE,
null: true,
description: "Severity level of the Sentry Error"
field :logger, GraphQL::STRING_TYPE,
null: true,
description: "Logger of the Sentry Error"
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
...@@ -47,7 +47,7 @@ module BlobHelper ...@@ -47,7 +47,7 @@ module BlobHelper
def edit_blob_button(project = @project, ref = @ref, path = @path, options = {}) def edit_blob_button(project = @project, ref = @ref, path = @path, options = {})
return unless blob = readable_blob(options, path, project, ref) return unless blob = readable_blob(options, path, project, ref)
common_classes = "btn btn-primary js-edit-blob #{options[:extra_class]}" common_classes = "btn btn-primary js-edit-blob ml-2 #{options[:extra_class]}"
edit_button_tag(blob, edit_button_tag(blob,
common_classes, common_classes,
...@@ -62,7 +62,7 @@ module BlobHelper ...@@ -62,7 +62,7 @@ module BlobHelper
return unless blob = readable_blob(options, path, project, ref) return unless blob = readable_blob(options, path, project, ref)
edit_button_tag(blob, edit_button_tag(blob,
'btn btn-inverted btn-primary ide-edit-button', 'btn btn-inverted btn-primary ide-edit-button ml-2',
_('Web IDE'), _('Web IDE'),
ide_edit_path(project, ref, path, options), ide_edit_path(project, ref, path, options),
project, project,
......
...@@ -4,6 +4,7 @@ module Clusters ...@@ -4,6 +4,7 @@ module Clusters
module Applications module Applications
class Ingress < ApplicationRecord class Ingress < ApplicationRecord
VERSION = '1.22.1' VERSION = '1.22.1'
MODSECURITY_LOG_CONTAINER_NAME = 'modsecurity-log'
self.table_name = 'clusters_applications_ingress' self.table_name = 'clusters_applications_ingress'
...@@ -85,7 +86,7 @@ module Clusters ...@@ -85,7 +86,7 @@ module Clusters
}, },
"extraContainers" => [ "extraContainers" => [
{ {
"name" => "modsecurity-log", "name" => MODSECURITY_LOG_CONTAINER_NAME,
"image" => "busybox", "image" => "busybox",
"args" => [ "args" => [
"/bin/sh", "/bin/sh",
......
...@@ -1938,6 +1938,8 @@ class Project < ApplicationRecord ...@@ -1938,6 +1938,8 @@ class Project < ApplicationRecord
.append(key: 'GITLAB_CI', value: 'true') .append(key: 'GITLAB_CI', value: 'true')
.append(key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url) .append(key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url)
.append(key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host) .append(key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host)
.append(key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s)
.append(key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol)
.append(key: 'CI_SERVER_NAME', value: 'GitLab') .append(key: 'CI_SERVER_NAME', value: 'GitLab')
.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION) .append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s) .append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s)
......
...@@ -2,18 +2,16 @@ ...@@ -2,18 +2,16 @@
.js-file-title.file-title-flex-parent .js-file-title.file-title-flex-parent
= render 'projects/blob/header_content', blob: blob = render 'projects/blob/header_content', blob: blob
.file-actions .file-actions<
= render 'projects/blob/viewer_switcher', blob: blob unless blame = render 'projects/blob/viewer_switcher', blob: blob unless blame
= edit_blob_button
.btn-group{ role: "group" }< = ide_edit_button
= edit_blob_button .btn-group.ml-2{ role: "group" }>
= ide_edit_button
.btn-group{ role: "group" }<
= render_if_exists 'projects/blob/header_file_locks_link' = render_if_exists 'projects/blob/header_file_locks_link'
- if current_user - if current_user
= replace_blob_link = replace_blob_link
= delete_blob_link = delete_blob_link
.btn-group{ role: "group" }< .btn-group.ml-2{ role: "group" }
= copy_blob_source_button(blob) unless blame = copy_blob_source_button(blob) unless blame
= open_raw_blob_button(blob) = open_raw_blob_button(blob)
= download_blob_button(blob) = download_blob_button(blob)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- simple_viewer = blob.simple_viewer - simple_viewer = blob.simple_viewer
- rich_viewer = blob.rich_viewer - rich_viewer = blob.rich_viewer
.btn-group.js-blob-viewer-switcher{ role: "group" } .btn-group.js-blob-viewer-switcher.ml-2{ role: "group" }>
- simple_label = "Display #{simple_viewer.switcher_title}" - simple_label = "Display #{simple_viewer.switcher_title}"
%button.btn.btn-default.btn-sm.js-blob-viewer-switch-btn.has-tooltip{ 'aria-label' => simple_label, title: simple_label, data: { viewer: 'simple', container: 'body' } }> %button.btn.btn-default.btn-sm.js-blob-viewer-switch-btn.has-tooltip{ 'aria-label' => simple_label, title: simple_label, data: { viewer: 'simple', container: 'body' } }>
= icon(simple_viewer.switcher_icon) = icon(simple_viewer.switcher_icon)
......
...@@ -22,6 +22,8 @@ ...@@ -22,6 +22,8 @@
- if user == current_user - if user == current_user
%span.badge.badge-success.prepend-left-5= _("It's you") %span.badge.badge-success.prepend-left-5= _("It's you")
= render_if_exists 'shared/members/ee/license_badge', user: user, group: @group
- if user.blocked? - if user.blocked?
%label.badge.badge-danger %label.badge.badge-danger
%strong= _("Blocked") %strong= _("Blocked")
......
...@@ -6,6 +6,7 @@ class AuthorizedProjectsWorker ...@@ -6,6 +6,7 @@ class AuthorizedProjectsWorker
feature_category :authentication_and_authorization feature_category :authentication_and_authorization
latency_sensitive_worker! latency_sensitive_worker!
weight 2
# This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore the # This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore the
# visibility of prepended modules. See https://github.com/rspec/rspec-mocks/issues/1231 # visibility of prepended modules. See https://github.com/rspec/rspec-mocks/issues/1231
......
...@@ -8,6 +8,8 @@ class ChatNotificationWorker ...@@ -8,6 +8,8 @@ class ChatNotificationWorker
sidekiq_options retry: false sidekiq_options retry: false
feature_category :chatops feature_category :chatops
latency_sensitive_worker! latency_sensitive_worker!
weight 2
# TODO: break this into multiple jobs # TODO: break this into multiple jobs
# as the `responder` uses external dependencies # as the `responder` uses external dependencies
# See https://gitlab.com/gitlab-com/gl-infra/scalability/issues/34 # See https://gitlab.com/gitlab-com/gl-infra/scalability/issues/34
......
...@@ -9,6 +9,7 @@ module SelfMonitoringProjectWorker ...@@ -9,6 +9,7 @@ module SelfMonitoringProjectWorker
# Other Functionality. Metrics seems to be the closest feature_category for # Other Functionality. Metrics seems to be the closest feature_category for
# this worker. # this worker.
feature_category :metrics feature_category :metrics
weight 2
end end
LEASE_TIMEOUT = 15.minutes.to_i LEASE_TIMEOUT = 15.minutes.to_i
......
...@@ -7,6 +7,24 @@ module WorkerAttributes ...@@ -7,6 +7,24 @@ module WorkerAttributes
# `worker_resource_boundary` attribute # `worker_resource_boundary` attribute
VALID_RESOURCE_BOUNDARIES = [:memory, :cpu, :unknown].freeze VALID_RESOURCE_BOUNDARIES = [:memory, :cpu, :unknown].freeze
NAMESPACE_WEIGHTS = {
auto_devops: 2,
auto_merge: 3,
chaos: 2,
deployment: 3,
mail_scheduler: 2,
notifications: 2,
pipeline_cache: 3,
pipeline_creation: 4,
pipeline_default: 3,
pipeline_hooks: 2,
pipeline_processing: 5,
# EE-specific
epics: 2,
incident_management: 2
}.stringify_keys.freeze
class_methods do class_methods do
def feature_category(value) def feature_category(value)
raise "Invalid category. Use `feature_category_not_owned!` to mark a worker as not owned" if value == :not_owned raise "Invalid category. Use `feature_category_not_owned!` to mark a worker as not owned" if value == :not_owned
...@@ -70,6 +88,16 @@ module WorkerAttributes ...@@ -70,6 +88,16 @@ module WorkerAttributes
worker_attributes[:resource_boundary] || :unknown worker_attributes[:resource_boundary] || :unknown
end end
def weight(value)
worker_attributes[:weight] = value
end
def get_weight
worker_attributes[:weight] ||
NAMESPACE_WEIGHTS[queue_namespace] ||
1
end
protected protected
# Returns a worker attribute declared on this class or its parent class. # Returns a worker attribute declared on this class or its parent class.
......
...@@ -4,6 +4,7 @@ class CreateEvidenceWorker ...@@ -4,6 +4,7 @@ class CreateEvidenceWorker
include ApplicationWorker include ApplicationWorker
feature_category :release_governance feature_category :release_governance
weight 2
def perform(release_id) def perform(release_id)
release = Release.find_by_id(release_id) release = Release.find_by_id(release_id)
......
...@@ -4,6 +4,7 @@ class CreateGpgSignatureWorker ...@@ -4,6 +4,7 @@ class CreateGpgSignatureWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management feature_category :source_code_management
weight 2
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(commit_shas, project_id) def perform(commit_shas, project_id)
......
...@@ -5,6 +5,7 @@ class EmailReceiverWorker ...@@ -5,6 +5,7 @@ class EmailReceiverWorker
feature_category :issue_tracking feature_category :issue_tracking
latency_sensitive_worker! latency_sensitive_worker!
weight 2
def perform(raw) def perform(raw)
return unless Gitlab::IncomingEmail.enabled? return unless Gitlab::IncomingEmail.enabled?
......
...@@ -8,6 +8,7 @@ class EmailsOnPushWorker ...@@ -8,6 +8,7 @@ class EmailsOnPushWorker
feature_category :source_code_management feature_category :source_code_management
latency_sensitive_worker! latency_sensitive_worker!
worker_resource_boundary :cpu worker_resource_boundary :cpu
weight 2
def perform(project_id, recipients, push_data, options = {}) def perform(project_id, recipients, push_data, options = {})
options.symbolize_keys! options.symbolize_keys!
......
...@@ -6,6 +6,7 @@ class GitlabShellWorker ...@@ -6,6 +6,7 @@ class GitlabShellWorker
feature_category :source_code_management feature_category :source_code_management
latency_sensitive_worker! latency_sensitive_worker!
weight 2
def perform(action, *arg) def perform(action, *arg)
Gitlab::GitalyClient::NamespaceService.allow do Gitlab::GitalyClient::NamespaceService.allow do
......
...@@ -5,6 +5,7 @@ class ImportIssuesCsvWorker ...@@ -5,6 +5,7 @@ class ImportIssuesCsvWorker
feature_category :issue_tracking feature_category :issue_tracking
worker_resource_boundary :cpu worker_resource_boundary :cpu
weight 2
sidekiq_retries_exhausted do |job| sidekiq_retries_exhausted do |job|
Upload.find(job['args'][2]).destroy Upload.find(job['args'][2]).destroy
......
...@@ -4,6 +4,7 @@ class InvalidGpgSignatureUpdateWorker ...@@ -4,6 +4,7 @@ class InvalidGpgSignatureUpdateWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management feature_category :source_code_management
weight 2
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(gpg_key_id) def perform(gpg_key_id)
......
...@@ -5,6 +5,7 @@ class MergeWorker ...@@ -5,6 +5,7 @@ class MergeWorker
feature_category :source_code_management feature_category :source_code_management
latency_sensitive_worker! latency_sensitive_worker!
weight 5
def perform(merge_request_id, current_user_id, params) def perform(merge_request_id, current_user_id, params)
params = params.with_indifferent_access params = params.with_indifferent_access
......
...@@ -7,6 +7,7 @@ class NewIssueWorker ...@@ -7,6 +7,7 @@ class NewIssueWorker
feature_category :issue_tracking feature_category :issue_tracking
latency_sensitive_worker! latency_sensitive_worker!
worker_resource_boundary :cpu worker_resource_boundary :cpu
weight 2
def perform(issue_id, user_id) def perform(issue_id, user_id)
return unless objects_found?(issue_id, user_id) return unless objects_found?(issue_id, user_id)
......
...@@ -7,6 +7,7 @@ class NewMergeRequestWorker ...@@ -7,6 +7,7 @@ class NewMergeRequestWorker
feature_category :source_code_management feature_category :source_code_management
latency_sensitive_worker! latency_sensitive_worker!
worker_resource_boundary :cpu worker_resource_boundary :cpu
weight 2
def perform(merge_request_id, user_id) def perform(merge_request_id, user_id)
return unless objects_found?(merge_request_id, user_id) return unless objects_found?(merge_request_id, user_id)
......
...@@ -6,6 +6,7 @@ class NewNoteWorker ...@@ -6,6 +6,7 @@ class NewNoteWorker
feature_category :issue_tracking feature_category :issue_tracking
latency_sensitive_worker! latency_sensitive_worker!
worker_resource_boundary :cpu worker_resource_boundary :cpu
weight 2
# Keep extra parameter to preserve backwards compatibility with # Keep extra parameter to preserve backwards compatibility with
# old `NewNoteWorker` jobs (can remove later) # old `NewNoteWorker` jobs (can remove later)
......
...@@ -5,6 +5,7 @@ class NewReleaseWorker ...@@ -5,6 +5,7 @@ class NewReleaseWorker
queue_namespace :notifications queue_namespace :notifications
feature_category :release_orchestration feature_category :release_orchestration
weight 2
def perform(release_id) def perform(release_id)
release = Release.preloaded.find_by_id(release_id) release = Release.preloaded.find_by_id(release_id)
......
...@@ -6,6 +6,7 @@ class PostReceive ...@@ -6,6 +6,7 @@ class PostReceive
feature_category :source_code_management feature_category :source_code_management
latency_sensitive_worker! latency_sensitive_worker!
worker_resource_boundary :cpu worker_resource_boundary :cpu
weight 5
def perform(gl_repository, identifier, changes, push_options = {}) def perform(gl_repository, identifier, changes, push_options = {})
project, repo_type = Gitlab::GlRepository.parse(gl_repository) project, repo_type = Gitlab::GlRepository.parse(gl_repository)
......
...@@ -12,6 +12,7 @@ class ProcessCommitWorker ...@@ -12,6 +12,7 @@ class ProcessCommitWorker
feature_category :source_code_management feature_category :source_code_management
latency_sensitive_worker! latency_sensitive_worker!
weight 3
# project_id - The ID of the project this commit belongs to. # project_id - The ID of the project this commit belongs to.
# user_id - The ID of the user that pushed the commit. # user_id - The ID of the user that pushed the commit.
......
...@@ -6,6 +6,7 @@ class RebaseWorker ...@@ -6,6 +6,7 @@ class RebaseWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management feature_category :source_code_management
weight 2
def perform(merge_request_id, current_user_id, skip_ci = false) def perform(merge_request_id, current_user_id, skip_ci = false)
current_user = User.find(current_user_id) current_user = User.find(current_user_id)
......
...@@ -4,6 +4,7 @@ class RemoteMirrorNotificationWorker ...@@ -4,6 +4,7 @@ class RemoteMirrorNotificationWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management feature_category :source_code_management
weight 2
def perform(remote_mirror_id) def perform(remote_mirror_id)
remote_mirror = RemoteMirror.find_by_id(remote_mirror_id) remote_mirror = RemoteMirror.find_by_id(remote_mirror_id)
......
...@@ -4,6 +4,7 @@ class UpdateExternalPullRequestsWorker ...@@ -4,6 +4,7 @@ class UpdateExternalPullRequestsWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management feature_category :source_code_management
weight 3
def perform(project_id, user_id, ref) def perform(project_id, user_id, ref)
project = Project.find_by_id(project_id) project = Project.find_by_id(project_id)
......
...@@ -6,6 +6,7 @@ class UpdateMergeRequestsWorker ...@@ -6,6 +6,7 @@ class UpdateMergeRequestsWorker
feature_category :source_code_management feature_category :source_code_management
latency_sensitive_worker! latency_sensitive_worker!
worker_resource_boundary :cpu worker_resource_boundary :cpu
weight 3
LOG_TIME_THRESHOLD = 90 # seconds LOG_TIME_THRESHOLD = 90 # seconds
......
---
title: Add WAF Anomaly Summary service
merge_request: 22736
author:
type: added
---
title: Add tags, external_base_url, gitlab_issue to Sentry Detailed Error graphql
merge_request: 23483
author:
type: added
---
title: Remove button group for edit and web ide in file header
merge_request: 23291
author:
type: other
---
title: Add CI variables to provide GitLab port and protocol
merge_request: 23296
author: Aidin Abedi
type: added
---
title: Allow users to read broadcast messages via API
merge_request: 23298
author: Rajendra Kadam
type: changed
---
title: Add accidentally deleted project config for custom apply suggestions
merge_request: 23687
author: Fabio Huser
type: fixed
---
title: "Support retrieval of disk statistics from Gitaly"
merge_request: 22226
author: Nels Nelson
type: added
...@@ -15,6 +15,10 @@ unless Gitlab::Runtime.sidekiq? ...@@ -15,6 +15,10 @@ unless Gitlab::Runtime.sidekiq?
data data
end end
# This isn't a user-reachable controller; we use it to check for a
# valid CSRF token in the API
config.lograge.ignore_actions = ['Gitlab::RequestForgeryProtection::Controller#index']
# Add request parameters to log output # Add request parameters to log output
config.lograge.custom_options = lambda do |event| config.lograge.custom_options = lambda do |event|
params = event.payload[:params] params = event.payload[:params]
......
scope(path: '*namespace_id/:project_id', concern :gitactionable do
format: nil, scope(controller: :git_http) do
constraints: { namespace_id: Gitlab::PathRegex.full_namespace_route_regex }) do get '/info/refs', action: :info_refs
scope(constraints: { project_id: Gitlab::PathRegex.project_git_route_regex }, module: :projects) do post '/git-upload-pack', action: :git_upload_pack
# Git HTTP clients ('git clone' etc.) post '/git-receive-pack', action: :git_receive_pack
scope(controller: :git_http) do end
get '/info/refs', action: :info_refs end
post '/git-upload-pack', action: :git_upload_pack
post '/git-receive-pack', action: :git_receive_pack
end
# Git LFS API (metadata) concern :lfsable do
scope(path: 'info/lfs/objects', controller: :lfs_api) do # Git LFS API (metadata)
post :batch scope(path: 'info/lfs/objects', controller: :lfs_api) do
post '/', action: :deprecated post :batch
get '/*oid', action: :deprecated post '/', action: :deprecated
end get '/*oid', action: :deprecated
end
scope(path: 'info/lfs') do scope(path: 'info/lfs') do
resources :lfs_locks, controller: :lfs_locks_api, path: 'locks' do resources :lfs_locks, controller: :lfs_locks_api, path: 'locks' do
post :unlock, on: :member post :unlock, on: :member
post :verify, on: :collection post :verify, on: :collection
end
end end
end
# GitLab LFS object storage # GitLab LFS object storage
scope(path: 'gitlab-lfs/objects/*oid', controller: :lfs_storage, constraints: { oid: /[a-f0-9]{64}/ }) do scope(path: 'gitlab-lfs/objects/*oid', controller: :lfs_storage, constraints: { oid: /[a-f0-9]{64}/ }) do
get '/', action: :download get '/', action: :download
scope constraints: { size: /[0-9]+/ } do scope constraints: { size: /[0-9]+/ } do
put '/*size/authorize', action: :upload_authorize put '/*size/authorize', action: :upload_authorize
put '/*size', action: :upload_finalize put '/*size', action: :upload_finalize
end end
end
end
scope(path: '*namespace_id/:repository_id',
format: nil,
constraints: { namespace_id: Gitlab::PathRegex.full_namespace_route_regex }) do
scope(constraints: { repository_id: Gitlab::PathRegex.project_git_route_regex }) do
scope(module: :repositories) do
concerns :gitactionable
concerns :lfsable
end end
end end
# Redirect /group/project.wiki.git to the project wiki # Redirect /group/project.wiki.git to the project wiki
scope(format: true, constraints: { project_id: Gitlab::PathRegex.project_wiki_git_route_regex, format: :git }) do scope(format: true, constraints: { repository_id: Gitlab::PathRegex.project_wiki_git_route_regex, format: :git }) do
wiki_redirect = redirect do |params, request| wiki_redirect = redirect do |params, request|
project_id = params[:project_id].delete_suffix('.wiki') project_id = params[:repository_id].delete_suffix('.wiki')
path = [params[:namespace_id], project_id, 'wikis'].join('/') path = [params[:namespace_id], project_id, 'wikis'].join('/')
path << "?#{request.query_string}" unless request.query_string.blank? path << "?#{request.query_string}" unless request.query_string.blank?
path path
...@@ -47,7 +55,7 @@ scope(path: '*namespace_id/:project_id', ...@@ -47,7 +55,7 @@ scope(path: '*namespace_id/:project_id',
end end
# Redirect /group/project/info/refs to /group/project.git/info/refs # Redirect /group/project/info/refs to /group/project.git/info/refs
scope(constraints: { project_id: Gitlab::PathRegex.project_route_regex }) do scope(constraints: { repository_id: Gitlab::PathRegex.project_route_regex }) do
# Allow /info/refs, /info/refs?service=git-upload-pack, and # Allow /info/refs, /info/refs?service=git-upload-pack, and
# /info/refs?service=git-receive-pack, but nothing else. # /info/refs?service=git-receive-pack, but nothing else.
# #
...@@ -58,7 +66,7 @@ scope(path: '*namespace_id/:project_id', ...@@ -58,7 +66,7 @@ scope(path: '*namespace_id/:project_id',
end end
ref_redirect = redirect do |params, request| ref_redirect = redirect do |params, request|
path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs" path = "#{params[:namespace_id]}/#{params[:repository_id]}.git/info/refs"
path << "?#{request.query_string}" unless request.query_string.blank? path << "?#{request.query_string}" unless request.query_string.blank?
path path
end end
......
# This file is generated automatically by
# bin/rake gitlab:sidekiq:sidekiq_queues_yml:generate
#
# Do not edit it manually!
#
# This configuration file should be exclusively used to set queue settings for # This configuration file should be exclusively used to set queue settings for
# Sidekiq. Any other setting should be specified using the Sidekiq CLI or the # Sidekiq. Any other setting should be specified using the Sidekiq CLI or the
# Sidekiq Ruby API (see config/initializers/sidekiq.rb). # Sidekiq Ruby API (see config/initializers/sidekiq.rb).
--- #
# All the queues to process and their weights. Every queue _must_ have a weight # All the queues to process and their weights. Every queue _must_ have a weight
# defined. # defined.
# #
...@@ -17,116 +22,217 @@ ...@@ -17,116 +22,217 @@
# to perform) is: # to perform) is:
# #
# chance = (queue weight / total weight of all queues) * 100 # chance = (queue weight / total weight of all queues) * 100
---
:queues: :queues:
- [post_receive, 5] - - adjourned_project_deletion
- [merge, 5] - 1
- [update_merge_requests, 3] - - admin_emails
- [process_commit, 3] - 1
- [new_note, 2] - - authorized_projects
- [new_issue, 2] - 2
- [notifications, 2] - - auto_devops
- [new_merge_request, 2] - 2
- [pipeline_processing, 5] - - auto_merge
- [pipeline_creation, 4] - 3
- [pipeline_default, 3] - - background_migration
- [pipeline_cache, 3] - 1
- [deployment, 3] - - chaos
- [auto_merge, 3] - 2
- [pipeline_hooks, 2] - - chat_notification
- [gitlab_shell, 2] - 2
- [email_receiver, 2] - - container_repository
- [emails_on_push, 2] - 1
- [mailers, 2] - - create_evidence
- [mail_scheduler, 2] - 2
- [invalid_gpg_signature_update, 2] - - create_github_webhook
- [create_gpg_signature, 2] - 2
- [rebase, 2] - - create_gpg_signature
- [upload_checksum, 1] - 2
- [repository_fork, 1] - - create_note_diff_file
- [repository_import, 1] - 1
- [github_importer, 1] - - cronjob
- [github_import_advance_stage, 1] - 1
- [project_service, 1] - - default
- [delete_user, 1] - 1
- [todos_destroyer, 1] - - delete_diff_files
- [delete_merged_branches, 1] - 1
- [authorized_projects, 2] - - delete_merged_branches
- [expire_build_instance_artifacts, 1] - 1
- [group_destroy, 1] - - delete_stored_files
- [irker, 1] - 1
- [namespaceless_project_destroy, 1] - - delete_user
- [project_cache, 1] - 1
- [project_destroy, 1] - - deployment
- [project_export, 1] - 3
- [web_hook, 1] - - design_management_new_version
- [repository_check, 1] - 1
- [git_garbage_collect, 1] - - detect_repository_languages
- [reactive_caching, 1] - 1
- [cronjob, 1] - - elastic_batch_project_indexer
- [default, 1] - 1
- [pages, 1] - - elastic_commit_indexer
- [system_hook_push, 1] - 1
- [propagate_service_template, 1] - - elastic_full_index
- [background_migration, 1] - 1
- [gcp_cluster, 1] - - elastic_indexer
- [project_migrate_hashed_storage, 1] - 1
- [project_rollback_hashed_storage, 1] - - elastic_namespace_indexer
- [hashed_storage, 1] - 1
- [pages_domain_verification, 1] - - elastic_namespace_rollout
- [pages_domain_ssl_renewal, 1] - 1
- [object_storage_upload, 1] - - email_receiver
- [object_storage, 1] - 2
- [file_hook, 1] - - emails_on_push
- [pipeline_background, 1] - 2
- [repository_update_remote_mirror, 1] - - epics
- [repository_remove_remote, 1] - 2
- [create_note_diff_file, 1] - - error_tracking_issue_link
- [delete_diff_files, 1] - 1
- [detect_repository_languages, 1] - - expire_build_instance_artifacts
- [auto_devops, 2] - 1
- [container_repository, 1] - - export_csv
- [object_pool, 1] - 1
- [repository_cleanup, 1] - - file_hook
- [delete_stored_files, 1] - 1
- [remote_mirror_notification, 2] - - gcp_cluster
- [project_daily_statistics, 1] - 1
- [import_issues_csv, 2] - - geo
- [chat_notification, 2] - 1
- [migrate_external_diffs, 1] - - git_garbage_collect
- [update_project_statistics, 1] - 1
- [phabricator_import_import_tasks, 1] - - github_import_advance_stage
- [update_namespace_statistics, 1] - 1
- [chaos, 2] - - github_importer
- [create_evidence, 2] - 1
- [group_export, 1] - - gitlab_shell
- [self_monitoring_project_create, 2] - 2
- [self_monitoring_project_delete, 2] - - group_destroy
- [error_tracking_issue_link, 2] - 1
- [merge_request_mergeability_check, 5] - - group_export
- 1
# EE-specific queues - - hashed_storage
- [analytics, 1] - 1
- [ldap_group_sync, 2] - - import_issues_csv
- [create_github_webhook, 2] - 2
- [geo, 1] - - incident_management
- [repository_update_mirror, 1] - 2
- [repository_push_audit_event, 1] - - invalid_gpg_signature_update
- [new_epic, 2] - 2
- [project_import_schedule, 1] - - irker
- [project_update_repository_storage, 1] - 1
- [admin_emails, 1] - - jira_connect
- [elastic_batch_project_indexer, 1] - 1
- [elastic_indexer, 1] - - ldap_group_sync
- [elastic_full_index, 1] - 2
- [elastic_commit_indexer, 1] - - mail_scheduler
- [elastic_namespace_indexer, 1] - 2
- [elastic_namespace_rollout, 1] - - mailers
- [export_csv, 1] - 2
- [incident_management, 2] - - merge
- [jira_connect, 1] - 5
- [update_external_pull_requests, 3] - - merge_request_mergeability_check
- [refresh_license_compliance_checks, 2] - 1
- [design_management_new_version, 1] - - migrate_external_diffs
- [epics, 2] - 1
- [personal_access_tokens, 1] - - namespaceless_project_destroy
- [adjourned_project_deletion, 1] - 1
- - new_epic
- 2
- - new_issue
- 2
- - new_merge_request
- 2
- - new_note
- 2
- - notifications
- 2
- - object_pool
- 1
- - object_storage
- 1
- - pages
- 1
- - pages_domain_ssl_renewal
- 1
- - pages_domain_verification
- 1
- - personal_access_tokens
- 1
- - phabricator_import_import_tasks
- 1
- - pipeline_background
- 1
- - pipeline_cache
- 3
- - pipeline_creation
- 4
- - pipeline_default
- 3
- - pipeline_hooks
- 2
- - pipeline_processing
- 5
- - post_receive
- 5
- - process_commit
- 3
- - project_cache
- 1
- - project_daily_statistics
- 1
- - project_destroy
- 1
- - project_export
- 1
- - project_import_schedule
- 1
- - project_service
- 1
- - project_update_repository_storage
- 1
- - propagate_service_template
- 1
- - reactive_caching
- 1
- - rebase
- 2
- - refresh_license_compliance_checks
- 2
- - remote_mirror_notification
- 2
- - repository_check
- 1
- - repository_cleanup
- 1
- - repository_fork
- 1
- - repository_import
- 1
- - repository_push_audit_event
- 1
- - repository_remove_remote
- 1
- - repository_update_mirror
- 1
- - repository_update_remote_mirror
- 1
- - self_monitoring_project_create
- 2
- - self_monitoring_project_delete
- 2
- - system_hook_push
- 1
- - todos_destroyer
- 1
- - update_external_pull_requests
- 3
- - update_merge_requests
- 3
- - update_namespace_statistics
- 1
- - update_project_statistics
- 1
- - upload_checksum
- 1
- - web_hook
- 1
# frozen_string_literal: true
class AddSourceToImportFailures < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :import_failures, :source, :string, limit: 128
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_01_23_155929) do ActiveRecord::Schema.define(version: 2020_01_24_053531) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
...@@ -2046,6 +2046,7 @@ ActiveRecord::Schema.define(version: 2020_01_23_155929) do ...@@ -2046,6 +2046,7 @@ ActiveRecord::Schema.define(version: 2020_01_23_155929) do
t.string "exception_message", limit: 255 t.string "exception_message", limit: 255
t.integer "retry_count" t.integer "retry_count"
t.integer "group_id" t.integer "group_id"
t.string "source", limit: 128
t.index ["correlation_id_value"], name: "index_import_failures_on_correlation_id_value" t.index ["correlation_id_value"], name: "index_import_failures_on_correlation_id_value"
t.index ["group_id"], name: "index_import_failures_on_group_id_not_null", where: "(group_id IS NOT NULL)" t.index ["group_id"], name: "index_import_failures_on_group_id_not_null", where: "(group_id IS NOT NULL)"
t.index ["project_id"], name: "index_import_failures_on_project_id_not_null", where: "(project_id IS NOT NULL)" t.index ["project_id"], name: "index_import_failures_on_project_id_not_null", where: "(project_id IS NOT NULL)"
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
Broadcast messages API operates on [broadcast messages](../user/admin_area/broadcast_messages.md). Broadcast messages API operates on [broadcast messages](../user/admin_area/broadcast_messages.md).
The broadcast message API is only accessible to administrators. All requests by: As of GitLab 12.8, GET requests do not require authentication. All other broadcast message API endpoints are accessible only to administrators. Non-GET requests by:
- Guests will result in `401 Unauthorized`. - Guests will result in `401 Unauthorized`.
- Regular users will result in `403 Forbidden`. - Regular users will result in `403 Forbidden`.
...@@ -20,7 +20,7 @@ GET /broadcast_messages ...@@ -20,7 +20,7 @@ GET /broadcast_messages
Example request: Example request:
```sh ```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/broadcast_messages curl https://gitlab.example.com/api/v4/broadcast_messages
``` ```
Example response: Example response:
...@@ -57,7 +57,7 @@ Parameters: ...@@ -57,7 +57,7 @@ Parameters:
Example request: Example request:
```sh ```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/broadcast_messages/1 curl https://gitlab.example.com/api/v4/broadcast_messages/1
``` ```
Example response: Example response:
......
...@@ -6065,6 +6065,11 @@ type SentryDetailedError { ...@@ -6065,6 +6065,11 @@ type SentryDetailedError {
""" """
culprit: String! culprit: String!
"""
External Base URL of the Sentry Instance
"""
externalBaseUrl: String!
""" """
External URL of the error External URL of the error
""" """
...@@ -6100,6 +6105,11 @@ type SentryDetailedError { ...@@ -6100,6 +6105,11 @@ type SentryDetailedError {
""" """
gitlabCommitPath: String gitlabCommitPath: String
"""
URL of GitLab Issue
"""
gitlabIssuePath: String
""" """
ID (global ID) of the error ID (global ID) of the error
""" """
...@@ -6155,6 +6165,11 @@ type SentryDetailedError { ...@@ -6155,6 +6165,11 @@ type SentryDetailedError {
""" """
status: SentryErrorStatus! status: SentryErrorStatus!
"""
Tags associated with the Sentry Error
"""
tags: SentryErrorTags!
""" """
Title of the error Title of the error
""" """
...@@ -6208,6 +6223,21 @@ enum SentryErrorStatus { ...@@ -6208,6 +6223,21 @@ enum SentryErrorStatus {
UNRESOLVED UNRESOLVED
} }
"""
State of a Sentry error
"""
type SentryErrorTags {
"""
Severity level of the Sentry Error
"""
level: String
"""
Logger of the Sentry Error
"""
logger: String
}
""" """
Represents a snippet entry Represents a snippet entry
""" """
......
...@@ -16746,6 +16746,24 @@ ...@@ -16746,6 +16746,24 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "externalBaseUrl",
"description": "External Base URL of the Sentry Instance",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "externalUrl", "name": "externalUrl",
"description": "External URL of the error", "description": "External URL of the error",
...@@ -16864,6 +16882,20 @@ ...@@ -16864,6 +16882,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "gitlabIssuePath",
"description": "URL of GitLab Issue",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "id", "name": "id",
"description": "ID (global ID) of the error", "description": "ID (global ID) of the error",
...@@ -17050,6 +17082,24 @@ ...@@ -17050,6 +17082,24 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "tags",
"description": "Tags associated with the Sentry Error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "SentryErrorTags",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "title", "name": "title",
"description": "Title of the error", "description": "Title of the error",
...@@ -17196,6 +17246,47 @@ ...@@ -17196,6 +17246,47 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "SentryErrorTags",
"description": "State of a Sentry error",
"fields": [
{
"name": "level",
"description": "Severity level of the Sentry Error",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "logger",
"description": "Logger of the Sentry Error",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "GrafanaIntegration", "name": "GrafanaIntegration",
......
...@@ -923,6 +923,7 @@ Autogenerated return type of RemoveAwardEmoji ...@@ -923,6 +923,7 @@ Autogenerated return type of RemoveAwardEmoji
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `count` | Int! | Count of occurrences | | `count` | Int! | Count of occurrences |
| `culprit` | String! | Culprit of the error | | `culprit` | String! | Culprit of the error |
| `externalBaseUrl` | String! | External Base URL of the Sentry Instance |
| `externalUrl` | String! | External URL of the error | | `externalUrl` | String! | External URL of the error |
| `firstReleaseLastCommit` | String | Commit the error was first seen | | `firstReleaseLastCommit` | String | Commit the error was first seen |
| `firstReleaseShortVersion` | String | Release version the error was first seen | | `firstReleaseShortVersion` | String | Release version the error was first seen |
...@@ -930,6 +931,7 @@ Autogenerated return type of RemoveAwardEmoji ...@@ -930,6 +931,7 @@ Autogenerated return type of RemoveAwardEmoji
| `frequency` | SentryErrorFrequency! => Array | Last 24hr stats of the error | | `frequency` | SentryErrorFrequency! => Array | Last 24hr stats of the error |
| `gitlabCommit` | String | GitLab commit SHA attributed to the Error based on the release version | | `gitlabCommit` | String | GitLab commit SHA attributed to the Error based on the release version |
| `gitlabCommitPath` | String | Path to the GitLab page for the GitLab commit attributed to the error | | `gitlabCommitPath` | String | Path to the GitLab page for the GitLab commit attributed to the error |
| `gitlabIssuePath` | String | URL of GitLab Issue |
| `id` | ID! | ID (global ID) of the error | | `id` | ID! | ID (global ID) of the error |
| `lastReleaseLastCommit` | String | Commit the error was last seen | | `lastReleaseLastCommit` | String | Commit the error was last seen |
| `lastReleaseShortVersion` | String | Release version the error was last seen | | `lastReleaseShortVersion` | String | Release version the error was last seen |
...@@ -941,6 +943,7 @@ Autogenerated return type of RemoveAwardEmoji ...@@ -941,6 +943,7 @@ Autogenerated return type of RemoveAwardEmoji
| `sentryProjectSlug` | String! | Slug of the project affected by the error | | `sentryProjectSlug` | String! | Slug of the project affected by the error |
| `shortId` | String! | Short ID (Sentry ID) of the error | | `shortId` | String! | Short ID (Sentry ID) of the error |
| `status` | SentryErrorStatus! | Status of the error | | `status` | SentryErrorStatus! | Status of the error |
| `tags` | SentryErrorTags! | Tags associated with the Sentry Error |
| `title` | String! | Title of the error | | `title` | String! | Title of the error |
| `type` | String! | Type of the error | | `type` | String! | Type of the error |
| `userCount` | Int! | Count of users affected by the error | | `userCount` | Int! | Count of users affected by the error |
...@@ -952,6 +955,15 @@ Autogenerated return type of RemoveAwardEmoji ...@@ -952,6 +955,15 @@ Autogenerated return type of RemoveAwardEmoji
| `count` | Int! | Count of errors received since the previously recorded time | | `count` | Int! | Count of errors received since the previously recorded time |
| `time` | Time! | Time the error frequency stats were recorded | | `time` | Time! | Time the error frequency stats were recorded |
## SentryErrorTags
State of a Sentry error
| Name | Type | Description |
| --- | ---- | ---------- |
| `level` | String | Severity level of the Sentry Error |
| `logger` | String | Logger of the Sentry Error |
## Snippet ## Snippet
Represents a snippet entry Represents a snippet entry
......
...@@ -292,6 +292,8 @@ export CI_RUNNER_TAGS="docker, linux" ...@@ -292,6 +292,8 @@ export CI_RUNNER_TAGS="docker, linux"
export CI_SERVER="yes" export CI_SERVER="yes"
export CI_SERVER_URL="https://example.com" export CI_SERVER_URL="https://example.com"
export CI_SERVER_HOST="example.com" export CI_SERVER_HOST="example.com"
export CI_SERVER_PORT="443"
export CI_SERVER_PROTOCOL="https"
export CI_SERVER_NAME="GitLab" export CI_SERVER_NAME="GitLab"
export CI_SERVER_REVISION="70606bf" export CI_SERVER_REVISION="70606bf"
export CI_SERVER_VERSION="8.9.0" export CI_SERVER_VERSION="8.9.0"
...@@ -686,6 +688,10 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then ...@@ -686,6 +688,10 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
++ CI_SERVER_URL=https://gitlab.com:3000 ++ CI_SERVER_URL=https://gitlab.com:3000
++ export CI_SERVER_HOST=gitlab.com ++ export CI_SERVER_HOST=gitlab.com
++ CI_SERVER_HOST=gitlab.com ++ CI_SERVER_HOST=gitlab.com
++ export CI_SERVER_PORT=3000
++ CI_SERVER_PORT=3000
++ export CI_SERVER_PROTOCOL=https
++ CI_SERVER_PROTOCOL=https
++ export CI_SERVER_NAME=GitLab ++ export CI_SERVER_NAME=GitLab
++ CI_SERVER_NAME=GitLab ++ CI_SERVER_NAME=GitLab
++ export CI_SERVER_VERSION=12.6.0-pre ++ export CI_SERVER_VERSION=12.6.0-pre
......
...@@ -114,6 +114,8 @@ future GitLab releases.** ...@@ -114,6 +114,8 @@ future GitLab releases.**
| `CI_SERVER` | all | all | Mark that job is executed in CI environment | | `CI_SERVER` | all | all | Mark that job is executed in CI environment |
| `CI_SERVER_URL` | 12.7 | all | The base URL of the GitLab instance, including protocol and port (like `https://gitlab.example.com:8080`) | | `CI_SERVER_URL` | 12.7 | all | The base URL of the GitLab instance, including protocol and port (like `https://gitlab.example.com:8080`) |
| `CI_SERVER_HOST` | 12.1 | all | Host component of the GitLab instance URL, without protocol and port (like `gitlab.example.com`) | | `CI_SERVER_HOST` | 12.1 | all | Host component of the GitLab instance URL, without protocol and port (like `gitlab.example.com`) |
| `CI_SERVER_PORT` | 12.8 | all | Port component of the GitLab instance URL, without host and protocol (like `3000`) |
| `CI_SERVER_PROTOCOL` | 12.8 | all | Protocol component of the GitLab instance URL, without host and port (like `https`) |
| `CI_SERVER_NAME` | all | all | The name of CI server that is used to coordinate jobs | | `CI_SERVER_NAME` | all | all | The name of CI server that is used to coordinate jobs |
| `CI_SERVER_REVISION` | all | all | GitLab revision that is used to schedule jobs | | `CI_SERVER_REVISION` | all | all | GitLab revision that is used to schedule jobs |
| `CI_SERVER_VERSION` | all | all | GitLab version that is used to schedule jobs | | `CI_SERVER_VERSION` | all | all | GitLab version that is used to schedule jobs |
......
...@@ -439,6 +439,16 @@ Suggestions covering multiple lines are limited to 100 lines _above_ and 100 ...@@ -439,6 +439,16 @@ Suggestions covering multiple lines are limited to 100 lines _above_ and 100
lines _below_ the commented diff line, allowing up to 200 changed lines per lines _below_ the commented diff line, allowing up to 200 changed lines per
suggestion. suggestion.
### Code block nested in Suggestions
If you need to make a suggestion that involves a
[fenced code block](../markdown.md#code-spans-and-blocks), wrap your suggestion in four backticks
instead of the usual three.
![A comment editor with a suggestion with a fenced code block](img/suggestion_code_block_editor_v12_8.png)
![Ouput of a comment with a suggestion with a fenced code block](img/suggestion_code_block_output_v12_8.png)
### Configure the commit message for applied Suggestions ### Configure the commit message for applied Suggestions
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/13086) in GitLab 12.7. > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/13086) in GitLab 12.7.
......
...@@ -4,9 +4,6 @@ module API ...@@ -4,9 +4,6 @@ module API
class BroadcastMessages < Grape::API class BroadcastMessages < Grape::API
include PaginationParams include PaginationParams
before { authenticate! }
before { authenticated_as_admin! }
resource :broadcast_messages do resource :broadcast_messages do
helpers do helpers do
def find_message def find_message
...@@ -40,6 +37,8 @@ module API ...@@ -40,6 +37,8 @@ module API
optional :target_path, type: String, desc: 'Target path' optional :target_path, type: String, desc: 'Target path'
end end
post do post do
authenticated_as_admin!
message = BroadcastMessage.create(declared_params(include_missing: false)) message = BroadcastMessage.create(declared_params(include_missing: false))
if message.persisted? if message.persisted?
...@@ -76,6 +75,8 @@ module API ...@@ -76,6 +75,8 @@ module API
optional :target_path, type: String, desc: 'Target path' optional :target_path, type: String, desc: 'Target path'
end end
put ':id' do put ':id' do
authenticated_as_admin!
message = find_message message = find_message
if message.update(declared_params(include_missing: false)) if message.update(declared_params(include_missing: false))
...@@ -93,6 +94,8 @@ module API ...@@ -93,6 +94,8 @@ module API
requires :id, type: Integer, desc: 'Broadcast message ID' requires :id, type: Integer, desc: 'Broadcast message ID'
end end
delete ':id' do delete ':id' do
authenticated_as_admin!
message = find_message message = find_message
destroy_conditionally!(message) destroy_conditionally!(message)
......
...@@ -104,14 +104,12 @@ module API ...@@ -104,14 +104,12 @@ module API
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_project def set_project
if params[:gl_repository] @project, @repo_type, @redirected_path =
@project, @repo_type = Gitlab::GlRepository.parse(params[:gl_repository]) if params[:gl_repository]
@redirected_path = nil Gitlab::GlRepository.parse(params[:gl_repository])
elsif params[:project] elsif params[:project]
@project, @repo_type, @redirected_path = Gitlab::RepoPath.parse(params[:project]) Gitlab::RepoPath.parse(params[:project])
else end
@project, @repo_type, @redirected_path = nil, nil, nil
end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
......
...@@ -4,7 +4,7 @@ module Constraints ...@@ -4,7 +4,7 @@ module Constraints
class ProjectUrlConstrainer class ProjectUrlConstrainer
def matches?(request, existence_check: true) def matches?(request, existence_check: true)
namespace_path = request.params[:namespace_id] namespace_path = request.params[:namespace_id]
project_path = request.params[:project_id] || request.params[:id] project_path = request.params[:project_id] || request.params[:id] || request.params[:repository_id]
full_path = [namespace_path, project_path].join('/') full_path = [namespace_path, project_path].join('/')
return false unless ProjectPathValidator.valid_path?(full_path) return false unless ProjectPathValidator.valid_path?(full_path)
......
...@@ -9,7 +9,6 @@ class Feature ...@@ -9,7 +9,6 @@ class Feature
%w[ %w[
cache_invalidator cache_invalidator
inforef_uploadpack_cache inforef_uploadpack_cache
get_tag_messages_go
filter_shas_with_signatures_go filter_shas_with_signatures_go
commit_without_batch_check commit_without_batch_check
].freeze ].freeze
......
...@@ -53,6 +53,20 @@ module Gitaly ...@@ -53,6 +53,20 @@ module Gitaly
storage_status&.fs_type storage_status&.fs_type
end end
def disk_used
disk_statistics_storage_status&.used
end
def disk_available
disk_statistics_storage_status&.available
end
# Simple convenience method for when obtaining both used and available
# statistics at once is preferred.
def disk_stats
disk_statistics_storage_status
end
def address def address
Gitlab::GitalyClient.address(@storage) Gitlab::GitalyClient.address(@storage)
rescue RuntimeError => e rescue RuntimeError => e
...@@ -65,6 +79,10 @@ module Gitaly ...@@ -65,6 +79,10 @@ module Gitaly
@storage_status ||= info.storage_statuses.find { |s| s.storage_name == storage } @storage_status ||= info.storage_statuses.find { |s| s.storage_name == storage }
end end
def disk_statistics_storage_status
@disk_statistics_storage_status ||= disk_statistics.storage_statuses.find { |s| s.storage_name == storage }
end
def matches_sha? def matches_sha?
match = server_version.match(SHA_VERSION_REGEX) match = server_version.match(SHA_VERSION_REGEX)
return false unless match return false unless match
...@@ -76,7 +94,19 @@ module Gitaly ...@@ -76,7 +94,19 @@ module Gitaly
@info ||= @info ||=
begin begin
Gitlab::GitalyClient::ServerService.new(@storage).info Gitlab::GitalyClient::ServerService.new(@storage).info
rescue GRPC::Unavailable, GRPC::DeadlineExceeded rescue GRPC::Unavailable, GRPC::DeadlineExceeded => ex
Gitlab::ErrorTracking.track_exception(ex)
# This will show the server as being out of date
Gitaly::ServerInfoResponse.new(git_version: '', server_version: '', storage_statuses: [])
end
end
def disk_statistics
@disk_statistics ||=
begin
Gitlab::GitalyClient::ServerService.new(@storage).disk_statistics
rescue GRPC::Unavailable, GRPC::DeadlineExceeded => ex
Gitlab::ErrorTracking.track_exception(ex)
# This will show the server as being out of date # This will show the server as being out of date
Gitaly::ServerInfoResponse.new(git_version: '', server_version: '', storage_statuses: []) Gitaly::ServerInfoResponse.new(git_version: '', server_version: '', storage_statuses: [])
end end
......
...@@ -432,10 +432,7 @@ module Gitlab ...@@ -432,10 +432,7 @@ module Gitlab
end end
def self.filesystem_id(storage) def self.filesystem_id(storage)
response = Gitlab::GitalyClient::ServerService.new(storage).info Gitlab::GitalyClient::ServerService.new(storage).storage_info&.filesystem_id
storage_status = response.storage_statuses.find { |status| status.storage_name == storage }
storage_status&.filesystem_id
end end
def self.filesystem_id_from_disk(storage) def self.filesystem_id_from_disk(storage)
...@@ -446,6 +443,14 @@ module Gitlab ...@@ -446,6 +443,14 @@ module Gitlab
nil nil
end end
def self.filesystem_disk_available(storage)
Gitlab::GitalyClient::ServerService.new(storage).storage_disk_statistics&.available
end
def self.filesystem_disk_used(storage)
Gitlab::GitalyClient::ServerService.new(storage).storage_disk_statistics&.used
end
def self.timeout(timeout_name) def self.timeout(timeout_name)
Gitlab::CurrentSettings.current_application_settings[timeout_name] Gitlab::CurrentSettings.current_application_settings[timeout_name]
end end
......
...@@ -13,6 +13,24 @@ module Gitlab ...@@ -13,6 +13,24 @@ module Gitlab
def info def info
GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new, timeout: GitalyClient.fast_timeout) GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new, timeout: GitalyClient.fast_timeout)
end end
def disk_statistics
GitalyClient.call(@storage, :server_service, :disk_statistics, Gitaly::DiskStatisticsRequest.new, timeout: GitalyClient.fast_timeout)
end
def storage_info
storage_specific(info)
end
def storage_disk_statistics
storage_specific(disk_statistics)
end
private
def storage_specific(response)
response.storage_statuses.find { |status| status.storage_name == @storage }
end
end end
end end
end end
...@@ -43,6 +43,9 @@ module Gitlab ...@@ -43,6 +43,9 @@ module Gitlab
# Initialize gon.features with any flags that should be # Initialize gon.features with any flags that should be
# made globally available to the frontend # made globally available to the frontend
push_frontend_feature_flag(:snippets_vue, default_enabled: false) push_frontend_feature_flag(:snippets_vue, default_enabled: false)
push_frontend_feature_flag(:monaco_snippets, default_enabled: false)
push_frontend_feature_flag(:monaco_blobs, default_enabled: false)
push_frontend_feature_flag(:monaco_ci, default_enabled: false)
end end
# Exposes the state of a feature flag to the frontend code. # Exposes the state of a feature flag to the frontend code.
......
...@@ -12,9 +12,14 @@ module Gitlab ...@@ -12,9 +12,14 @@ module Gitlab
@association = importable.association(:import_failures) @association = importable.association(:import_failures)
end end
def with_retry(relation_key, relation_index) def with_retry(action:, relation_key: nil, relation_index: nil)
on_retry = -> (exception, retry_count, *_args) do on_retry = -> (exception, retry_count, *_args) do
log_import_failure(relation_key, relation_index, exception, retry_count) log_import_failure(
source: action,
relation_key: relation_key,
relation_index: relation_index,
exception: exception,
retry_count: retry_count)
end end
Retriable.with_context(:relation_import, on_retry: on_retry) do Retriable.with_context(:relation_import, on_retry: on_retry) do
...@@ -22,8 +27,9 @@ module Gitlab ...@@ -22,8 +27,9 @@ module Gitlab
end end
end end
def log_import_failure(relation_key, relation_index, exception, retry_count = 0) def log_import_failure(source:, relation_key: nil, relation_index: nil, exception:, retry_count: 0)
extra = { extra = {
source: source,
relation_key: relation_key, relation_key: relation_key,
relation_index: relation_index, relation_index: relation_index,
retry_count: retry_count retry_count: retry_count
......
...@@ -21,7 +21,9 @@ module Gitlab ...@@ -21,7 +21,9 @@ module Gitlab
RelationRenameService.rename(@tree_hash) RelationRenameService.rename(@tree_hash)
if relation_tree_restorer.restore if relation_tree_restorer.restore
@project.merge_requests.set_latest_merge_request_diff_ids! import_failure_service.with_retry(action: 'set_latest_merge_request_diff_ids!') do
@project.merge_requests.set_latest_merge_request_diff_ids!
end
true true
else else
...@@ -72,6 +74,10 @@ module Gitlab ...@@ -72,6 +74,10 @@ module Gitlab
def reader def reader
@reader ||= Gitlab::ImportExport::Reader.new(shared: @shared) @reader ||= Gitlab::ImportExport::Reader.new(shared: @shared)
end end
def import_failure_service
@import_failure_service ||= ImportFailureService.new(@project)
end
end end
end end
end end
...@@ -73,13 +73,17 @@ module Gitlab ...@@ -73,13 +73,17 @@ module Gitlab
relation_object.assign_attributes(importable_class_sym => @importable) relation_object.assign_attributes(importable_class_sym => @importable)
import_failure_service.with_retry(relation_key, relation_index) do import_failure_service.with_retry(action: 'relation_object.save!', relation_key: relation_key, relation_index: relation_index) do
relation_object.save! relation_object.save!
end end
save_id_mapping(relation_key, data_hash, relation_object) save_id_mapping(relation_key, data_hash, relation_object)
rescue => e rescue => e
import_failure_service.log_import_failure(relation_key, relation_index, e) import_failure_service.log_import_failure(
source: 'process_relation_item!',
relation_key: relation_key,
relation_index: relation_index,
exception: e)
end end
def import_failure_service def import_failure_service
......
...@@ -12,12 +12,12 @@ module Gitlab ...@@ -12,12 +12,12 @@ module Gitlab
ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance' ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'
WHITELISTED_GIT_ROUTES = { WHITELISTED_GIT_ROUTES = {
'projects/git_http' => %w{git_upload_pack git_receive_pack} 'repositories/git_http' => %w{git_upload_pack git_receive_pack}
}.freeze }.freeze
WHITELISTED_GIT_LFS_ROUTES = { WHITELISTED_GIT_LFS_ROUTES = {
'projects/lfs_api' => %w{batch}, 'repositories/lfs_api' => %w{batch},
'projects/lfs_locks_api' => %w{verify create unlock} 'repositories/lfs_locks_api' => %w{verify create unlock}
}.freeze }.freeze
WHITELISTED_GIT_REVISION_ROUTES = { WHITELISTED_GIT_REVISION_ROUTES = {
......
...@@ -6,6 +6,7 @@ module Gitlab ...@@ -6,6 +6,7 @@ module Gitlab
module SidekiqConfig module SidekiqConfig
FOSS_QUEUE_CONFIG_PATH = 'app/workers/all_queues.yml' FOSS_QUEUE_CONFIG_PATH = 'app/workers/all_queues.yml'
EE_QUEUE_CONFIG_PATH = 'ee/app/workers/all_queues.yml' EE_QUEUE_CONFIG_PATH = 'ee/app/workers/all_queues.yml'
SIDEKIQ_QUEUES_PATH = 'config/sidekiq_queues.yml'
QUEUE_CONFIG_PATHS = [ QUEUE_CONFIG_PATHS = [
FOSS_QUEUE_CONFIG_PATH, FOSS_QUEUE_CONFIG_PATH,
...@@ -13,11 +14,19 @@ module Gitlab ...@@ -13,11 +14,19 @@ module Gitlab
].compact.freeze ].compact.freeze
# For queues that don't have explicit workers - default and mailers # For queues that don't have explicit workers - default and mailers
DummyWorker = Struct.new(:queue) DummyWorker = Struct.new(:queue, :weight) do
def queue_namespace
nil
end
def get_weight
weight
end
end
DEFAULT_WORKERS = [ DEFAULT_WORKERS = [
Gitlab::SidekiqConfig::Worker.new(DummyWorker.new('default'), ee: false), Gitlab::SidekiqConfig::Worker.new(DummyWorker.new('default', 1), ee: false),
Gitlab::SidekiqConfig::Worker.new(DummyWorker.new('mailers'), ee: false) Gitlab::SidekiqConfig::Worker.new(DummyWorker.new('mailers', 2), ee: false)
].freeze ].freeze
class << self class << self
...@@ -30,7 +39,7 @@ module Gitlab ...@@ -30,7 +39,7 @@ module Gitlab
def config_queues def config_queues
@config_queues ||= begin @config_queues ||= begin
config = YAML.load_file(Rails.root.join('config/sidekiq_queues.yml')) config = YAML.load_file(Rails.root.join(SIDEKIQ_QUEUES_PATH))
config[:queues].map(&:first) config[:queues].map(&:first)
end end
end end
...@@ -65,6 +74,28 @@ module Gitlab ...@@ -65,6 +74,28 @@ module Gitlab
Gitlab.ee? && ee_workers != YAML.safe_load(File.read(EE_QUEUE_CONFIG_PATH)) Gitlab.ee? && ee_workers != YAML.safe_load(File.read(EE_QUEUE_CONFIG_PATH))
end end
def queues_for_sidekiq_queues_yml
namespaces_with_equal_weights =
workers
.group_by(&:queue_namespace)
.map(&:last)
.select { |workers| workers.map(&:get_weight).uniq.count == 1 }
.map(&:first)
namespaces = namespaces_with_equal_weights.map(&:queue_namespace).to_set
remaining_queues = workers.reject { |worker| namespaces.include?(worker.queue_namespace) }
(namespaces_with_equal_weights.map(&:namespace_and_weight) +
remaining_queues.map(&:queue_and_weight)).sort
end
def sidekiq_queues_yml_outdated?
# YAML.load is OK here as we control the file contents
config_queues = YAML.load(File.read(SIDEKIQ_QUEUES_PATH))[:queues] # rubocop:disable Security/YAMLLoad
queues_for_sidekiq_queues_yml != config_queues
end
private private
def find_workers(root, ee:) def find_workers(root, ee:)
......
...@@ -7,8 +7,9 @@ module Gitlab ...@@ -7,8 +7,9 @@ module Gitlab
attr_reader :klass attr_reader :klass
delegate :feature_category_not_owned?, :get_feature_category, delegate :feature_category_not_owned?, :get_feature_category,
:get_worker_resource_boundary, :latency_sensitive_worker?, :get_weight, :get_worker_resource_boundary,
:queue, :worker_has_external_dependencies?, :latency_sensitive_worker?, :queue, :queue_namespace,
:worker_has_external_dependencies?,
to: :klass to: :klass
def initialize(klass, ee:) def initialize(klass, ee:)
...@@ -35,7 +36,7 @@ module Gitlab ...@@ -35,7 +36,7 @@ module Gitlab
# Put namespaced queues first # Put namespaced queues first
def to_sort def to_sort
[queue.include?(':') ? 0 : 1, queue] [queue_namespace ? 0 : 1, queue]
end end
# YAML representation # YAML representation
...@@ -46,6 +47,14 @@ module Gitlab ...@@ -46,6 +47,14 @@ module Gitlab
def to_yaml def to_yaml
queue queue
end end
def namespace_and_weight
[queue_namespace, get_weight]
end
def queue_and_weight
[queue, get_weight]
end
end end
end end
end end
...@@ -4,8 +4,13 @@ return if Rails.env.production? ...@@ -4,8 +4,13 @@ return if Rails.env.production?
namespace :gitlab do namespace :gitlab do
namespace :sidekiq do namespace :sidekiq do
def write_yaml(path, banner, object)
File.write(path, banner + YAML.dump(object))
end
namespace :all_queues_yml do namespace :all_queues_yml do
def write_yaml(path, object) desc 'GitLab | Sidekiq | Generate all_queues.yml based on worker definitions'
task generate: :environment do
banner = <<~BANNER banner = <<~BANNER
# This file is generated automatically by # This file is generated automatically by
# bin/rake gitlab:sidekiq:all_queues_yml:generate # bin/rake gitlab:sidekiq:all_queues_yml:generate
...@@ -13,17 +18,12 @@ namespace :gitlab do ...@@ -13,17 +18,12 @@ namespace :gitlab do
# Do not edit it manually! # Do not edit it manually!
BANNER BANNER
File.write(path, banner + YAML.dump(object))
end
desc 'GitLab | Sidekiq | Generate all_queues.yml based on worker definitions'
task generate: :environment do
foss_workers, ee_workers = Gitlab::SidekiqConfig.workers_for_all_queues_yml foss_workers, ee_workers = Gitlab::SidekiqConfig.workers_for_all_queues_yml
write_yaml(Gitlab::SidekiqConfig::FOSS_QUEUE_CONFIG_PATH, foss_workers) write_yaml(Gitlab::SidekiqConfig::FOSS_QUEUE_CONFIG_PATH, banner, foss_workers)
if Gitlab.ee? if Gitlab.ee?
write_yaml(Gitlab::SidekiqConfig::EE_QUEUE_CONFIG_PATH, ee_workers) write_yaml(Gitlab::SidekiqConfig::EE_QUEUE_CONFIG_PATH, banner, ee_workers)
end end
end end
...@@ -44,5 +44,57 @@ namespace :gitlab do ...@@ -44,5 +44,57 @@ namespace :gitlab do
end end
end end
end end
namespace :sidekiq_queues_yml do
desc 'GitLab | Sidekiq | Generate sidekiq_queues.yml based on worker definitions'
task generate: :environment do
banner = <<~BANNER
# This file is generated automatically by
# bin/rake gitlab:sidekiq:sidekiq_queues_yml:generate
#
# Do not edit it manually!
#
# This configuration file should be exclusively used to set queue settings for
# Sidekiq. Any other setting should be specified using the Sidekiq CLI or the
# Sidekiq Ruby API (see config/initializers/sidekiq.rb).
#
# All the queues to process and their weights. Every queue _must_ have a weight
# defined.
#
# The available weights are as follows
#
# 1: low priority
# 2: medium priority
# 3: high priority
# 5: _super_ high priority, this should only be used for _very_ important queues
#
# As per http://stackoverflow.com/a/21241357/290102 the formula for calculating
# the likelihood of a job being popped off a queue (given all queues have work
# to perform) is:
#
# chance = (queue weight / total weight of all queues) * 100
BANNER
queues_and_weights = Gitlab::SidekiqConfig.queues_for_sidekiq_queues_yml
write_yaml(Gitlab::SidekiqConfig::SIDEKIQ_QUEUES_PATH, banner, queues: queues_and_weights)
end
desc 'GitLab | Sidekiq | Validate that sidekiq_queues.yml matches worker definitions'
task check: :environment do
if Gitlab::SidekiqConfig.sidekiq_queues_yml_outdated?
raise <<~MSG
Changes in worker queues found, please update the metadata by running:
bin/rake gitlab:sidekiq:sidekiq_queues_yml:generate
Then commit and push the changes from:
- #{Gitlab::SidekiqConfig::SIDEKIQ_QUEUES_PATH}
MSG
end
end
end
end end
end end
...@@ -38,11 +38,13 @@ unless Rails.env.production? ...@@ -38,11 +38,13 @@ unless Rails.env.production?
] ]
if Gitlab.ee? if Gitlab.ee?
# This task will fail on CE installations (e.g. gitlab-org/gitlab-foss) # These tasks will fail on FOSS installations
# since it will detect strings in the locale files that do not exist in # (e.g. gitlab-org/gitlab-foss) since they test against a single
# the source files. To work around this we will only enable this task on # file that is generated by an EE installation, which can
# EE installations. # contain values that a FOSS installation won't find. To work
# around this we will only enable this task on EE installations.
tasks << 'gettext:updated_check' tasks << 'gettext:updated_check'
tasks << 'gitlab:sidekiq:sidekiq_queues_yml:check'
end end
tasks.each do |task| tasks.each do |task|
......
...@@ -10419,6 +10419,9 @@ msgstr "" ...@@ -10419,6 +10419,9 @@ msgstr ""
msgid "Is using license seat:" msgid "Is using license seat:"
msgstr "" msgstr ""
msgid "Is using seat"
msgstr ""
msgid "IssuableStatus|Closed" msgid "IssuableStatus|Closed"
msgstr "" msgstr ""
......
...@@ -13,6 +13,10 @@ module QA::Page ...@@ -13,6 +13,10 @@ module QA::Page
element :pipeline_path element :pipeline_path
end end
view 'app/assets/javascripts/jobs/components/sidebar.vue' do
element :retry_button
end
def successful?(timeout: 60) def successful?(timeout: 60)
raise "Timed out waiting for the build trace to load" unless loaded? raise "Timed out waiting for the build trace to load" unless loaded?
raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout) raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout)
...@@ -33,6 +37,10 @@ module QA::Page ...@@ -33,6 +37,10 @@ module QA::Page
result result
end end
def retry!
click_element :retry_button
end
private private
def loaded?(wait: 60) def loaded?(wait: 60)
......
...@@ -52,6 +52,14 @@ module QA ...@@ -52,6 +52,14 @@ module QA
end end
end end
def remove_variable(location: :first)
within('.ci-variable-row-body', match: location) do
find('button.ci-variable-row-remove-button').click
end
save_variables
end
private private
def toggle_masked(masked_node, masked) def toggle_masked(masked_node, masked)
......
...@@ -46,12 +46,7 @@ module QA ...@@ -46,12 +46,7 @@ module QA
deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}" deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}"
Resource::CiVariable.fabricate_via_browser_ui! do |resource| make_ci_variable(deploy_key_name, key)
resource.project = @project
resource.key = deploy_key_name
resource.value = key.private_key
resource.masked = false
end
gitlab_ci = <<~YAML gitlab_ci = <<~YAML
cat-config: cat-config:
...@@ -90,6 +85,17 @@ module QA ...@@ -90,6 +85,17 @@ module QA
expect(job.output).to include(sha1sum) expect(job.output).to include(sha1sum)
end end
end end
private
def make_ci_variable(key_name, key)
Resource::CiVariable.fabricate_via_api! do |resource|
resource.project = @project
resource.key = key_name
resource.value = key.private_key
resource.masked = false
end
end
end end
end end
end end
......
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
describe LfsRequest do describe LfsRequest do
include ProjectForksHelper include ProjectForksHelper
controller(Projects::GitHttpClientController) do controller(Repositories::GitHttpClientController) do
# `described_class` is not available in this context # `described_class` is not available in this context
include LfsRequest include LfsRequest
......
# frozen_string_literal: true
require 'spec_helper'
describe Projects::GitHttpController do
include GitHttpHelpers
let_it_be(:project) { create(:project, :public, :repository) }
let(:project_params) do
{
namespace_id: project.namespace.to_param,
project_id: project.path + '.git'
}
end
let(:params) { project_params }
describe 'HEAD #info_refs' do
it 'returns 403' do
head :info_refs, params: { namespace_id: project.namespace.to_param, project_id: project.path + '.git' }
expect(response.status).to eq(403)
end
end
describe 'GET #info_refs' do
let(:params) { project_params.merge(service: 'git-upload-pack') }
it 'returns 401 for unauthenticated requests to public repositories when http protocol is disabled' do
stub_application_setting(enabled_git_access_protocol: 'ssh')
get :info_refs, params: params
expect(response.status).to eq(401)
end
context 'with authorized user' do
let(:user) { project.owner }
before do
request.headers.merge! auth_env(user.username, user.password, nil)
end
it 'returns 200' do
get :info_refs, params: params
expect(response.status).to eq(200)
end
it 'updates the user activity' do
expect_next_instance_of(Users::ActivityService) do |activity_service|
expect(activity_service).to receive(:execute)
end
get :info_refs, params: params
end
end
context 'with exceptions' do
before do
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
end
it 'returns 503 with GRPC Unavailable' do
allow(controller).to receive(:access_check).and_raise(GRPC::Unavailable)
get :info_refs, params: params
expect(response.status).to eq(503)
end
it 'returns 503 with timeout error' do
allow(controller).to receive(:access_check).and_raise(Gitlab::GitAccess::TimeoutError)
get :info_refs, params: params
expect(response.status).to eq(503)
expect(response.body).to eq 'Gitlab::GitAccess::TimeoutError'
end
end
end
describe 'POST #git_upload_pack' do
before do
allow(controller).to receive(:authenticate_user).and_return(true)
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
allow(controller).to receive(:access_check).and_return(nil)
end
after do
post :git_upload_pack, params: params
end
context 'on a read-only instance' do
before do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
end
it 'does not update project statistics' do
expect(ProjectDailyStatisticsWorker).not_to receive(:perform_async)
end
end
it 'updates project statistics' do
expect(ProjectDailyStatisticsWorker).to receive(:perform_async)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Repositories::GitHttpController do
include GitHttpHelpers
let_it_be(:project) { create(:project, :public, :repository) }
let(:namespace_id) { project.namespace.to_param }
let(:repository_id) { project.path + '.git' }
let(:project_params) do
{
namespace_id: namespace_id,
repository_id: repository_id
}
end
let(:params) { project_params }
describe 'HEAD #info_refs' do
it 'returns 403' do
head :info_refs, params: params
expect(response.status).to eq(403)
end
end
shared_examples 'info_refs behavior' do
describe 'GET #info_refs' do
let(:params) { project_params.merge(service: 'git-upload-pack') }
it 'returns 401 for unauthenticated requests to public repositories when http protocol is disabled' do
stub_application_setting(enabled_git_access_protocol: 'ssh')
allow(controller).to receive(:basic_auth_provided?).and_call_original
expect(controller).to receive(:http_download_allowed?).and_call_original
get :info_refs, params: params
expect(response.status).to eq(401)
end
context 'with authorized user' do
let(:user) { project.owner }
before do
request.headers.merge! auth_env(user.username, user.password, nil)
end
it 'returns 200' do
get :info_refs, params: params
expect(response.status).to eq(200)
end
it 'updates the user activity' do
expect_next_instance_of(Users::ActivityService) do |activity_service|
expect(activity_service).to receive(:execute)
end
get :info_refs, params: params
end
end
context 'with exceptions' do
before do
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
end
it 'returns 503 with GRPC Unavailable' do
allow(controller).to receive(:access_check).and_raise(GRPC::Unavailable)
get :info_refs, params: params
expect(response.status).to eq(503)
end
it 'returns 503 with timeout error' do
allow(controller).to receive(:access_check).and_raise(Gitlab::GitAccess::TimeoutError)
get :info_refs, params: params
expect(response.status).to eq(503)
expect(response.body).to eq 'Gitlab::GitAccess::TimeoutError'
end
end
end
end
shared_examples 'git_upload_pack behavior' do |expected|
describe 'POST #git_upload_pack' do
before do
allow(controller).to receive(:authenticate_user).and_return(true)
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
allow(controller).to receive(:access_check).and_return(nil)
end
after do
post :git_upload_pack, params: params
end
context 'on a read-only instance' do
before do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
end
it 'does not update project statistics' do
expect(ProjectDailyStatisticsWorker).not_to receive(:perform_async)
end
end
if expected
it 'updates project statistics' do
expect(ProjectDailyStatisticsWorker).to receive(:perform_async)
end
else
it 'does not update project statistics' do
expect(ProjectDailyStatisticsWorker).not_to receive(:perform_async)
end
end
end
end
shared_examples 'access checker class' do
let(:params) { project_params.merge(service: 'git-upload-pack') }
it 'calls the right access class checker with the right object' do
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
access_double = double
expect(expected_class).to receive(:new).with(anything, expected_object, 'http', anything).and_return(access_double)
allow(access_double).to receive(:check).and_return(false)
get :info_refs, params: params
end
end
context 'when repository container is a project' do
it_behaves_like 'info_refs behavior'
it_behaves_like 'git_upload_pack behavior', true
it_behaves_like 'access checker class' do
let(:expected_class) { Gitlab::GitAccess }
let(:expected_object) { project }
end
end
end
...@@ -20,6 +20,7 @@ describe GitlabSchema.types['SentryDetailedError'] do ...@@ -20,6 +20,7 @@ describe GitlabSchema.types['SentryDetailedError'] do
message message
culprit culprit
externalUrl externalUrl
externalBaseUrl
sentryProjectId sentryProjectId
sentryProjectName sentryProjectName
sentryProjectSlug sentryProjectSlug
...@@ -30,8 +31,10 @@ describe GitlabSchema.types['SentryDetailedError'] do ...@@ -30,8 +31,10 @@ describe GitlabSchema.types['SentryDetailedError'] do
lastReleaseLastCommit lastReleaseLastCommit
firstReleaseShortVersion firstReleaseShortVersion
lastReleaseShortVersion lastReleaseShortVersion
gitlabIssuePath
gitlabCommit gitlabCommit
gitlabCommitPath gitlabCommitPath
tags
] ]
is_expected.to have_graphql_fields(*expected_fields) is_expected.to have_graphql_fields(*expected_fields)
......
...@@ -66,6 +66,53 @@ describe Gitaly::Server do ...@@ -66,6 +66,53 @@ describe Gitaly::Server do
end end
end end
context "when examining disk statistics for a given server" do
let(:disk_available) { 42 }
let(:disk_used) { 42 }
let(:storage_status) { double('storage_status') }
before do
allow(storage_status).to receive(:storage_name).and_return('default')
allow(storage_status).to receive(:available).and_return(disk_available)
allow(storage_status).to receive(:used).and_return(disk_used)
response = double("response")
allow(response).to receive(:storage_statuses).and_return([storage_status])
allow_next_instance_of(Gitlab::GitalyClient::ServerService) do |instance|
allow(instance).to receive(:disk_statistics).and_return(response)
end
end
describe '#disk_available' do
subject { server.disk_available }
it { is_expected.to be_present }
it "returns disk available for the storage of the instantiated server" do
is_expected.to eq(disk_available)
end
end
describe '#disk_used' do
subject { server.disk_used }
it { is_expected.to be_present }
it "returns disk used for the storage of the instantiated server" do
is_expected.to eq(disk_used)
end
end
describe '#disk_stats' do
subject { server.disk_stats }
it { is_expected.to be_present }
it "returns the storage of the instantiated server" do
is_expected.to eq(storage_status)
end
end
end
describe '#expected_version?' do describe '#expected_version?' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
......
...@@ -52,7 +52,7 @@ describe Gitlab::GitalyClient do ...@@ -52,7 +52,7 @@ describe Gitlab::GitalyClient do
end end
describe '.filesystem_id' do describe '.filesystem_id' do
it 'returns an empty string when the storage is not found in the response' do it 'returns an empty string when the relevant storage status is not found in the response' do
response = double("response") response = double("response")
allow(response).to receive(:storage_statuses).and_return([]) allow(response).to receive(:storage_statuses).and_return([])
allow_next_instance_of(Gitlab::GitalyClient::ServerService) do |instance| allow_next_instance_of(Gitlab::GitalyClient::ServerService) do |instance|
...@@ -63,6 +63,63 @@ describe Gitlab::GitalyClient do ...@@ -63,6 +63,63 @@ describe Gitlab::GitalyClient do
end end
end end
context 'when the relevant storage status is not found' do
before do
response = double('response')
allow(response).to receive(:storage_statuses).and_return([])
allow_next_instance_of(Gitlab::GitalyClient::ServerService) do |instance|
allow(instance).to receive(:disk_statistics).and_return(response)
expect(instance).to receive(:storage_disk_statistics)
end
end
describe '.filesystem_disk_available' do
it 'returns nil when the relevant storage status is not found in the response' do
expect(described_class.filesystem_disk_available('default')).to eq(nil)
end
end
describe '.filesystem_disk_used' do
it 'returns nil when the relevant storage status is not found in the response' do
expect(described_class.filesystem_disk_used('default')).to eq(nil)
end
end
end
context 'when the relevant storage status is found' do
let(:disk_available) { 42 }
let(:disk_used) { 42 }
let(:storage_status) { double('storage_status') }
before do
allow(storage_status).to receive(:storage_name).and_return('default')
allow(storage_status).to receive(:used).and_return(disk_used)
allow(storage_status).to receive(:available).and_return(disk_available)
response = double('response')
allow(response).to receive(:storage_statuses).and_return([storage_status])
allow_next_instance_of(Gitlab::GitalyClient::ServerService) do |instance|
allow(instance).to receive(:disk_statistics).and_return(response)
end
expect_next_instance_of(Gitlab::GitalyClient::ServerService) do |instance|
expect(instance).to receive(:storage_disk_statistics).and_return(storage_status)
end
end
describe '.filesystem_disk_available' do
it 'returns disk available when the relevant storage status is found in the response' do
expect(storage_status).to receive(:available)
expect(described_class.filesystem_disk_available('default')).to eq(disk_available)
end
end
describe '.filesystem_disk_used' do
it 'returns disk used when the relevant storage status is found in the response' do
expect(storage_status).to receive(:used)
expect(described_class.filesystem_disk_used('default')).to eq(disk_used)
end
end
end
describe '.stub_class' do describe '.stub_class' do
it 'returns the gRPC health check stub' do it 'returns the gRPC health check stub' do
expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub) expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub)
......
...@@ -6,6 +6,7 @@ describe Gitlab::ImportExport::ImportFailureService do ...@@ -6,6 +6,7 @@ describe Gitlab::ImportExport::ImportFailureService do
let(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') } let(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') }
let(:label) { create(:label) } let(:label) { create(:label) }
let(:subject) { described_class.new(importable) } let(:subject) { described_class.new(importable) }
let(:action) { "save_relation" }
let(:relation_key) { "labels" } let(:relation_key) { "labels" }
let(:relation_index) { 0 } let(:relation_index) { 0 }
...@@ -15,7 +16,12 @@ describe Gitlab::ImportExport::ImportFailureService do ...@@ -15,7 +16,12 @@ describe Gitlab::ImportExport::ImportFailureService do
let(:correlation_id) { 'my-correlation-id' } let(:correlation_id) { 'my-correlation-id' }
let(:retry_count) { 2 } let(:retry_count) { 2 }
let(:log_import_failure) do let(:log_import_failure) do
subject.log_import_failure(relation_key, relation_index, exception, retry_count) subject.log_import_failure(
source: action,
relation_key: relation_key,
relation_index: relation_index,
exception: exception,
retry_count: retry_count)
end end
before do before do
...@@ -44,7 +50,7 @@ describe Gitlab::ImportExport::ImportFailureService do ...@@ -44,7 +50,7 @@ describe Gitlab::ImportExport::ImportFailureService do
describe '#with_retry' do describe '#with_retry' do
let(:perform_retry) do let(:perform_retry) do
subject.with_retry(relation_key, relation_index) do subject.with_retry(action: action, relation_key: relation_key, relation_index: relation_index) do
label.save! label.save!
end end
end end
...@@ -60,7 +66,12 @@ describe Gitlab::ImportExport::ImportFailureService do ...@@ -60,7 +66,12 @@ describe Gitlab::ImportExport::ImportFailureService do
end end
it 'retries and logs import failure once with correct params' do it 'retries and logs import failure once with correct params' do
expect(subject).to receive(:log_import_failure).with(relation_key, relation_index, instance_of(exception), 1).once expect(subject).to receive(:log_import_failure).with(
source: action,
relation_key: relation_key,
relation_index: relation_index,
exception: instance_of(exception),
retry_count: 1).once
perform_retry perform_retry
end end
...@@ -85,7 +96,11 @@ describe Gitlab::ImportExport::ImportFailureService do ...@@ -85,7 +96,11 @@ describe Gitlab::ImportExport::ImportFailureService do
maximum_retry_count.times do |index| maximum_retry_count.times do |index|
retry_count = index + 1 retry_count = index + 1
expect(subject).to receive(:log_import_failure).with(relation_key, relation_index, instance_of(exception), retry_count) expect(subject).to receive(:log_import_failure).with(
source: action, relation_key: relation_key,
relation_index: relation_index,
exception: instance_of(exception),
retry_count: retry_count)
end end
expect { perform_retry }.to raise_exception(exception) expect { perform_retry }.to raise_exception(exception)
......
...@@ -498,6 +498,58 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -498,6 +498,58 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end end
end end
context 'when post import action throw non-retriable exception' do
let(:exception) { StandardError.new('post_import_error') }
before do
setup_import_export_config('light')
expect(project)
.to receive(:merge_requests)
.and_raise(exception)
end
it 'report post import error' do
expect(restored_project_json).to eq(false)
expect(shared.errors).to include('post_import_error')
end
end
context 'when post import action throw retriable exception one time' do
let(:exception) { GRPC::DeadlineExceeded.new }
before do
setup_import_export_config('light')
expect(project)
.to receive(:merge_requests)
.and_raise(exception)
expect(project)
.to receive(:merge_requests)
.and_call_original
expect(restored_project_json).to eq(true)
end
it_behaves_like 'restores project successfully',
issues: 1,
labels: 2,
label_with_priorities: 'A project label',
milestones: 1,
first_issue_labels: 1,
services: 1,
import_failures: 1
it 'records the failures in the database' do
import_failure = ImportFailure.last
expect(import_failure.project_id).to eq(project.id)
expect(import_failure.relation_key).to be_nil
expect(import_failure.relation_index).to be_nil
expect(import_failure.exception_class).to eq('GRPC::DeadlineExceeded')
expect(import_failure.exception_message).to be_present
expect(import_failure.correlation_id_value).not_to be_empty
expect(import_failure.created_at).to be_present
end
end
context 'when the project has overridden params in import data' do context 'when the project has overridden params in import data' do
before do before do
setup_import_export_config('light') setup_import_export_config('light')
......
...@@ -3,8 +3,11 @@ ...@@ -3,8 +3,11 @@
require 'fast_spec_helper' require 'fast_spec_helper'
describe Gitlab::SidekiqConfig::Worker do describe Gitlab::SidekiqConfig::Worker do
def worker_with_queue(queue) def create_worker(queue:, weight: 0)
described_class.new(double(queue: queue), ee: false) namespace = queue.include?(':') && queue.split(':').first
inner_worker = double(queue: queue, queue_namespace: namespace, get_weight: weight)
described_class.new(inner_worker, ee: false)
end end
describe '#ee?' do describe '#ee?' do
...@@ -34,9 +37,9 @@ describe Gitlab::SidekiqConfig::Worker do ...@@ -34,9 +37,9 @@ describe Gitlab::SidekiqConfig::Worker do
describe 'delegations' do describe 'delegations' do
[ [
:feature_category_not_owned?, :get_feature_category, :feature_category_not_owned?, :get_feature_category, :get_weight,
:get_worker_resource_boundary, :latency_sensitive_worker?, :queue, :get_worker_resource_boundary, :latency_sensitive_worker?, :queue,
:worker_has_external_dependencies? :queue_namespace, :worker_has_external_dependencies?
].each do |meth| ].each do |meth|
it "delegates #{meth} to the worker class" do it "delegates #{meth} to the worker class" do
worker = double worker = double
...@@ -50,8 +53,8 @@ describe Gitlab::SidekiqConfig::Worker do ...@@ -50,8 +53,8 @@ describe Gitlab::SidekiqConfig::Worker do
describe 'sorting' do describe 'sorting' do
it 'sorts queues with a namespace before those without a namespace' do it 'sorts queues with a namespace before those without a namespace' do
namespaced_worker = worker_with_queue('namespace:queue') namespaced_worker = create_worker(queue: 'namespace:queue')
plain_worker = worker_with_queue('a_queue') plain_worker = create_worker(queue: 'a_queue')
expect([plain_worker, namespaced_worker].sort) expect([plain_worker, namespaced_worker].sort)
.to eq([namespaced_worker, plain_worker]) .to eq([namespaced_worker, plain_worker])
...@@ -59,12 +62,12 @@ describe Gitlab::SidekiqConfig::Worker do ...@@ -59,12 +62,12 @@ describe Gitlab::SidekiqConfig::Worker do
it 'sorts alphabetically by queue' do it 'sorts alphabetically by queue' do
workers = [ workers = [
worker_with_queue('namespace:a'), create_worker(queue: 'namespace:a'),
worker_with_queue('namespace:b'), create_worker(queue: 'namespace:b'),
worker_with_queue('other_namespace:a'), create_worker(queue: 'other_namespace:a'),
worker_with_queue('other_namespace:b'), create_worker(queue: 'other_namespace:b'),
worker_with_queue('a'), create_worker(queue: 'a'),
worker_with_queue('b') create_worker(queue: 'b')
] ]
expect(workers.shuffle.sort).to eq(workers) expect(workers.shuffle.sort).to eq(workers)
...@@ -73,12 +76,26 @@ describe Gitlab::SidekiqConfig::Worker do ...@@ -73,12 +76,26 @@ describe Gitlab::SidekiqConfig::Worker do
describe 'YAML encoding' do describe 'YAML encoding' do
it 'encodes the worker in YAML as a string of the queue' do it 'encodes the worker in YAML as a string of the queue' do
worker_a = worker_with_queue('a') worker_a = create_worker(queue: 'a')
worker_b = worker_with_queue('b') worker_b = create_worker(queue: 'b')
expect(YAML.dump(worker_a)).to eq(YAML.dump('a')) expect(YAML.dump(worker_a)).to eq(YAML.dump('a'))
expect(YAML.dump([worker_a, worker_b])) expect(YAML.dump([worker_a, worker_b]))
.to eq(YAML.dump(%w[a b])) .to eq(YAML.dump(%w[a b]))
end end
end end
describe '#namespace_and_weight' do
it 'returns a namespace, weight pair for the worker' do
expect(create_worker(queue: 'namespace:a', weight: 2).namespace_and_weight)
.to eq(['namespace', 2])
end
end
describe '#queue_and_weight' do
it 'returns a queue, weight pair for the worker' do
expect(create_worker(queue: 'namespace:a', weight: 2).queue_and_weight)
.to eq(['namespace:a', 2])
end
end
end end
...@@ -80,4 +80,64 @@ describe Gitlab::SidekiqConfig do ...@@ -80,4 +80,64 @@ describe Gitlab::SidekiqConfig do
expect(described_class.all_queues_yml_outdated?).to be(false) expect(described_class.all_queues_yml_outdated?).to be(false)
end end
end end
describe '.queues_for_sidekiq_queues_yml' do
before do
workers = [
Namespaces::RootStatisticsWorker,
Namespaces::ScheduleAggregationWorker,
MergeWorker,
ProcessCommitWorker
].map { |worker| described_class::Worker.new(worker, ee: false) }
allow(described_class).to receive(:workers).and_return(workers)
end
it 'returns queues and weights, aggregating namespaces with the same weight' do
expected_queues = [
['merge', 5],
['process_commit', 3],
['update_namespace_statistics', 1]
]
expect(described_class.queues_for_sidekiq_queues_yml).to eq(expected_queues)
end
end
describe '.sidekiq_queues_yml_outdated?' do
before do
workers = [
Namespaces::RootStatisticsWorker,
Namespaces::ScheduleAggregationWorker,
MergeWorker,
ProcessCommitWorker
].map { |worker| described_class::Worker.new(worker, ee: false) }
allow(described_class).to receive(:workers).and_return(workers)
end
let(:expected_queues) do
[
['merge', 5],
['process_commit', 3],
['update_namespace_statistics', 1]
]
end
it 'returns true if the YAML file does not match the application code' do
allow(File).to receive(:read)
.with(described_class::SIDEKIQ_QUEUES_PATH)
.and_return(YAML.dump(queues: expected_queues.reverse))
expect(described_class.sidekiq_queues_yml_outdated?).to be(true)
end
it 'returns false if the YAML file matches the application code' do
allow(File).to receive(:read)
.with(described_class::SIDEKIQ_QUEUES_PATH)
.and_return(YAML.dump(queues: expected_queues))
expect(described_class.sidekiq_queues_yml_outdated?).to be(false)
end
end
end end
...@@ -2391,6 +2391,8 @@ describe Ci::Build do ...@@ -2391,6 +2391,8 @@ describe Ci::Build do
{ key: 'GITLAB_CI', value: 'true', public: true, masked: false }, { key: 'GITLAB_CI', value: 'true', public: true, masked: false },
{ key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url, public: true, masked: false }, { key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url, public: true, masked: false },
{ key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host, public: true, masked: false }, { key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host, public: true, masked: false },
{ key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s, public: true, masked: false },
{ key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol, public: true, masked: false },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false }, { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false }, { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false },
{ key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false }, { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false },
......
...@@ -8,22 +8,10 @@ describe API::BroadcastMessages do ...@@ -8,22 +8,10 @@ describe API::BroadcastMessages do
set(:message) { create(:broadcast_message) } set(:message) { create(:broadcast_message) }
describe 'GET /broadcast_messages' do describe 'GET /broadcast_messages' do
it 'returns a 401 for anonymous users' do it 'returns an Array of BroadcastMessages' do
get api('/broadcast_messages')
expect(response).to have_gitlab_http_status(401)
end
it 'returns a 403 for users' do
get api('/broadcast_messages', user)
expect(response).to have_gitlab_http_status(403)
end
it 'returns an Array of BroadcastMessages for admins' do
create(:broadcast_message) create(:broadcast_message)
get api('/broadcast_messages', admin) get api('/broadcast_messages')
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
...@@ -34,21 +22,9 @@ describe API::BroadcastMessages do ...@@ -34,21 +22,9 @@ describe API::BroadcastMessages do
end end
describe 'GET /broadcast_messages/:id' do describe 'GET /broadcast_messages/:id' do
it 'returns a 401 for anonymous users' do it 'returns the specified message' do
get api("/broadcast_messages/#{message.id}") get api("/broadcast_messages/#{message.id}")
expect(response).to have_gitlab_http_status(401)
end
it 'returns a 403 for users' do
get api("/broadcast_messages/#{message.id}", user)
expect(response).to have_gitlab_http_status(403)
end
it 'returns the specified message for admins' do
get api("/broadcast_messages/#{message.id}", admin)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response['id']).to eq message.id expect(json_response['id']).to eq message.id
expect(json_response.keys) expect(json_response.keys)
......
...@@ -57,6 +57,10 @@ describe 'getting a detailed sentry error' do ...@@ -57,6 +57,10 @@ describe 'getting a detailed sentry error' do
expect(error_data['firstSeen']).to eql sentry_detailed_error.first_seen expect(error_data['firstSeen']).to eql sentry_detailed_error.first_seen
expect(error_data['lastSeen']).to eql sentry_detailed_error.last_seen expect(error_data['lastSeen']).to eql sentry_detailed_error.last_seen
expect(error_data['gitlabCommit']).to be nil expect(error_data['gitlabCommit']).to be nil
expect(error_data['externalBaseUrl']).to eq sentry_detailed_error.external_base_url
expect(error_data['gitlabIssuePath']).to eq sentry_detailed_error.gitlab_issue
expect(error_data['tags']['logger']).to eq sentry_detailed_error.tags[:logger]
expect(error_data['tags']['level']).to eq sentry_detailed_error.tags[:level]
end end
it 'is expected to return the frequency correctly' do it 'is expected to return the frequency correctly' do
......
...@@ -326,7 +326,7 @@ describe API::Internal::Base do ...@@ -326,7 +326,7 @@ describe API::Internal::Base do
expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-get-tag-messages-go' => 'true', 'gitaly-feature-filter-shas-with-signatures-go' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true') expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-filter-shas-with-signatures-go' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true')
expect(user.reload.last_activity_on).to eql(Date.today) expect(user.reload.last_activity_on).to eql(Date.today)
end end
end end
...@@ -346,7 +346,7 @@ describe API::Internal::Base do ...@@ -346,7 +346,7 @@ describe API::Internal::Base do
expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-get-tag-messages-go' => 'true', 'gitaly-feature-filter-shas-with-signatures-go' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true') expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-filter-shas-with-signatures-go' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true')
expect(user.reload.last_activity_on).to be_nil expect(user.reload.last_activity_on).to be_nil
end end
end end
...@@ -594,7 +594,7 @@ describe API::Internal::Base do ...@@ -594,7 +594,7 @@ describe API::Internal::Base do
expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-get-tag-messages-go' => 'true', 'gitaly-feature-filter-shas-with-signatures-go' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true') expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-filter-shas-with-signatures-go' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true')
end end
end end
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment