summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/application_setting.rb5
-rw-r--r--app/models/ci/build.rb38
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/ci/runner.rb27
-rw-r--r--app/models/clusters/applications/prometheus.rb32
-rw-r--r--app/models/clusters/cluster.rb3
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/concerns/redis_cacheable.rb41
-rw-r--r--app/models/event.rb4
-rw-r--r--app/models/external_issue.rb2
-rw-r--r--app/models/group.rb3
-rw-r--r--app/models/identity.rb9
-rw-r--r--app/models/key.rb7
-rw-r--r--app/models/lfs_file_lock.rb12
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/merge_request.rb8
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/network/graph.rb7
-rw-r--r--app/models/project.rb9
-rw-r--r--app/models/project_auto_devops.rb8
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb75
-rw-r--r--app/models/repository.rb17
-rw-r--r--app/models/snippet.rb21
-rw-r--r--app/models/user.rb9
26 files changed, 317 insertions, 36 deletions
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 80bda7f22ff..0dee6df525d 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -117,6 +117,11 @@ class ApplicationSetting < ActiveRecord::Base
validates :repository_storages, presence: true
validate :check_repository_storages
+ validates :auto_devops_domain,
+ allow_blank: true,
+ hostname: { allow_numeric_hostname: true, require_valid_tld: true },
+ if: :auto_devops_enabled?
+
validates :enabled_git_access_protocol,
inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true }
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 20534b8eed0..490edf4ac57 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -41,12 +41,41 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
+
+ # This convoluted mess is because we need to handle two cases of
+ # artifact files during the migration. And a simple OR clause
+ # makes it impossible to optimize.
+
+ # Instead we want to use UNION ALL and do two carefully
+ # constructed disjoint queries. But Rails cannot handle UNION or
+ # UNION ALL queries so we do the query in a subquery and wrap it
+ # in an otherwise redundant WHERE IN query (IN is fine for
+ # non-null columns).
+
+ # This should all be ripped out when the migration is finished and
+ # replaced with just the new storage to avoid the extra work.
+
scope :with_artifacts, ->() do
- where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
- '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id'))
+ old = Ci::Build.select(:id).where(%q[artifacts_file <> ''])
+ new = Ci::Build.select(:id).where(%q[(artifacts_file IS NULL OR artifacts_file = '') AND EXISTS (?)],
+ Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id'))
+ where('ci_builds.id IN (? UNION ALL ?)', old, new)
end
- scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
- scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
+
+ scope :with_artifacts_not_expired, ->() do
+ old = Ci::Build.select(:id).where(%q[artifacts_file <> '' AND (artifacts_expire_at IS NULL OR artifacts_expire_at > ?)], Time.now)
+ new = Ci::Build.select(:id).where(%q[(artifacts_file IS NULL OR artifacts_file = '') AND EXISTS (?)],
+ Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id AND (expire_at IS NULL OR expire_at > ?)', Time.now))
+ where('ci_builds.id IN (? UNION ALL ?)', old, new)
+ end
+
+ scope :with_expired_artifacts, ->() do
+ old = Ci::Build.select(:id).where(%q[artifacts_file <> '' AND artifacts_expire_at < ?], Time.now)
+ new = Ci::Build.select(:id).where(%q[(artifacts_file IS NULL OR artifacts_file = '') AND EXISTS (?)],
+ Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id AND expire_at < ?', Time.now))
+ where('ci_builds.id IN (? UNION ALL ?)', old, new)
+ end
+
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) }
scope :ref_protected, -> { where(protected: true) }
@@ -543,6 +572,7 @@ module Ci
variables = [
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
+ { key: 'GITLAB_FEATURES', value: project.namespace.features.join(','), public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index f84bf132854..2abe90dd181 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -394,7 +394,7 @@ module Ci
@config_processor ||= begin
Gitlab::Ci::YamlProcessor.new(ci_yaml_file)
- rescue Gitlab::Ci::YamlProcessor::ValidationError, Psych::SyntaxError => e
+ rescue Gitlab::Ci::YamlProcessor::ValidationError => e
self.yaml_errors = e.message
nil
rescue
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index dcbb397fb78..13c784bea0d 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -2,9 +2,11 @@ module Ci
class Runner < ActiveRecord::Base
extend Gitlab::Ci::Model
include Gitlab::SQL::Pattern
+ include RedisCacheable
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour
+ UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes
AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze
@@ -47,6 +49,8 @@ module Ci
ref_protected: 1
}
+ cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at
+
# Searches for runners matching the given query.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
@@ -152,6 +156,18 @@ module Ci
ensure_runner_queue_value == value if value.present?
end
+ def update_cached_info(values)
+ values = values&.slice(:version, :revision, :platform, :architecture) || {}
+ values[:contacted_at] = Time.now
+
+ cache_attributes(values)
+
+ if persist_cached_data?
+ self.assign_attributes(values)
+ self.save if self.changed?
+ end
+ end
+
private
def cleanup_runner_queue
@@ -164,6 +180,17 @@ module Ci
"runner:build_queue:#{self.token}"
end
+ def persist_cached_data?
+ # Use a random threshold to prevent beating DB updates.
+ # It generates a distribution between [40m, 80m].
+
+ contacted_at_max_age = UPDATE_DB_RUNNER_INFO_EVERY + Random.rand(UPDATE_DB_RUNNER_INFO_EVERY)
+
+ real_contacted_at = read_attribute(:contacted_at)
+ real_contacted_at.nil? ||
+ (Time.now - real_contacted_at) >= contacted_at_max_age
+ end
+
def tag_constraints
unless has_tags? || run_untagged?
errors.add(:tags_list,
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 9b0787ee6ca..aa22e9d5d58 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -10,10 +10,26 @@ module Clusters
default_value_for :version, VERSION
+ state_machine :status do
+ after_transition any => [:installed] do |application|
+ application.cluster.projects.each do |project|
+ project.find_or_initialize_service('prometheus').update(active: true)
+ end
+ end
+ end
+
def chart
'stable/prometheus'
end
+ def service_name
+ 'prometheus-prometheus-server'
+ end
+
+ def service_port
+ 80
+ end
+
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
@@ -21,6 +37,22 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end
+
+ def proxy_client
+ return unless kube_client
+
+ proxy_url = kube_client.proxy_url('service', service_name, service_port, Gitlab::Kubernetes::Helm::NAMESPACE)
+
+ # ensures headers containing auth data are appended to original k8s client options
+ options = kube_client.rest_client.options.merge(headers: kube_client.headers)
+ RestClient::Resource.new(proxy_url, options)
+ end
+
+ private
+
+ def kube_client
+ cluster&.kubeclient
+ end
end
end
end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 5ecbd4cbceb..8678f70f78c 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -49,6 +49,9 @@ module Clusters
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
+ scope :for_environment, -> (env) { where(environment_scope: ['*', '', env.slug]) }
+ scope :for_all_environments, -> { where(environment_scope: ['*', '']) }
+
def status_name
if provider
provider.status_name
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 2d2d89af030..8c960389652 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -116,6 +116,10 @@ class Commit
raw.id
end
+ def project_id
+ project.id
+ end
+
def ==(other)
other.is_a?(self.class) && raw == other.raw
end
diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb
new file mode 100644
index 00000000000..b889f4202dc
--- /dev/null
+++ b/app/models/concerns/redis_cacheable.rb
@@ -0,0 +1,41 @@
+module RedisCacheable
+ extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
+
+ CACHED_ATTRIBUTES_EXPIRY_TIME = 24.hours
+
+ class_methods do
+ def cached_attr_reader(*attributes)
+ attributes.each do |attribute|
+ define_method("#{attribute}") do
+ cached_attribute(attribute) || read_attribute(attribute)
+ end
+ end
+ end
+ end
+
+ def cached_attribute(attribute)
+ (cached_attributes || {})[attribute]
+ end
+
+ def cache_attributes(values)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME)
+ end
+ end
+
+ private
+
+ def cache_attribute_key
+ "cache:#{self.class.name}:#{self.id}:attributes"
+ end
+
+ def cached_attributes
+ strong_memoize(:cached_attributes) do
+ Gitlab::Redis::SharedState.with do |redis|
+ data = redis.get(cache_attribute_key)
+ JSON.parse(data, symbolize_names: true) if data
+ end
+ end
+ end
+end
diff --git a/app/models/event.rb b/app/models/event.rb
index 8a79100de5a..75538ba196c 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -96,10 +96,6 @@ class Event < ActiveRecord::Base
self.inheritance_column = 'action'
- # "data" will be removed in 10.0 but it may be possible that JOINs happen that
- # include this column, hence we're ignoring it as well.
- ignore_column :data
-
class << self
def model_name
ActiveModel::Name.new(self, nil, 'event')
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 2aaba2e4c90..282fd7edcb7 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -39,7 +39,7 @@ class ExternalIssue
end
def to_reference(_from = nil, full: nil)
- id
+ reference_link_text
end
def reference_link_text(from = nil)
diff --git a/app/models/group.rb b/app/models/group.rb
index 5b7f1b38612..75bf013ecd2 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -31,9 +31,12 @@ class Group < Namespace
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ accepts_nested_attributes_for :variables, allow_destroy: true
+
validate :visibility_level_allowed_by_projects
validate :visibility_level_allowed_by_sub_groups
validate :visibility_level_allowed_by_parent
+ validates :variables, variable_duplicates: true
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
diff --git a/app/models/identity.rb b/app/models/identity.rb
index b3fa7d8176a..2b433e9b988 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -9,6 +9,7 @@ class Identity < ActiveRecord::Base
validates :user_id, uniqueness: { scope: :provider }
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
+ after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider?
scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do
@@ -34,4 +35,12 @@ class Identity < ActiveRecord::Base
self.extern_uid = Identity.normalize_uid(self.provider, self.extern_uid)
end
+
+ def user_synced_attributes_metadata_from_provider?
+ user.user_synced_attributes_metadata&.provider == provider
+ end
+
+ def clear_user_synced_attributes
+ user.user_synced_attributes_metadata&.destroy
+ end
end
diff --git a/app/models/key.rb b/app/models/key.rb
index ae5769c0627..7406c98c99e 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -33,8 +33,9 @@ class Key < ActiveRecord::Base
after_destroy :refresh_user_cache
def key=(value)
- write_attribute(:key, value.present? ? Gitlab::SSHPublicKey.sanitize(value) : nil)
-
+ value&.delete!("\n\r")
+ value.strip! unless value.blank?
+ write_attribute(:key, value)
@public_key = nil
end
@@ -96,7 +97,7 @@ class Key < ActiveRecord::Base
def generate_fingerprint
self.fingerprint = nil
- return unless public_key.valid?
+ return unless self.key.present?
self.fingerprint = public_key.fingerprint
end
diff --git a/app/models/lfs_file_lock.rb b/app/models/lfs_file_lock.rb
new file mode 100644
index 00000000000..50bb6ca382d
--- /dev/null
+++ b/app/models/lfs_file_lock.rb
@@ -0,0 +1,12 @@
+class LfsFileLock < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :user
+
+ validates :project_id, :user_id, :path, presence: true
+
+ def can_be_unlocked_by?(current_user, forced = false)
+ return true if current_user.id == user_id
+
+ forced && current_user.can?(:admin_project, project)
+ end
+end
diff --git a/app/models/member.rb b/app/models/member.rb
index c47145667b5..2d17795e62d 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -314,7 +314,7 @@ class Member < ActiveRecord::Base
end
def notification_setting
- @notification_setting ||= user.notification_settings_for(source)
+ @notification_setting ||= user&.notification_settings_for(source)
end
def notifiable?(type, opts = {})
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index d025062f562..5bec68ce4f6 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -158,10 +158,12 @@ class MergeRequest < ActiveRecord::Base
end
def rebase_in_progress?
- # The source project can be deleted
- return false unless source_project
+ strong_memoize(:rebase_in_progress) do
+ # The source project can be deleted
+ next false unless source_project
- source_project.repository.rebase_in_progress?(id)
+ source_project.repository.rebase_in_progress?(id)
+ end
end
# Use this method whenever you need to make sure the head_pipeline is synced with the
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 69a846da9be..c1c27ccf3e5 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -290,7 +290,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def keep_around_commits
- [repository, merge_request.source_project.repository].each do |repo|
+ [repository, merge_request.source_project.repository].uniq.each do |repo|
repo.keep_around(start_commit_sha)
repo.keep_around(head_commit_sha)
repo.keep_around(base_commit_sha)
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index d95489ee9f2..db274ea8172 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -243,6 +243,10 @@ class Namespace < ActiveRecord::Base
all_projects.with_storage_feature(:repository).find_each(&:remove_exports)
end
+ def features
+ []
+ end
+
private
def path_or_parent_changed?
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index c351d2012c6..1e0d1f9edcb 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -61,11 +61,8 @@ module Network
@reserved[i] = []
end
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37436
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- commits_sort_by_ref.each do |commit|
- place_chain(commit)
- end
+ commits_sort_by_ref.each do |commit|
+ place_chain(commit)
end
# find parent spaces for not overlap lines
diff --git a/app/models/project.rb b/app/models/project.rb
index 3edb6109fb8..2ba6a863500 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -179,6 +179,7 @@ class Project < ActiveRecord::Base
has_many :releases
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :lfs_objects, through: :lfs_objects_projects
+ has_many :lfs_file_locks
has_many :project_group_links
has_many :invited_groups, through: :project_group_links, source: :group
has_many :pages_domains
@@ -260,6 +261,7 @@ class Project < ActiveRecord::Base
validates :repository_storage,
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
+ validates :variables, variable_duplicates: { scope: :environment_scope }
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -1587,8 +1589,11 @@ class Project < ActiveRecord::Base
end
def protected_for?(ref)
- ProtectedBranch.protected?(self, ref) ||
+ if repository.branch_exists?(ref)
+ ProtectedBranch.protected?(self, ref)
+ elsif repository.tag_exists?(ref)
ProtectedTag.protected?(self, ref)
+ end
end
def deployment_variables
@@ -1600,7 +1605,7 @@ class Project < ActiveRecord::Base
def auto_devops_variables
return [] unless auto_devops_enabled?
- auto_devops&.variables || []
+ (auto_devops || build_auto_devops)&.variables
end
def append_or_update_attribute(name, value)
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 9a52edbff8e..112ed7ed434 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -6,13 +6,17 @@ class ProjectAutoDevops < ActiveRecord::Base
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
+ def instance_domain
+ Gitlab::CurrentSettings.auto_devops_domain
+ end
+
def has_domain?
- domain.present?
+ domain.present? || instance_domain.present?
end
def variables
variables = []
- variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain, public: true } if domain.present?
+ variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain.presence || instance_domain, public: true } if has_domain?
variables
end
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 30eafe31454..436a870b0c4 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -10,6 +10,8 @@ class JiraService < IssueTrackerService
before_update :reset_password
+ alias_method :project_url, :url
+
# This is confusing, but JiraService does not really support these events.
# The values here are required to display correct options in the service
# configuration screen.
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index fa7b3f2bcaf..1bb576ff971 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -7,11 +7,14 @@ class PrometheusService < MonitoringService
# Access to prometheus is directly through the API
prop_accessor :api_url
+ boolean_accessor :manual_configuration
- with_options presence: true, if: :activated? do
+ with_options presence: true, if: :manual_configuration? do
validates :api_url, url: true
end
+ before_save :synchronize_service_state!
+
after_save :clear_reactive_cache!
def initialize_properties
@@ -20,12 +23,20 @@ class PrometheusService < MonitoringService
end
end
+ def show_active_box?
+ false
+ end
+
+ def editable?
+ manual_configuration? || !prometheus_installed?
+ end
+
def title
'Prometheus'
end
def description
- s_('PrometheusService|Prometheus monitoring')
+ s_('PrometheusService|Time-series monitoring service')
end
def self.to_param
@@ -33,8 +44,16 @@ class PrometheusService < MonitoringService
end
def fields
+ return [] unless editable?
+
[
{
+ type: 'checkbox',
+ name: 'manual_configuration',
+ title: s_('PrometheusService|Active'),
+ required: true
+ },
+ {
type: 'text',
name: 'api_url',
title: 'API URL',
@@ -59,7 +78,7 @@ class PrometheusService < MonitoringService
end
def deployment_metrics(deployment)
- metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &method(:rename_data_to_metrics))
+ metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.environment.id, deployment.id, &method(:rename_data_to_metrics))
metrics&.merge(deployment_time: deployment.created_at.to_i) || {}
end
@@ -68,7 +87,7 @@ class PrometheusService < MonitoringService
end
def additional_deployment_metrics(deployment)
- with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.id, &:itself)
+ with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.environment.id, deployment.id, &:itself)
end
def matched_metrics
@@ -79,6 +98,9 @@ class PrometheusService < MonitoringService
def calculate_reactive_cache(query_class_name, *args)
return unless active? && project && !project.pending_delete?
+ environment_id = args.first
+ client = client(environment_id)
+
data = Kernel.const_get(query_class_name).new(client).query(*args)
{
success: true,
@@ -89,14 +111,55 @@ class PrometheusService < MonitoringService
{ success: false, result: err.message }
end
- def client
- @prometheus ||= Gitlab::PrometheusClient.new(api_url: api_url)
+ def client(environment_id = nil)
+ if manual_configuration?
+ Gitlab::PrometheusClient.new(RestClient::Resource.new(api_url))
+ else
+ cluster = cluster_with_prometheus(environment_id)
+ raise Gitlab::PrometheusError, "couldn't find cluster with Prometheus installed" unless cluster
+
+ rest_client = client_from_cluster(cluster)
+ raise Gitlab::PrometheusError, "couldn't create proxy Prometheus client" unless rest_client
+
+ Gitlab::PrometheusClient.new(rest_client)
+ end
+ end
+
+ def prometheus_installed?
+ return false if template?
+ return false unless project
+
+ project.clusters.enabled.any? { |cluster| cluster.application_prometheus&.installed? }
end
private
+ def cluster_with_prometheus(environment_id = nil)
+ clusters = if environment_id
+ ::Environment.find_by(id: environment_id).try do |env|
+ # sort results by descending order based on environment_scope being longer
+ # thus more closely matching environment slug
+ project.clusters.enabled.for_environment(env).sort_by { |c| c.environment_scope&.length }.reverse!
+ end
+ else
+ project.clusters.enabled.for_all_environments
+ end
+
+ clusters&.detect { |cluster| cluster.application_prometheus&.installed? }
+ end
+
+ def client_from_cluster(cluster)
+ cluster.application_prometheus.proxy_client
+ end
+
def rename_data_to_metrics(metrics)
metrics[:metrics] = metrics.delete :data
metrics
end
+
+ def synchronize_service_state!
+ self.active = prometheus_installed? || manual_configuration?
+
+ true
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 3d6f8f0c305..4f754b11da4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -164,6 +164,13 @@ class Repository
commits
end
+ # Returns a list of commits that are not present in any reference
+ def new_commits(newrev)
+ refs = ::Gitlab::Git::RevList.new(raw, newrev: newrev).new_refs
+
+ refs.map { |sha| commit(sha.strip) }
+ end
+
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
unless exists? && has_visible_content? && query.present?
@@ -586,7 +593,15 @@ class Repository
def license_key
return unless exists?
- Licensee.license(path).try(:key)
+ # The licensee gem creates a Rugged object from the path:
+ # https://github.com/benbalter/licensee/blob/v8.7.0/lib/licensee/projects/git_project.rb
+ begin
+ Licensee.license(path).try(:key)
+ # Normally we would rescue Rugged::Error, but that is banned by lint-rugged
+ # and we need to migrate this endpoint to Gitaly:
+ # https://gitlab.com/gitlab-org/gitaly/issues/1026
+ rescue
+ end
end
cache_method :license_key
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 7c8716f8c18..a58c208279e 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -74,6 +74,27 @@ class Snippet < ActiveRecord::Base
@link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end
+ # Returns a collection of snippets that are either public or visible to the
+ # logged in user.
+ #
+ # This method does not verify the user actually has the access to the project
+ # the snippet is in, so it should be only used on a relation that's already scoped
+ # for project access
+ def self.public_or_visible_to_user(user = nil)
+ if user
+ authorized = user
+ .project_authorizations
+ .select(1)
+ .where('project_authorizations.project_id = snippets.project_id')
+
+ levels = Gitlab::VisibilityLevel.levels_for_user(user)
+
+ where('EXISTS (?) OR snippets.visibility_level IN (?) or snippets.author_id = (?)', authorized, levels, user.id)
+ else
+ public_to_user
+ end
+ end
+
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{id}"
diff --git a/app/models/user.rb b/app/models/user.rb
index 05c93d3cb17..5e84d2da805 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -249,7 +249,7 @@ class User < ActiveRecord::Base
def find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
- where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase)
+ where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip)
else
find_by(conditions)
end
@@ -551,7 +551,7 @@ class User < ActiveRecord::Base
gpg_keys.each(&:update_invalid_gpg_signatures)
end
- # Returns the groups a user has access to
+ # Returns the groups a user has access to, either through a membership or a project authorization
def authorized_groups
union = Gitlab::SQL::Union
.new([groups.select(:id), authorized_projects.select(:namespace_id)])
@@ -559,6 +559,11 @@ class User < ActiveRecord::Base
Group.where("namespaces.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
end
+ # Returns the groups a user is a member of, either directly or through a parent group
+ def membership_groups
+ Gitlab::GroupHierarchy.new(groups).base_and_descendants
+ end
+
# Returns a relation of groups the user has access to, including their parent
# and child groups (recursively).
def all_expanded_groups