Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/api/entities.rb16
-rw-r--r--lib/api/helpers/headers_helpers.rb6
-rw-r--r--lib/api/internal.rb55
-rw-r--r--lib/api/issues.rb6
-rw-r--r--lib/api/members.rb5
-rw-r--r--lib/api/projects.rb67
-rw-r--r--lib/api/runner.rb34
-rw-r--r--lib/api/settings.rb150
-rw-r--r--lib/api/users.rb35
-rw-r--r--lib/banzai/pipeline/emoji_pipeline.rb17
-rw-r--r--lib/feature.rb4
-rw-r--r--lib/gitlab/auth/activity.rb77
-rw-r--r--lib/gitlab/auth/blocked_user_tracker.rb59
-rw-r--r--lib/gitlab/bare_repository_import/importer.rb36
-rw-r--r--lib/gitlab/bare_repository_import/repository.rb2
-rw-r--r--lib/gitlab/ci/config/entry/artifacts.rb13
-rw-r--r--lib/gitlab/ci/config/entry/commands.rb13
-rw-r--r--lib/gitlab/ci/config/entry/reports.rb32
-rw-r--r--lib/gitlab/ci/config/entry/validators.rb14
-rw-r--r--lib/gitlab/ci/status/build/failed.rb19
-rw-r--r--lib/gitlab/ci/trace.rb2
-rw-r--r--lib/gitlab/cleanup/project_upload_file_finder.rb66
-rw-r--r--lib/gitlab/cleanup/project_uploads.rb125
-rw-r--r--lib/gitlab/email/handler/create_issue_handler.rb4
-rw-r--r--lib/gitlab/email/handler/create_merge_request_handler.rb4
-rw-r--r--lib/gitlab/email/handler/create_note_handler.rb4
-rw-r--r--lib/gitlab/email/handler/unsubscribe_handler.rb4
-rw-r--r--lib/gitlab/git/conflict/resolver.rb2
-rw-r--r--lib/gitlab/git/repository.rb43
-rw-r--r--lib/gitlab/git/repository_mirroring.rb4
-rw-r--r--lib/gitlab/git_post_receive.rb12
-rw-r--r--lib/gitlab/github_import/importer/pull_requests_importer.rb2
-rw-r--r--lib/gitlab/gpg.rb12
-rw-r--r--lib/gitlab/graphql/authorize.rb9
-rw-r--r--lib/gitlab/graphql/authorize/authorize_resource.rb46
-rw-r--r--lib/gitlab/graphql/errors.rb1
-rw-r--r--lib/gitlab/graphql/mount_mutation.rb18
-rw-r--r--lib/gitlab/graphs/commits.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/import_export/merge_request_parser.rb6
-rw-r--r--lib/gitlab/import_sources.rb12
-rw-r--r--lib/gitlab/kubernetes/config_map.rb10
-rw-r--r--lib/gitlab/kubernetes/helm.rb2
-rw-r--r--lib/gitlab/kubernetes/helm/api.rb20
-rw-r--r--lib/gitlab/language_detection.rb68
-rw-r--r--lib/gitlab/middleware/basic_health_check.rb43
-rw-r--r--lib/gitlab/prometheus/metric.rb2
-rw-r--r--lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb1
-rw-r--r--lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb1
-rw-r--r--lib/gitlab/prometheus/queries/query_additional_metrics.rb8
-rw-r--r--lib/gitlab/template_helper.rb22
-rw-r--r--lib/tasks/gitlab/check.rake25
-rw-r--r--lib/tasks/gitlab/cleanup.rake39
-rw-r--r--lib/tasks/gitlab/git.rake85
54 files changed, 985 insertions, 380 deletions
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index e883687f2db..f858d9fa23d 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -62,6 +62,14 @@ module API
expose :admin?, as: :is_admin
end
+ class UserStatus < Grape::Entity
+ expose :emoji
+ expose :message
+ expose :message_html do |entity|
+ MarkupHelper.markdown_field(entity, :message)
+ end
+ end
+
class Email < Grape::Entity
expose :id, :email
end
@@ -1236,7 +1244,13 @@ module API
end
class Artifacts < Grape::Entity
- expose :name, :untracked, :paths, :when, :expire_in
+ expose :name
+ expose :untracked
+ expose :paths
+ expose :when
+ expose :expire_in
+ expose :artifact_type
+ expose :artifact_format
end
class Cache < Grape::Entity
diff --git a/lib/api/helpers/headers_helpers.rb b/lib/api/helpers/headers_helpers.rb
index cde51fccc62..c9c44e3c218 100644
--- a/lib/api/helpers/headers_helpers.rb
+++ b/lib/api/helpers/headers_helpers.rb
@@ -3,7 +3,11 @@ module API
module HeadersHelpers
def set_http_headers(header_data)
header_data.each do |key, value|
- header "X-Gitlab-#{key.to_s.split('_').collect(&:capitalize).join('-')}", value
+ if value.is_a?(Enumerable)
+ raise ArgumentError.new("Header value should be a string")
+ end
+
+ header "X-Gitlab-#{key.to_s.split('_').collect(&:capitalize).join('-')}", value.to_s
end
end
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index a9803be9f69..516f25db15b 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -11,7 +11,8 @@ module API
#
# Params:
# key_id - ssh key id for Git over SSH
- # user_id - user id for Git over HTTP
+ # user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode
+ # username - user name for Git over SSH in keyless SSH cert mode
# protocol - Git access protocol being used, e.g. HTTP or SSH
# project - project full_path (not path on disk)
# action - git action (git-upload-pack or git-receive-pack)
@@ -28,6 +29,8 @@ module API
Key.find_by(id: params[:key_id])
elsif params[:user_id]
User.find_by(id: params[:user_id])
+ elsif params[:username]
+ User.find_by_username(params[:username])
end
protocol = params[:protocol]
@@ -58,6 +61,7 @@ module API
{
status: true,
gl_repository: gl_repository,
+ gl_id: Gitlab::GlId.gl_id(user),
gl_username: user&.username,
# This repository_path is a bogus value but gitlab-shell still requires
@@ -71,10 +75,17 @@ module API
post "/lfs_authenticate" do
status 200
- key = Key.find(params[:key_id])
- key.update_last_used_at
+ if params[:key_id]
+ actor = Key.find(params[:key_id])
+ actor.update_last_used_at
+ elsif params[:user_id]
+ actor = User.find_by(id: params[:user_id])
+ raise ActiveRecord::RecordNotFound.new("No such user id!") unless actor
+ else
+ raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!")
+ end
- token_handler = Gitlab::LfsToken.new(key)
+ token_handler = Gitlab::LfsToken.new(actor)
{
username: token_handler.actor_name,
@@ -100,7 +111,7 @@ module API
end
#
- # Discover user by ssh key or user id
+ # Discover user by ssh key, user id or username
#
get "/discover" do
if params[:key_id]
@@ -108,6 +119,8 @@ module API
user = key.user
elsif params[:user_id]
user = User.find_by(id: params[:user_id])
+ elsif params[:username]
+ user = User.find_by(username: params[:username])
end
present user, with: Entities::UserSafe
@@ -141,22 +154,30 @@ module API
post '/two_factor_recovery_codes' do
status 200
- key = Key.find_by(id: params[:key_id])
+ if params[:key_id]
+ key = Key.find_by(id: params[:key_id])
- if key
- key.update_last_used_at
- else
- break { 'success' => false, 'message' => 'Could not find the given key' }
- end
+ if key
+ key.update_last_used_at
+ else
+ break { 'success' => false, 'message' => 'Could not find the given key' }
+ end
- if key.is_a?(DeployKey)
- break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' }
- end
+ if key.is_a?(DeployKey)
+ break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' }
+ end
+
+ user = key.user
- user = key.user
+ unless user
+ break { success: false, message: 'Could not find a user for the given key' }
+ end
+ elsif params[:user_id]
+ user = User.find_by(id: params[:user_id])
- unless user
- break { success: false, message: 'Could not find a user for the given key' }
+ unless user
+ break { success: false, message: 'Could not find the given user' }
+ end
end
unless user.two_factor_enabled?
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 25185d6edc8..bda05d1795b 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -162,6 +162,9 @@ module API
desc: 'The IID of a merge request for which to resolve discussions'
optional :discussion_to_resolve, type: String,
desc: 'The ID of a discussion to resolve, also pass `merge_request_to_resolve_discussions_of`'
+ optional :iid, type: Integer,
+ desc: 'The internal ID of a project issue. Available only for admins and project owners.'
+
use :issue_params
end
post ':id/issues' do
@@ -169,9 +172,10 @@ module API
authorize! :create_issue, user_project
- # Setting created_at time only allowed for admins and project owners
+ # Setting created_at time or iid only allowed for admins and project owners
unless current_user.admin? || user_project.owner == current_user
params.delete(:created_at)
+ params.delete(:iid)
end
issue_params = declared_params(include_missing: false)
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 3d2220fed96..d23dd834c69 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -75,7 +75,10 @@ module API
member = source.members.find_by(user_id: params[:user_id])
conflict!('Member already exists') if member
- member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at])
+ user = User.find_by_id(params[:user_id])
+ not_found!('User') unless user
+
+ member = source.add_user(user, params[:access_level], current_user: current_user, expires_at: params[:expires_at])
if !member
not_allowed! # This currently can only be reached in EE
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index eadde7b17bb..7adde79d6c3 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -13,6 +13,10 @@ module API
# EE::API::Projects would override this helper
end
+ params :optional_update_params_ee do
+ # EE::API::Projects would override this helper
+ end
+
# EE::API::Projects would override this method
def apply_filters(projects)
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
@@ -21,6 +25,37 @@ module API
projects
end
+
+ def verify_update_project_attrs!(project, attrs)
+ end
+ end
+
+ def self.update_params_at_least_one_of
+ [
+ :jobs_enabled,
+ :resolve_outdated_diff_discussions,
+ :ci_config_path,
+ :container_registry_enabled,
+ :default_branch,
+ :description,
+ :issues_enabled,
+ :lfs_enabled,
+ :merge_requests_enabled,
+ :merge_method,
+ :name,
+ :only_allow_merge_if_all_discussions_are_resolved,
+ :only_allow_merge_if_pipeline_succeeds,
+ :path,
+ :printing_merge_request_link_enabled,
+ :public_builds,
+ :request_access_enabled,
+ :shared_runners_enabled,
+ :snippets_enabled,
+ :tag_list,
+ :visibility,
+ :wiki_enabled,
+ :avatar
+ ]
end
helpers do
@@ -252,39 +287,13 @@ module API
success Entities::Project
end
params do
- # CE
- at_least_one_of_ce =
- [
- :jobs_enabled,
- :resolve_outdated_diff_discussions,
- :ci_config_path,
- :container_registry_enabled,
- :default_branch,
- :description,
- :issues_enabled,
- :lfs_enabled,
- :merge_requests_enabled,
- :merge_method,
- :name,
- :only_allow_merge_if_all_discussions_are_resolved,
- :only_allow_merge_if_pipeline_succeeds,
- :path,
- :printing_merge_request_link_enabled,
- :public_builds,
- :request_access_enabled,
- :shared_runners_enabled,
- :snippets_enabled,
- :tag_list,
- :visibility,
- :wiki_enabled,
- :avatar
- ]
optional :name, type: String, desc: 'The name of the project'
optional :default_branch, type: String, desc: 'The default branch of the project'
optional :path, type: String, desc: 'The path of the repository'
use :optional_project_params
- at_least_one_of(*at_least_one_of_ce)
+
+ at_least_one_of(*::API::Projects.update_params_at_least_one_of)
end
put ':id' do
authorize_admin_project
@@ -294,6 +303,8 @@ module API
attrs = translate_params_for_compatibility(attrs)
+ verify_update_project_attrs!(user_project, attrs)
+
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
if result[:status] == :success
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index d0cc0945a5f..c9931c2d603 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -80,7 +80,15 @@ module API
params do
requires :token, type: String, desc: %q(Runner's authentication token)
optional :last_update, type: String, desc: %q(Runner's queue last_update token)
- optional :info, type: Hash, desc: %q(Runner's metadata)
+ optional :info, type: Hash, desc: %q(Runner's metadata) do
+ optional :name, type: String, desc: %q(Runner's name)
+ optional :version, type: String, desc: %q(Runner's version)
+ optional :revision, type: String, desc: %q(Runner's revision)
+ optional :platform, type: String, desc: %q(Runner's platform)
+ optional :architecture, type: String, desc: %q(Runner's architecture)
+ optional :executor, type: String, desc: %q(Runner's executor)
+ optional :features, type: Hash, desc: %q(Runner's features)
+ end
optional :session, type: Hash, desc: %q(Runner's session data) do
optional :url, type: String, desc: %q(Session's url)
optional :certificate, type: String, desc: %q(Session's certificate)
@@ -108,9 +116,8 @@ module API
if result.valid?
if result.build
- Gitlab::Metrics.add_event(:build_found,
- project: result.build.project.full_path)
- present result.build, with: Entities::JobRequest::Response
+ Gitlab::Metrics.add_event(:build_found)
+ present Ci::BuildRunnerPresenter.new(result.build), with: Entities::JobRequest::Response
else
Gitlab::Metrics.add_event(:build_not_found)
header 'X-GitLab-Last-Update', new_update
@@ -140,8 +147,7 @@ module API
job.trace.set(params[:trace]) if params[:trace]
- Gitlab::Metrics.add_event(:update_build,
- project: job.project.full_path)
+ Gitlab::Metrics.add_event(:update_build)
case params[:state].to_s
when 'running'
@@ -233,6 +239,10 @@ module API
requires :id, type: Integer, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token)
optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
+ optional :artifact_type, type: String, desc: %q(The type of artifact),
+ default: 'archive', values: Ci::JobArtifact.file_types.keys
+ optional :artifact_format, type: String, desc: %q(The format of artifact),
+ default: 'zip', values: Ci::JobArtifact.file_formats.keys
optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse))
optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse))
optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse))
@@ -256,29 +266,29 @@ module API
bad_request!('Missing artifacts file!') unless artifacts
file_to_large! unless artifacts.size < max_artifacts_size
- bad_request!("Already uploaded") if job.job_artifacts_archive
-
expire_in = params['expire_in'] ||
Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in
- job.build_job_artifacts_archive(
+ job.job_artifacts.build(
project: job.project,
file: artifacts,
- file_type: :archive,
+ file_type: params['artifact_type'],
+ file_format: params['artifact_format'],
file_sha256: artifacts.sha256,
expire_in: expire_in)
if metadata
- job.build_job_artifacts_metadata(
+ job.job_artifacts.build(
project: job.project,
file: metadata,
file_type: :metadata,
+ file_format: :gzip,
file_sha256: metadata.sha256,
expire_in: expire_in)
end
if job.update(artifacts_expire_in: expire_in)
- present job, with: Entities::JobRequest::Response
+ present Ci::BuildRunnerPresenter.new(job), with: Entities::JobRequest::Response
else
render_validation_error!(job)
end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 1ca7d23203b..897010217dc 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -20,116 +20,114 @@ module API
success Entities::ApplicationSetting
end
params do
+ optional :admin_notification_email, type: String, desc: 'Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.'
+ optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
+ optional :after_sign_out_path, type: String, desc: 'We will redirect users to this page after they sign out'
+ optional :akismet_enabled, type: Boolean, desc: 'Helps prevent bots from creating issues'
+ given akismet_enabled: ->(val) { val } do
+ requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com'
+ end
+ optional :clientside_sentry_enabled, type: Boolean, desc: 'Sentry can also be used for reporting and logging clientside exceptions. https://sentry.io/for/javascript/'
+ given clientside_sentry_enabled: ->(val) { val } do
+ requires :clientside_sentry_dsn, type: String, desc: 'Clientside Sentry Data Source Name'
+ end
+ optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
+ optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master'
+ optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
+ optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility'
- optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
- optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.'
- optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project manifest],
- desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources'
- optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.'
- optional :project_export_enabled, type: Boolean, desc: 'Enable project export'
- optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled'
- optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
- optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
- optional :session_expire_delay, type: Integer, desc: 'Session duration in minutes. GitLab restart is required to apply changes.'
- optional :user_oauth_applications, type: Boolean, desc: 'Allow users to register any application to use GitLab as an OAuth provider'
- optional :user_default_external, type: Boolean, desc: 'Newly registered users will by default be external'
- optional :signup_enabled, type: Boolean, desc: 'Flag indicating if sign up is enabled'
- optional :send_user_confirmation_email, type: Boolean, desc: 'Send confirmation email on sign-up'
- optional :domain_whitelist, type: String, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
optional :domain_blacklist_enabled, type: Boolean, desc: 'Enable domain blacklist for sign ups'
given domain_blacklist_enabled: ->(val) { val } do
requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
end
- optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
- optional :password_authentication_enabled_for_web, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface'
- optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5
- optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5
- mutually_exclusive :password_authentication_enabled_for_web, :password_authentication_enabled, :signin_enabled
- optional :password_authentication_enabled_for_git, type: Boolean, desc: 'Flag indicating if password authentication is enabled for Git over HTTP(S)'
- optional :performance_bar_allowed_group_path, type: String, desc: 'Path of the group that is allowed to toggle the performance bar.'
- optional :performance_bar_allowed_group_id, type: String, desc: 'Depreated: Use :performance_bar_allowed_group_path instead. Path of the group that is allowed to toggle the performance bar.' # support legacy names, can be removed in v6
- optional :performance_bar_enabled, type: String, desc: 'Deprecated: Pass `performance_bar_allowed_group_path: nil` instead. Allow enabling the performance.' # support legacy names, can be removed in v6
- optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication'
- given require_two_factor_authentication: ->(val) { val } do
- requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication'
- end
- optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page'
- optional :after_sign_out_path, type: String, desc: 'We will redirect users to this page after they sign out'
- optional :sign_in_text, type: String, desc: 'The sign in text of the GitLab application'
+ optional :domain_whitelist, type: String, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
+ optional :email_author_in_body, type: Boolean, desc: 'Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.'
+ optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.'
+ optional :gitaly_timeout_default, type: Integer, desc: 'Default Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
+ optional :gitaly_timeout_fast, type: Integer, desc: 'Gitaly fast operation timeout, in seconds. Set to 0 to disable timeouts.'
+ optional :gitaly_timeout_medium, type: Integer, desc: 'Medium Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
+ optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled'
optional :help_page_hide_commercial_content, type: Boolean, desc: 'Hide marketing-related entries from help'
- optional :help_page_text, type: String, desc: 'Custom text displayed on the help page'
optional :help_page_support_url, type: String, desc: 'Alternate support URL for help page'
- optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects'
- given shared_runners_enabled: ->(val) { val } do
- requires :shared_runners_text, type: String, desc: 'Shared runners text '
+ optional :help_page_text, type: String, desc: 'Custom text displayed on the help page'
+ optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page'
+ optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)'
+ given housekeeping_enabled: ->(val) { val } do
+ requires :housekeeping_bitmaps_enabled, type: Boolean, desc: "Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance."
+ requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run."
+ requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run."
+ requires :housekeeping_incremental_repack_period, type: Integer, desc: "Number of Git pushes after which an incremental 'git repack' is run."
+ end
+ optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.'
+ optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project manifest],
+ desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
+ optional :koding_enabled, type: Boolean, desc: 'Enable Koding'
+ given koding_enabled: ->(val) { val } do
+ requires :koding_url, type: String, desc: 'The Koding team URL'
end
optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts"
- optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
+ optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
- optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
- optional :prometheus_metrics_enabled, type: Boolean, desc: 'Enable Prometheus metrics'
optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics'
given metrics_enabled: ->(val) { val } do
requires :metrics_host, type: String, desc: 'The InfluxDB host'
- requires :metrics_port, type: Integer, desc: 'The UDP port to use for connecting to InfluxDB'
- requires :metrics_pool_size, type: Integer, desc: 'The amount of InfluxDB connections to open'
- requires :metrics_timeout, type: Integer, desc: 'The amount of seconds after which an InfluxDB connection will time out'
requires :metrics_method_call_threshold, type: Integer, desc: 'A method call is only tracked when it takes longer to complete than the given amount of milliseconds.'
- requires :metrics_sample_interval, type: Integer, desc: 'The sampling interval in seconds'
requires :metrics_packet_size, type: Integer, desc: 'The amount of points to store in a single UDP packet'
+ requires :metrics_pool_size, type: Integer, desc: 'The amount of InfluxDB connections to open'
+ requires :metrics_port, type: Integer, desc: 'The UDP port to use for connecting to InfluxDB'
+ requires :metrics_sample_interval, type: Integer, desc: 'The sampling interval in seconds'
+ requires :metrics_timeout, type: Integer, desc: 'The amount of seconds after which an InfluxDB connection will time out'
end
- optional :sidekiq_throttling_enabled, type: Boolean, desc: 'Enable Sidekiq Job Throttling'
- given sidekiq_throttling_enabled: ->(val) { val } do
- requires :sidekiq_throttling_queus, type: Array[String], desc: 'Choose which queues you wish to throttle'
- requires :sidekiq_throttling_factor, type: Float, desc: 'The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive.'
+ optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5
+ optional :password_authentication_enabled_for_web, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface'
+ mutually_exclusive :password_authentication_enabled_for_web, :password_authentication_enabled, :signin_enabled
+ optional :password_authentication_enabled_for_git, type: Boolean, desc: 'Flag indicating if password authentication is enabled for Git over HTTP(S)'
+ optional :performance_bar_allowed_group_id, type: String, desc: 'Deprecated: Use :performance_bar_allowed_group_path instead. Path of the group that is allowed to toggle the performance bar.' # support legacy names, can be removed in v6
+ optional :performance_bar_allowed_group_path, type: String, desc: 'Path of the group that is allowed to toggle the performance bar.'
+ optional :performance_bar_enabled, type: String, desc: 'Deprecated: Pass `performance_bar_allowed_group_path: nil` instead. Allow enabling the performance.' # support legacy names, can be removed in v6
+ optional :plantuml_enabled, type: Boolean, desc: 'Enable PlantUML'
+ given plantuml_enabled: ->(val) { val } do
+ requires :plantuml_url, type: String, desc: 'The PlantUML server URL'
end
+ optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
+ optional :project_export_enabled, type: Boolean, desc: 'Enable project export'
+ optional :prometheus_metrics_enabled, type: Boolean, desc: 'Enable Prometheus metrics'
optional :recaptcha_enabled, type: Boolean, desc: 'Helps prevent bots from creating accounts'
given recaptcha_enabled: ->(val) { val } do
requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
end
- optional :akismet_enabled, type: Boolean, desc: 'Helps prevent bots from creating issues'
- given akismet_enabled: ->(val) { val } do
- requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com'
+ optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
+ optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects'
+ optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication'
+ given require_two_factor_authentication: ->(val) { val } do
+ requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication'
end
- optional :admin_notification_email, type: String, desc: 'Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.'
+ optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.'
+ optional :send_user_confirmation_email, type: Boolean, desc: 'Send confirmation email on sign-up'
optional :sentry_enabled, type: Boolean, desc: 'Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: https://getsentry.com'
given sentry_enabled: ->(val) { val } do
requires :sentry_dsn, type: String, desc: 'Sentry Data Source Name'
end
- optional :clientside_sentry_enabled, type: Boolean, desc: 'Sentry can also be used for reporting and logging clientside exceptions. https://sentry.io/for/javascript/'
- given clientside_sentry_enabled: ->(val) { val } do
- requires :clientside_sentry_dsn, type: String, desc: 'Clientside Sentry Data Source Name'
- end
- optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects'
- optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
- optional :koding_enabled, type: Boolean, desc: 'Enable Koding'
- given koding_enabled: ->(val) { val } do
- requires :koding_url, type: String, desc: 'The Koding team URL'
- end
- optional :plantuml_enabled, type: Boolean, desc: 'Enable PlantUML'
- given plantuml_enabled: ->(val) { val } do
- requires :plantuml_url, type: String, desc: 'The PlantUML server URL'
+ optional :session_expire_delay, type: Integer, desc: 'Session duration in minutes. GitLab restart is required to apply changes.'
+ optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects'
+ given shared_runners_enabled: ->(val) { val } do
+ requires :shared_runners_text, type: String, desc: 'Shared runners text '
end
- optional :version_check_enabled, type: Boolean, desc: 'Let GitLab inform you when an update is available.'
- optional :email_author_in_body, type: Boolean, desc: 'Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.'
- optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.'
- optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)'
- given housekeeping_enabled: ->(val) { val } do
- requires :housekeeping_bitmaps_enabled, type: Boolean, desc: "Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance."
- requires :housekeeping_incremental_repack_period, type: Integer, desc: "Number of Git pushes after which an incremental 'git repack' is run."
- requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run."
- requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run."
+ optional :sidekiq_throttling_enabled, type: Boolean, desc: 'Enable Sidekiq Job Throttling'
+ given sidekiq_throttling_enabled: ->(val) { val } do
+ requires :sidekiq_throttling_factor, type: Float, desc: 'The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive.'
+ requires :sidekiq_throttling_queues, type: Array[String], desc: 'Choose which queues you wish to throttle'
end
+ optional :sign_in_text, type: String, desc: 'The sign in text of the GitLab application'
+ optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5
+ optional :signup_enabled, type: Boolean, desc: 'Flag indicating if sign up is enabled'
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
- optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
- optional :gitaly_timeout_default, type: Integer, desc: 'Default Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
- optional :gitaly_timeout_medium, type: Integer, desc: 'Medium Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
- optional :gitaly_timeout_fast, type: Integer, desc: 'Gitaly fast operation timeout, in seconds. Set to 0 to disable timeouts.'
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
+ optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins'
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
diff --git a/lib/api/users.rb b/lib/api/users.rb
index e83887b3e9e..b0811bb4aad 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -121,6 +121,17 @@ module API
present user, opts
end
+ desc "Get the status of a user"
+ params do
+ requires :id_or_username, type: String, desc: 'The ID or username of the user'
+ end
+ get ":id_or_username/status" do
+ user = find_user(params[:id_or_username])
+ not_found!('User') unless user && can?(current_user, :read_user, user)
+
+ present user.status || {}, with: Entities::UserStatus
+ end
+
desc 'Create a user. Available only for admins.' do
success Entities::UserPublic
end
@@ -740,6 +751,30 @@ module API
present paginate(activities), with: Entities::UserActivity
end
+
+ desc 'Set the status of the current user' do
+ success Entities::UserStatus
+ end
+ params do
+ optional :emoji, type: String, desc: "The emoji to set on the status"
+ optional :message, type: String, desc: "The status message to set"
+ end
+ put "status" do
+ forbidden! unless can?(current_user, :update_user_status, current_user)
+
+ if ::Users::SetStatusService.new(current_user, declared_params).execute
+ present current_user.status, with: Entities::UserStatus
+ else
+ render_validation_error!(current_user.status)
+ end
+ end
+
+ desc 'get the status of the current user' do
+ success Entities::UserStatus
+ end
+ get 'status' do
+ present current_user.status || {}, with: Entities::UserStatus
+ end
end
end
end
diff --git a/lib/banzai/pipeline/emoji_pipeline.rb b/lib/banzai/pipeline/emoji_pipeline.rb
new file mode 100644
index 00000000000..a1b522f4303
--- /dev/null
+++ b/lib/banzai/pipeline/emoji_pipeline.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Pipeline
+ class EmojiPipeline < BasePipeline
+ # These filters will only perform sanitization of the content, preventing
+ # XSS, and replace emoji.
+ def self.filters
+ @filters ||= FilterArray[
+ Filter::HtmlEntityFilter,
+ Filter::SanitizationFilter,
+ Filter::EmojiFilter
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/feature.rb b/lib/feature.rb
index d27b2b0e72f..09c5ef3ad94 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -46,6 +46,10 @@ class Feature
get(key).enabled?(thing)
end
+ def disabled?(key, thing = nil)
+ !enabled?(key, thing)
+ end
+
def enable(key, thing = true)
get(key).enable(thing)
end
diff --git a/lib/gitlab/auth/activity.rb b/lib/gitlab/auth/activity.rb
new file mode 100644
index 00000000000..9f84c578d4f
--- /dev/null
+++ b/lib/gitlab/auth/activity.rb
@@ -0,0 +1,77 @@
+module Gitlab
+ module Auth
+ ##
+ # Metrics and logging for user authentication activity.
+ #
+ class Activity
+ extend Gitlab::Utils::StrongMemoize
+
+ COUNTERS = {
+ user_authenticated: 'Counter of successful authentication events',
+ user_unauthenticated: 'Counter of authentication failures',
+ user_not_found: 'Counter of failed log-ins when user is unknown',
+ user_password_invalid: 'Counter of failed log-ins with invalid password',
+ user_session_override: 'Counter of manual log-ins and sessions overrides',
+ user_session_destroyed: 'Counter of user sessions being destroyed',
+ user_two_factor_authenticated: 'Counter of two factor authentications',
+ user_sessionless_authentication: 'Counter of sessionless authentications',
+ user_blocked: 'Counter of sign in attempts when user is blocked'
+ }.freeze
+
+ def initialize(user, opts)
+ @user = user
+ @opts = opts
+ end
+
+ def user_authentication_failed!
+ self.class.user_unauthenticated_counter_increment!
+
+ case @opts[:message]
+ when :not_found_in_database
+ self.class.user_not_found_counter_increment!
+ when :invalid
+ self.class.user_password_invalid_counter_increment!
+ end
+
+ self.class.user_blocked_counter_increment! if @user&.blocked?
+ end
+
+ def user_authenticated!
+ self.class.user_authenticated_counter_increment!
+ end
+
+ def user_session_override!
+ self.class.user_session_override_counter_increment!
+
+ case @opts[:message]
+ when :two_factor_authenticated
+ self.class.user_two_factor_authenticated_counter_increment!
+ when :sessionless_sign_in
+ self.class.user_sessionless_authentication_counter_increment!
+ end
+ end
+
+ def user_session_destroyed!
+ self.class.user_session_destroyed_counter_increment!
+ end
+
+ def self.each_counter
+ COUNTERS.each_pair do |metric, description|
+ yield "#{metric}_counter", metric, description
+ end
+ end
+
+ each_counter do |counter, metric, description|
+ define_singleton_method(counter) do
+ strong_memoize(counter) do
+ Gitlab::Metrics.counter("gitlab_auth_#{metric}_total".to_sym, description)
+ end
+ end
+
+ define_singleton_method("#{counter}_increment!") do
+ public_send(counter).increment # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/blocked_user_tracker.rb b/lib/gitlab/auth/blocked_user_tracker.rb
index 7609a7b04f6..b6d2adc834b 100644
--- a/lib/gitlab/auth/blocked_user_tracker.rb
+++ b/lib/gitlab/auth/blocked_user_tracker.rb
@@ -2,37 +2,58 @@
module Gitlab
module Auth
class BlockedUserTracker
+ include Gitlab::Utils::StrongMemoize
+
ACTIVE_RECORD_REQUEST_PARAMS = 'action_dispatch.request.request_parameters'
- def self.log_if_user_blocked(env)
- message = env.dig('warden.options', :message)
+ def initialize(env)
+ @env = env
+ end
- # Devise calls User#active_for_authentication? on the User model and then
- # throws an exception to Warden with User#inactive_message:
- # https://github.com/plataformatec/devise/blob/v4.2.1/lib/devise/hooks/activatable.rb#L8
- #
- # Since Warden doesn't pass the user record to the failure handler, we
- # need to do a database lookup with the username. We can limit the
- # lookups to happen when the user was blocked by checking the inactive
- # message passed along by Warden.
- return unless message == User::BLOCKED_MESSAGE
+ def user_blocked?
+ user&.blocked?
+ end
- # Check for either LDAP or regular GitLab account logins
- login = env.dig(ACTIVE_RECORD_REQUEST_PARAMS, 'username') ||
- env.dig(ACTIVE_RECORD_REQUEST_PARAMS, 'user', 'login')
+ def user
+ return unless has_user_blocked_message?
- return unless login.present?
+ strong_memoize(:user) do
+ # Check for either LDAP or regular GitLab account logins
+ login = @env.dig(ACTIVE_RECORD_REQUEST_PARAMS, 'username') ||
+ @env.dig(ACTIVE_RECORD_REQUEST_PARAMS, 'user', 'login')
- user = User.by_login(login)
+ User.by_login(login) if login.present?
+ end
+ rescue TypeError
+ end
- return unless user&.blocked?
+ def log_blocked_user_activity!
+ return unless user_blocked?
- Gitlab::AppLogger.info("Failed login for blocked user: user=#{user.username} ip=#{env['REMOTE_ADDR']}")
+ Gitlab::AppLogger.info("Failed login for blocked user: user=#{user.username} ip=#{@env['REMOTE_ADDR']}")
SystemHooksService.new.execute_hooks_for(user, :failed_login)
-
true
rescue TypeError
end
+
+ private
+
+ ##
+ # Devise calls User#active_for_authentication? on the User model and then
+ # throws an exception to Warden with User#inactive_message:
+ # https://github.com/plataformatec/devise/blob/v4.2.1/lib/devise/hooks/activatable.rb#L8
+ #
+ # Since Warden doesn't pass the user record to the failure handler, we
+ # need to do a database lookup with the username. We can limit the
+ # lookups to happen when the user was blocked by checking the inactive
+ # message passed along by Warden.
+ #
+ def has_user_blocked_message?
+ strong_memoize(:user_blocked_message) do
+ message = @env.dig('warden.options', :message)
+ message == User::BLOCKED_MESSAGE
+ end
+ end
end
end
end
diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb
index 4ca5a78e068..04aa6aab771 100644
--- a/lib/gitlab/bare_repository_import/importer.rb
+++ b/lib/gitlab/bare_repository_import/importer.rb
@@ -26,6 +26,12 @@ module Gitlab
end
end
+ # This is called from within a rake task only used by Admins, so allow writing
+ # to STDOUT
+ def self.log(message)
+ puts message # rubocop:disable Rails/Output
+ end
+
attr_reader :user, :project_name, :bare_repo
delegate :log, to: :class
@@ -59,11 +65,10 @@ module Gitlab
import_type: 'bare_repository',
namespace_id: group&.id).execute
- if project.persisted? && mv_repo(project)
+ if project.persisted? && mv_repositories(project)
log " * Created #{project.name} (#{project_full_path})".color(:green)
project.write_repository_config
- Gitlab::Git::Repository.create_hooks(project.repository.path_to_repo, Gitlab.config.gitlab_shell.hooks_path)
ProjectCacheWorker.perform_async(project.id)
else
@@ -74,12 +79,11 @@ module Gitlab
project
end
- def mv_repo(project)
- storage_path = storage_path_for_shard(project.repository_storage)
- FileUtils.mv(repo_path, project.repository.path_to_repo)
+ def mv_repositories(project)
+ mv_repo(bare_repo.repo_path, project.repository)
if bare_repo.wiki_exists?
- FileUtils.mv(wiki_path, File.join(storage_path, project.disk_path + '.wiki.git'))
+ mv_repo(bare_repo.wiki_path, project.wiki.repository)
end
true
@@ -89,6 +93,11 @@ module Gitlab
false
end
+ def mv_repo(path, repository)
+ repository.create_from_bundle(bundle(path))
+ FileUtils.rm_rf(path)
+ end
+
def storage_path_for_shard(shard)
Gitlab.config.repositories.storages[shard].legacy_disk_path
end
@@ -101,10 +110,17 @@ module Gitlab
Groups::NestedCreateService.new(user, group_path: group_path).execute
end
- # This is called from within a rake task only used by Admins, so allow writing
- # to STDOUT
- def self.log(message)
- puts message # rubocop:disable Rails/Output
+ def bundle(repo_path)
+ # TODO: we could save some time and disk space by using
+ # `git bundle create - --all` and streaming the bundle directly to
+ # Gitaly, rather than writing it on disk first
+ bundle_path = "#{repo_path}.bundle"
+ cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} bundle create #{bundle_path} --all)
+ output, status = Gitlab::Popen.popen(cmd)
+
+ raise output unless status.zero?
+
+ bundle_path
end
end
end
diff --git a/lib/gitlab/bare_repository_import/repository.rb b/lib/gitlab/bare_repository_import/repository.rb
index fe267248275..c0c666dfb7b 100644
--- a/lib/gitlab/bare_repository_import/repository.rb
+++ b/lib/gitlab/bare_repository_import/repository.rb
@@ -1,5 +1,3 @@
-# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/953
-#
module Gitlab
module BareRepositoryImport
class Repository
diff --git a/lib/gitlab/ci/config/entry/artifacts.rb b/lib/gitlab/ci/config/entry/artifacts.rb
index 8275aacee9b..e80f9d2e452 100644
--- a/lib/gitlab/ci/config/entry/artifacts.rb
+++ b/lib/gitlab/ci/config/entry/artifacts.rb
@@ -6,13 +6,16 @@ module Gitlab
# Entry that represents a configuration of job artifacts.
#
class Artifacts < Node
+ include Configurable
include Validatable
include Attributable
- ALLOWED_KEYS = %i[name untracked paths when expire_in].freeze
+ ALLOWED_KEYS = %i[name untracked paths reports when expire_in].freeze
attributes ALLOWED_KEYS
+ entry :reports, Entry::Reports, description: 'Report-type artifacts.'
+
validations do
validates :config, type: Hash
validates :config, allowed_keys: ALLOWED_KEYS
@@ -21,6 +24,7 @@ module Gitlab
validates :name, type: String
validates :untracked, boolean: true
validates :paths, array_of_strings: true
+ validates :reports, type: Hash
validates :when,
inclusion: { in: %w[on_success on_failure always],
message: 'should be on_success, on_failure ' \
@@ -28,6 +32,13 @@ module Gitlab
validates :expire_in, duration: true
end
end
+
+ helpers :reports
+
+ def value
+ @config[:reports] = reports_value if @config.key?(:reports)
+ @config
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/commands.rb b/lib/gitlab/ci/config/entry/commands.rb
index 65d19db249c..9f66f11be9b 100644
--- a/lib/gitlab/ci/config/entry/commands.rb
+++ b/lib/gitlab/ci/config/entry/commands.rb
@@ -9,18 +9,7 @@ module Gitlab
include Validatable
validations do
- include LegacyValidationHelpers
-
- validate do
- unless string_or_array_of_strings?(config)
- errors.add(:config,
- 'should be a string or an array of strings')
- end
- end
-
- def string_or_array_of_strings?(field)
- validate_string(field) || validate_array_of_strings(field)
- end
+ validates :config, array_of_strings_or_string: true
end
def value
diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb
new file mode 100644
index 00000000000..5963f3eb90c
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/reports.rb
@@ -0,0 +1,32 @@
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents a configuration of job artifacts.
+ #
+ class Reports < Node
+ include Validatable
+ include Attributable
+
+ ALLOWED_KEYS = %i[junit].freeze
+
+ attributes ALLOWED_KEYS
+
+ validations do
+ validates :config, type: Hash
+ validates :config, allowed_keys: ALLOWED_KEYS
+
+ with_options allow_nil: true do
+ validates :junit, array_of_strings_or_string: true
+ end
+ end
+
+ def value
+ @config.transform_values { |v| Array(v) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb
index 55658900628..b3c889ee92f 100644
--- a/lib/gitlab/ci/config/entry/validators.rb
+++ b/lib/gitlab/ci/config/entry/validators.rb
@@ -130,6 +130,20 @@ module Gitlab
end
end
+ class ArrayOfStringsOrStringValidator < RegexpValidator
+ def validate_each(record, attribute, value)
+ unless validate_array_of_strings_or_string(value)
+ record.errors.add(attribute, 'should be an array of strings or a string')
+ end
+ end
+
+ private
+
+ def validate_array_of_strings_or_string(values)
+ validate_array_of_strings(values) || validate_string(values)
+ end
+ end
+
class TypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
type = options[:with]
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index 155f4fc1343..703f0b9217b 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -4,12 +4,13 @@ module Gitlab
module Build
class Failed < Status::Extended
REASONS = {
- 'unknown_failure' => 'unknown failure',
- 'script_failure' => 'script failure',
- 'api_failure' => 'API failure',
- 'stuck_or_timeout_failure' => 'stuck or timeout failure',
- 'runner_system_failure' => 'runner system failure',
- 'missing_dependency_failure' => 'missing dependency failure'
+ unknown_failure: 'unknown failure',
+ script_failure: 'script failure',
+ api_failure: 'API failure',
+ stuck_or_timeout_failure: 'stuck or timeout failure',
+ runner_system_failure: 'runner system failure',
+ missing_dependency_failure: 'missing dependency failure',
+ runner_unsupported: 'unsupported runner'
}.freeze
def status_tooltip
@@ -31,7 +32,11 @@ module Gitlab
end
def description
- "<br> (#{REASONS[subject.failure_reason]})"
+ "<br> (#{failure_reason_message})"
+ end
+
+ def failure_reason_message
+ REASONS.fetch(subject.failure_reason.to_sym)
end
end
end
diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb
index ee54b893598..93e219a21f9 100644
--- a/lib/gitlab/ci/trace.rb
+++ b/lib/gitlab/ci/trace.rb
@@ -164,6 +164,8 @@ module Gitlab
def create_build_trace!(job, path)
File.open(path) do |stream|
+ # TODO: Set `file_format: :raw` after we've cleaned up legacy traces migration
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20307
job.create_job_artifacts_trace!(
project: job.project,
file_type: :trace,
diff --git a/lib/gitlab/cleanup/project_upload_file_finder.rb b/lib/gitlab/cleanup/project_upload_file_finder.rb
new file mode 100644
index 00000000000..2ee8b60e76a
--- /dev/null
+++ b/lib/gitlab/cleanup/project_upload_file_finder.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Cleanup
+ class ProjectUploadFileFinder
+ FIND_BATCH_SIZE = 500
+ ABSOLUTE_UPLOAD_DIR = FileUploader.root.freeze
+ EXCLUDED_SYSTEM_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/-/*".freeze
+ EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*".freeze
+ EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*".freeze
+
+ # Paths are relative to the upload directory
+ def each_file_batch(batch_size: FIND_BATCH_SIZE, &block)
+ cmd = build_find_command(ABSOLUTE_UPLOAD_DIR)
+
+ Open3.popen2(*cmd) do |stdin, stdout, status_thread|
+ yield_paths_in_batches(stdout, batch_size, &block)
+
+ raise "Find command failed" unless status_thread.value.success?
+ end
+ end
+
+ private
+
+ def yield_paths_in_batches(stdout, batch_size, &block)
+ paths = []
+
+ stdout.each_line("\0") do |line|
+ paths << line.chomp("\0")
+
+ if paths.size >= batch_size
+ yield(paths)
+ paths = []
+ end
+ end
+
+ yield(paths) if paths.any?
+ end
+
+ def build_find_command(search_dir)
+ cmd = %W[find -L #{search_dir}
+ -type f
+ ! ( -path #{EXCLUDED_SYSTEM_UPLOADS_PATH} -prune )
+ ! ( -path #{EXCLUDED_HASHED_UPLOADS_PATH} -prune )
+ ! ( -path #{EXCLUDED_TMP_UPLOADS_PATH} -prune )
+ -print0]
+
+ ionice = which_ionice
+ cmd = %W[#{ionice} -c Idle] + cmd if ionice
+
+ log_msg = "find command: \"#{cmd.join(' ')}\""
+ Rails.logger.info log_msg
+
+ cmd
+ end
+
+ def which_ionice
+ Gitlab::Utils.which('ionice')
+ rescue StandardError
+ # In this case, returning false is relatively safe,
+ # even though it isn't very nice
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cleanup/project_uploads.rb b/lib/gitlab/cleanup/project_uploads.rb
new file mode 100644
index 00000000000..b88e00311d5
--- /dev/null
+++ b/lib/gitlab/cleanup/project_uploads.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Cleanup
+ class ProjectUploads
+ LOST_AND_FOUND = File.join(ProjectUploadFileFinder::ABSOLUTE_UPLOAD_DIR, '-', 'project-lost-found')
+
+ attr_reader :logger
+
+ def initialize(logger: nil)
+ @logger = logger || Rails.logger
+ end
+
+ def run!(dry_run: true)
+ logger.info "Looking for orphaned project uploads to clean up#{'. Dry run' if dry_run}..."
+
+ each_orphan_file do |path, upload_path|
+ result = cleanup(path, upload_path, dry_run)
+
+ logger.info result
+ end
+ end
+
+ private
+
+ def cleanup(path, upload_path, dry_run)
+ # This happened in staging:
+ # `find` returned a path on which `File.delete` raised `Errno::ENOENT`
+ return "Cannot find file: #{path}" unless File.exist?(path)
+
+ correct_path = upload_path && find_correct_path(upload_path)
+
+ if correct_path
+ move(path, correct_path, 'fix', dry_run)
+ else
+ move_to_lost_and_found(path, dry_run)
+ end
+ end
+
+ # Accepts a path in the form of "#{hex_secret}/#{filename}"
+ def find_correct_path(upload_path)
+ upload = Upload.find_by(uploader: 'FileUploader', path: upload_path)
+ return unless upload && upload.local?
+
+ upload.absolute_path
+ rescue => e
+ logger.error e.message
+
+ # absolute_path depends on a lot of code. If it doesn't work, then it
+ # it doesn't matter if the upload file is in the right place. Treat it
+ # as uncorrectable.
+ # I.e. the project record might be missing, which raises an exception.
+ nil
+ end
+
+ def move_to_lost_and_found(path, dry_run)
+ new_path = path.sub(/\A#{ProjectUploadFileFinder::ABSOLUTE_UPLOAD_DIR}/, LOST_AND_FOUND)
+
+ move(path, new_path, 'move to lost and found', dry_run)
+ end
+
+ def move(path, new_path, prefix, dry_run)
+ action = "#{prefix} #{path} -> #{new_path}"
+
+ if dry_run
+ "Can #{action}"
+ else
+ begin
+ FileUtils.mkdir_p(File.dirname(new_path))
+ FileUtils.mv(path, new_path)
+
+ "Did #{action}"
+ rescue => e
+ "Error during #{action}: #{e.inspect}"
+ end
+ end
+ end
+
+ # Yields absolute paths of project upload files that are not in the
+ # uploads table
+ def each_orphan_file
+ ProjectUploadFileFinder.new.each_file_batch do |file_paths|
+ logger.debug "Processing batch of #{file_paths.size} project upload file paths, starting with #{file_paths.first}"
+
+ file_paths.each do |path|
+ pup = ProjectUploadPath.from_path(path)
+
+ yield(path, pup.upload_path) if pup.orphan?
+ end
+ end
+ end
+
+ class ProjectUploadPath
+ PROJECT_FULL_PATH_REGEX = %r{\A#{FileUploader.root}/(.+)/(\h+/[^/]+)\z}.freeze
+
+ attr_reader :full_path, :upload_path
+
+ def initialize(full_path, upload_path)
+ @full_path = full_path
+ @upload_path = upload_path
+ end
+
+ def self.from_path(path)
+ path_matched = path.match(PROJECT_FULL_PATH_REGEX)
+ return new(nil, nil) unless path_matched
+
+ new(path_matched[1], path_matched[2])
+ end
+
+ def orphan?
+ return true if full_path.nil? || upload_path.nil?
+
+ # It's possible to reduce to one query, but `where_full_path_in` is complex
+ !Upload.exists?(path: upload_path, model_id: project_id, model_type: 'Project', uploader: 'FileUploader')
+ end
+
+ private
+
+ def project_id
+ @project_id ||= Project.where_full_path_in([full_path]).pluck(:id)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index 764f93f6d3d..fc8615afcae 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -36,10 +36,6 @@ module Gitlab
@project ||= Project.find_by_full_path(project_path)
end
- def metrics_params
- super.merge(project: project&.full_path)
- end
-
private
def create_issue
diff --git a/lib/gitlab/email/handler/create_merge_request_handler.rb b/lib/gitlab/email/handler/create_merge_request_handler.rb
index 2f864f2082b..2316e58c3fc 100644
--- a/lib/gitlab/email/handler/create_merge_request_handler.rb
+++ b/lib/gitlab/email/handler/create_merge_request_handler.rb
@@ -40,10 +40,6 @@ module Gitlab
@project ||= Project.find_by_full_path(project_path)
end
- def metrics_params
- super.merge(project: project&.full_path)
- end
-
private
def create_merge_request
diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb
index 5791dbd0484..379b114e957 100644
--- a/lib/gitlab/email/handler/create_note_handler.rb
+++ b/lib/gitlab/email/handler/create_note_handler.rb
@@ -28,10 +28,6 @@ module Gitlab
record_name: 'comment')
end
- def metrics_params
- super.merge(project: project&.full_path)
- end
-
private
def author
diff --git a/lib/gitlab/email/handler/unsubscribe_handler.rb b/lib/gitlab/email/handler/unsubscribe_handler.rb
index ea80e21532e..56751e4e41e 100644
--- a/lib/gitlab/email/handler/unsubscribe_handler.rb
+++ b/lib/gitlab/email/handler/unsubscribe_handler.rb
@@ -20,10 +20,6 @@ module Gitlab
noteable.unsubscribe(sent_notification.recipient)
end
- def metrics_params
- super.merge(project: project&.full_path)
- end
-
private
def sent_notification
diff --git a/lib/gitlab/git/conflict/resolver.rb b/lib/gitlab/git/conflict/resolver.rb
index 0e4a973301f..6dc792c16b8 100644
--- a/lib/gitlab/git/conflict/resolver.rb
+++ b/lib/gitlab/git/conflict/resolver.rb
@@ -17,7 +17,7 @@ module Gitlab
end
rescue GRPC::FailedPrecondition => e
raise Gitlab::Git::Conflict::Resolver::ConflictSideMissing.new(e.message)
- rescue Rugged::ReferenceError, Rugged::OdbError, GRPC::BadStatus => e
+ rescue GRPC::BadStatus => e
raise Gitlab::Git::CommandError.new(e)
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 0356e8efc5c..73151e4a4c5 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -39,19 +39,6 @@ module Gitlab
ChecksumError = Class.new(StandardError)
class << self
- # Unlike `new`, `create` takes the repository path
- def create(repo_path, bare: true, symlink_hooks_to: nil)
- FileUtils.mkdir_p(repo_path, mode: 0770)
-
- # Equivalent to `git --git-path=#{repo_path} init [--bare]`
- repo = Rugged::Repository.init_at(repo_path, bare)
- repo.close
-
- create_hooks(repo_path, symlink_hooks_to) if symlink_hooks_to.present?
-
- true
- end
-
def create_hooks(repo_path, global_hooks_path)
local_hooks_path = File.join(repo_path, 'hooks')
real_local_hooks_path = :not_found
@@ -558,7 +545,9 @@ module Gitlab
if is_enabled
gitaly_operation_client.user_update_branch(branch_name, user, newrev, oldrev)
else
- OperationService.new(user, self).update_branch(branch_name, newrev, oldrev)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ OperationService.new(user, self).update_branch(branch_name, newrev, oldrev)
+ end
end
end
end
@@ -832,18 +821,9 @@ module Gitlab
Gitlab::Git.check_namespace!(source_repository)
source_repository = RemoteRepository.new(source_repository) unless source_repository.is_a?(RemoteRepository)
- message, status = GitalyClient.migrate(:fetch_ref) do |is_enabled|
- if is_enabled
- gitaly_fetch_ref(source_repository, source_ref: source_ref, target_ref: target_ref)
- else
- # When removing this code, also remove source_repository#path
- # to remove deprecated method calls
- local_fetch_ref(source_repository.path, source_ref: source_ref, target_ref: target_ref)
- end
- end
-
- # Make sure ref was created, and raise Rugged::ReferenceError when not
- raise Rugged::ReferenceError, message if status != 0
+ args = %W(fetch --no-tags -f #{GITALY_INTERNAL_URL} #{source_ref}:#{target_ref})
+ message, status = run_git(args, env: source_repository.fetch_env)
+ raise Gitlab::Git::CommandError, message if status != 0
target_ref
end
@@ -1244,17 +1224,6 @@ module Gitlab
gitaly_repository_client.apply_gitattributes(revision)
end
- def local_fetch_ref(source_path, source_ref:, target_ref:)
- args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
- run_git(args)
- end
-
- def gitaly_fetch_ref(source_repository, source_ref:, target_ref:)
- args = %W(fetch --no-tags -f #{GITALY_INTERNAL_URL} #{source_ref}:#{target_ref})
-
- run_git(args, env: source_repository.fetch_env)
- end
-
def gitaly_delete_refs(*ref_names)
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
end
diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb
index 8835bfb2481..65eb5cc18cf 100644
--- a/lib/gitlab/git/repository_mirroring.rb
+++ b/lib/gitlab/git/repository_mirroring.rb
@@ -6,7 +6,9 @@ module Gitlab
if is_enabled
gitaly_ref_client.remote_branches(remote_name)
else
- rugged_remote_branches(remote_name)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ rugged_remote_branches(remote_name)
+ end
end
end
end
diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb
index 742118b76a8..e731e654f3c 100644
--- a/lib/gitlab/git_post_receive.rb
+++ b/lib/gitlab/git_post_receive.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class GitPostReceive
include Gitlab::Identifier
@@ -14,10 +16,11 @@ module Gitlab
end
def changes_refs
- return enum_for(:changes_refs) unless block_given?
+ return changes unless block_given?
changes.each do |change|
- oldrev, newrev, ref = change.strip.split(' ')
+ change.strip!
+ oldrev, newrev, ref = change.split(' ')
yield oldrev, newrev, ref
end
@@ -26,13 +29,10 @@ module Gitlab
private
def deserialize_changes(changes)
- changes = utf8_encode_changes(changes)
- changes.lines
+ utf8_encode_changes(changes).each_line
end
def utf8_encode_changes(changes)
- changes = changes.dup
-
changes.force_encoding('UTF-8')
return changes if changes.valid_encoding?
diff --git a/lib/gitlab/github_import/importer/pull_requests_importer.rb b/lib/gitlab/github_import/importer/pull_requests_importer.rb
index e70361c163b..a52866c4b08 100644
--- a/lib/gitlab/github_import/importer/pull_requests_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_requests_importer.rb
@@ -43,7 +43,7 @@ module Gitlab
Rails.logger
.info("GitHub importer finished updating repository for #{pname}")
- repository_updates_counter.increment(project: pname)
+ repository_updates_counter.increment
end
def update_repository?(pr)
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
index a4263369269..8a91e034377 100644
--- a/lib/gitlab/gpg.rb
+++ b/lib/gitlab/gpg.rb
@@ -71,8 +71,16 @@ module Gitlab
if MUTEX.locked? && MUTEX.owned?
optimistic_using_tmp_keychain(&block)
else
- MUTEX.synchronize do
- optimistic_using_tmp_keychain(&block)
+ if Gitlab.rails5?
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ MUTEX.synchronize do
+ optimistic_using_tmp_keychain(&block)
+ end
+ end
+ else
+ MUTEX.synchronize do
+ optimistic_using_tmp_keychain(&block)
+ end
end
end
end
diff --git a/lib/gitlab/graphql/authorize.rb b/lib/gitlab/graphql/authorize.rb
index 04f25c53e49..93a903915b0 100644
--- a/lib/gitlab/graphql/authorize.rb
+++ b/lib/gitlab/graphql/authorize.rb
@@ -10,7 +10,14 @@ module Gitlab
end
def required_permissions
- @required_permissions ||= []
+ # If the `#authorize` call is used on multiple classes, we add the
+ # permissions specified on a subclass, to the ones that were specified
+ # on it's superclass.
+ @required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions)
+ superclass.required_permissions.dup
+ else
+ []
+ end
end
def authorize(*permissions)
diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb
new file mode 100644
index 00000000000..40895686a8a
--- /dev/null
+++ b/lib/gitlab/graphql/authorize/authorize_resource.rb
@@ -0,0 +1,46 @@
+module Gitlab
+ module Graphql
+ module Authorize
+ module AuthorizeResource
+ extend ActiveSupport::Concern
+
+ included do
+ extend Gitlab::Graphql::Authorize
+ end
+
+ def find_object(*args)
+ raise NotImplementedError, "Implement #find_object in #{self.class.name}"
+ end
+
+ def authorized_find(*args)
+ object = find_object(*args)
+
+ object if authorized?(object)
+ end
+
+ def authorized_find!(*args)
+ object = find_object(*args)
+ authorize!(object)
+
+ object
+ end
+
+ def authorize!(object)
+ unless authorized?(object)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ end
+ end
+
+ def authorized?(object)
+ self.class.required_permissions.all? do |ability|
+ # The actions could be performed across multiple objects. In which
+ # case the current user is common, and we could benefit from the
+ # caching in `DeclarativePolicy`.
+ Ability.allowed?(current_user, ability, object, scope: :user)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/errors.rb b/lib/gitlab/graphql/errors.rb
index 1d8e8307ab9..f8c7ec24be1 100644
--- a/lib/gitlab/graphql/errors.rb
+++ b/lib/gitlab/graphql/errors.rb
@@ -3,6 +3,7 @@ module Gitlab
module Errors
BaseError = Class.new(GraphQL::ExecutionError)
ArgumentError = Class.new(BaseError)
+ ResourceNotAvailable = Class.new(BaseError)
end
end
end
diff --git a/lib/gitlab/graphql/mount_mutation.rb b/lib/gitlab/graphql/mount_mutation.rb
new file mode 100644
index 00000000000..8cab84d7a5f
--- /dev/null
+++ b/lib/gitlab/graphql/mount_mutation.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module MountMutation
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def mount_mutation(mutation_class)
+ # Using an underscored field name symbol will make `graphql-ruby`
+ # standardize the field name
+ field mutation_class.graphql_name.underscore.to_sym,
+ mutation: mutation_class
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb
index 3caf9036459..c4ffc19df09 100644
--- a/lib/gitlab/graphs/commits.rb
+++ b/lib/gitlab/graphs/commits.rb
@@ -18,7 +18,7 @@ module Gitlab
end
def commit_per_day
- @commit_per_day ||= @commits.size / (@duration + 1)
+ @commit_per_day ||= (@commits.size.to_f / (@duration + 1)).round(1)
end
def collect_data
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index da3667faf7a..f69f98a78a3 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -107,6 +107,7 @@ excluded_attributes:
- :storage_version
- :remote_mirror_available_overridden
- :description_html
+ - :repository_languages
snippets:
- :expired_at
merge_request_diff:
diff --git a/lib/gitlab/import_export/merge_request_parser.rb b/lib/gitlab/import_export/merge_request_parser.rb
index d0527f014a7..62a1833b39c 100644
--- a/lib/gitlab/import_export/merge_request_parser.rb
+++ b/lib/gitlab/import_export/merge_request_parser.rb
@@ -27,7 +27,11 @@ module Gitlab
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1295
def fetch_ref
- @project.repository.fetch_ref(@project.repository, source_ref: @diff_head_sha, target_ref: @merge_request.source_branch)
+ target_ref = Gitlab::Git::BRANCH_REF_PREFIX + @merge_request.source_branch
+
+ unless @project.repository.fetch_source_branch!(@project.repository, @diff_head_sha, target_ref)
+ Rails.logger.warn("Import/Export warning: Failed to create #{target_ref} for MR: #{@merge_request.iid}")
+ end
end
def branch_exists?(branch_name)
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index af9b880ef9e..45816bee176 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -22,24 +22,28 @@ module Gitlab
class << self
def options
- @options ||= Hash[ImportTable.map { |importer| [importer.title, importer.name] }]
+ Hash[import_table.map { |importer| [importer.title, importer.name] }]
end
def values
- @values ||= ImportTable.map(&:name)
+ import_table.map(&:name)
end
def importer_names
- @importer_names ||= ImportTable.select(&:importer).map(&:name)
+ import_table.select(&:importer).map(&:name)
end
def importer(name)
- ImportTable.find { |import_source| import_source.name == name }.importer
+ import_table.find { |import_source| import_source.name == name }.importer
end
def title(name)
options.key(name)
end
+
+ def import_table
+ ImportTable
+ end
end
end
end
diff --git a/lib/gitlab/kubernetes/config_map.rb b/lib/gitlab/kubernetes/config_map.rb
index 95e1054919d..8a8a59a9cd4 100644
--- a/lib/gitlab/kubernetes/config_map.rb
+++ b/lib/gitlab/kubernetes/config_map.rb
@@ -1,7 +1,7 @@
module Gitlab
module Kubernetes
class ConfigMap
- def initialize(name, values)
+ def initialize(name, values = "")
@name = name
@values = values
end
@@ -13,6 +13,10 @@ module Gitlab
resource
end
+ def config_map_name
+ "values-content-configuration-#{name}"
+ end
+
private
attr_reader :name, :values
@@ -25,10 +29,6 @@ module Gitlab
}
end
- def config_map_name
- "values-content-configuration-#{name}"
- end
-
def namespace
Gitlab::Kubernetes::Helm::NAMESPACE
end
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index 0f0588b8b23..530ccf88053 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -1,7 +1,7 @@
module Gitlab
module Kubernetes
module Helm
- HELM_VERSION = '2.7.0'.freeze
+ HELM_VERSION = '2.7.2'.freeze
NAMESPACE = 'gitlab-managed-apps'.freeze
end
end
diff --git a/lib/gitlab/kubernetes/helm/api.rb b/lib/gitlab/kubernetes/helm/api.rb
index 2edd34109ba..c4de9a398cc 100644
--- a/lib/gitlab/kubernetes/helm/api.rb
+++ b/lib/gitlab/kubernetes/helm/api.rb
@@ -8,9 +8,9 @@ module Gitlab
end
def install(command)
- @namespace.ensure_exists!
+ namespace.ensure_exists!
create_config_map(command) if command.config_map?
- @kubeclient.create_pod(command.pod_resource)
+ kubeclient.create_pod(command.pod_resource)
end
##
@@ -20,23 +20,25 @@ module Gitlab
#
# values: "Pending", "Running", "Succeeded", "Failed", "Unknown"
#
- def installation_status(pod_name)
- @kubeclient.get_pod(pod_name, @namespace.name).status.phase
+ def status(pod_name)
+ kubeclient.get_pod(pod_name, namespace.name).status.phase
end
- def installation_log(pod_name)
- @kubeclient.get_pod_log(pod_name, @namespace.name).body
+ def log(pod_name)
+ kubeclient.get_pod_log(pod_name, namespace.name).body
end
- def delete_installation_pod!(pod_name)
- @kubeclient.delete_pod(pod_name, @namespace.name)
+ def delete_pod!(pod_name)
+ kubeclient.delete_pod(pod_name, namespace.name)
end
private
+ attr_reader :kubeclient, :namespace
+
def create_config_map(command)
command.config_map_resource.tap do |config_map_resource|
- @kubeclient.create_config_map(config_map_resource)
+ kubeclient.create_config_map(config_map_resource)
end
end
end
diff --git a/lib/gitlab/language_detection.rb b/lib/gitlab/language_detection.rb
new file mode 100644
index 00000000000..a41435fdb79
--- /dev/null
+++ b/lib/gitlab/language_detection.rb
@@ -0,0 +1,68 @@
+module Gitlab
+ class LanguageDetection
+ MAX_LANGUAGES = 5
+
+ def initialize(repository, repository_languages)
+ @repository = repository
+ @repository_languages = repository_languages
+ end
+
+ def languages
+ detection.keys
+ end
+
+ def language_color(name)
+ detection.dig(name, :color)
+ end
+
+ # Newly detected languages, returned in a structure accepted by
+ # Gitlab::Database.bulk_insert
+ def insertions(programming_languages)
+ lang_to_id = programming_languages.map { |p| [p.name, p.id] }.to_h
+
+ (languages - previous_language_names).map do |new_lang|
+ {
+ project_id: @repository.project.id,
+ share: detection[new_lang][:value],
+ programming_language_id: lang_to_id[new_lang]
+ }
+ end
+ end
+
+ # updates analyses which records only require updating of their share
+ def updates
+ to_update = @repository_languages.select do |lang|
+ detection.key?(lang.name) && detection[lang.name][:value] != lang.share
+ end
+
+ to_update.map do |lang|
+ { programming_language_id: lang.programming_language_id, share: detection[lang.name][:value] }
+ end
+ end
+
+ # Returns the ids of the programming languages that do not occur in the detection
+ # as current repository languages
+ def deletions
+ @repository_languages.map do |repo_lang|
+ next if detection.key?(repo_lang.name)
+
+ repo_lang.programming_language_id
+ end.compact
+ end
+
+ private
+
+ def previous_language_names
+ @previous_language_names ||= @repository_languages.map(&:name)
+ end
+
+ def detection
+ @detection ||=
+ @repository
+ .languages
+ .first(MAX_LANGUAGES)
+ .map { |l| [l[:label], l] }
+ .to_h
+ end
+ end
+end
diff --git a/lib/gitlab/middleware/basic_health_check.rb b/lib/gitlab/middleware/basic_health_check.rb
new file mode 100644
index 00000000000..f2a03217098
--- /dev/null
+++ b/lib/gitlab/middleware/basic_health_check.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+# This middleware provides a health check that does not hit the database. Its purpose
+# is to notify the prober that the application server is handling requests, but a 200
+# response does not signify that the database or other services are ready.
+#
+# See https://thisdata.com/blog/making-a-rails-health-check-that-doesnt-hit-the-database/ for
+# more details.
+
+module Gitlab
+ module Middleware
+ class BasicHealthCheck
+ # This can't be frozen because Rails::Rack::Logger wraps the body
+ # rubocop:disable Style/MutableConstant
+ OK_RESPONSE = [200, { 'Content-Type' => 'text/plain' }, ["GitLab OK"]]
+ EMPTY_RESPONSE = [404, { 'Content-Type' => 'text/plain' }, [""]]
+ # rubocop:enable Style/MutableConstant
+ HEALTH_PATH = '/-/health'
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ return @app.call(env) unless env['PATH_INFO'] == HEALTH_PATH
+
+ request = Rack::Request.new(env)
+
+ return OK_RESPONSE if client_ip_whitelisted?(request)
+
+ EMPTY_RESPONSE
+ end
+
+ def client_ip_whitelisted?(request)
+ ip_whitelist.any? { |e| e.include?(request.ip) }
+ end
+
+ def ip_whitelist
+ @ip_whitelist ||= Settings.monitoring.ip_whitelist.map(&IPAddr.method(:new))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/prometheus/metric.rb b/lib/gitlab/prometheus/metric.rb
index f54b2c6aaff..13cc59df795 100644
--- a/lib/gitlab/prometheus/metric.rb
+++ b/lib/gitlab/prometheus/metric.rb
@@ -3,7 +3,7 @@ module Gitlab
class Metric
include ActiveModel::Model
- attr_accessor :title, :required_metrics, :weight, :y_label, :queries
+ attr_accessor :id, :title, :required_metrics, :weight, :y_label, :queries
validates :title, :required_metrics, :weight, :y_label, :queries, presence: true
diff --git a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
index e677ec84cd4..8534afcc849 100644
--- a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
+++ b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
@@ -8,6 +8,7 @@ module Gitlab
Deployment.find_by(id: deployment_id).try do |deployment|
query_metrics(
deployment.project,
+ deployment.environment,
common_query_context(
deployment.environment,
timeframe_start: (deployment.created_at - 30.minutes).to_f,
diff --git a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
index 9273e69e158..e3af217b202 100644
--- a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
+++ b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
@@ -8,6 +8,7 @@ module Gitlab
::Environment.find_by(id: environment_id).try do |environment|
query_metrics(
environment.project,
+ environment,
common_query_context(environment, timeframe_start: 8.hours.ago.to_f, timeframe_end: Time.now.to_f)
)
end
diff --git a/lib/gitlab/prometheus/queries/query_additional_metrics.rb b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
index f5879de1e94..3be35f189d0 100644
--- a/lib/gitlab/prometheus/queries/query_additional_metrics.rb
+++ b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
@@ -2,7 +2,7 @@ module Gitlab
module Prometheus
module Queries
module QueryAdditionalMetrics
- def query_metrics(project, query_context)
+ def query_metrics(project, environment, query_context)
matched_metrics(project).map(&query_group(query_context))
.select(&method(:group_with_any_metrics))
end
@@ -14,12 +14,16 @@ module Gitlab
lambda do |group|
metrics = group.metrics.map do |metric|
- {
+ metric_hsh = {
title: metric.title,
weight: metric.weight,
y_label: metric.y_label,
queries: metric.queries.map(&query_processor).select(&method(:query_with_result))
}
+
+ metric_hsh[:id] = metric.id if metric.id
+
+ metric_hsh
end
{
diff --git a/lib/gitlab/template_helper.rb b/lib/gitlab/template_helper.rb
new file mode 100644
index 00000000000..3b8e45e0688
--- /dev/null
+++ b/lib/gitlab/template_helper.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module TemplateHelper
+ include Gitlab::Utils::StrongMemoize
+
+ def prepare_template_environment(file_path)
+ return unless file_path.present?
+
+ FileUtils.mkdir_p(File.dirname(import_upload_path))
+ FileUtils.copy_entry(file_path, import_upload_path)
+ end
+
+ def import_upload_path
+ strong_memoize(:import_upload_path) do
+ Gitlab::ImportExport.import_upload_path(filename: tmp_filename)
+ end
+ end
+
+ def tmp_filename
+ SecureRandom.hex
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index a8acafa9cd9..e5b5f3548e4 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -385,14 +385,6 @@ namespace :gitlab do
end
end
- namespace :repo do
- desc "GitLab | Check the integrity of the repositories managed by GitLab"
- task check: :gitlab_environment do
- puts "This task is deprecated. Please use gitlab:git:fsck instead".color(:red)
- Rake::Task["gitlab:git:fsck"].execute
- end
- end
-
namespace :orphans do
desc 'Gitlab | Check for orphaned namespaces and repositories'
task check: :gitlab_environment do
@@ -422,23 +414,6 @@ namespace :gitlab do
end
end
- namespace :user do
- desc "GitLab | Check the integrity of a specific user's repositories"
- task :check_repos, [:username] => :gitlab_environment do |t, args|
- username = args[:username] || prompt("Check repository integrity for username? ".color(:blue))
- user = User.find_by(username: username)
- if user
- repo_dirs = user.authorized_projects.map do |p|
- p.repository.path_to_repo
- end
-
- repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) }
- else
- puts "\nUser '#{username}' not found".color(:red)
- end
- end
- end
-
# Helper methods
##########################
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 5e07b12ee1c..a2feb074b1d 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -7,9 +7,8 @@ namespace :gitlab do
desc "GitLab | Cleanup | Clean namespaces"
task dirs: :gitlab_environment do
warn_user_is_not_gitlab
- remove_flag = ENV['REMOVE']
- namespaces = Namespace.pluck(:path)
+ namespaces = Namespace.pluck(:path)
namespaces << HASHED_REPOSITORY_NAME # add so that it will be ignored
Gitlab.config.repositories.storages.each do |name, repository_storage|
git_base_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository_storage.legacy_disk_path }
@@ -31,7 +30,7 @@ namespace :gitlab do
end
all_dirs.each do |dir_path|
- if remove_flag
+ if remove?
if FileUtils.rm_rf dir_path
puts "Removed...#{dir_path}".color(:red)
else
@@ -43,7 +42,7 @@ namespace :gitlab do
end
end
- unless remove_flag
+ unless remove?
puts "To cleanup this directories run this command with REMOVE=true".color(:yellow)
end
end
@@ -104,5 +103,37 @@ namespace :gitlab do
puts "To block these users run this command with BLOCK=true".color(:yellow)
end
end
+
+ desc "GitLab | Cleanup | Clean orphaned project uploads"
+ task project_uploads: :gitlab_environment do
+ warn_user_is_not_gitlab
+
+ cleaner = Gitlab::Cleanup::ProjectUploads.new(logger: logger)
+ cleaner.run!(dry_run: dry_run?)
+
+ if dry_run?
+ logger.info "To clean up these files run this command with DRY_RUN=false".color(:yellow)
+ end
+ end
+
+ def remove?
+ ENV['REMOVE'] == 'true'
+ end
+
+ def dry_run?
+ ENV['DRY_RUN'] != 'false'
+ end
+
+ def logger
+ return @logger if defined?(@logger)
+
+ @logger = if Rails.env.development? || Rails.env.production?
+ Logger.new(STDOUT).tap do |stdout_logger|
+ stdout_logger.extend(ActiveSupport::Logger.broadcast(Rails.logger))
+ end
+ else
+ Rails.logger
+ end
+ end
end
end
diff --git a/lib/tasks/gitlab/git.rake b/lib/tasks/gitlab/git.rake
index cb4f7e5c8a8..8a53b51d4fe 100644
--- a/lib/tasks/gitlab/git.rake
+++ b/lib/tasks/gitlab/git.rake
@@ -1,87 +1,24 @@
namespace :gitlab do
namespace :git do
- desc "GitLab | Git | Repack"
- task repack: :gitlab_environment do
- failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} repack -a --quiet), "Repacking repo")
- if failures.empty?
- puts "Done".color(:green)
- else
- output_failures(failures)
- end
- end
-
- desc "GitLab | Git | Run garbage collection on all repos"
- task gc: :gitlab_environment do
- failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} gc --auto --quiet), "Garbage Collecting")
- if failures.empty?
- puts "Done".color(:green)
- else
- output_failures(failures)
- end
- end
-
- desc "GitLab | Git | Prune all repos"
- task prune: :gitlab_environment do
- failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} prune), "Git Prune")
- if failures.empty?
- puts "Done".color(:green)
- else
- output_failures(failures)
- end
- end
-
desc 'GitLab | Git | Check all repos integrity'
task fsck: :gitlab_environment do
- failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} fsck --name-objects --no-progress), "Checking integrity") do |repo|
- check_config_lock(repo)
- check_ref_locks(repo)
- end
-
- if failures.empty?
- puts "Done".color(:green)
- else
- output_failures(failures)
- end
- end
-
- def perform_git_cmd(cmd, message)
- puts "Starting #{message} on all repositories"
-
failures = []
- all_repos do |repo|
- if system(*cmd, chdir: repo)
- puts "Performed #{message} at #{repo}"
- else
- failures << repo
+ Project.find_each(batch_size: 100) do |project|
+ begin
+ project.repository.fsck
+
+ rescue => e
+ failures << "#{project.full_path} on #{project.repository_storage}: #{e}"
end
- yield(repo) if block_given?
+ puts "Performed integrity check for #{project.repository.full_path}"
end
- failures
- end
-
- def output_failures(failures)
- puts "The following repositories reported errors:".color(:red)
- failures.each { |f| puts "- #{f}" }
- end
-
- def check_config_lock(repo_dir)
- config_exists = File.exist?(File.join(repo_dir, 'config.lock'))
- config_output = config_exists ? 'yes'.color(:red) : 'no'.color(:green)
-
- puts "'config.lock' file exists?".color(:yellow) + " ... #{config_output}"
- end
-
- def check_ref_locks(repo_dir)
- lock_files = Dir.glob(File.join(repo_dir, 'refs/heads/*.lock'))
-
- if lock_files.present?
- puts "Ref lock files exist:".color(:red)
-
- lock_files.each { |lock_file| puts " #{lock_file}" }
+ if failures.empty?
+ puts "Done".color(:green)
else
- puts "No ref lock files exist".color(:green)
+ puts "The following repositories reported errors:".color(:red)
+ failures.each { |f| puts "- #{f}" }
end
end
end