summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/clusters/applications/helm.rb49
-rw-r--r--app/models/clusters/applications/ingress.rb4
-rw-r--r--app/models/clusters/applications/jupyter.rb4
-rw-r--r--app/models/clusters/applications/prometheus.rb4
-rw-r--r--app/models/clusters/applications/runner.rb4
-rw-r--r--app/models/clusters/concerns/application_data.rb26
-rw-r--r--app/models/concerns/avatarable.rb7
-rw-r--r--app/models/concerns/issuable.rb6
-rw-r--r--app/models/concerns/reactive_caching.rb28
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/list.rb6
-rw-r--r--app/models/merge_request.rb68
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/postgresql/replication_slot.rb32
-rw-r--r--app/models/project.rb6
-rw-r--r--app/models/todo.rb11
19 files changed, 220 insertions, 51 deletions
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 04495ab2908..bbe7811841a 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -297,7 +297,7 @@ class ApplicationSetting < ActiveRecord::Base
unique_ips_limit_per_user: 10,
unique_ips_limit_time_window: 3600,
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
- instance_statistics_visibility_private: true,
+ instance_statistics_visibility_private: false,
user_default_external: false
}
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 75dfa00d12e..e4aed76f611 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -606,12 +606,12 @@ module Ci
end
def has_test_reports?
- complete? && builds.with_test_reports.any?
+ complete? && builds.latest.with_test_reports.any?
end
def test_reports
Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
- builds.with_test_reports.each do |build|
+ builds.latest.with_test_reports.each do |build|
build.collect_test_reports!(test_reports)
end
end
diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb
index 61df6174c86..55bbf7cae7e 100644
--- a/app/models/clusters/applications/helm.rb
+++ b/app/models/clusters/applications/helm.rb
@@ -1,15 +1,28 @@
# frozen_string_literal: true
+require 'openssl'
+
module Clusters
module Applications
class Helm < ActiveRecord::Base
self.table_name = 'clusters_applications_helm'
+ attr_encrypted :ca_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-cbc'
+
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION
+ before_create :create_keys_and_certs
+
+ def issue_client_cert
+ ca_cert_obj.issue
+ end
+
def set_initial_status
return unless not_installable?
@@ -17,7 +30,41 @@ module Clusters
end
def install_command
- Gitlab::Kubernetes::Helm::InitCommand.new(name)
+ Gitlab::Kubernetes::Helm::InitCommand.new(
+ name: name,
+ files: files
+ )
+ end
+
+ def has_ssl?
+ ca_key.present? && ca_cert.present?
+ end
+
+ private
+
+ def files
+ {
+ 'ca.pem': ca_cert,
+ 'cert.pem': tiller_cert.cert_string,
+ 'key.pem': tiller_cert.key_string
+ }
+ end
+
+ def create_keys_and_certs
+ ca_cert = Gitlab::Kubernetes::Helm::Certificate.generate_root
+ self.ca_key = ca_cert.key_string
+ self.ca_cert = ca_cert.cert_string
+ end
+
+ def tiller_cert
+ @tiller_cert ||= ca_cert_obj.issue(expires_in: Gitlab::Kubernetes::Helm::Certificate::INFINITE_EXPIRY)
+ end
+
+ def ca_cert_obj
+ return unless has_ssl?
+
+ Gitlab::Kubernetes::Helm::Certificate
+ .from_strings(ca_key, ca_cert)
end
end
end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 2440efe76ab..93f654e0638 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -37,10 +37,10 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
- name,
+ name: name,
version: VERSION,
chart: chart,
- values: values
+ files: files
)
end
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index 33d54ba86fe..ef1c76c03bd 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -38,10 +38,10 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
- name,
+ name: name,
version: VERSION,
chart: chart,
- values: values,
+ files: files,
repository: repository
)
end
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index ccb415b3fe2..88399dbbb95 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -46,10 +46,10 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
- name,
+ name: name,
version: VERSION,
chart: chart,
- values: values
+ files: files
)
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 426aed91089..bde255723c8 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -31,10 +31,10 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
- name,
+ name: name,
version: VERSION,
chart: chart,
- values: values,
+ files: files,
repository: repository
)
end
diff --git a/app/models/clusters/concerns/application_data.rb b/app/models/clusters/concerns/application_data.rb
index 14e004b9a57..52498f123ff 100644
--- a/app/models/clusters/concerns/application_data.rb
+++ b/app/models/clusters/concerns/application_data.rb
@@ -14,8 +14,34 @@ module Clusters
File.read(chart_values_file)
end
+ def files
+ @files ||= begin
+ files = { 'values.yaml': values }
+
+ files.merge!(certificate_files) if cluster.application_helm.has_ssl?
+
+ files
+ end
+ end
+
private
+ def certificate_files
+ {
+ 'ca.pem': ca_cert,
+ 'cert.pem': helm_cert.cert_string,
+ 'key.pem': helm_cert.key_string
+ }
+ end
+
+ def ca_cert
+ cluster.application_helm.ca_cert
+ end
+
+ def helm_cert
+ @helm_cert ||= cluster.application_helm.issue_client_cert
+ end
+
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index 095897b08e3..a6d604a580d 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -19,7 +19,7 @@ module Avatarable
# We use avatar_path instead of overriding avatar_url because of carrierwave.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
- avatar_path(only_path: args.fetch(:only_path, true)) || super
+ avatar_path(only_path: args.fetch(:only_path, true), size: args[:size]) || super
end
def retrieve_upload(identifier, paths)
@@ -40,12 +40,13 @@ module Avatarable
end
end
- def avatar_path(only_path: true)
+ def avatar_path(only_path: true, size: nil)
return unless self[:avatar].present?
asset_host = ActionController::Base.asset_host
use_asset_host = asset_host.present?
use_authentication = respond_to?(:public?) && !public?
+ query_params = size&.nonzero? ? "?width=#{size}" : ""
# Avatars for private and internal groups and projects require authentication to be viewed,
# which means they can only be served by Rails, on the regular GitLab host.
@@ -64,7 +65,7 @@ module Avatarable
url_base << gitlab_config.relative_url_root
end
- url_base + avatar.local_url
+ url_base + avatar.local_url + query_params
end
# Path that is persisted in the tracking Upload model. Used to fetch the
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index dfdc55b68d1..1588f76989b 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -243,6 +243,12 @@ module Issuable
opened?
end
+ def overdue?
+ return false unless respond_to?(:due_date)
+
+ due_date.try(:past?) || false
+ end
+
def user_notes_count
if notes.loaded?
# Use the in-memory association to select and count to avoid hitting the db
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index 9155d82d567..65cc7a751f9 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -42,6 +42,8 @@
module ReactiveCaching
extend ActiveSupport::Concern
+ InvalidateReactiveCache = Class.new(StandardError)
+
included do
class_attribute :reactive_cache_lease_timeout
@@ -63,15 +65,19 @@ module ReactiveCaching
end
def with_reactive_cache(*args, &blk)
- bootstrap = !within_reactive_cache_lifetime?(*args)
- Rails.cache.write(alive_reactive_cache_key(*args), true, expires_in: self.class.reactive_cache_lifetime)
+ unless within_reactive_cache_lifetime?(*args)
+ refresh_reactive_cache!(*args)
+ return nil
+ end
- if bootstrap
- ReactiveCachingWorker.perform_async(self.class, id, *args)
- nil
- else
+ keep_alive_reactive_cache!(*args)
+
+ begin
data = Rails.cache.read(full_reactive_cache_key(*args))
yield data if data.present?
+ rescue InvalidateReactiveCache
+ refresh_reactive_cache!(*args)
+ nil
end
end
@@ -96,6 +102,16 @@ module ReactiveCaching
private
+ def refresh_reactive_cache!(*args)
+ clear_reactive_cache!(*args)
+ keep_alive_reactive_cache!(*args)
+ ReactiveCachingWorker.perform_async(self.class, id, *args)
+ end
+
+ def keep_alive_reactive_cache!(*args)
+ Rails.cache.write(alive_reactive_cache_key(*args), true, expires_in: self.class.reactive_cache_lifetime)
+ end
+
def full_reactive_cache_key(*qualifiers)
prefix = self.class.reactive_cache_key
prefix = prefix.call(self) if prefix.respond_to?(:call)
diff --git a/app/models/group.rb b/app/models/group.rb
index cd548fc0061..106a1f4a94c 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -41,6 +41,8 @@ class Group < Namespace
has_many :boards
has_many :badges, class_name: 'GroupBadge'
+ has_many :todos
+
accepts_nested_attributes_for :variables, allow_destroy: true
validate :visibility_level_allowed_by_projects
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 0d135f54038..94cf12f3c2b 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -278,10 +278,6 @@ class Issue < ActiveRecord::Base
user ? readable_by?(user) : publicly_visible?
end
- def overdue?
- due_date.try(:past?) || false
- end
-
def check_for_spam?
project.public? && (title_changed? || description_changed?)
end
diff --git a/app/models/list.rb b/app/models/list.rb
index eabe3ffccbb..1a30acc83cf 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -4,7 +4,7 @@ class List < ActiveRecord::Base
belongs_to :board
belongs_to :label
- enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3 }
+ enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3, milestone: 4 }
validates :board, :list_type, presence: true
validates :label, :position, presence: true, if: :label?
@@ -27,11 +27,11 @@ class List < ActiveRecord::Base
end
def destroyable?
- label?
+ self.class.destroyable_types.include?(list_type&.to_sym)
end
def movable?
- label?
+ self.class.movable_types.include?(list_type&.to_sym)
end
def title
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 6de44751f1b..396647a14ae 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -16,8 +16,8 @@ class MergeRequest < ActiveRecord::Base
include ReactiveCaching
self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
- self.reactive_cache_refresh_interval = 1.hour
- self.reactive_cache_lifetime = 1.hour
+ self.reactive_cache_refresh_interval = 10.minutes
+ self.reactive_cache_lifetime = 10.minutes
ignore_column :locked_at,
:ref_fetched,
@@ -60,6 +60,8 @@ class MergeRequest < ActiveRecord::Base
class_name: 'MergeRequestsClosingIssues',
dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+ has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue
+
belongs_to :assignee, class_name: "User"
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
@@ -763,8 +765,9 @@ class MergeRequest < ActiveRecord::Base
# Calculating this information for a number of merge requests requires
# running `ReferenceExtractor` on each of them separately.
# This optimization does not apply to issues from external sources.
- def cache_merge_request_closes_issues!(current_user)
+ def cache_merge_request_closes_issues!(current_user = self.author)
return unless project.issues_enabled?
+ return if closed? || merged?
transaction do
self.merge_requests_closing_issues.delete_all
@@ -777,6 +780,18 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def visible_closing_issues_for(current_user = self.author)
+ strong_memoize(:visible_closing_issues_for) do
+ if self.target_project.has_external_issue_tracker?
+ closes_issues(current_user)
+ else
+ cached_closes_issues.select do |issue|
+ Ability.allowed?(current_user, :read_issue, issue)
+ end
+ end
+ end
+ end
+
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
@@ -796,7 +811,7 @@ class MergeRequest < ActiveRecord::Base
ext = Gitlab::ReferenceExtractor.new(project, current_user)
ext.analyze("#{title}\n#{description}")
- ext.issues - closes_issues(current_user)
+ ext.issues - visible_closing_issues_for(current_user)
end
def target_project_path
@@ -844,7 +859,7 @@ class MergeRequest < ActiveRecord::Base
end
def merge_commit_message(include_description: false)
- closes_issues_references = closes_issues.map do |issue|
+ closes_issues_references = visible_closing_issues_for.map do |issue|
issue.to_reference(target_project)
end
@@ -1026,16 +1041,21 @@ class MergeRequest < ActiveRecord::Base
return { status: :error, status_reason: 'This merge request does not have test reports' }
end
- with_reactive_cache(
- :compare_test_results,
- base_pipeline&.iid,
- actual_head_pipeline.iid) { |data| data } || { status: :parsing }
+ with_reactive_cache(:compare_test_results) do |data|
+ unless Ci::CompareTestReportsService.new(project)
+ .latest?(base_pipeline, actual_head_pipeline, data)
+ raise InvalidateReactiveCache
+ end
+
+ data
+ end || { status: :parsing }
end
def calculate_reactive_cache(identifier, *args)
case identifier.to_sym
when :compare_test_results
- Ci::CompareTestReportsService.new(project).execute(*args)
+ Ci::CompareTestReportsService.new(project).execute(
+ base_pipeline, actual_head_pipeline)
else
raise NotImplementedError, "Unknown identifier: #{identifier}"
end
@@ -1074,23 +1094,29 @@ class MergeRequest < ActiveRecord::Base
def can_be_reverted?(current_user)
return false unless merge_commit
+ return false unless merged_at
- merged_at = metrics&.merged_at
- notes_association = notes_with_associations
-
- if merged_at
- # It is not guaranteed that Note#created_at will be strictly later than
- # MergeRequestMetric#merged_at. Nanoseconds on MySQL may break this
- # comparison, as will a HA environment if clocks are not *precisely*
- # synchronized. Add a minute's leeway to compensate for both possibilities
- cutoff = merged_at - 1.minute
+ # It is not guaranteed that Note#created_at will be strictly later than
+ # MergeRequestMetric#merged_at. Nanoseconds on MySQL may break this
+ # comparison, as will a HA environment if clocks are not *precisely*
+ # synchronized. Add a minute's leeway to compensate for both possibilities
+ cutoff = merged_at - 1.minute
- notes_association = notes_association.where('created_at >= ?', cutoff)
- end
+ notes_association = notes_with_associations.where('created_at >= ?', cutoff)
!merge_commit.has_been_reverted?(current_user, notes_association)
end
+ def merged_at
+ strong_memoize(:merged_at) do
+ next unless merged?
+
+ metrics&.merged_at ||
+ merge_event&.created_at ||
+ notes.system.reorder(nil).find_by(note: 'merged')&.created_at
+ end
+ end
+
def can_be_cherry_picked?
merge_commit.present?
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 969d34ae09a..2e343b8f9f8 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -231,6 +231,10 @@ class Note < ActiveRecord::Base
!for_personal_snippet?
end
+ def for_issuable?
+ for_issue? || for_merge_request?
+ end
+
def skip_project_check?
!for_project_noteable?
end
diff --git a/app/models/postgresql/replication_slot.rb b/app/models/postgresql/replication_slot.rb
new file mode 100644
index 00000000000..70c7432e6b5
--- /dev/null
+++ b/app/models/postgresql/replication_slot.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Postgresql
+ class ReplicationSlot < ActiveRecord::Base
+ self.table_name = 'pg_replication_slots'
+
+ # Returns true if the lag observed across all replication slots exceeds a
+ # given threshold.
+ #
+ # max - The maximum replication lag size, in bytes. Based on GitLab.com
+ # statistics it takes between 1 and 5 seconds to replicate around
+ # 100 MB of data.
+ def self.lag_too_great?(max = 100.megabytes)
+ lag_function = "#{Gitlab::Database.pg_wal_lsn_diff}" \
+ "(#{Gitlab::Database.pg_current_wal_insert_lsn}(), restart_lsn)::bigint"
+
+ # We force the use of a transaction here so the query always goes to the
+ # primary, even when using the EE DB load balancer.
+ sizes = transaction { pluck(lag_function) }
+ too_great = sizes.count { |size| size >= max }
+
+ # If too many replicas are falling behind too much, the availability of a
+ # GitLab instance might suffer. To prevent this from happening we require
+ # at least 1 replica to have data recent enough.
+ if sizes.any? && too_great.positive?
+ (sizes.length - too_great) <= 1
+ else
+ false
+ end
+ end
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index cb4d2610e0d..36089995ed3 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -507,6 +507,10 @@ class Project < ActiveRecord::Base
end
end
+ def has_auto_devops_implicitly_enabled?
+ auto_devops&.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?
+ end
+
def has_auto_devops_implicitly_disabled?
auto_devops&.enabled.nil? && !Gitlab::CurrentSettings.auto_devops_enabled?
end
@@ -654,6 +658,8 @@ class Project < ActiveRecord::Base
project_import_data.credentials ||= {}
project_import_data.credentials = project_import_data.credentials.merge(credentials)
end
+
+ project_import_data
end
def import?
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 5f5c2f9073d..48d92ad04b3 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -24,15 +24,18 @@ class Todo < ActiveRecord::Base
belongs_to :author, class_name: "User"
belongs_to :note
belongs_to :project
+ belongs_to :group
belongs_to :target, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations
belongs_to :user
delegate :name, :email, to: :author, prefix: true, allow_nil: true
- validates :action, :project, :target_type, :user, presence: true
+ validates :action, :target_type, :user, presence: true
validates :author, presence: true
validates :target_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit?
+ validates :project, presence: true, unless: :group_id
+ validates :group, presence: true, unless: :project_id
scope :pending, -> { with_state(:pending) }
scope :done, -> { with_state(:done) }
@@ -46,7 +49,7 @@ class Todo < ActiveRecord::Base
state :done
end
- after_save :keep_around_commit
+ after_save :keep_around_commit, if: :commit_id
class << self
# Priority sorting isn't displayed in the dropdown, because we don't show
@@ -81,6 +84,10 @@ class Todo < ActiveRecord::Base
end
end
+ def parent
+ project
+ end
+
def unmergeable?
action == UNMERGEABLE
end