diff options
Diffstat (limited to 'app/services')
35 files changed, 514 insertions, 138 deletions
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb index 7eeaf8aade1..471df6e2d0c 100644 --- a/app/services/application_settings/update_service.rb +++ b/app/services/application_settings/update_service.rb @@ -15,6 +15,8 @@ module ApplicationSettings update_terms(@params.delete(:terms)) + add_to_outbound_local_requests_whitelist(@params.delete(:add_to_outbound_local_requests_whitelist)) + if params.key?(:performance_bar_allowed_group_path) params[:performance_bar_allowed_group_id] = performance_bar_allowed_group_id end @@ -32,6 +34,13 @@ module ApplicationSettings params.key?(:usage_ping_enabled) || params.key?(:version_check_enabled) end + def add_to_outbound_local_requests_whitelist(values) + values_array = Array(values).reject(&:empty?) + return if values_array.empty? + + @application_setting.add_to_outbound_local_requests_whitelist(values_array) + end + def update_terms(terms) return unless terms.present? diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 707caee482c..0a069320936 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -17,6 +17,14 @@ module Auth end def self.full_access_token(*names) + access_token(%w(*), names) + end + + def self.pull_access_token(*names) + access_token(['pull'], names) + end + + def self.access_token(actions, names) names = names.flatten registry = Gitlab.config.registry token = JSONWebToken::RSAToken.new(registry.key) @@ -25,7 +33,7 @@ module Auth token.expire_time = token_expire_at token[:access] = names.map do |name| - { type: 'repository', name: name, actions: %w(*) } + { type: 'repository', name: name, actions: actions } end token.encoded diff --git a/app/services/ci/play_build_service.rb b/app/services/ci/play_build_service.rb index eb0b070657d..9f922ffde81 100644 --- a/app/services/ci/play_build_service.rb +++ b/app/services/ci/play_build_service.rb @@ -2,7 +2,7 @@ module Ci class PlayBuildService < ::BaseService - def execute(build) + def execute(build, job_variables_attributes = nil) unless can?(current_user, :update_build, build) raise Gitlab::Access::AccessDeniedError end @@ -10,7 +10,7 @@ module Ci # Try to enqueue the build, otherwise create a duplicate. # if build.enqueue - build.tap { |action| action.update(user: current_user) } + build.tap { |action| action.update(user: current_user, job_variables_attributes: job_variables_attributes || []) } else Ci::Build.retry(build, current_user) end diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index aaf56048b5c..99d4ff9ecd1 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -4,35 +4,69 @@ module Ci class ProcessPipelineService < BaseService attr_reader :pipeline - def execute(pipeline) + def execute(pipeline, trigger_build_ids = nil) @pipeline = pipeline update_retried - new_builds = - stage_indexes_of_created_processables.map do |index| - process_stage(index) - end + success = process_stages_without_needs + + # we evaluate dependent needs, + # only when the another job has finished + success = process_builds_with_needs(trigger_build_ids) || success @pipeline.update_status - new_builds.flatten.any? + success end private - def process_stage(index) + def process_stages_without_needs + stage_indexes_of_created_processables_without_needs.flat_map do |index| + process_stage_without_needs(index) + end.any? + end + + def process_stage_without_needs(index) current_status = status_for_prior_stages(index) - return if HasStatus::BLOCKED_STATUS.include?(current_status) + return unless HasStatus::COMPLETED_STATUSES.include?(current_status) - if HasStatus::COMPLETED_STATUSES.include?(current_status) - created_processables_in_stage(index).select do |build| - Gitlab::OptimisticLocking.retry_lock(build) do |subject| - Ci::ProcessBuildService.new(project, @user) - .execute(build, current_status) - end - end + created_processables_in_stage_without_needs(index).select do |build| + process_build(build, current_status) + end + end + + def process_builds_with_needs(trigger_build_ids) + return false unless trigger_build_ids.present? + return false unless Feature.enabled?(:ci_dag_support, project) + + # rubocop: disable CodeReuse/ActiveRecord + trigger_build_names = pipeline.statuses + .where(id: trigger_build_ids) + .select(:name) + # rubocop: enable CodeReuse/ActiveRecord + + created_processables + .with_needs(trigger_build_names) + .find_each + .map(&method(:process_build_with_needs)) + .any? + end + + def process_build_with_needs(build) + current_status = status_for_build_needs(build.needs.map(&:name)) + + return unless HasStatus::COMPLETED_STATUSES.include?(current_status) + + process_build(build, current_status) + end + + def process_build(build, current_status) + Gitlab::OptimisticLocking.retry_lock(build) do |subject| + Ci::ProcessBuildService.new(project, @user) + .execute(subject, current_status) end end @@ -43,17 +77,33 @@ module Ci # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord - def stage_indexes_of_created_processables - created_processables.order(:stage_idx).pluck(Arel.sql('DISTINCT stage_idx')) + def status_for_build_needs(needs) + pipeline.builds.where(name: needs).latest.status || 'success' end # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord - def created_processables_in_stage(index) - created_processables.where(stage_idx: index) + def stage_indexes_of_created_processables_without_needs + created_processables_without_needs.order(:stage_idx) + .pluck(Arel.sql('DISTINCT stage_idx')) end # rubocop: enable CodeReuse/ActiveRecord + # rubocop: disable CodeReuse/ActiveRecord + def created_processables_in_stage_without_needs(index) + created_processables_without_needs + .where(stage_idx: index) + end + # rubocop: enable CodeReuse/ActiveRecord + + def created_processables_without_needs + if Feature.enabled?(:ci_dag_support, project) + pipeline.processables.created.without_needs + else + pipeline.processables.created + end + end + def created_processables pipeline.processables.created end diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb index fab8a179843..338495ba030 100644 --- a/app/services/ci/retry_build_service.rb +++ b/app/services/ci/retry_build_service.rb @@ -5,7 +5,7 @@ module Ci CLONE_ACCESSORS = %i[pipeline project ref tag options name allow_failure stage stage_id stage_idx trigger_request yaml_variables when environment coverage_regex - description tag_list protected].freeze + description tag_list protected needs].freeze def execute(build) reprocess!(build).tap do |new_build| diff --git a/app/services/clusters/refresh_service.rb b/app/services/clusters/refresh_service.rb deleted file mode 100644 index 3752a306793..00000000000 --- a/app/services/clusters/refresh_service.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Clusters - class RefreshService - def self.create_or_update_namespaces_for_cluster(cluster) - projects_with_missing_kubernetes_namespaces_for_cluster(cluster).each do |project| - create_or_update_namespace(cluster, project) - end - end - - def self.create_or_update_namespaces_for_project(project) - clusters_with_missing_kubernetes_namespaces_for_project(project).each do |cluster| - create_or_update_namespace(cluster, project) - end - end - - def self.projects_with_missing_kubernetes_namespaces_for_cluster(cluster) - cluster.all_projects.missing_kubernetes_namespace(cluster.kubernetes_namespaces) - end - - private_class_method :projects_with_missing_kubernetes_namespaces_for_cluster - - def self.clusters_with_missing_kubernetes_namespaces_for_project(project) - project.clusters.managed.missing_kubernetes_namespace(project.kubernetes_namespaces) - end - - private_class_method :clusters_with_missing_kubernetes_namespaces_for_project - - def self.create_or_update_namespace(cluster, project) - kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project) - - ::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( - cluster: cluster, - kubernetes_namespace: kubernetes_namespace - ).execute - end - - private_class_method :create_or_update_namespace - end -end diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb index 6d466c2fc9c..97fbb70f350 100644 --- a/app/services/cohorts_service.rb +++ b/app/services/cohorts_service.rb @@ -95,10 +95,6 @@ class CohortsService # rubocop: enable CodeReuse/ActiveRecord def column_to_date(column) - if Gitlab::Database.postgresql? - "CAST(DATE_TRUNC('month', #{column}) AS date)" - else - "STR_TO_DATE(DATE_FORMAT(#{column}, '%Y-%m-01'), '%Y-%m-%d')" - end + "CAST(DATE_TRUNC('month', #{column}) AS date)" end end diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb index 01bd685712b..a51ac9aa593 100644 --- a/app/services/groups/nested_create_service.rb +++ b/app/services/groups/nested_create_service.rb @@ -18,10 +18,6 @@ module Groups return namespace end - if group_path.include?('/') && !Group.supports_nested_objects? - raise 'Nested groups are not supported on MySQL' - end - create_group_path end diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb index 98e7c311572..fe7e07ef9f0 100644 --- a/app/services/groups/transfer_service.rb +++ b/app/services/groups/transfer_service.rb @@ -43,7 +43,6 @@ module Groups def ensure_allowed_transfer raise_transfer_error(:group_is_already_root) if group_is_already_root? - raise_transfer_error(:database_not_supported) unless Group.supports_nested_objects? raise_transfer_error(:same_parent_as_current) if same_parent? raise_transfer_error(:invalid_policies) unless valid_policies? raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path? diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index db673cace81..77c2224ee3b 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -358,6 +358,7 @@ class IssuableBaseService < BaseService assignees: issuable.assignees.to_a } associations[:total_time_spent] = issuable.total_time_spent if issuable.respond_to?(:total_time_spent) + associations[:description] = issuable.description associations end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 7cd825aa967..c8f4412c9f2 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -61,6 +61,8 @@ module Issues if added_mentions.present? notification_service.async.new_mentions_in_issue(issue, added_mentions, current_user) end + + ZoomNotesService.new(issue, project, current_user, old_description: old_associations[:description]).execute end def handle_task_changes(issuable) diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index c8d5e563cd8..0164760920f 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -31,7 +31,7 @@ module Members return unless member.is_a?(GroupMember) && member.user && member.group delete_project_members(member) - delete_subgroup_members(member) if Group.supports_nested_objects? + delete_subgroup_members(member) end def delete_project_members(member) diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 109c964e577..b28f80939ae 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -11,15 +11,18 @@ module MergeRequests # https://gitlab.com/gitlab-org/gitlab-ce/issues/53658 merge_quick_actions_into_params!(merge_request, only: [:target_branch]) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) if params.has_key?(:force_remove_source_branch) - merge_request.assign_attributes(params) + # Assign the projects first so we can use policies for `filter_params` merge_request.author = current_user + merge_request.source_project = find_source_project + merge_request.target_project = find_target_project + + filter_params(merge_request) + merge_request.assign_attributes(params.to_h.compact) + merge_request.compare_commits = [] - merge_request.source_project = find_source_project - merge_request.target_project = find_target_project - merge_request.target_branch = find_target_branch - merge_request.can_be_created = projects_and_branches_valid? - ensure_milestone_available(merge_request) + merge_request.target_branch = find_target_branch + merge_request.can_be_created = projects_and_branches_valid? # compare branches only if branches are valid, otherwise # compare_branches may raise an error @@ -50,12 +53,14 @@ module MergeRequests to: :merge_request def find_source_project + source_project = project_from_params(:source_project) return source_project if source_project.present? && can?(current_user, :create_merge_request_from, source_project) project end def find_target_project + target_project = project_from_params(:target_project) return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project) target_project = project.default_merge_request_target @@ -65,6 +70,17 @@ module MergeRequests project end + def project_from_params(param_name) + project_from_params = params.delete(param_name) + + id_param_name = :"#{param_name}_id" + if project_from_params.nil? && params[id_param_name] + project_from_params = Project.find_by_id(params.delete(id_param_name)) + end + + project_from_params + end + def find_target_branch target_branch || target_project.default_branch end diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb index 9fa50c9448f..962e2327b3e 100644 --- a/app/services/merge_requests/mergeability_check_service.rb +++ b/app/services/merge_requests/mergeability_check_service.rb @@ -3,6 +3,7 @@ module MergeRequests class MergeabilityCheckService < ::BaseService include Gitlab::Utils::StrongMemoize + include Gitlab::ExclusiveLeaseHelpers delegate :project, to: :@merge_request delegate :repository, to: :project @@ -21,13 +22,35 @@ module MergeRequests # where we need the current state of the merge ref in repository, the `recheck` # argument is required. # + # retry_lease - Concurrent calls wait for at least 10 seconds until the + # lease is granted (other process finishes running). Returns an error + # ServiceResponse if the lease is not granted during this time. + # # Returns a ServiceResponse indicating merge_status is/became can_be_merged # and the merge-ref is synced. Success in case of being/becoming mergeable, # error otherwise. - def execute(recheck: false) + def execute(recheck: false, retry_lease: true) return ServiceResponse.error(message: 'Invalid argument') unless merge_request return ServiceResponse.error(message: 'Unsupported operation') if Gitlab::Database.read_only? + return check_mergeability(recheck) unless merge_ref_auto_sync_lock_enabled? + + in_write_lock(retry_lease: retry_lease) do |retried| + # When multiple calls are waiting for the same lock (retry_lease), + # it's possible that when granted, the MR status was already updated for + # that object, therefore we reset if there was a lease retry. + merge_request.reset if retried + + check_mergeability(recheck) + end + rescue FailedToObtainLockError => error + ServiceResponse.error(message: error.message) + end + + private + + attr_reader :merge_request + def check_mergeability(recheck) recheck! if recheck update_merge_status @@ -46,9 +69,21 @@ module MergeRequests ServiceResponse.success(payload: payload) end - private + # It's possible for this service to send concurrent requests to Gitaly in order + # to "git update-ref" the same ref. Therefore we handle a light exclusive + # lease here. + # + def in_write_lock(retry_lease:, &block) + lease_key = "mergeability_check:#{merge_request.id}" - attr_reader :merge_request + lease_opts = { + ttl: 1.minute, + retries: retry_lease ? 10 : 0, + sleep_sec: retry_lease ? 1.second : 0 + } + + in_lock(lease_key, lease_opts, &block) + end def payload strong_memoize(:payload) do @@ -116,5 +151,9 @@ module MergeRequests def merge_ref_auto_sync_enabled? Feature.enabled?(:merge_ref_auto_sync, project, default_enabled: true) end + + def merge_ref_auto_sync_lock_enabled? + Feature.enabled?(:merge_ref_auto_sync_lock, project, default_enabled: true) + end end end diff --git a/app/services/metrics/dashboard/base_service.rb b/app/services/metrics/dashboard/base_service.rb new file mode 100644 index 00000000000..b331bf51874 --- /dev/null +++ b/app/services/metrics/dashboard/base_service.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# Searches a projects repository for a metrics dashboard and formats the output. +# Expects any custom dashboards will be located in `.gitlab/dashboards` +module Metrics + module Dashboard + class BaseService < ::BaseService + PROCESSING_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError + NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError + + def get_dashboard + return error('Insufficient permissions.', :unauthorized) unless allowed? + + success(dashboard: process_dashboard) + rescue NOT_FOUND_ERROR + error("#{dashboard_path} could not be found.", :not_found) + rescue PROCESSING_ERROR => e + error(e.message, :unprocessable_entity) + end + + # Summary of all known dashboards for the service. + # @return [Array<Hash>] ex) [{ path: String, default: Boolean }] + def self.all_dashboard_paths(_project) + raise NotImplementedError + end + + # Returns an un-processed dashboard from the cache. + def raw_dashboard + Gitlab::Metrics::Dashboard::Cache.fetch(cache_key) { get_raw_dashboard } + end + + private + + # Determines whether users should be able to view + # dashboards at all. + def allowed? + Ability.allowed?(current_user, :read_environment, project) + end + + # Returns a new dashboard Hash, supplemented with DB info + def process_dashboard + Gitlab::Metrics::Dashboard::Processor + .new(project, params[:environment], raw_dashboard) + .process(insert_project_metrics: insert_project_metrics?) + end + + # @return [String] Relative filepath of the dashboard yml + def dashboard_path + params[:dashboard_path] + end + + # @return [Hash] an unmodified dashboard + def get_raw_dashboard + raise NotImplementedError + end + + # @return [String] + def cache_key + raise NotImplementedError + end + + # Determines whether custom metrics should be included + # in the processed output. + # @return [Boolean] + def insert_project_metrics? + false + end + end + end +end diff --git a/app/services/metrics/dashboard/default_embed_service.rb b/app/services/metrics/dashboard/default_embed_service.rb new file mode 100644 index 00000000000..8b01b44fc98 --- /dev/null +++ b/app/services/metrics/dashboard/default_embed_service.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +# Responsible for returning a filtered system dashboard +# containing only the default embedded metrics. In future, +# this class may be updated to support filtering to +# alternate metrics/panels. +# +# Why isn't this filtering in a processing stage? By filtering +# here, we ensure the dynamically-determined dashboard is cached. +# +# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards. +module Metrics + module Dashboard + class DefaultEmbedService < ::Metrics::Dashboard::BaseService + # For the default filtering for embedded metrics, + # uses the 'id' key in dashboard-yml definition for + # identification. + DEFAULT_EMBEDDED_METRICS_IDENTIFIERS = %w( + system_metrics_kubernetes_container_memory_total + system_metrics_kubernetes_container_cores_total + ).freeze + + # Returns a new dashboard with only the matching + # metrics from the system dashboard, stripped of groups. + # @return [Hash] + def get_raw_dashboard + panels = panel_groups.each_with_object([]) do |group, panels| + matched_panels = group['panels'].select { |panel| matching_panel?(panel) } + + panels.concat(matched_panels) + end + + { 'panel_groups' => [{ 'panels' => panels }] } + end + + def cache_key + "dynamic_metrics_dashboard_#{metric_identifiers.join('_')}" + end + + private + + # Returns an array of the panels groups on the + # system dashboard + def panel_groups + ::Metrics::Dashboard::SystemDashboardService + .new(project, nil) + .raw_dashboard['panel_groups'] + end + + # Identifies a panel as "matching" if any metric ids in + # the panel is in the list of identifiers to collect. + def matching_panel?(panel) + panel['metrics'].any? do |metric| + metric_identifiers.include?(metric['id']) + end + end + + def metric_identifiers + DEFAULT_EMBEDDED_METRICS_IDENTIFIERS + end + end + end +end diff --git a/app/services/metrics/dashboard/project_dashboard_service.rb b/app/services/metrics/dashboard/project_dashboard_service.rb new file mode 100644 index 00000000000..756d387c0e6 --- /dev/null +++ b/app/services/metrics/dashboard/project_dashboard_service.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# Searches a projects repository for a metrics dashboard and formats the output. +# Expects any custom dashboards will be located in `.gitlab/dashboards` +# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards. +module Metrics + module Dashboard + class ProjectDashboardService < ::Metrics::Dashboard::BaseService + DASHBOARD_ROOT = ".gitlab/dashboards" + + class << self + def all_dashboard_paths(project) + file_finder(project) + .list_files_for(DASHBOARD_ROOT) + .map do |filepath| + { + path: filepath, + display_name: name_for_path(filepath), + default: false + } + end + end + + def file_finder(project) + Gitlab::Template::Finders::RepoTemplateFinder.new(project, DASHBOARD_ROOT, '.yml') + end + + # Grabs the filepath after the base directory. + def name_for_path(filepath) + filepath.delete_prefix("#{DASHBOARD_ROOT}/") + end + end + + private + + # Searches the project repo for a custom-defined dashboard. + def get_raw_dashboard + yml = self.class.file_finder(project).read(dashboard_path) + + YAML.safe_load(yml) + end + + def cache_key + "project_#{project.id}_metrics_dashboard_#{dashboard_path}" + end + end + end +end diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb new file mode 100644 index 00000000000..fcd71aadb03 --- /dev/null +++ b/app/services/metrics/dashboard/system_dashboard_service.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# Fetches the system metrics dashboard and formats the output. +# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards. +module Metrics + module Dashboard + class SystemDashboardService < ::Metrics::Dashboard::BaseService + SYSTEM_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml' + SYSTEM_DASHBOARD_NAME = 'Default' + + class << self + def all_dashboard_paths(_project) + [{ + path: SYSTEM_DASHBOARD_PATH, + display_name: SYSTEM_DASHBOARD_NAME, + default: true + }] + end + + def system_dashboard?(filepath) + filepath == SYSTEM_DASHBOARD_PATH + end + end + + private + + def dashboard_path + SYSTEM_DASHBOARD_PATH + end + + # Returns the base metrics shipped with every GitLab service. + def get_raw_dashboard + yml = File.read(Rails.root.join(dashboard_path)) + + YAML.safe_load(yml) + end + + def cache_key + "metrics_dashboard_#{dashboard_path}" + end + + def insert_project_metrics? + true + end + end + end +end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 1b46f6d8a72..194c4a43dbc 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -21,7 +21,7 @@ module Notes if quick_actions_service.supported?(note) options = { merge_request_diff_head_sha: merge_request_diff_head_sha } - content, update_params = quick_actions_service.execute(note, options) + content, update_params, message = quick_actions_service.execute(note, options) only_commands = content.empty? @@ -52,7 +52,7 @@ module Notes # We must add the error after we call #save because errors are reset # when #save is called if only_commands - note.errors.add(:commands_only, 'Commands applied') + note.errors.add(:commands_only, message.presence || _('Commands did not apply')) end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index a55771ed538..21fab22e0d4 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -595,7 +595,7 @@ class NotificationService end def deliver_access_request_email(recipient, member) - mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.notification_email).deliver_later + mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.id).deliver_later end def fallback_to_group_owners_maintainers?(recipients, member) diff --git a/app/services/projects/base_move_relations_service.rb b/app/services/projects/base_move_relations_service.rb index 24dec1f3a45..3a159cef58b 100644 --- a/app/services/projects/base_move_relations_service.rb +++ b/app/services/projects/base_move_relations_service.rb @@ -10,17 +10,5 @@ module Projects true end - - private - - # rubocop: disable CodeReuse/ActiveRecord - def prepare_relation(relation, id_param = :id) - if Gitlab::Database.postgresql? - relation - else - relation.model.where("#{id_param}": relation.pluck(id_param)) - end - end - # rubocop: enable CodeReuse/ActiveRecord end end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index b805a7f1211..a1279bfb3a3 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -210,11 +210,20 @@ module Projects end def flush_caches(project) - project.repository.before_delete + ignore_git_errors(repo_path) { project.repository.before_delete } - Repository.new(wiki_path, project, disk_path: repo_path).before_delete + ignore_git_errors(wiki_path) { Repository.new(wiki_path, project, disk_path: repo_path).before_delete } Projects::ForksCountService.new(project).delete_cache end + + # If we get a Gitaly error, the repository may be corrupted. We can + # ignore these errors since we're going to trash the repositories + # anyway. + def ignore_git_errors(disk_path, &block) + yield + rescue Gitlab::Git::CommandError => e + Gitlab::GitLogger.warn(class: self.class.name, project_id: project.id, disk_path: disk_path, message: e.to_s) + end end end diff --git a/app/services/projects/fetch_statistics_increment_service.rb b/app/services/projects/fetch_statistics_increment_service.rb index 8644e6bf313..b150fd2d9f1 100644 --- a/app/services/projects/fetch_statistics_increment_service.rb +++ b/app/services/projects/fetch_statistics_increment_service.rb @@ -12,14 +12,9 @@ module Projects increment_fetch_count_sql = <<~SQL INSERT INTO #{table_name} (project_id, date, fetch_count) VALUES (#{project.id}, '#{Date.today}', 1) + ON CONFLICT (project_id, date) DO UPDATE SET fetch_count = #{table_name}.fetch_count + 1 SQL - increment_fetch_count_sql += if Gitlab::Database.postgresql? - "ON CONFLICT (project_id, date) DO UPDATE SET fetch_count = #{table_name}.fetch_count + 1" - else - "ON DUPLICATE KEY UPDATE fetch_count = #{table_name}.fetch_count + 1" - end - ActiveRecord::Base.connection.execute(increment_fetch_count_sql) end diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb index b6a3af8c7b8..01419563538 100644 --- a/app/services/projects/move_deploy_keys_projects_service.rb +++ b/app/services/projects/move_deploy_keys_projects_service.rb @@ -16,8 +16,7 @@ module Projects private def move_deploy_keys_projects - prepare_relation(non_existent_deploy_keys_projects) - .update_all(project_id: @project.id) + non_existent_deploy_keys_projects.update_all(project_id: @project.id) end # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb index 308a54ad06e..10e19014db4 100644 --- a/app/services/projects/move_lfs_objects_projects_service.rb +++ b/app/services/projects/move_lfs_objects_projects_service.rb @@ -16,8 +16,7 @@ module Projects private def move_lfs_objects_projects - prepare_relation(non_existent_lfs_objects_projects) - .update_all(project_id: @project.lfs_storage_project.id) + non_existent_lfs_objects_projects.update_all(project_id: @project.lfs_storage_project.id) end def remove_remaining_lfs_objects_project diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb index e740c44bd26..65a888fe26b 100644 --- a/app/services/projects/move_notification_settings_service.rb +++ b/app/services/projects/move_notification_settings_service.rb @@ -16,8 +16,7 @@ module Projects private def move_notification_settings - prepare_relation(non_existent_notifications) - .update_all(source_id: @project.id) + non_existent_notifications.update_all(source_id: @project.id) end # Remove remaining notification settings from source_project diff --git a/app/services/projects/move_project_authorizations_service.rb b/app/services/projects/move_project_authorizations_service.rb index 2985ba89014..c95ad60ab5e 100644 --- a/app/services/projects/move_project_authorizations_service.rb +++ b/app/services/projects/move_project_authorizations_service.rb @@ -21,8 +21,7 @@ module Projects private def move_project_authorizations - prepare_relation(non_existent_authorization, :user_id) - .update_all(project_id: @project.id) + non_existent_authorization.update_all(project_id: @project.id) end def remove_remaining_authorizations diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb index cf4b291c761..d1aa9af2bcb 100644 --- a/app/services/projects/move_project_group_links_service.rb +++ b/app/services/projects/move_project_group_links_service.rb @@ -20,8 +20,7 @@ module Projects private def move_group_links - prepare_relation(non_existent_group_links) - .update_all(project_id: @project.id) + non_existent_group_links.update_all(project_id: @project.id) end # Remove remaining project group links from source_project diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb index faf389241d2..de4e7e5a1e3 100644 --- a/app/services/projects/move_project_members_service.rb +++ b/app/services/projects/move_project_members_service.rb @@ -20,7 +20,7 @@ module Projects private def move_project_members - prepare_relation(non_existent_members).update_all(source_id: @project.id) + non_existent_members.update_all(source_id: @project.id) end def remove_remaining_members diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 8ff73522e5f..7f944e25887 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -31,17 +31,19 @@ module QuickActions end # Takes a text and interprets the commands that are extracted from it. - # Returns the content without commands, and hash of changes to be applied to a record. + # Returns the content without commands, a hash of changes to be applied to a record + # and a string containing the execution_message to show to the user. def execute(content, quick_action_target, only: nil) - return [content, {}] unless current_user.can?(:use_quick_actions) + return [content, {}, ''] unless current_user.can?(:use_quick_actions) @quick_action_target = quick_action_target @updates = {} + @execution_message = {} content, commands = extractor.extract_commands(content, only: only) extract_updates(commands) - [content, @updates] + [content, @updates, execution_messages_for(commands)] end # Takes a text and interprets the commands that are extracted from it. @@ -119,8 +121,12 @@ module QuickActions labels_params.scan(/"([^"]+)"|([^ ]+)/).flatten.compact end - def find_label_references(labels_param) - find_labels(labels_param).map(&:to_reference) + def find_label_references(labels_param, format = :id) + labels_to_reference(find_labels(labels_param), format) + end + + def labels_to_reference(labels, format = :id) + labels.map { |l| l.to_reference(format: format) } end def find_label_ids(labels_param) @@ -128,11 +134,24 @@ module QuickActions end def explain_commands(commands) + map_commands(commands, :explain) + end + + def execution_messages_for(commands) + map_commands(commands, :execute_message).join(' ') + end + + def map_commands(commands, method) commands.map do |name, arg| definition = self.class.definition_by_name(name) next unless definition - definition.explain(self, arg) + case method + when :explain + definition.explain(self, arg) + when :execute_message + @execution_message[name.to_sym] || definition.execute_message(self, arg) + end end.compact end diff --git a/app/services/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb index e5ef8c15456..8ffd22de127 100644 --- a/app/services/self_monitoring/project/create_service.rb +++ b/app/services/self_monitoring/project/create_service.rb @@ -14,6 +14,7 @@ module SelfMonitoring steps :validate_admins, :create_project, :add_project_members, + :add_to_whitelist, :add_prometheus_manual_configuration def initialize @@ -59,15 +60,29 @@ module SelfMonitoring end end - def add_prometheus_manual_configuration + def add_to_whitelist return success unless prometheus_enabled? return success unless prometheus_listen_address.present? - # TODO: Currently, adding the internal prometheus server as a manual configuration - # is only possible if the setting to allow webhooks and services to connect - # to local network is on. - # https://gitlab.com/gitlab-org/gitlab-ce/issues/44496 will add - # a whitelist that will allow connections to certain ips on the local network. + uri = parse_url(internal_prometheus_listen_address_uri) + return error(_('Prometheus listen_address is not a valid URI')) unless uri + + result = ApplicationSettings::UpdateService.new( + Gitlab::CurrentSettings.current_application_settings, + project_owner, + outbound_local_requests_whitelist: [uri.normalized_host] + ).execute + + if result + success + else + error(_('Could not add prometheus URL to whitelist')) + end + end + + def add_prometheus_manual_configuration + return success unless prometheus_enabled? + return success unless prometheus_listen_address.present? service = project.find_or_initialize_service('prometheus') @@ -79,6 +94,11 @@ module SelfMonitoring success end + def parse_url(uri_string) + Addressable::URI.parse(uri_string) + rescue Addressable::URI::InvalidURIError, TypeError + end + def prometheus_enabled? Gitlab.config.prometheus.enable rescue Settingslogic::MissingSetting diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index e4564bc9b00..e30debbbe75 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -597,6 +597,14 @@ module SystemNoteService note_text =~ /\A#{cross_reference_note_prefix}/i end + def zoom_link_added(issue, project, author) + create_note(NoteSummary.new(issue, project, author, _('a Zoom call was added to this issue'), action: 'pinned_embed')) + end + + def zoom_link_removed(issue, project, author) + create_note(NoteSummary.new(issue, project, author, _('a Zoom call was removed from this issue'), action: 'pinned_embed')) + end + private # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb index 4a26d2be2af..ae67b4f5256 100644 --- a/app/services/users/refresh_authorized_projects_service.rb +++ b/app/services/users/refresh_authorized_projects_service.rb @@ -102,13 +102,7 @@ module Users end def fresh_authorizations - klass = if Group.supports_nested_objects? - Gitlab::ProjectAuthorizations::WithNestedGroups - else - Gitlab::ProjectAuthorizations::WithoutNestedGroups - end - - klass.new(user).calculate + Gitlab::ProjectAuthorizations.new(user).calculate end end end diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 6d675c026bb..8c294218708 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -17,8 +17,10 @@ class WebHookService @hook = hook @data = data @hook_name = hook_name.to_s - @request_options = { timeout: Gitlab.config.gitlab.webhook_timeout } - @request_options.merge!(allow_local_requests: true) if @hook.is_a?(SystemHook) + @request_options = { + timeout: Gitlab.config.gitlab.webhook_timeout, + allow_local_requests: hook.allow_local_requests? + } end def execute diff --git a/app/services/zoom_notes_service.rb b/app/services/zoom_notes_service.rb new file mode 100644 index 00000000000..983a7fcacd1 --- /dev/null +++ b/app/services/zoom_notes_service.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +class ZoomNotesService + def initialize(issue, project, current_user, old_description: nil) + @issue = issue + @project = project + @current_user = current_user + @old_description = old_description + end + + def execute + return if @issue.description == @old_description + + if zoom_link_added? + zoom_link_added_notification + elsif zoom_link_removed? + zoom_link_removed_notification + end + end + + private + + def zoom_link_added? + has_zoom_link?(@issue.description) && !has_zoom_link?(@old_description) + end + + def zoom_link_removed? + !has_zoom_link?(@issue.description) && has_zoom_link?(@old_description) + end + + def has_zoom_link?(text) + Gitlab::ZoomLinkExtractor.new(text).match? + end + + def zoom_link_added_notification + SystemNoteService.zoom_link_added(@issue, @project, @current_user) + end + + def zoom_link_removed_notification + SystemNoteService.zoom_link_removed(@issue, @project, @current_user) + end +end |