diff options
Diffstat (limited to 'app/models')
58 files changed, 434 insertions, 385 deletions
diff --git a/app/models/active_session.rb b/app/models/active_session.rb index f355b02c428..fdd210f0fba 100644 --- a/app/models/active_session.rb +++ b/app/models/active_session.rb @@ -3,6 +3,8 @@ class ActiveSession include ActiveModel::Model + SESSION_BATCH_SIZE = 200 + attr_accessor :created_at, :updated_at, :session_id, :ip_address, :browser, :os, :device_name, :device_type, @@ -91,12 +93,12 @@ class ActiveSession end def self.list_sessions(user) - sessions_from_ids(session_ids_for_user(user)) + sessions_from_ids(session_ids_for_user(user.id)) end - def self.session_ids_for_user(user) + def self.session_ids_for_user(user_id) Gitlab::Redis::SharedState.with do |redis| - redis.smembers(lookup_key_name(user.id)) + redis.smembers(lookup_key_name(user_id)) end end @@ -106,10 +108,12 @@ class ActiveSession Gitlab::Redis::SharedState.with do |redis| session_keys = session_ids.map { |session_id| "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" } - redis.mget(session_keys).compact.map do |raw_session| - # rubocop:disable Security/MarshalLoad - Marshal.load(raw_session) - # rubocop:enable Security/MarshalLoad + session_keys.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch| + redis.mget(session_keys_batch).compact.map do |raw_session| + # rubocop:disable Security/MarshalLoad + Marshal.load(raw_session) + # rubocop:enable Security/MarshalLoad + end end end end @@ -125,7 +129,7 @@ class ActiveSession end def self.cleaned_up_lookup_entries(redis, user) - session_ids = session_ids_for_user(user) + session_ids = session_ids_for_user(user.id) entries = raw_active_session_entries(session_ids, user.id) # remove expired keys. diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb index 52b5a7b4a91..28aab279545 100644 --- a/app/models/chat_team.rb +++ b/app/models/chat_team.rb @@ -12,6 +12,6 @@ class ChatTeam < ApplicationRecord # Either the group is not found, or the user doesn't have the proper # access on the mattermost instance. In the first case, we're done either way # in the latter case, we can't recover by retrying, so we just log what happened - Rails.logger.error("Mattermost team deletion failed: #{e}") + Rails.logger.error("Mattermost team deletion failed: #{e}") # rubocop:disable Gitlab/RailsLogger end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 89cc082d0bc..da70cb9a9a7 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -266,7 +266,7 @@ module Ci begin Ci::Build.retry(build, build.user) rescue Gitlab::Access::AccessDeniedError => ex - Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}" + Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}" # rubocop:disable Gitlab/RailsLogger end end end @@ -531,6 +531,14 @@ module Ci trace.exist? end + def has_live_trace? + trace.live_trace_exist? + end + + def has_archived_trace? + trace.archived_trace_exist? + end + def artifacts_file job_artifacts_archive&.file end @@ -578,7 +586,7 @@ module Ci end def valid_token?(token) - self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) + self.token && ActiveSupport::SecurityUtils.secure_compare(token, self.token) end def has_tags? diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index f80e98e5bca..e132cb045e2 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -176,6 +176,10 @@ module Ci end end + def self.archived_trace_exists_for?(job_id) + where(job_id: job_id).trace.take&.file&.file&.exists? + end + private def file_format_adapter_class diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 20ca4a9ab24..c2eb51ba100 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -196,7 +196,7 @@ module Ci sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC' query = ApplicationRecord.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend - order(query) + order(Arel.sql(query)) end scope :for_user, -> (user) { where(user: user) } @@ -236,8 +236,6 @@ module Ci if limit ids = relation.limit(limit).select(:id) - # MySQL does not support limit in subquery - ids = ids.pluck(:id) if Gitlab::Database.mysql? relation = relation.where(id: ids) end diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index ba8cea0cea9..42d4e86fe8d 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -4,11 +4,8 @@ module Ci class PipelineSchedule < ApplicationRecord extend Gitlab::Ci::Model include Importable - include IgnorableColumn include StripAttribute - ignore_column :deleted_at - belongs_to :project belongs_to :owner, class_name: 'User' has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline' diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 07d00503861..43ff874ac23 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -264,7 +264,7 @@ module Ci private def cleanup_runner_queue - Gitlab::Redis::Queues.with do |redis| + Gitlab::Redis::SharedState.with do |redis| redis.del(runner_queue_key) end end diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 8927bb9bc18..8792c5cf98b 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -3,17 +3,15 @@ module Ci class Trigger < ApplicationRecord extend Gitlab::Ci::Model - include IgnorableColumn include Presentable - ignore_column :deleted_at - belongs_to :project belongs_to :owner, class_name: "User" has_many :trigger_requests validates :token, presence: true, uniqueness: true + validates :owner, presence: true, unless: :supports_legacy_tokens? before_validation :set_default_values @@ -37,8 +35,13 @@ module Ci self.owner_id.blank? end + def supports_legacy_tokens? + Feature.enabled?(:use_legacy_pipeline_triggers, project) + end + def can_access_project? - self.owner_id.blank? || Ability.allowed?(self.owner, :create_build, project) + supports_legacy_tokens? && legacy? || + Ability.allowed?(self.owner, :create_build, project) end end end diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index f0256ff4d41..6533b7a186e 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Runner < ApplicationRecord - VERSION = '0.6.0'.freeze + VERSION = '0.7.0'.freeze self.table_name = 'clusters_applications_runners' @@ -29,13 +29,6 @@ module Clusters content_values.to_yaml end - # Need to investigate if pipelines run by this runner will stop upon the - # executor pod stopping - # I.e.run a pipeline, and uninstall runner while pipeline is running - def allowed_to_uninstall? - false - end - def install_command Gitlab::Kubernetes::Helm::InstallCommand.new( name: name, @@ -47,6 +40,14 @@ module Clusters ) end + def prepare_uninstall + runner&.update!(active: false) + end + + def post_uninstall + runner.destroy! + end + private def ensure_runner diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb index 4514498b84b..803a65726d3 100644 --- a/app/models/clusters/concerns/application_core.rb +++ b/app/models/clusters/concerns/application_core.rb @@ -46,6 +46,16 @@ module Clusters command.version = version end end + + def prepare_uninstall + # Override if your application needs any action before + # being uninstalled by Helm + end + + def post_uninstall + # Override if your application needs any action after + # being uninstalled by Helm + end end end end diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb index 54a3dda6d75..342d766f723 100644 --- a/app/models/clusters/concerns/application_status.rb +++ b/app/models/clusters/concerns/application_status.rb @@ -59,29 +59,33 @@ module Clusters transition [:scheduled] => :uninstalling end - before_transition any => [:scheduled] do |app_status, _| - app_status.status_reason = nil + before_transition any => [:scheduled] do |application, _| + application.status_reason = nil end - before_transition any => [:errored] do |app_status, transition| + before_transition any => [:errored] do |application, transition| status_reason = transition.args.first - app_status.status_reason = status_reason if status_reason + application.status_reason = status_reason if status_reason end - before_transition any => [:updating] do |app_status, _| - app_status.status_reason = nil + before_transition any => [:updating] do |application, _| + application.status_reason = nil end - before_transition any => [:update_errored, :uninstall_errored] do |app_status, transition| + before_transition any => [:update_errored, :uninstall_errored] do |application, transition| status_reason = transition.args.first - app_status.status_reason = status_reason if status_reason + application.status_reason = status_reason if status_reason end - before_transition any => [:installed, :updated] do |app_status, _| + before_transition any => [:installed, :updated] do |application, _| # When installing any application we are also performing an update # of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so # therefore we need to reflect that in the database. - app_status.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION) + application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION) + end + + after_transition any => [:uninstalling], :use_transactions => false do |application, _| + application.prepare_uninstall end end end diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 42203a5f214..9713e79f525 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -87,6 +87,16 @@ module CacheMarkdownField __send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend end + # Updates the markdown cache if necessary, then returns the field + # Unlike `cached_html_for` it returns `nil` if the field does not exist + def updated_cached_html_for(markdown_field) + return unless cached_markdown_fields.markdown_fields.include?(markdown_field) + + refresh_markdown_cache if attribute_invalidated?(cached_markdown_fields.html_field(markdown_field)) + + cached_html_for(markdown_field) + end + def latest_cached_markdown_version @latest_cached_markdown_version ||= (Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) | local_version end @@ -139,8 +149,9 @@ module CacheMarkdownField # The HTML becomes invalid if any dependent fields change. For now, assume # author and project invalidate the cache in all circumstances. define_method(invalidation_method) do - invalidations = changed_markdown_fields & [markdown_field.to_s, *INVALIDATED_BY] - invalidations.delete(markdown_field.to_s) if changed_markdown_fields.include?("#{markdown_field}_html") + changed_fields = changed_attributes.keys + invalidations = changed_fields & [markdown_field.to_s, *INVALIDATED_BY] + invalidations.delete(markdown_field.to_s) if changed_fields.include?("#{markdown_field}_html") !invalidations.empty? || !cached_html_up_to_date?(markdown_field) end end diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb index 8cbf4bcfaf7..53dff2adfc3 100644 --- a/app/models/concerns/cacheable_attributes.rb +++ b/app/models/concerns/cacheable_attributes.rb @@ -49,7 +49,7 @@ module CacheableAttributes current_without_cache.tap { |current_record| current_record&.cache! } rescue => e if Rails.env.production? - Rails.logger.warn("Cached record for #{name} couldn't be loaded, falling back to uncached record: #{e}") + Rails.logger.warn("Cached record for #{name} couldn't be loaded, falling back to uncached record: #{e}") # rubocop:disable Gitlab/RailsLogger else raise e end diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb index c93b6589ee7..abddbf1c7e3 100644 --- a/app/models/concerns/case_sensitivity.rb +++ b/app/models/concerns/case_sensitivity.rb @@ -40,14 +40,10 @@ module CaseSensitivity end def lower_value(value) - return value if Gitlab::Database.mysql? - Arel::Nodes::NamedFunction.new('LOWER', [Arel::Nodes.build_quoted(value)]) end def lower_column(column) - return column if Gitlab::Database.mysql? - column.lower end end diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb index e1d5ce7f7d4..91dda803031 100644 --- a/app/models/concerns/ci/contextable.rb +++ b/app/models/concerns/ci/contextable.rb @@ -59,6 +59,7 @@ module Ci variables.append(key: 'CI', value: 'true') variables.append(key: 'GITLAB_CI', value: 'true') variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(',')) + variables.append(key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host) variables.append(key: 'CI_SERVER_NAME', value: 'GitLab') variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION) variables.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s) diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb index 8f28c897eb6..e1a8725e728 100644 --- a/app/models/concerns/deployment_platform.rb +++ b/app/models/concerns/deployment_platform.rb @@ -12,19 +12,10 @@ module DeploymentPlatform private def find_deployment_platform(environment) - find_platform_kubernetes(environment) || + find_platform_kubernetes_with_cte(environment) || find_instance_cluster_platform_kubernetes(environment: environment) end - def find_platform_kubernetes(environment) - if Feature.enabled?(:clusters_cte) - find_platform_kubernetes_with_cte(environment) - else - find_cluster_platform_kubernetes(environment: environment) || - find_group_cluster_platform_kubernetes(environment: environment) - end - end - # EE would override this and utilize environment argument def find_platform_kubernetes_with_cte(_environment) Clusters::ClustersHierarchy.new(self).base_and_ancestors @@ -33,18 +24,6 @@ module DeploymentPlatform end # EE would override this and utilize environment argument - def find_cluster_platform_kubernetes(environment: nil) - clusters.enabled.default_environment - .last&.platform_kubernetes - end - - # EE would override this and utilize environment argument - def find_group_cluster_platform_kubernetes(environment: nil) - Clusters::Cluster.enabled.default_environment.ancestor_clusters_for_clusterable(self) - .first&.platform_kubernetes - end - - # EE would override this and utilize environment argument def find_instance_cluster_platform_kubernetes(environment: nil) Clusters::Instance.new.clusters.enabled.default_environment .first&.platform_kubernetes diff --git a/app/models/concerns/from_union.rb b/app/models/concerns/from_union.rb index 9b8595b1211..e28dee34815 100644 --- a/app/models/concerns/from_union.rb +++ b/app/models/concerns/from_union.rb @@ -40,11 +40,7 @@ module FromUnion .new(members, remove_duplicates: remove_duplicates) .to_sql - # This pattern is necessary as a bug in Rails 4 can cause the use of - # `from("string here").includes(:foo)` to break ActiveRecord. This is - # fixed in https://github.com/rails/rails/pull/25374, which is released as - # part of Rails 5. - from([Arel.sql("(#{union}) #{alias_as}")]) + from(Arel.sql("(#{union}) #{alias_as}")) end # rubocop: enable Gitlab/Union end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 78bcce2f592..27a5c3d5286 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -33,22 +33,24 @@ module HasStatus canceled = scope_relevant.canceled.select('count(*)').to_sql warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false' - "(CASE - WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success' - WHEN (#{builds})=(#{skipped}) THEN 'skipped' - WHEN (#{builds})=(#{success}) THEN 'success' - WHEN (#{builds})=(#{created}) THEN 'created' - WHEN (#{builds})=(#{preparing}) THEN 'preparing' - WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' - WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' - WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' - WHEN (#{running})+(#{pending})>0 THEN 'running' - WHEN (#{manual})>0 THEN 'manual' - WHEN (#{scheduled})>0 THEN 'scheduled' - WHEN (#{preparing})>0 THEN 'preparing' - WHEN (#{created})>0 THEN 'running' - ELSE 'failed' - END)" + Arel.sql( + "(CASE + WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success' + WHEN (#{builds})=(#{skipped}) THEN 'skipped' + WHEN (#{builds})=(#{success}) THEN 'success' + WHEN (#{builds})=(#{created}) THEN 'created' + WHEN (#{builds})=(#{preparing}) THEN 'preparing' + WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' + WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' + WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' + WHEN (#{running})+(#{pending})>0 THEN 'running' + WHEN (#{manual})>0 THEN 'manual' + WHEN (#{scheduled})>0 THEN 'scheduled' + WHEN (#{preparing})>0 THEN 'preparing' + WHEN (#{created})>0 THEN 'running' + ELSE 'failed' + END)" + ) end def status @@ -88,22 +90,22 @@ module HasStatus state :scheduled, value: 'scheduled' end - scope :created, -> { where(status: 'created') } - scope :preparing, -> { where(status: 'preparing') } - scope :relevant, -> { where(status: AVAILABLE_STATUSES - ['created']) } - scope :running, -> { where(status: 'running') } - scope :pending, -> { where(status: 'pending') } - scope :success, -> { where(status: 'success') } - scope :failed, -> { where(status: 'failed') } - scope :canceled, -> { where(status: 'canceled') } - scope :skipped, -> { where(status: 'skipped') } - scope :manual, -> { where(status: 'manual') } - scope :scheduled, -> { where(status: 'scheduled') } - scope :alive, -> { where(status: [:created, :preparing, :pending, :running]) } - scope :created_or_pending, -> { where(status: [:created, :pending]) } - scope :running_or_pending, -> { where(status: [:running, :pending]) } - scope :finished, -> { where(status: [:success, :failed, :canceled]) } - scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } + scope :created, -> { with_status(:created) } + scope :preparing, -> { with_status(:preparing) } + scope :relevant, -> { without_status(:created) } + scope :running, -> { with_status(:running) } + scope :pending, -> { with_status(:pending) } + scope :success, -> { with_status(:success) } + scope :failed, -> { with_status(:failed) } + scope :canceled, -> { with_status(:canceled) } + scope :skipped, -> { with_status(:skipped) } + scope :manual, -> { with_status(:manual) } + scope :scheduled, -> { with_status(:scheduled) } + scope :alive, -> { with_status(:created, :preparing, :pending, :running) } + scope :created_or_pending, -> { with_status(:created, :pending) } + scope :running_or_pending, -> { with_status(:running, :pending) } + scope :finished, -> { with_status(:success, :failed, :canceled) } + scope :failed_or_canceled, -> { with_status(:failed, :canceled) } scope :cancelable, -> do where(status: [:running, :preparing, :pending, :created, :scheduled]) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 952de92cae1..e60b6497cb7 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -427,4 +427,11 @@ module Issuable def wipless_title_changed(old_title) old_title != title end + + ## + # Overridden on EE module + # + def supports_milestone? + respond_to?(:milestone_id) + end end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 0d88b34fb48..2f3f9b399d9 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -63,6 +63,9 @@ module Mentionable skip_project_check: skip_project_check? ).merge(mentionable_params) + cached_html = self.try(:updated_cached_html_for, attr.to_sym) + options[:rendered] = cached_html if cached_html + extractor.analyze(text, options) end diff --git a/app/models/concerns/project_api_compatibility.rb b/app/models/concerns/project_api_compatibility.rb index cb00efb06df..631b2a11e9a 100644 --- a/app/models/concerns/project_api_compatibility.rb +++ b/app/models/concerns/project_api_compatibility.rb @@ -9,12 +9,10 @@ module ProjectAPICompatibility end def auto_devops_enabled=(value) - self.build_auto_devops if self.auto_devops&.enabled.nil? - self.auto_devops.update! enabled: value + (auto_devops || build_auto_devops).enabled = value end def auto_devops_deploy_strategy=(value) - self.build_auto_devops if self.auto_devops&.enabled.nil? - self.auto_devops.update! deploy_strategy: value + (auto_devops || build_auto_devops).deploy_strategy = value end end diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb index 22b6b1d720c..e4fe46d722a 100644 --- a/app/models/concerns/relative_positioning.rb +++ b/app/models/concerns/relative_positioning.rb @@ -179,7 +179,7 @@ module RelativePositioning relation = yield relation if block_given? relation - .pluck(self.class.parent_column, "#{calculation}(relative_position) AS position") + .pluck(self.class.parent_column, Arel.sql("#{calculation}(relative_position) AS position")) .first&. last end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index b9ffc64e4a9..116e8967651 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -33,29 +33,12 @@ module Routable # # Returns a single object, or nil. def find_by_full_path(path, follow_redirects: false) - # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so - # any literal matches come first, for this we have to use "BINARY". - # Without this there's still no guarantee in what order MySQL will return - # rows. - # - # Why do we do this? - # - # Even though we have Rails validation on Route for unique paths - # (case-insensitive), there are old projects in our DB (and possibly - # clients' DBs) that have the same path with different cases. - # See https://gitlab.com/gitlab-org/gitlab-ce/issues/18603. Also note that - # our unique index is case-sensitive in Postgres. - binary = Gitlab::Database.mysql? ? 'BINARY' : '' - order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)" + order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)") found = where_full_path_in([path]).reorder(order_sql).take return found if found if follow_redirects - if Gitlab::Database.postgresql? - joins(:redirect_routes).find_by("LOWER(redirect_routes.path) = LOWER(?)", path) - else - joins(:redirect_routes).find_by(redirect_routes: { path: path }) - end + joins(:redirect_routes).find_by("LOWER(redirect_routes.path) = LOWER(?)", path) end end @@ -67,27 +50,13 @@ module Routable # # Returns an ActiveRecord::Relation. def where_full_path_in(paths) - wheres = [] - cast_lower = Gitlab::Database.postgresql? + return none if paths.empty? - paths.each do |path| - path = connection.quote(path) - - where = - if cast_lower - "(LOWER(routes.path) = LOWER(#{path}))" - else - "(routes.path = #{path})" - end - - wheres << where + wheres = paths.map do |path| + "(LOWER(routes.path) = LOWER(#{connection.quote(path)}))" end - if wheres.empty? - none - else - joins(:route).where(wheres.join(' OR ')) - end + joins(:route).where(wheres.join(' OR ')) end end diff --git a/app/models/concerns/stepable.rb b/app/models/concerns/stepable.rb new file mode 100644 index 00000000000..d00a049a004 --- /dev/null +++ b/app/models/concerns/stepable.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Stepable + extend ActiveSupport::Concern + + def steps + self.class._all_steps + end + + def execute_steps + initial_result = {} + + steps.inject(initial_result) do |previous_result, callback| + result = method(callback).call + + if result[:status] == :error + result[:failed_step] = callback + + break result + end + + previous_result.merge(result) + end + end + + class_methods do + def _all_steps + @_all_steps ||= [] + end + + def steps(*methods) + _all_steps.concat methods + end + end +end diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb index a15dc19e07a..78544405c49 100644 --- a/app/models/concerns/storage/legacy_namespace.rb +++ b/app/models/concerns/storage/legacy_namespace.rb @@ -64,7 +64,7 @@ module Storage unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path) - Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" + Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger # if we cannot move namespace directory we should rollback # db changes in order to prevent out of sync between db and fs diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index b42adad94ba..8b536a123fc 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -15,7 +15,8 @@ module Taskable INCOMPLETE_PATTERN = /(\[[\s]\])/.freeze ITEM_PATTERN = %r{ ^ - \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list + (?:(?:>\s{0,4})*) # optional blockquote characters + \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list \s+ # whitespace prefix has to be always presented for a list item (\[\s\]|\[[xX]\]) # checkbox (\s.+) # followed by whitespace and some text. diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb index 8c769be0489..1293df571a3 100644 --- a/app/models/concerns/token_authenticatable.rb +++ b/app/models/concerns/token_authenticatable.rb @@ -52,7 +52,7 @@ module TokenAuthenticatable mod.define_method("#{token_field}_matches?") do |other_token| token = read_attribute(token_field) - token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(other_token, token) + token.present? && ActiveSupport::SecurityUtils.secure_compare(other_token, token) end end diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb deleted file mode 100644 index d0f5b6970b1..00000000000 --- a/app/models/cycle_analytics.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -class CycleAnalytics - STAGES = %i[issue plan code test review staging production].freeze - - def initialize(project, options) - @project = project - @options = options - end - - def all_medians_per_stage - STAGES.each_with_object({}) do |stage_name, medians_per_stage| - medians_per_stage[stage_name] = self[stage_name].median - end - end - - def summary - @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project, - from: @options[:from], - current_user: @options[:current_user]).data - end - - def stats - @stats ||= stats_per_stage - end - - def no_stats? - stats.all? { |hash| hash[:value].nil? } - end - - def permissions(user:) - Gitlab::CycleAnalytics::Permissions.get(user: user, project: @project) - end - - def [](stage_name) - Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options) - end - - private - - def stats_per_stage - STAGES.map do |stage_name| - self[stage_name].as_json - end - end -end diff --git a/app/models/cycle_analytics/group_level.rb b/app/models/cycle_analytics/group_level.rb new file mode 100644 index 00000000000..a41e1375484 --- /dev/null +++ b/app/models/cycle_analytics/group_level.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module CycleAnalytics + class GroupLevel + include LevelBase + attr_reader :options, :group + + def initialize(group:, options:) + @group = group + @options = options.merge(group: group) + end + + def summary + @summary ||= ::Gitlab::CycleAnalytics::GroupStageSummary.new(group, options: options).data + end + + def permissions(*) + STAGES.each_with_object({}) do |stage, obj| + obj[stage] = true + end + end + + def stats + @stats ||= STAGES.map do |stage_name| + self[stage_name].as_json(serializer: GroupAnalyticsStageSerializer) + end + end + end +end diff --git a/app/models/cycle_analytics/level_base.rb b/app/models/cycle_analytics/level_base.rb new file mode 100644 index 00000000000..543349ebf8f --- /dev/null +++ b/app/models/cycle_analytics/level_base.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module CycleAnalytics + module LevelBase + STAGES = %i[issue plan code test review staging production].freeze + + def all_medians_by_stage + STAGES.each_with_object({}) do |stage_name, medians_per_stage| + medians_per_stage[stage_name] = self[stage_name].project_median + end + end + + def stats + @stats ||= STAGES.map do |stage_name| + self[stage_name].as_json + end + end + + def no_stats? + stats.all? { |hash| hash[:value].nil? } + end + + def [](stage_name) + Gitlab::CycleAnalytics::Stage[stage_name].new(options: options) + end + end +end diff --git a/app/models/cycle_analytics/project_level.rb b/app/models/cycle_analytics/project_level.rb new file mode 100644 index 00000000000..4aa426c58a1 --- /dev/null +++ b/app/models/cycle_analytics/project_level.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module CycleAnalytics + class ProjectLevel + include LevelBase + attr_reader :project, :options + + def initialize(project, options:) + @project = project + @options = options.merge(project: project) + end + + def summary + @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project, + from: options[:from], + current_user: options[:current_user]).data + end + + def permissions(user:) + Gitlab::CycleAnalytics::Permissions.get(user: user, project: project) + end + end +end diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb index 74aa04ab7d0..ec52f1ed370 100644 --- a/app/models/dashboard_group_milestone.rb +++ b/app/models/dashboard_group_milestone.rb @@ -15,8 +15,7 @@ class DashboardGroupMilestone < GlobalMilestone milestones = Milestone.of_groups(groups.select(:id)) .reorder_by_due_date_asc .order_by_name_asc - .active milestones = milestones.search_title(params[:search_title]) if params[:search_title].present? - milestones.map { |m| new(m) } + Milestone.filter_by_state(milestones, params[:state]).map { |m| new(m) } end end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index b69cda4f2f9..68586e7a1fd 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -128,17 +128,8 @@ class Deployment < ApplicationRecord merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.finished_at) end - # Need to use `map` instead of `select` because MySQL doesn't allow `SELECT`ing from the same table - # that we're updating. - merge_request_ids = - if Gitlab::Database.postgresql? - merge_requests.select(:id) - elsif Gitlab::Database.mysql? - merge_requests.map(&:id) - end - MergeRequest::Metrics - .where(merge_request_id: merge_request_ids, first_deployed_to_production_at: nil) + .where(merge_request_id: merge_requests.select(:id), first_deployed_to_production_at: nil) .update_all(first_deployed_to_production_at: finished_at) end diff --git a/app/models/email.rb b/app/models/email.rb index 0ddaa049c3b..580633d3232 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -4,9 +4,8 @@ class Email < ApplicationRecord include Sortable include Gitlab::SQL::Pattern - belongs_to :user + belongs_to :user, optional: false - validates :user_id, presence: true validates :email, presence: true, uniqueness: true, devise_email: true validate :unique_email, if: ->(email) { email.email_changed? } diff --git a/app/models/environment.rb b/app/models/environment.rb index b8ee54c1696..392481ea0cc 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -4,11 +4,6 @@ class Environment < ApplicationRecord include Gitlab::Utils::StrongMemoize include ReactiveCaching - # Used to generate random suffixes for the slug - LETTERS = ('a'..'z').freeze - NUMBERS = ('0'..'9').freeze - SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a - belongs_to :project, required: true has_many :deployments, -> { success }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent @@ -203,40 +198,6 @@ class Environment < ApplicationRecord super.presence || generate_slug end - # An environment name is not necessarily suitable for use in URLs, DNS - # or other third-party contexts, so provide a slugified version. A slug has - # the following properties: - # * contains only lowercase letters (a-z), numbers (0-9), and '-' - # * begins with a letter - # * has a maximum length of 24 bytes (OpenShift limitation) - # * cannot end with `-` - def generate_slug - # Lowercase letters and numbers only - slugified = +name.to_s.downcase.gsub(/[^a-z0-9]/, '-') - - # Must start with a letter - slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0]) - - # Repeated dashes are invalid (OpenShift limitation) - slugified.gsub!(/\-+/, '-') - - # Maximum length: 24 characters (OpenShift limitation) - slugified = slugified[0..23] - - # Cannot end with a dash (Kubernetes label limitation) - slugified.chop! if slugified.end_with?('-') - - # Add a random suffix, shortening the current string if necessary, if it - # has been slugified. This ensures uniqueness. - if slugified != name - slugified = slugified[0..16] - slugified << '-' unless slugified.end_with?('-') - slugified << random_suffix - end - - self.slug = slugified - end - def external_url_for(path, commit_sha) return unless self.external_url @@ -274,11 +235,7 @@ class Environment < ApplicationRecord private - # Slugifying a name may remove the uniqueness guarantee afforded by it being - # based on name (which must be unique). To compensate, we add a random - # 6-byte suffix in those circumstances. This is not *guaranteed* uniqueness, - # but the chance of collisions is vanishingly small - def random_suffix - (0..5).map { SUFFIX_CHARS.sample }.join + def generate_slug + self.slug = Gitlab::Slug::Environment.new(name).generate end end diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb index 465a42759df..d7dc64190d6 100644 --- a/app/models/environment_status.rb +++ b/app/models/environment_status.rb @@ -40,7 +40,7 @@ class EnvironmentStatus end def changes - return [] if project.route_map_for(sha).nil? + return [] unless has_route_map? changed_files.map { |file| build_change(file) }.compact end @@ -50,6 +50,10 @@ class EnvironmentStatus .merge_request_diff_files.where(deleted_file: false) end + def has_route_map? + project.route_map_for(sha).present? + end + private PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze diff --git a/app/models/group.rb b/app/models/group.rb index 9520db1bc0a..26ce2957e9b 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -144,6 +144,12 @@ class Group < Namespace notification_settings(hierarchy_order: hierarchy_order).where(user: user) end + def notification_email_for(user) + # Finds the closest notification_setting with a `notification_email` + notification_settings = notification_settings_for(user, hierarchy_order: :asc) + notification_settings.find { |n| n.notification_email.present? }&.notification_email + end + def to_reference(_from = nil, full: nil) "#{self.class.reference_prefix}#{full_path}" end @@ -416,6 +422,10 @@ class Group < Namespace super || ::Gitlab::CurrentSettings.default_project_creation end + def subgroup_creation_level + super || ::Gitlab::Access::OWNER_SUBGROUP_ACCESS + end + private def update_two_factor_requirement diff --git a/app/models/issue.rb b/app/models/issue.rb index 982a94315bd..8c5dd5e382e 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -13,11 +13,8 @@ class Issue < ApplicationRecord include RelativePositioning include TimeTrackable include ThrottledTouch - include IgnorableColumn include LabelEventable - ignore_column :assignee_id, :branch_name, :deleted_at - DueDateStruct = Struct.new(:title, :name).freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze @@ -134,7 +131,7 @@ class Issue < ApplicationRecord when 'due_date' then order_due_date_asc when 'due_date_asc' then order_due_date_asc when 'due_date_desc' then order_due_date_desc - when 'relative_position' then order_relative_position_asc + when 'relative_position' then order_relative_position_asc.with_order_id_desc else super end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 53977748c30..68e6e48fb7d 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -7,7 +7,6 @@ class MergeRequest < ApplicationRecord include Noteable include Referable include Presentable - include IgnorableColumn include TimeTrackable include ManualInverseAssociation include EachBatch @@ -24,10 +23,6 @@ class MergeRequest < ApplicationRecord SORTING_PREFERENCE_FIELD = :merge_requests_sort - ignore_column :locked_at, - :ref_fetched, - :deleted_at - belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" belongs_to :merge_user, class_name: "User" @@ -588,7 +583,11 @@ class MergeRequest < ApplicationRecord end def diff_refs - persisted? ? merge_request_diff.diff_refs : repository_diff_refs + if importing? || persisted? + merge_request_diff.diff_refs + else + repository_diff_refs + end end # Instead trying to fetch the diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb index 22cedf57b86..5c53cfd8c27 100644 --- a/app/models/merge_requests_closing_issues.rb +++ b/app/models/merge_requests_closing_issues.rb @@ -25,7 +25,7 @@ class MergeRequestsClosingIssues < ApplicationRecord class << self def count_for_collection(ids, current_user) - closing_merge_requests(ids, current_user).group(:issue_id).pluck('issue_id', 'COUNT(*) as count') + closing_merge_requests(ids, current_user).group(:issue_id).pluck('issue_id', Arel.sql('COUNT(*) as count')) end def count_for_issue(id, current_user) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index af50293a179..b8d7348268a 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -8,13 +8,10 @@ class Namespace < ApplicationRecord include AfterCommitQueue include Storage::LegacyNamespace include Gitlab::SQL::Pattern - include IgnorableColumn include FeatureGate include FromUnion include Gitlab::Utils::StrongMemoize - ignore_column :deleted_at - # Prevent users from creating unreasonably deep level of nesting. # The number 20 was taken based on maximum nesting level of # Android repo (15) + some extra backup. @@ -41,8 +38,7 @@ class Namespace < ApplicationRecord validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :name, presence: true, - length: { maximum: 255 }, - namespace_name: true + length: { maximum: 255 } validates :description, length: { maximum: 255 } validates :path, @@ -264,6 +260,11 @@ class Namespace < ApplicationRecord false end + # Overridden in EE::Namespace + def feature_available?(_feature) + false + end + def full_path_before_last_save if parent_id_before_last_save.nil? path_before_last_save diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index d6d879c6d89..27c122d3559 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -12,15 +12,17 @@ class PagesDomain < ApplicationRecord validates :domain, hostname: { allow_numeric_hostname: true } validates :domain, uniqueness: { case_sensitive: false } - validates :certificate, presence: { message: 'must be present if HTTPS-only is enabled' }, if: ->(domain) { domain.project&.pages_https_only? } + validates :certificate, presence: { message: 'must be present if HTTPS-only is enabled' }, + if: :certificate_should_be_present? validates :certificate, certificate: true, if: ->(domain) { domain.certificate.present? } - validates :key, presence: { message: 'must be present if HTTPS-only is enabled' }, if: ->(domain) { domain.project&.pages_https_only? } + validates :key, presence: { message: 'must be present if HTTPS-only is enabled' }, + if: :certificate_should_be_present? validates :key, certificate_key: true, if: ->(domain) { domain.key.present? } validates :verification_code, presence: true, allow_blank: false validate :validate_pages_domain validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? } - validate :validate_intermediates, if: ->(domain) { domain.certificate.present? } + validate :validate_intermediates, if: ->(domain) { domain.certificate.present? && domain.certificate_changed? } attr_encrypted :key, mode: :per_attribute_iv_and_salt, @@ -249,4 +251,8 @@ class PagesDomain < ApplicationRecord rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError nil end + + def certificate_should_be_present? + !auto_ssl_enabled? && project&.pages_https_only? + end end diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index f69f0e2dccb..7ae431eaad7 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -7,6 +7,7 @@ class PersonalAccessToken < ApplicationRecord add_authentication_token_field :token, digest: true REDIS_EXPIRY_TIME = 3.minutes + TOKEN_LENGTH = 20 serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize diff --git a/app/models/project.rb b/app/models/project.rb index bfc35b77b8f..8030c645e2e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -31,7 +31,6 @@ class Project < ApplicationRecord include FeatureGate include OptionallySearch include FromUnion - include IgnorableColumn extend Gitlab::Cache::RequestCache extend Gitlab::ConfigHelper @@ -56,8 +55,6 @@ class Project < ApplicationRecord VALID_MIRROR_PORTS = [22, 80, 443].freeze VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze - ignore_column :import_status, :import_jid, :import_error - cache_markdown_field :description, pipeline: :description delegate :feature_available?, :builds_enabled?, :wiki_enabled?, @@ -280,7 +277,7 @@ class Project < ApplicationRecord has_many :project_deploy_tokens has_many :deploy_tokens, through: :project_deploy_tokens - has_one :auto_devops, class_name: 'ProjectAutoDevops' + has_one :auto_devops, class_name: 'ProjectAutoDevops', inverse_of: :project, autosave: true has_many :custom_attributes, class_name: 'ProjectCustomAttribute' has_many :project_badges, class_name: 'ProjectBadge' @@ -357,9 +354,10 @@ class Project < ApplicationRecord scope :with_unmigrated_storage, -> { where('storage_version < :version OR storage_version IS NULL', version: LATEST_STORAGE_VERSION) } # last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push - scope :sorted_by_activity, -> { reorder("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC") } + scope :sorted_by_activity, -> { reorder(Arel.sql("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC")) } scope :sorted_by_stars_desc, -> { reorder(star_count: :desc) } scope :sorted_by_stars_asc, -> { reorder(star_count: :asc) } + scope :sorted_by_name_asc_limited, ->(limit) { reorder(name: :asc).limit(limit) } scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } @@ -432,7 +430,7 @@ class Project < ApplicationRecord numericality: { greater_than_or_equal_to: 10.minutes, less_than: 1.month, only_integer: true, - message: _('needs to be beetween 10 minutes and 1 month') } + message: _('needs to be between 10 minutes and 1 month') } # Used by Projects::CleanupService to hold a map of rewritten object IDs mount_uploader :bfg_object_map, AttachmentUploader @@ -444,22 +442,6 @@ class Project < ApplicationRecord without_deleted.find_by_id(id) end - # Paginates a collection using a `WHERE id < ?` condition. - # - # before - A project ID to use for filtering out projects with an equal or - # greater ID. If no ID is given, all projects are included. - # - # limit - The maximum number of rows to include. - def self.paginate_in_descending_order_using_id( - before: nil, - limit: Kaminari.config.default_per_page - ) - relation = order_id_desc.limit(limit) - relation = relation.where('projects.id < ?', before) if before - - relation - end - def self.eager_load_namespace_and_owner includes(namespace: :owner) end @@ -612,7 +594,7 @@ class Project < ApplicationRecord end end - def initialize(attributes = {}) + def initialize(attributes = nil) # We can't use default_value_for because the database has a default # value of 0 for visibility_level. If someone attempts to create a # private project, default_value_for will assume that the @@ -622,6 +604,8 @@ class Project < ApplicationRecord # # To fix the problem, we assign the actual default in the application if # no explicit visibility has been initialized. + attributes ||= {} + unless visibility_attribute_present?(attributes) attributes[:visibility_level] = Gitlab::CurrentSettings.default_project_visibility end @@ -688,10 +672,6 @@ class Project < ApplicationRecord { scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) } end - def multiple_mr_assignees_enabled? - Feature.enabled?(:multiple_merge_request_assignees, self) - end - def daily_statistics_enabled? Feature.enabled?(:project_daily_statistics, self, default_enabled: true) end @@ -784,6 +764,7 @@ class Project < ApplicationRecord job_id end + # rubocop:disable Gitlab/RailsLogger def log_import_activity(job_id, type: :import) job_type = type.to_s.capitalize @@ -793,6 +774,7 @@ class Project < ApplicationRecord Rails.logger.error("#{job_type} job failed to create for #{full_path}.") end end + # rubocop:enable Gitlab/RailsLogger def reset_cache_and_import_attrs run_after_commit do @@ -1499,12 +1481,20 @@ class Project < ApplicationRecord !namespace.share_with_group_lock end - def pipeline_for(ref, sha = nil) + def pipeline_for(ref, sha = nil, id = nil) + if id.present? + pipelines_for(ref, sha).find_by(id: id) + else + pipelines_for(ref, sha).take + end + end + + def pipelines_for(ref, sha = nil) sha ||= commit(ref).try(:sha) return unless sha - ci_pipelines.order(id: :desc).find_by(sha: sha, ref: ref) + ci_pipelines.order(id: :desc).where(sha: sha, ref: ref) end def latest_successful_pipeline_for_default_branch @@ -1555,7 +1545,7 @@ class Project < ApplicationRecord end def valid_runners_token?(token) - self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) + self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token) end # rubocop: disable CodeReuse/ServiceClass @@ -1665,6 +1655,7 @@ class Project < ApplicationRecord end # rubocop: enable CodeReuse/ServiceClass + # rubocop:disable Gitlab/RailsLogger def write_repository_config(gl_full_path: full_path) # We'd need to keep track of project full path otherwise directory tree # created with hashed storage enabled cannot be usefully imported using @@ -1674,6 +1665,7 @@ class Project < ApplicationRecord Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.") nil end + # rubocop:enable Gitlab/RailsLogger def after_import repository.after_import @@ -1715,6 +1707,7 @@ class Project < ApplicationRecord @pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self) end + # rubocop:disable Gitlab/RailsLogger def add_export_job(current_user:, after_export_strategy: nil, params: {}) job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params) @@ -1724,6 +1717,7 @@ class Project < ApplicationRecord Rails.logger.error "Export job failed to start for project ID #{self.id}" end end + # rubocop:enable Gitlab/RailsLogger def import_export_shared @import_export_shared ||= Gitlab::ImportExport::Shared.new(self) @@ -1914,9 +1908,8 @@ class Project < ApplicationRecord @route_maps_by_commit ||= Hash.new do |h, sha| h[sha] = begin data = repository.route_map_for(sha) - next unless data - Gitlab::RouteMap.new(data) + Gitlab::RouteMap.new(data) if data rescue Gitlab::RouteMap::FormatError nil end diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index 67c12363a3c..f39f54f0434 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -5,7 +5,7 @@ class ProjectAutoDevops < ApplicationRecord ignore_column :domain - belongs_to :project + belongs_to :project, inverse_of: :auto_devops enum deploy_strategy: { continuous: 0, diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 7ff06655de0..78e82955342 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -86,6 +86,8 @@ class ProjectFeature < ApplicationRecord default_value_for :wiki_access_level, value: ENABLED, allows_nil: false default_value_for :repository_access_level, value: ENABLED, allows_nil: false + default_value_for(:pages_access_level, allows_nil: false) { |feature| feature.project&.public? ? ENABLED : PRIVATE } + def feature_available?(feature, user) # This feature might not be behind a feature flag at all, so default to true return false unless ::Feature.enabled?(feature, user, default_enabled: true) diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb index 1605345efd5..23adffb33d8 100644 --- a/app/models/project_import_state.rb +++ b/app/models/project_import_state.rb @@ -65,7 +65,7 @@ class ProjectImportState < ApplicationRecord update_column(:last_error, sanitized_message) rescue ActiveRecord::ActiveRecordError => e - Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}") + Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}") # rubocop:disable Gitlab/RailsLogger ensure @errors = original_errors end diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index f0ef2d925ab..47106d7bdbb 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -7,7 +7,7 @@ class CiService < Service default_value_for :category, 'ci' def valid_token?(token) - self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) + self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token) end def self.supported_events diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index a3b89b2543a..7ab79242cc3 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -54,7 +54,7 @@ class JiraService < IssueTrackerService username: self.username, password: self.password, site: URI.join(url, '/').to_s, # Intended to find the root - context_path: url.path.chomp('/'), + context_path: url.path, auth_type: :basic, read_timeout: 120, use_cookies: true, @@ -103,6 +103,12 @@ class JiraService < IssueTrackerService "#{url}/secure/CreateIssue.jspa" end + alias_method :original_url, :url + + def url + original_url&.chomp('/') + end + def execute(push) # This method is a no-op, because currently JiraService does not # support any events. @@ -250,7 +256,7 @@ class JiraService < IssueTrackerService end log_info("Successfully posted", client_url: client_url) - "SUCCESS: Successfully posted to http://jira.example.net." + "SUCCESS: Successfully posted to #{client_url}." end end diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb index bfabc6d262c..5f5cff97808 100644 --- a/app/models/project_services/slash_commands_service.rb +++ b/app/models/project_services/slash_commands_service.rb @@ -12,7 +12,7 @@ class SlashCommandsService < Service def valid_token?(token) self.respond_to?(:token) && self.token.present? && - ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) + ActiveSupport::SecurityUtils.secure_compare(token, self.token) end def self.supported_events diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb index 62090444f79..b8e7673dcf5 100644 --- a/app/models/prometheus_metric.rb +++ b/app/models/prometheus_metric.rb @@ -3,68 +3,7 @@ class PrometheusMetric < ApplicationRecord belongs_to :project, validate: true, inverse_of: :prometheus_metrics - enum group: { - # built-in groups - nginx_ingress_vts: -1, - ha_proxy: -2, - aws_elb: -3, - nginx: -4, - kubernetes: -5, - nginx_ingress: -6, - - # custom/user groups - business: 0, - response: 1, - system: 2 - } - - GROUP_DETAILS = { - # built-in groups - nginx_ingress_vts: { - group_title: _('Response metrics (NGINX Ingress VTS)'), - required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg), - priority: 10 - }.freeze, - nginx_ingress: { - group_title: _('Response metrics (NGINX Ingress)'), - required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum), - priority: 10 - }.freeze, - ha_proxy: { - group_title: _('Response metrics (HA Proxy)'), - required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total), - priority: 10 - }.freeze, - aws_elb: { - group_title: _('Response metrics (AWS ELB)'), - required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum), - priority: 10 - }.freeze, - nginx: { - group_title: _('Response metrics (NGINX)'), - required_metrics: %w(nginx_server_requests nginx_server_requestMsec), - priority: 10 - }.freeze, - kubernetes: { - group_title: _('System metrics (Kubernetes)'), - required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total), - priority: 5 - }.freeze, - - # custom/user groups - business: { - group_title: _('Business metrics (Custom)'), - priority: 0 - }.freeze, - response: { - group_title: _('Response metrics (Custom)'), - priority: -5 - }.freeze, - system: { - group_title: _('System metrics (Custom)'), - priority: -10 - }.freeze - }.freeze + enum group: PrometheusMetricEnums.groups validates :title, presence: true validates :query, presence: true @@ -121,6 +60,6 @@ class PrometheusMetric < ApplicationRecord private def group_details(group) - GROUP_DETAILS.fetch(group.to_sym) + PrometheusMetricEnums.group_details.fetch(group.to_sym) end end diff --git a/app/models/prometheus_metric_enums.rb b/app/models/prometheus_metric_enums.rb new file mode 100644 index 00000000000..6cb22cc69cd --- /dev/null +++ b/app/models/prometheus_metric_enums.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module PrometheusMetricEnums + def self.groups + { + # built-in groups + nginx_ingress_vts: -1, + ha_proxy: -2, + aws_elb: -3, + nginx: -4, + kubernetes: -5, + nginx_ingress: -6, + + # custom/user groups + business: 0, + response: 1, + system: 2 + } + end + + def self.group_details + { + # built-in groups + nginx_ingress_vts: { + group_title: _('Response metrics (NGINX Ingress VTS)'), + required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg), + priority: 10 + }.freeze, + nginx_ingress: { + group_title: _('Response metrics (NGINX Ingress)'), + required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum), + priority: 10 + }.freeze, + ha_proxy: { + group_title: _('Response metrics (HA Proxy)'), + required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total), + priority: 10 + }.freeze, + aws_elb: { + group_title: _('Response metrics (AWS ELB)'), + required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum), + priority: 10 + }.freeze, + nginx: { + group_title: _('Response metrics (NGINX)'), + required_metrics: %w(nginx_server_requests nginx_server_requestMsec), + priority: 10 + }.freeze, + kubernetes: { + group_title: _('System metrics (Kubernetes)'), + required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total), + priority: 5 + }.freeze, + + # custom/user groups + business: { + group_title: _('Business metrics (Custom)'), + priority: 0 + }.freeze, + response: { + group_title: _('Response metrics (Custom)'), + priority: -5 + }.freeze, + system: { + group_title: _('System metrics (Custom)'), + priority: -10 + }.freeze + }.freeze + end +end diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index af705b29f7a..6b5605f9999 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -31,7 +31,7 @@ class RemoteMirror < ApplicationRecord scope :enabled, -> { where(enabled: true) } scope :started, -> { with_update_status(:started) } - scope :stuck, -> { started.where('last_update_at < ? OR (last_update_at IS NULL AND updated_at < ?)', 1.day.ago, 1.day.ago) } + scope :stuck, -> { started.where('last_update_at < ? OR (last_update_at IS NULL AND updated_at < ?)', 1.hour.ago, 3.hours.ago) } state_machine :update_status, initial: :none do event :update_start do @@ -173,7 +173,7 @@ class RemoteMirror < ApplicationRecord result = URI.parse(url) result.password = '*****' if result.password - result.user = '*****' if result.user && result.user != "git" # tokens or other data may be saved as user + result.user = '*****' if result.user && result.user != 'git' # tokens or other data may be saved as user result.to_s end diff --git a/app/models/repository.rb b/app/models/repository.rb index a25d5abfa64..187382ad182 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -273,7 +273,7 @@ class Repository # This will still fail if the file is corrupted (e.g. 0 bytes) raw_repository.write_ref(keep_around_ref_name(sha), sha) rescue Gitlab::Git::CommandError => ex - Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" + Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" # rubocop:disable Gitlab/RailsLogger end end @@ -934,6 +934,7 @@ class Repository async_remove_remote(remote_name) if tmp_remote_name end + # rubocop:disable Gitlab/RailsLogger def async_remove_remote(remote_name) return unless remote_name @@ -947,6 +948,7 @@ class Repository job_id end + # rubocop:enable Gitlab/RailsLogger def fetch_source_branch!(source_repository, source_branch, local_ref) raw_repository.fetch_source_branch!(source_repository.raw_repository, source_branch, local_ref) diff --git a/app/models/ssh_host_key.rb b/app/models/ssh_host_key.rb index b6fb39ee81f..9bd35d30845 100644 --- a/app/models/ssh_host_key.rb +++ b/app/models/ssh_host_key.rb @@ -106,7 +106,7 @@ class SshHostKey if status.success? && !errors.present? { known_hosts: known_hosts } else - Rails.logger.debug("Failed to detect SSH host keys for #{id}: #{errors}") + Rails.logger.debug("Failed to detect SSH host keys for #{id}: #{errors}") # rubocop:disable Gitlab/RailsLogger { error: 'Failed to detect SSH host keys' } end diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb index b483c677be9..928c773c307 100644 --- a/app/models/storage/legacy_project.rb +++ b/app/models/storage/legacy_project.rb @@ -41,7 +41,7 @@ module Storage gitlab_shell.mv_repository(repository_storage, "#{old_full_path}.wiki", "#{new_full_path}.wiki") return true rescue => e - Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}" + Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}" # rubocop:disable Gitlab/RailsLogger # Returning false does not rollback after_* transaction but gives # us information about failing some of tasks return false diff --git a/app/models/uploads/base.rb b/app/models/uploads/base.rb index f9814159958..29f376670da 100644 --- a/app/models/uploads/base.rb +++ b/app/models/uploads/base.rb @@ -7,7 +7,7 @@ module Uploads attr_reader :logger def initialize(logger: nil) - @logger ||= Rails.logger + @logger ||= Rails.logger # rubocop:disable Gitlab/RailsLogger end def delete_keys_async(keys_to_delete) diff --git a/app/models/user.rb b/app/models/user.rb index 26be197209a..b439d1c0c16 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -185,6 +185,7 @@ class User < ApplicationRecord before_validation :set_notification_email, if: :new_record? before_validation :set_public_email, if: :public_email_changed? before_validation :set_commit_email, if: :commit_email_changed? + before_save :default_private_profile_to_false before_save :set_public_email, if: :public_email_changed? # in case validation is skipped before_save :set_commit_email, if: :commit_email_changed? # in case validation is skipped before_save :ensure_incoming_email_token @@ -1117,9 +1118,10 @@ class User < ApplicationRecord def ensure_namespace_correct if namespace - namespace.path = namespace.name = username if username_changed? + namespace.path = username if username_changed? + namespace.name = name if name_changed? else - build_namespace(path: username, name: username) + build_namespace(path: username, name: name) end end @@ -1257,6 +1259,11 @@ class User < ApplicationRecord end end + def notification_email_for(notification_group) + # Return group-specific email address if present, otherwise return global notification email address + notification_group&.notification_email_for(self) || notification_email + end + def notification_settings_for(source) if notification_settings.loaded? notification_settings.find do |notification| @@ -1490,6 +1497,12 @@ class User < ApplicationRecord private + def default_private_profile_to_false + return unless private_profile_changed? && private_profile.nil? + + self.private_profile = false + end + def has_current_license? false end |