diff options
Diffstat (limited to 'app/models/concerns')
-rw-r--r-- | app/models/concerns/bulk_member_access_load.rb | 5 | ||||
-rw-r--r-- | app/models/concerns/ci/has_deployment_name.rb | 2 | ||||
-rw-r--r-- | app/models/concerns/ci/has_status.rb | 19 | ||||
-rw-r--r-- | app/models/concerns/cross_database_modification.rb | 2 | ||||
-rw-r--r-- | app/models/concerns/deployment_platform.rb | 2 | ||||
-rw-r--r-- | app/models/concerns/integrations/loggable.rb | 37 | ||||
-rw-r--r-- | app/models/concerns/integrations/reset_secret_fields.rb | 41 | ||||
-rw-r--r-- | app/models/concerns/integrations/slack_mattermost_notifier.rb | 12 | ||||
-rw-r--r-- | app/models/concerns/issuable.rb | 4 | ||||
-rw-r--r-- | app/models/concerns/limitable.rb | 4 | ||||
-rw-r--r-- | app/models/concerns/merge_request_reviewer_state.rb | 2 | ||||
-rw-r--r-- | app/models/concerns/packages/destructible.rb | 2 | ||||
-rw-r--r-- | app/models/concerns/pg_full_text_searchable.rb | 14 | ||||
-rw-r--r-- | app/models/concerns/project_services_loggable.rb | 28 | ||||
-rw-r--r-- | app/models/concerns/routable.rb | 4 | ||||
-rw-r--r-- | app/models/concerns/sha256_attribute.rb | 45 | ||||
-rw-r--r-- | app/models/concerns/sha_attribute.rb | 64 |
17 files changed, 174 insertions, 113 deletions
diff --git a/app/models/concerns/bulk_member_access_load.rb b/app/models/concerns/bulk_member_access_load.rb index efc65e55e40..c3aa3019abb 100644 --- a/app/models/concerns/bulk_member_access_load.rb +++ b/app/models/concerns/bulk_member_access_load.rb @@ -12,6 +12,11 @@ module BulkMemberAccessLoad end end + def purge_resource_id_from_request_store(resource_klass, resource_id) + Gitlab::SafeRequestPurger.execute(resource_key: max_member_access_for_resource_key(resource_klass), + resource_ids: [resource_id]) + end + def max_member_access_for_resource_key(klass) "max_member_access_for_#{klass.name.underscore.pluralize}:#{self.class}:#{self.id}" end diff --git a/app/models/concerns/ci/has_deployment_name.rb b/app/models/concerns/ci/has_deployment_name.rb index fe288134872..887653e846e 100644 --- a/app/models/concerns/ci/has_deployment_name.rb +++ b/app/models/concerns/ci/has_deployment_name.rb @@ -5,7 +5,7 @@ module Ci extend ActiveSupport::Concern def count_user_deployment? - Feature.enabled?(:job_deployment_count) && deployment_name? + deployment_name? end def deployment_name? diff --git a/app/models/concerns/ci/has_status.rb b/app/models/concerns/ci/has_status.rb index 313c767e59f..cca66c3ec94 100644 --- a/app/models/concerns/ci/has_status.rb +++ b/app/models/concerns/ci/has_status.rb @@ -7,16 +7,15 @@ module Ci DEFAULT_STATUS = 'created' BLOCKED_STATUS = %w[manual scheduled].freeze AVAILABLE_STATUSES = %w[created waiting_for_resource preparing pending running success failed canceled skipped manual scheduled].freeze - # TODO: replace STARTED_STATUSES with data from BUILD_STARTED_RUNNING_STATUSES in https://gitlab.com/gitlab-org/gitlab/-/issues/273378 - # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82149#note_865508501 - BUILD_STARTED_RUNNING_STATUSES = %w[running success failed].freeze - STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze + STARTED_STATUSES = %w[running success failed].freeze ACTIVE_STATUSES = %w[waiting_for_resource preparing pending running].freeze COMPLETED_STATUSES = %w[success failed canceled skipped].freeze + STOPPED_STATUSES = COMPLETED_STATUSES + BLOCKED_STATUS ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze - CANCELABLE_STATUSES = %w[running waiting_for_resource preparing pending created scheduled].freeze + ALIVE_STATUSES = (ACTIVE_STATUSES + ['created']).freeze + CANCELABLE_STATUSES = (ALIVE_STATUSES + ['scheduled']).freeze STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3, failed: 4, canceled: 5, skipped: 6, manual: 7, scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze @@ -47,6 +46,10 @@ module Ci def completed_statuses COMPLETED_STATUSES.map(&:to_sym) end + + def stopped_statuses + STOPPED_STATUSES.map(&:to_sym) + end end included do @@ -78,8 +81,8 @@ module Ci scope :skipped, -> { with_status(:skipped) } scope :manual, -> { with_status(:manual) } scope :scheduled, -> { with_status(:scheduled) } - scope :alive, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running) } - scope :alive_or_scheduled, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled) } + scope :alive, -> { with_status(*ALIVE_STATUSES) } + scope :alive_or_scheduled, -> { with_status(*klass::CANCELABLE_STATUSES) } scope :created_or_pending, -> { with_status(:created, :pending) } scope :running_or_pending, -> { with_status(:running, :pending) } scope :finished, -> { with_status(:success, :failed, :canceled) } @@ -98,7 +101,7 @@ module Ci end def started? - STARTED_STATUSES.include?(status) && started_at + STARTED_STATUSES.include?(status) && !!started_at end def active? diff --git a/app/models/concerns/cross_database_modification.rb b/app/models/concerns/cross_database_modification.rb index 85645e482f6..dea62f03f91 100644 --- a/app/models/concerns/cross_database_modification.rb +++ b/app/models/concerns/cross_database_modification.rb @@ -103,7 +103,7 @@ module CrossDatabaseModification def track_gitlab_schema_in_current_transaction? return false unless Feature::FlipperFeature.table_exists? - Feature.enabled?(:track_gitlab_schema_in_current_transaction, default_enabled: :yaml) + Feature.enabled?(:track_gitlab_schema_in_current_transaction) rescue ActiveRecord::NoDatabaseError, PG::ConnectionBad false end diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb index d9c622f247a..2b5e1a204cb 100644 --- a/app/models/concerns/deployment_platform.rb +++ b/app/models/concerns/deployment_platform.rb @@ -3,7 +3,7 @@ module DeploymentPlatform # rubocop:disable Gitlab/ModuleWithInstanceVariables def deployment_platform(environment: nil) - return if Feature.disabled?(:certificate_based_clusters, default_enabled: :yaml, type: :ops) + return unless self.namespace.certificate_based_clusters_enabled? @deployment_platform ||= {} diff --git a/app/models/concerns/integrations/loggable.rb b/app/models/concerns/integrations/loggable.rb new file mode 100644 index 00000000000..57847ea335c --- /dev/null +++ b/app/models/concerns/integrations/loggable.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Integrations + module Loggable + def log_info(message, params = {}) + message = build_message(message, params) + + logger.info(message) + end + + def log_error(message, params = {}) + message = build_message(message, params) + + logger.error(message) + end + + def log_exception(error, params = {}) + Gitlab::ExceptionLogFormatter.format!(error, params) + + log_error(params[:message] || error.message, params) + end + + def build_message(message, params = {}) + { + integration_class: self.class.name, + integration_id: id, + project_id: project&.id, + project_path: project&.full_path, + message: message + }.merge(params) + end + + def logger + Gitlab::IntegrationsLogger + end + end +end diff --git a/app/models/concerns/integrations/reset_secret_fields.rb b/app/models/concerns/integrations/reset_secret_fields.rb new file mode 100644 index 00000000000..f79c4392f19 --- /dev/null +++ b/app/models/concerns/integrations/reset_secret_fields.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# Integrations should reset their "secret" fields (type: 'password') when certain "exposing" +# fields are changed (e.g. URLs), to avoid leaking secrets to unauthorized parties. +# The result of this is that users have to reenter the secrets to confirm the change. +module Integrations + module ResetSecretFields + extend ActiveSupport::Concern + + included do + before_validation :reset_secret_fields!, if: :reset_secret_fields? + end + + def exposing_secrets_fields + # TODO: Once all integrations use `Integrations::Field` we can remove the `.try` here. + # See: https://gitlab.com/groups/gitlab-org/-/epics/7652 + fields.select { _1.try(:exposes_secrets) }.pluck(:name) + end + + private + + def reset_secret_fields? + exposing_secrets_fields.any? do |field| + public_send("#{field}_changed?") # rubocop:disable GitlabSecurity/PublicSend + end + end + + def reset_secret_fields! + secret_fields.each do |field| + next if public_send("#{field}_touched?") # rubocop:disable GitlabSecurity/PublicSend + + public_send("#{field}=", nil) # rubocop:disable GitlabSecurity/PublicSend + + # NOTE: Some of our specs also write to properties in addition to data fields, + # in order to test backwards compatibility. So in those cases we also need to + # clear the field in properties, since the setter above will only affect the data field. + self.properties = properties.except(field) if properties.present? + end + end + end +end diff --git a/app/models/concerns/integrations/slack_mattermost_notifier.rb b/app/models/concerns/integrations/slack_mattermost_notifier.rb index be13701289a..3bdaa852ddf 100644 --- a/app/models/concerns/integrations/slack_mattermost_notifier.rb +++ b/app/models/concerns/integrations/slack_mattermost_notifier.rb @@ -14,11 +14,21 @@ module Integrations # - https://gitlab.com/gitlab-org/slack-notifier#middleware # - https://gitlab.com/gitlab-org/gitlab/-/issues/347048 notifier = ::Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient)) - notifier.ping( + responses = notifier.ping( message.pretext, attachments: message.attachments, fallback: message.fallback ) + + responses.each do |response| + unless response.success? + log_error('SlackMattermostNotifier HTTP error response', + request_host: response.request.uri.host, + response_code: response.code, + response_body: response.body + ) + end + end end class HTTPClient diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index dbd760a9c45..713a4386fee 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -195,7 +195,7 @@ module Issuable end def supports_escalation? - return false unless ::Feature.enabled?(:incident_escalations, project, default_enabled: :yaml) + return false unless ::Feature.enabled?(:incident_escalations, project) incident? end @@ -520,7 +520,7 @@ module Issuable changes.merge!(hook_association_changes(old_associations)) end - Gitlab::HookData::IssuableBuilder.new(self).build(user: user, changes: changes) + Gitlab::DataBuilder::Issuable.new(self).build(user: user, changes: changes) end def labels_array diff --git a/app/models/concerns/limitable.rb b/app/models/concerns/limitable.rb index fab1aa21634..6ff540b7866 100644 --- a/app/models/concerns/limitable.rb +++ b/app/models/concerns/limitable.rb @@ -28,8 +28,8 @@ module Limitable def validate_scoped_plan_limit_not_exceeded scope_relation = self.public_send(limit_scope) # rubocop:disable GitlabSecurity/PublicSend return unless scope_relation - return if limit_feature_flag && ::Feature.disabled?(limit_feature_flag, scope_relation, default_enabled: :yaml) - return if limit_feature_flag_for_override && ::Feature.enabled?(limit_feature_flag_for_override, scope_relation, default_enabled: :yaml) + return if limit_feature_flag && ::Feature.disabled?(limit_feature_flag, scope_relation) + return if limit_feature_flag_for_override && ::Feature.enabled?(limit_feature_flag_for_override, scope_relation) relation = limit_relation ? self.public_send(limit_relation) : self.class.where(limit_scope => scope_relation) # rubocop:disable GitlabSecurity/PublicSend limits = scope_relation.actual_limits diff --git a/app/models/concerns/merge_request_reviewer_state.rb b/app/models/concerns/merge_request_reviewer_state.rb index 893d06b4da8..18ec1c253e1 100644 --- a/app/models/concerns/merge_request_reviewer_state.rb +++ b/app/models/concerns/merge_request_reviewer_state.rb @@ -16,8 +16,6 @@ module MergeRequestReviewerState belongs_to :updated_state_by, class_name: 'User', foreign_key: :updated_state_by_user_id - after_initialize :set_state, unless: :persisted? - def attention_requested_by return unless attention_requested? diff --git a/app/models/concerns/packages/destructible.rb b/app/models/concerns/packages/destructible.rb index a3b7d8580c1..647c63b7f60 100644 --- a/app/models/concerns/packages/destructible.rb +++ b/app/models/concerns/packages/destructible.rb @@ -5,7 +5,7 @@ module Packages extend ActiveSupport::Concern class_methods do - def next_pending_destruction(order_by: nil) + def next_pending_destruction(order_by:) set = pending_destruction.limit(1).lock('FOR UPDATE SKIP LOCKED') set = set.order(order_by) if order_by set.take diff --git a/app/models/concerns/pg_full_text_searchable.rb b/app/models/concerns/pg_full_text_searchable.rb index 68357c44300..bfc539ee392 100644 --- a/app/models/concerns/pg_full_text_searchable.rb +++ b/app/models/concerns/pg_full_text_searchable.rb @@ -80,6 +80,15 @@ module PgFullTextSearchable pg_full_text_searchable_columns[column[:name]] = column[:weight] end + # When multiple updates are done in a transaction, `saved_changes` will only report the latest save + # and we may miss an update to the searchable columns. + # As a workaround, we set a dirty flag here and update the search data in `after_save_commit`. + after_save do + next unless pg_full_text_searchable_columns.keys.any? { |f| saved_changes.has_key?(f) } + + @update_pg_full_text_search_data = true + end + # We update this outside the transaction because this could raise an error if the resulting tsvector # is too long. When that happens, we still persist the create / update but the model will not have a # search data record. This is fine in most cases because this is a very rare occurrence and only happens @@ -87,9 +96,8 @@ module PgFullTextSearchable # # We also do not want to use a subtransaction here due to: https://gitlab.com/groups/gitlab-org/-/epics/6540 after_save_commit do - next unless pg_full_text_searchable_columns.keys.any? { |f| saved_changes.has_key?(f) } - - update_search_data! + update_search_data! if @update_pg_full_text_search_data + @update_pg_full_text_search_data = nil end end diff --git a/app/models/concerns/project_services_loggable.rb b/app/models/concerns/project_services_loggable.rb deleted file mode 100644 index e5385435138..00000000000 --- a/app/models/concerns/project_services_loggable.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module ProjectServicesLoggable - def log_info(message, params = {}) - message = build_message(message, params) - - logger.info(message) - end - - def log_error(message, params = {}) - message = build_message(message, params) - - logger.error(message) - end - - def build_message(message, params = {}) - { - service_class: self.class.name, - project_id: project&.id, - project_path: project&.full_path, - message: message - }.merge(params) - end - - def logger - Gitlab::ProjectServiceLogger - end -end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index 2cf95ac0dae..5b759dedb26 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -97,7 +97,7 @@ module Routable def full_name # We have to test for persistence as the cache key uses #updated_at - return (route&.name || build_full_name) unless persisted? && Feature.enabled?(:cached_route_lookups, self, type: :ops, default_enabled: :yaml) + return (route&.name || build_full_name) unless persisted? && Feature.enabled?(:cached_route_lookups, self, type: :ops) # Return the name as-is if the parent is missing return name if route.nil? && parent.nil? && name.present? @@ -115,7 +115,7 @@ module Routable def full_path # We have to test for persistence as the cache key uses #updated_at - return (route&.path || build_full_path) unless persisted? && Feature.enabled?(:cached_route_lookups, self, type: :ops, default_enabled: :yaml) + return (route&.path || build_full_path) unless persisted? && Feature.enabled?(:cached_route_lookups, self, type: :ops) # Return the path as-is if the parent is missing return path if route.nil? && parent.nil? && path.present? diff --git a/app/models/concerns/sha256_attribute.rb b/app/models/concerns/sha256_attribute.rb deleted file mode 100644 index 3c906642b1a..00000000000 --- a/app/models/concerns/sha256_attribute.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module Sha256Attribute - extend ActiveSupport::Concern - - class_methods do - def sha256_attribute(name) - return if ENV['STATIC_VERIFICATION'] - - validate_binary_column_exists!(name) unless Rails.env.production? - - attribute(name, Gitlab::Database::Sha256Attribute.new) - end - - # This only gets executed in non-production environments as an additional check to ensure - # the column is the correct type. In production it should behave like any other attribute. - # See https://gitlab.com/gitlab-org/gitlab/merge_requests/5502 for more discussion - def validate_binary_column_exists!(name) - return unless database_exists? - - unless table_exists? - warn "WARNING: sha256_attribute #{name.inspect} is invalid since the table doesn't exist - you may need to run database migrations" - return - end - - column = columns.find { |c| c.name == name.to_s } - - unless column - warn "WARNING: sha256_attribute #{name.inspect} is invalid since the column doesn't exist - you may need to run database migrations" - return - end - - unless column.type == :binary - raise ArgumentError, "sha256_attribute #{name.inspect} is invalid since the column type is not :binary" - end - rescue StandardError => error - Gitlab::AppLogger.error "Sha256Attribute initialization: #{error.message}" - raise - end - - def database_exists? - database.exists? - end - end -end diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb index e49f4d03bda..701d2fda5c5 100644 --- a/app/models/concerns/sha_attribute.rb +++ b/app/models/concerns/sha_attribute.rb @@ -3,39 +3,71 @@ module ShaAttribute extend ActiveSupport::Concern - # Needed for the database method - include DatabaseReflection + class ShaAttributeTypeMismatchError < StandardError + def initialize(column_name, column_type) + @column_name = column_name + @column_type = column_type + end + + def message + "sha_attribute :#{@column_name} should be a :binary column but it is :#{@column_type}" + end + end + + class Sha256AttributeTypeMismatchError < ShaAttributeTypeMismatchError + def message + "sha256_attribute :#{@column_name} should be a :binary column but it is :#{@column_type}" + end + end class_methods do def sha_attribute(name) return if ENV['STATIC_VERIFICATION'] - validate_binary_column_exists!(name) if Rails.env.development? || Rails.env.test? + sha_attribute_fields << name attribute(name, Gitlab::Database::ShaAttribute.new) end + def sha_attribute_fields + @sha_attribute_fields ||= [] + end + + def sha256_attribute(name) + return if ENV['STATIC_VERIFICATION'] + + sha256_attribute_fields << name + + attribute(name, Gitlab::Database::Sha256Attribute.new) + end + + def sha256_attribute_fields + @sha256_attribute_fields ||= [] + end + # This only gets executed in non-production environments as an additional check to ensure # the column is the correct type. In production it should behave like any other attribute. # See https://gitlab.com/gitlab-org/gitlab/merge_requests/5502 for more discussion - def validate_binary_column_exists!(name) - return unless database_exists? - return unless table_exists? + def load_schema! + super - column = columns.find { |c| c.name == name.to_s } + return if Rails.env.production? - return unless column + sha_attribute_fields.each do |field| + column = columns_hash[field.to_s] - unless column.type == :binary - raise ArgumentError, "sha_attribute #{name.inspect} is invalid since the column type is not :binary" + if column && column.type != :binary + raise ShaAttributeTypeMismatchError.new(column.name, column.type) + end end - rescue StandardError => error - Gitlab::AppLogger.error "ShaAttribute initialization: #{error.message}" - raise - end - def database_exists? - database.exists? + sha256_attribute_fields.each do |field| + column = columns_hash[field.to_s] + + if column && column.type != :binary + raise Sha256AttributeTypeMismatchError.new(column.name, column.type) + end + end end end end |