summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/analytics/cycle_analytics.rb9
-rw-r--r--app/models/analytics/cycle_analytics/project_stage.rb9
-rw-r--r--app/models/application_setting.rb5
-rw-r--r--app/models/application_setting_implementation.rb4
-rw-r--r--app/models/ci/build.rb4
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/clusters/applications/cert_manager.rb8
-rw-r--r--app/models/clusters/applications/knative.rb12
-rw-r--r--app/models/clusters/applications/prometheus.rb4
-rw-r--r--app/models/clusters/clusters_hierarchy.rb4
-rw-r--r--app/models/commit_status.rb21
-rw-r--r--app/models/concerns/has_status.rb5
-rw-r--r--app/models/merge_request.rb13
-rw-r--r--app/models/namespace.rb7
-rw-r--r--app/models/notification_recipient.rb8
-rw-r--r--app/models/project.rb16
-rw-r--r--app/models/project_services/chat_message/pipeline_message.rb2
-rw-r--r--app/models/project_services/emails_on_push_service.rb1
-rw-r--r--app/models/project_services/slash_commands_service.rb2
-rw-r--r--app/models/remote_mirror.rb57
-rw-r--r--app/models/repository.rb22
-rw-r--r--app/models/user.rb7
22 files changed, 174 insertions, 50 deletions
diff --git a/app/models/analytics/cycle_analytics.rb b/app/models/analytics/cycle_analytics.rb
new file mode 100644
index 00000000000..626fc91cc41
--- /dev/null
+++ b/app/models/analytics/cycle_analytics.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Analytics
+ module CycleAnalytics
+ def self.table_name_prefix
+ 'analytics_cycle_analytics_'
+ end
+ end
+end
diff --git a/app/models/analytics/cycle_analytics/project_stage.rb b/app/models/analytics/cycle_analytics/project_stage.rb
new file mode 100644
index 00000000000..88c8cb40ccb
--- /dev/null
+++ b/app/models/analytics/cycle_analytics/project_stage.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Analytics
+ module CycleAnalytics
+ class ProjectStage < ApplicationRecord
+ belongs_to :project
+ end
+ end
+end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index cb6346421ec..2a99c6e5c59 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -99,6 +99,11 @@ class ApplicationSetting < ApplicationRecord
presence: true,
if: :plantuml_enabled
+ validates :snowplow_collector_hostname,
+ presence: true,
+ hostname: true,
+ if: :snowplow_enabled
+
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index b7a4d7aa803..55ac1e129cf 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -97,6 +97,10 @@ module ApplicationSettingImplementation
usage_stats_set_by_user_id: nil,
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
commit_email_hostname: default_commit_email_hostname,
+ snowplow_collector_hostname: nil,
+ snowplow_cookie_domain: nil,
+ snowplow_enabled: false,
+ snowplow_site_id: nil,
protected_ci_variables: false,
local_markdown_version: 0,
outbound_local_requests_whitelist: [],
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index ac88d9714ac..3c0efca31db 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -384,7 +384,7 @@ module Ci
return unless has_environment?
strong_memoize(:expanded_environment_name) do
- ExpandVariables.expand(environment, simple_variables)
+ ExpandVariables.expand(environment, -> { simple_variables })
end
end
@@ -716,7 +716,7 @@ module Ci
depended_jobs = depends_on_builds
# find all jobs that are needed
- if Feature.enabled?(:ci_dag_support, project) && needs.exists?
+ if Feature.enabled?(:ci_dag_support, project, default_enabled: true) && needs.exists?
depended_jobs = depended_jobs.where(name: needs.select(:name))
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 3b28eb246db..0a943a33bbb 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -328,6 +328,10 @@ module Ci
config_sources.values_at(:repository_source, :auto_devops_source, :unknown_source)
end
+ def self.bridgeable_statuses
+ ::Ci::Pipeline::AVAILABLE_STATUSES - %w[created preparing pending]
+ end
+
def stages_count
statuses.select(:stage).distinct.count
end
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index 2fc1b67dfd2..6bd7473c8ff 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -64,11 +64,15 @@ module Clusters
end
def delete_private_key
- "kubectl delete secret -n #{Gitlab::Kubernetes::Helm::NAMESPACE} #{private_key_name} --ignore-not-found" if private_key_name.present?
+ return unless private_key_name.present?
+
+ args = %W(secret -n #{Gitlab::Kubernetes::Helm::NAMESPACE} #{private_key_name} --ignore-not-found)
+
+ Gitlab::Kubernetes::KubectlCmd.delete(*args)
end
def delete_crd(definition)
- "kubectl delete crd #{definition} --ignore-not-found"
+ Gitlab::Kubernetes::KubectlCmd.delete("crd", definition, "--ignore-not-found")
end
def cluster_issuer_file
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 5eae23659ae..244fe738396 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -89,7 +89,7 @@ module Clusters
def delete_knative_services
cluster.kubernetes_namespaces.map do |kubernetes_namespace|
- "kubectl delete ksvc --all -n #{kubernetes_namespace.namespace}"
+ Gitlab::Kubernetes::KubectlCmd.delete("ksvc", "--all", "-n", kubernetes_namespace.namespace)
end
end
@@ -99,14 +99,14 @@ module Clusters
def delete_knative_namespaces
[
- "kubectl delete --ignore-not-found ns knative-serving",
- "kubectl delete --ignore-not-found ns knative-build"
+ Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "ns", "knative-serving"),
+ Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "ns", "knative-build")
]
end
def delete_knative_and_istio_crds
api_resources.map do |crd|
- "kubectl delete --ignore-not-found crd #{crd}"
+ Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "crd", "#{crd}")
end
end
@@ -119,13 +119,13 @@ module Clusters
def install_knative_metrics
return [] unless cluster.application_prometheus_available?
- ["kubectl apply -f #{METRICS_CONFIG}"]
+ [Gitlab::Kubernetes::KubectlCmd.apply_file(METRICS_CONFIG)]
end
def delete_knative_istio_metrics
return [] unless cluster.application_prometheus_available?
- ["kubectl delete --ignore-not-found -f #{METRICS_CONFIG}"]
+ [Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "-f", METRICS_CONFIG)]
end
def verify_cluster?
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 08e52f32bb3..f31a6b8b50e 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -106,13 +106,13 @@ module Clusters
def install_knative_metrics
return [] unless cluster.application_knative_available?
- ["kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}"]
+ [Gitlab::Kubernetes::KubectlCmd.apply_file(Clusters::Applications::Knative::METRICS_CONFIG)]
end
def delete_knative_istio_metrics
return [] unless cluster.application_knative_available?
- ["kubectl delete -f #{Clusters::Applications::Knative::METRICS_CONFIG}"]
+ [Gitlab::Kubernetes::KubectlCmd.delete("-f", Clusters::Applications::Knative::METRICS_CONFIG)]
end
end
end
diff --git a/app/models/clusters/clusters_hierarchy.rb b/app/models/clusters/clusters_hierarchy.rb
index dab034b7234..5556fc8d3f0 100644
--- a/app/models/clusters/clusters_hierarchy.rb
+++ b/app/models/clusters/clusters_hierarchy.rb
@@ -46,7 +46,7 @@ module Clusters
def group_clusters_base_query
group_parent_id_alias = alias_as_column(groups[:parent_id], 'group_parent_id')
- join_sources = ::Group.left_joins(:clusters).join_sources
+ join_sources = ::Group.left_joins(:clusters).arel.join_sources
model
.unscoped
@@ -59,7 +59,7 @@ module Clusters
def project_clusters_base_query
projects = ::Project.arel_table
project_parent_id_alias = alias_as_column(projects[:namespace_id], 'group_parent_id')
- join_sources = ::Project.left_joins(:clusters).join_sources
+ join_sources = ::Project.left_joins(:clusters).arel.join_sources
model
.unscoped
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index a88cac6b8e6..4be4d95b4a1 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -40,8 +40,11 @@ class CommitStatus < ApplicationRecord
scope :ordered, -> { order(:name) }
scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
+ scope :before_stage, -> (index) { where('stage_idx < ?', index) }
+ scope :for_stage, -> (index) { where(stage_idx: index) }
scope :after_stage, -> (index) { where('stage_idx > ?', index) }
scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) }
+ scope :for_ids, -> (ids) { where(id: ids) }
scope :with_needs, -> (names = nil) do
needs = Ci::BuildNeed.scoped_build.select(1)
@@ -49,8 +52,10 @@ class CommitStatus < ApplicationRecord
where('EXISTS (?)', needs).preload(:needs)
end
- scope :without_needs, -> do
- where('NOT EXISTS (?)', Ci::BuildNeed.scoped_build.select(1))
+ scope :without_needs, -> (names = nil) do
+ needs = Ci::BuildNeed.scoped_build.select(1)
+ needs = needs.where(name: names) if names
+ where('NOT EXISTS (?)', needs)
end
# We use `CommitStatusEnums.failure_reasons` here so that EE can more easily
@@ -149,6 +154,18 @@ class CommitStatus < ApplicationRecord
end
end
+ def self.names
+ select(:name)
+ end
+
+ def self.status_for_prior_stages(index)
+ before_stage(index).latest.status || 'success'
+ end
+
+ def self.status_for_names(names)
+ where(name: names).latest.status || 'success'
+ end
+
def locking_enabled?
will_save_change_to_status?
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 27a5c3d5286..71ebb586c13 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -106,10 +106,15 @@ module HasStatus
scope :running_or_pending, -> { with_status(:running, :pending) }
scope :finished, -> { with_status(:success, :failed, :canceled) }
scope :failed_or_canceled, -> { with_status(:failed, :canceled) }
+ scope :incomplete, -> { without_statuses(completed_statuses) }
scope :cancelable, -> do
where(status: [:running, :preparing, :pending, :created, :scheduled])
end
+
+ scope :without_statuses, -> (names) do
+ with_status(all_state_names - names.to_a)
+ end
end
def started?
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 4306dd9266f..bfd636fa62a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -220,18 +220,7 @@ class MergeRequest < ApplicationRecord
end
def rebase_in_progress?
- (rebase_jid.present? && Gitlab::SidekiqStatus.running?(rebase_jid)) ||
- gitaly_rebase_in_progress?
- end
-
- # TODO: remove the Gitaly lookup after v12.1, when rebase_jid will be reliable
- def gitaly_rebase_in_progress?
- strong_memoize(:gitaly_rebase_in_progress) do
- # The source project can be deleted
- next false unless source_project
-
- source_project.repository.rebase_in_progress?(id)
- end
+ rebase_jid.present? && Gitlab::SidekiqStatus.running?(rebase_jid)
end
# Use this method whenever you need to make sure the head_pipeline is synced with the
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 058350b16ce..9f9c4288667 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -172,6 +172,13 @@ class Namespace < ApplicationRecord
end
end
+ # any ancestor can disable emails for all descendants
+ def emails_disabled?
+ strong_memoize(:emails_disabled) do
+ Feature.enabled?(:emails_disabled, self, default_enabled: true) && self_and_ancestors.where(emails_disabled: true).exists?
+ end
+ end
+
def lfs_enabled?
# User namespace will always default to the global setting
Gitlab.config.lfs.enabled
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index a7f73c0f29c..8e44e3d8e17 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -4,6 +4,7 @@ class NotificationRecipient
include Gitlab::Utils::StrongMemoize
attr_reader :user, :type, :reason
+
def initialize(user, type, **opts)
unless NotificationSetting.levels.key?(type) || type == :subscription
raise ArgumentError, "invalid type: #{type.inspect}"
@@ -30,6 +31,7 @@ class NotificationRecipient
def notifiable?
return false unless has_access?
+ return false if emails_disabled?
return false if own_activity?
# even users with :disabled notifications receive manual subscriptions
@@ -109,6 +111,12 @@ class NotificationRecipient
private
+ # They are disabled if the project or group has disallowed it.
+ # No need to check the group if there is already a project
+ def emails_disabled?
+ @project ? @project.emails_disabled? : @group&.emails_disabled?
+ end
+
def read_ability
return if @skip_read_ability
return @read_ability if instance_variable_defined?(:@read_ability)
diff --git a/app/models/project.rb b/app/models/project.rb
index a6e43efa1f3..8efe4b06f87 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -283,6 +283,7 @@ class Project < ApplicationRecord
has_one :ci_cd_settings, class_name: 'ProjectCiCdSetting', inverse_of: :project, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :remote_mirrors, inverse_of: :project
+ has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage'
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true
@@ -631,6 +632,13 @@ class Project < ApplicationRecord
alias_method :ancestors, :ancestors_upto
+ def emails_disabled?
+ strong_memoize(:emails_disabled) do
+ # disabling in the namespace overrides the project setting
+ Feature.enabled?(:emails_disabled, self, default_enabled: true) && (super || namespace.emails_disabled?)
+ end
+ end
+
def lfs_enabled?
return namespace.lfs_enabled? if self[:lfs_enabled].nil?
@@ -1230,6 +1238,14 @@ class Project < ApplicationRecord
end
end
+ def has_active_hooks?(hooks_scope = :push_hooks)
+ hooks.hooks_for(hooks_scope).any? || SystemHook.hooks_for(hooks_scope).any?
+ end
+
+ def has_active_services?(hooks_scope = :push_hooks)
+ services.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend
+ end
+
def valid_repo?
repository.exists?
rescue
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index 4edf263433f..a3793d9937b 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -68,7 +68,7 @@ module ChatMessage
title_link: pipeline_url,
fields: attachments_fields,
footer: project.name,
- footer_icon: project.avatar_url,
+ footer_icon: project.avatar_url(only_path: false),
ts: finished_at
}]
end
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index 45de64a9990..8ca40138a8f 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -24,6 +24,7 @@ class EmailsOnPushService < Service
def execute(push_data)
return unless supported_events.include?(push_data[:object_kind])
+ return if project.emails_disabled?
EmailsOnPushWorker.perform_async(
project_id,
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index 5f5cff97808..cb16ad75d14 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -35,6 +35,8 @@ class SlashCommandsService < Service
chat_user = find_chat_user(params)
if chat_user&.user
+ return Gitlab::SlashCommands::Presenters::Access.new.access_denied unless chat_user.user.can?(:use_slash_commands)
+
Gitlab::SlashCommands::Command.new(project, chat_user, params).execute
else
url = authorize_chat_name_url(params)
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index 6b5605f9999..c9ee0653d86 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -4,6 +4,8 @@ class RemoteMirror < ApplicationRecord
include AfterCommitQueue
include MirrorAuthentication
+ MAX_FIRST_RUNTIME = 3.hours
+ MAX_INCREMENTAL_RUNTIME = 1.hour
PROTECTED_BACKOFF_DELAY = 1.minute
UNPROTECTED_BACKOFF_DELAY = 5.minutes
@@ -31,11 +33,18 @@ 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.hour.ago, 3.hours.ago) }
+
+ scope :stuck, -> do
+ started
+ .where('(last_update_started_at < ? AND last_update_at IS NOT NULL)',
+ MAX_INCREMENTAL_RUNTIME.ago)
+ .or(where('(last_update_started_at < ? AND last_update_at IS NULL)',
+ MAX_FIRST_RUNTIME.ago))
+ end
state_machine :update_status, initial: :none do
event :update_start do
- transition [:none, :finished, :failed] => :started
+ transition any => :started
end
event :update_finish do
@@ -46,9 +55,14 @@ class RemoteMirror < ApplicationRecord
transition started: :failed
end
+ event :update_retry do
+ transition started: :to_retry
+ end
+
state :started
state :finished
state :failed
+ state :to_retry
after_transition any => :started do |remote_mirror, _|
Gitlab::Metrics.add_event(:remote_mirrors_running)
@@ -138,16 +152,27 @@ class RemoteMirror < ApplicationRecord
end
def updated_since?(timestamp)
- last_update_started_at && last_update_started_at > timestamp && !update_failed?
+ return false if failed?
+
+ last_update_started_at && last_update_started_at > timestamp
end
def mark_for_delete_if_blank_url
mark_for_destruction if url.blank?
end
- def mark_as_failed(error_message)
- update_column(:last_error, Gitlab::UrlSanitizer.sanitize(error_message))
- update_fail
+ def update_error_message(error_message)
+ self.last_error = Gitlab::UrlSanitizer.sanitize(error_message)
+ end
+
+ def mark_for_retry!(error_message)
+ update_error_message(error_message)
+ update_retry!
+ end
+
+ def mark_as_failed!(error_message)
+ update_error_message(error_message)
+ update_fail!
end
def url=(value)
@@ -190,6 +215,18 @@ class RemoteMirror < ApplicationRecord
update_column(:error_notification_sent, true)
end
+ def backoff_delay
+ if self.only_protected_branches
+ PROTECTED_BACKOFF_DELAY
+ else
+ UNPROTECTED_BACKOFF_DELAY
+ end
+ end
+
+ def max_runtime
+ last_update_at.present? ? MAX_INCREMENTAL_RUNTIME : MAX_FIRST_RUNTIME
+ end
+
private
def store_credentials
@@ -219,14 +256,6 @@ class RemoteMirror < ApplicationRecord
self.last_update_started_at >= Time.now - backoff_delay
end
- def backoff_delay
- if self.only_protected_branches
- PROTECTED_BACKOFF_DELAY
- else
- UNPROTECTED_BACKOFF_DELAY
- end
- end
-
def reset_fields
update_columns(
last_error: nil,
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 58abfaef801..6f63cd32da4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -389,11 +389,15 @@ class Repository
expire_statistics_caches
end
- # Runs code after a repository has been created.
- def after_create
+ def expire_status_cache
expire_exists_cache
expire_root_ref_cache
expire_emptiness_caches
+ end
+
+ # Runs code after a repository has been created.
+ def after_create
+ expire_status_cache
repository_event(:create_repository)
end
@@ -418,25 +422,29 @@ class Repository
end
# Runs code before pushing (= creating or removing) a tag.
+ #
+ # Note that this doesn't expire the tags. You may need to call
+ # expire_caches_for_tags or expire_tags_cache.
def before_push_tag
+ repository_event(:push_tag)
+ end
+
+ def expire_caches_for_tags
expire_statistics_caches
expire_emptiness_caches
expire_tags_cache
-
- repository_event(:push_tag)
end
# Runs code before removing a tag.
def before_remove_tag
- expire_tags_cache
- expire_statistics_caches
+ expire_caches_for_tags
repository_event(:remove_tag)
end
# Runs code after removing a tag.
def after_remove_tag
- expire_tags_cache
+ expire_caches_for_tags
end
# Runs code after the HEAD of a repository is changed.
diff --git a/app/models/user.rb b/app/models/user.rb
index 374e00987c5..6131a8dc710 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1507,6 +1507,13 @@ class User < ApplicationRecord
super
end
+ # override from Devise::Confirmable
+ def confirmation_period_valid?
+ return false if Feature.disabled?(:soft_email_confirmation)
+
+ super
+ end
+
private
def default_private_profile_to_false