diff options
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/application_setting.rb | 2 | ||||
-rw-r--r-- | app/models/ci/pipeline.rb | 4 | ||||
-rw-r--r-- | app/models/clusters/applications/helm.rb | 49 | ||||
-rw-r--r-- | app/models/clusters/applications/ingress.rb | 4 | ||||
-rw-r--r-- | app/models/clusters/applications/jupyter.rb | 4 | ||||
-rw-r--r-- | app/models/clusters/applications/prometheus.rb | 4 | ||||
-rw-r--r-- | app/models/clusters/applications/runner.rb | 4 | ||||
-rw-r--r-- | app/models/clusters/concerns/application_data.rb | 26 | ||||
-rw-r--r-- | app/models/concerns/avatarable.rb | 7 | ||||
-rw-r--r-- | app/models/concerns/issuable.rb | 6 | ||||
-rw-r--r-- | app/models/concerns/reactive_caching.rb | 28 | ||||
-rw-r--r-- | app/models/group.rb | 2 | ||||
-rw-r--r-- | app/models/issue.rb | 4 | ||||
-rw-r--r-- | app/models/list.rb | 6 | ||||
-rw-r--r-- | app/models/merge_request.rb | 68 | ||||
-rw-r--r-- | app/models/note.rb | 4 | ||||
-rw-r--r-- | app/models/postgresql/replication_slot.rb | 32 | ||||
-rw-r--r-- | app/models/project.rb | 6 | ||||
-rw-r--r-- | app/models/todo.rb | 11 |
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 |