From 8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 17 Dec 2020 11:59:07 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-7-stable-ee --- .../admin/propagate_integration_service.rb | 2 +- .../process_prometheus_alert_service.rb | 12 ++- .../application_settings/update_service.rb | 26 +++++- .../container_registry_authentication_service.rb | 12 ++- .../dependency_proxy_authentication_service.rb | 43 ++++++++++ app/services/boards/lists/create_service.rb | 28 ++++--- app/services/boards/lists/generate_service.rb | 6 +- .../ci/compare_codequality_reports_service.rb | 17 ++++ app/services/ci/create_pipeline_service.rb | 5 +- app/services/ci/list_config_variables_service.rb | 24 ++++++ app/services/ci/test_cases_service.rb | 44 ---------- app/services/ci/test_failure_history_service.rb | 95 ++++++++++++++++++++++ app/services/ci/update_build_state_service.rb | 20 ++++- .../prometheus_health_check_service.rb | 6 +- .../clusters/aws/authorize_role_service.rb | 24 +++++- .../clusters/aws/fetch_credentials_service.rb | 9 +- app/services/concerns/exclusive_lease_guard.rb | 2 +- .../concerns/users/participable_service.rb | 59 ++++++++++---- .../cleanup_service.rb | 3 +- .../dependency_proxy/auth_token_service.rb | 21 +++++ app/services/dependency_proxy/base_service.rb | 10 +++ .../dependency_proxy/download_blob_service.rb | 10 --- .../find_or_create_manifest_service.rb | 52 ++++++++++++ .../dependency_proxy/head_manifest_service.rb | 29 +++++++ .../dependency_proxy/pull_manifest_service.rb | 18 +++- .../environments/canary_ingress/update_service.rb | 70 ++++++++++++++++ app/services/feature_flags/create_service.rb | 9 -- app/services/git/base_hooks_service.rb | 5 +- app/services/git/branch_hooks_service.rb | 2 +- app/services/groups/create_service.rb | 2 +- app/services/groups/transfer_service.rb | 13 +++ app/services/import/bitbucket_server_service.rb | 8 +- app/services/import/github_service.rb | 13 ++- .../incidents/update_severity_service.rb | 2 +- app/services/integrations/test/project_service.rb | 6 +- app/services/issuable/import_csv/base_service.rb | 15 ++-- app/services/issues/base_service.rb | 16 ---- app/services/issues/clone_service.rb | 90 ++++++++++++++++++++ app/services/issues/create_service.rb | 16 ++++ app/services/issues/export_csv_service.rb | 2 +- app/services/issues/update_service.rb | 15 +++- app/services/jira/requests/base.rb | 7 +- app/services/jira_connect/sync_service.rb | 12 +-- app/services/members/create_service.rb | 6 ++ .../members/invitation_reminder_email_service.rb | 6 -- .../merge_requests/after_create_service.rb | 2 + app/services/merge_requests/base_service.rb | 2 +- app/services/merge_requests/update_service.rb | 2 +- app/services/notes/create_service.rb | 2 +- app/services/notification_service.rb | 16 +++- app/services/onboarding_progress_service.rb | 11 +++ .../packages/composer/create_package_service.rb | 2 + .../packages/conan/create_package_file_service.rb | 9 +- app/services/packages/create_event_service.rb | 10 ++- app/services/packages/create_package_service.rb | 16 +++- .../generic/create_package_file_service.rb | 5 +- .../generic/find_or_create_package_service.rb | 6 +- .../maven/find_or_create_package_service.rb | 2 +- .../packages/npm/create_package_service.rb | 8 -- .../packages/pypi/create_package_service.rb | 3 + app/services/pages/legacy_storage_lease.rb | 28 +++++++ app/services/pages/zip_directory_service.rb | 83 +++++++++++++++++++ app/services/post_receive_service.rb | 6 ++ app/services/projects/alerting/notify_service.rb | 14 +++- .../container_repository/delete_tags_service.rb | 1 + app/services/projects/participants_service.rb | 2 +- .../projects/prometheus/alerts/notify_service.rb | 40 +++++---- ...schedule_bulk_repository_shard_moves_service.rb | 35 ++++++++ app/services/projects/transfer_service.rb | 11 ++- app/services/projects/update_pages_service.rb | 16 +++- app/services/releases/base_service.rb | 20 ++--- app/services/releases/create_service.rb | 22 +---- .../resource_events/change_labels_service.rb | 2 + .../service_desk_settings/update_service.rb | 2 +- app/services/submit_usage_ping_service.rb | 2 - app/services/system_hooks_service.rb | 35 ++++---- app/services/system_note_service.rb | 4 + app/services/system_notes/issuables_service.rb | 23 ++++++ app/services/upload_service.rb | 8 +- app/services/users/approve_service.rb | 9 +- app/services/users/reject_service.rb | 28 +++++++ app/services/users/set_status_service.rb | 19 +++-- app/services/users/validate_otp_service.rb | 6 +- 83 files changed, 1114 insertions(+), 290 deletions(-) create mode 100644 app/services/auth/dependency_proxy_authentication_service.rb create mode 100644 app/services/ci/compare_codequality_reports_service.rb delete mode 100644 app/services/ci/test_cases_service.rb create mode 100644 app/services/ci/test_failure_history_service.rb create mode 100644 app/services/dependency_proxy/auth_token_service.rb create mode 100644 app/services/dependency_proxy/find_or_create_manifest_service.rb create mode 100644 app/services/dependency_proxy/head_manifest_service.rb create mode 100644 app/services/environments/canary_ingress/update_service.rb create mode 100644 app/services/issues/clone_service.rb create mode 100644 app/services/onboarding_progress_service.rb create mode 100644 app/services/pages/legacy_storage_lease.rb create mode 100644 app/services/pages/zip_directory_service.rb create mode 100644 app/services/projects/schedule_bulk_repository_shard_moves_service.rb create mode 100644 app/services/users/reject_service.rb (limited to 'app/services') diff --git a/app/services/admin/propagate_integration_service.rb b/app/services/admin/propagate_integration_service.rb index ddd5add42bd..253c3a84fef 100644 --- a/app/services/admin/propagate_integration_service.rb +++ b/app/services/admin/propagate_integration_service.rb @@ -7,7 +7,7 @@ module Admin def propagate if integration.instance? update_inherited_integrations - create_integration_for_groups_without_integration if Feature.enabled?(:group_level_integrations, default_enabled: true) + create_integration_for_groups_without_integration create_integration_for_projects_without_integration else update_inherited_descendant_integrations diff --git a/app/services/alert_management/process_prometheus_alert_service.rb b/app/services/alert_management/process_prometheus_alert_service.rb index 28ce5401a6c..753162bfdbf 100644 --- a/app/services/alert_management/process_prometheus_alert_service.rb +++ b/app/services/alert_management/process_prometheus_alert_service.rb @@ -1,10 +1,16 @@ # frozen_string_literal: true module AlertManagement - class ProcessPrometheusAlertService < BaseService + class ProcessPrometheusAlertService + include BaseServiceUtility include Gitlab::Utils::StrongMemoize include ::IncidentManagement::Settings + def initialize(project, payload) + @project = project + @payload = payload + end + def execute return bad_request unless incoming_payload.has_required_attributes? @@ -19,6 +25,8 @@ module AlertManagement private + attr_reader :project, :payload + def process_alert_management_alert if incoming_payload.resolved? process_resolved_alert_management_alert @@ -127,7 +135,7 @@ module AlertManagement strong_memoize(:incoming_payload) do Gitlab::AlertManagement::Payload.parse( project, - params, + payload, monitoring_tool: Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus] ) end diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb index df9217bea32..7792b811b4e 100644 --- a/app/services/application_settings/update_service.rb +++ b/app/services/application_settings/update_service.rb @@ -9,6 +9,16 @@ module ApplicationSettings MARKDOWN_CACHE_INVALIDATING_PARAMS = %w(asset_proxy_enabled asset_proxy_url asset_proxy_secret_key asset_proxy_whitelist).freeze def execute + result = update_settings + + auto_approve_blocked_users if result + + result + end + + private + + def update_settings validate_classification_label(application_setting, :external_authorization_service_default_label) unless bypass_external_auth? if application_setting.errors.any? @@ -40,8 +50,6 @@ module ApplicationSettings @application_setting.save end - private - def usage_stats_updated? params.key?(:usage_ping_enabled) || params.key?(:version_check_enabled) end @@ -95,6 +103,20 @@ module ApplicationSettings def bypass_external_auth? params.key?(:external_authorization_service_enabled) && !Gitlab::Utils.to_boolean(params[:external_authorization_service_enabled]) end + + def auto_approve_blocked_users + return unless should_auto_approve_blocked_users? + + ApproveBlockedPendingApprovalUsersWorker.perform_async(current_user.id) + end + + def should_auto_approve_blocked_users? + return false unless application_setting.previous_changes.key?(:require_admin_approval_after_user_signup) + + enabled_previous, enabled_current = application_setting.previous_changes[:require_admin_approval_after_user_signup] + + enabled_previous && !enabled_current + end end end diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 831a25a637e..d74f20511bd 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -130,6 +130,7 @@ module Auth ContainerRepository.create_from_path!(path) end + # Overridden in EE def can_access?(requested_project, requested_action) return false unless requested_project.container_registry_enabled? return false if requested_project.repository_access_level == ::ProjectFeature::DISABLED @@ -226,11 +227,16 @@ module Auth end end + # Overridden in EE + def extra_info + {} + end + def log_if_actions_denied(type, requested_project, requested_actions, authorized_actions) return if requested_actions == authorized_actions log_info = { - message: "Denied container registry permissions", + message: 'Denied container registry permissions', scope_type: type, requested_project_path: requested_project.full_path, requested_actions: requested_actions, @@ -238,9 +244,11 @@ module Auth username: current_user&.username, user_id: current_user&.id, project_path: project&.full_path - }.compact + }.merge!(extra_info).compact Gitlab::AuthLogger.warn(log_info) end end end + +Auth::ContainerRegistryAuthenticationService.prepend_if_ee('EE::Auth::ContainerRegistryAuthenticationService') diff --git a/app/services/auth/dependency_proxy_authentication_service.rb b/app/services/auth/dependency_proxy_authentication_service.rb new file mode 100644 index 00000000000..1b8c16b7c79 --- /dev/null +++ b/app/services/auth/dependency_proxy_authentication_service.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Auth + class DependencyProxyAuthenticationService < BaseService + AUDIENCE = 'dependency_proxy' + HMAC_KEY = 'gitlab-dependency-proxy' + DEFAULT_EXPIRE_TIME = 1.minute + + def execute(authentication_abilities:) + return error('dependency proxy not enabled', 404) unless ::Gitlab.config.dependency_proxy.enabled + return error('access forbidden', 403) unless current_user + + { token: authorized_token.encoded } + end + + class << self + include ::Gitlab::Utils::StrongMemoize + + def secret + strong_memoize(:secret) do + OpenSSL::HMAC.hexdigest( + 'sha256', + ::Settings.attr_encrypted_db_key_base, + HMAC_KEY + ) + end + end + + def token_expire_at + Time.current + Gitlab::CurrentSettings.container_registry_token_expire_delay.minutes + end + end + + private + + def authorized_token + JSONWebToken::HMACToken.new(self.class.secret).tap do |token| + token['user_id'] = current_user.id + token.expire_time = self.class.token_expire_at + end + end + end +end diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index 9c7a165776e..a21ceee083f 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -6,17 +6,21 @@ module Boards include Gitlab::Utils::StrongMemoize def execute(board) - List.transaction do - case type - when :backlog - create_backlog(board) - else - target = target(board) - position = next_position(board) - - create_list(board, type, target, position) - end - end + list = case type + when :backlog + create_backlog(board) + else + target = target(board) + position = next_position(board) + + return ServiceResponse.error(message: _('%{board_target} not found') % { board_target: type.to_s.capitalize }) if target.blank? + + create_list(board, type, target, position) + end + + return ServiceResponse.error(message: list.errors.full_messages) unless list.persisted? + + ServiceResponse.success(payload: { list: list }) end private @@ -33,7 +37,7 @@ module Boards def target(board) strong_memoize(:target) do - available_labels.find(params[:label_id]) + available_labels.find_by(id: params[:label_id]) # rubocop: disable CodeReuse/ActiveRecord end end diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 4fbf1026019..d74320e92a3 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -7,7 +7,11 @@ module Boards return false unless board.lists.movable.empty? List.transaction do - label_params.each { |params| create_list(board, params) } + label_params.each do |params| + response = create_list(board, params) + + raise ActiveRecord::Rollback unless response.success? + end end true diff --git a/app/services/ci/compare_codequality_reports_service.rb b/app/services/ci/compare_codequality_reports_service.rb new file mode 100644 index 00000000000..20f5378f051 --- /dev/null +++ b/app/services/ci/compare_codequality_reports_service.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Ci + class CompareCodequalityReportsService < CompareReportsBaseService + def comparer_class + Gitlab::Ci::Reports::CodequalityReportsComparer + end + + def serializer_class + CodequalityReportsComparerSerializer + end + + def get_report(pipeline) + pipeline&.codequality_reports + end + end +end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index e3bab2de44e..dbe81521cfc 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -18,6 +18,7 @@ module Ci Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules, Gitlab::Ci::Pipeline::Chain::Seed, Gitlab::Ci::Pipeline::Chain::Limit::Size, + Gitlab::Ci::Pipeline::Chain::Limit::Deployments, Gitlab::Ci::Pipeline::Chain::Validate::External, Gitlab::Ci::Pipeline::Chain::Populate, Gitlab::Ci::Pipeline::Chain::StopDryRun, @@ -90,7 +91,9 @@ module Ci # rubocop: enable Metrics/ParameterLists def execute!(*args, &block) - execute(*args, &block).tap do |pipeline| + source, params = args[0], Hash(args[1]) + + execute(source, **params, &block).tap do |pipeline| unless pipeline.persisted? raise CreateError, pipeline.full_error_messages end diff --git a/app/services/ci/list_config_variables_service.rb b/app/services/ci/list_config_variables_service.rb index 4a5b3a92a2c..88dac514bb9 100644 --- a/app/services/ci/list_config_variables_service.rb +++ b/app/services/ci/list_config_variables_service.rb @@ -2,7 +2,26 @@ module Ci class ListConfigVariablesService < ::BaseService + include ReactiveCaching + + self.reactive_cache_key = ->(service) { [service.class.name, service.id] } + self.reactive_cache_work_type = :external_dependency + self.reactive_cache_worker_finder = ->(id, *_args) { from_cache(id) } + + def self.from_cache(id) + project_id, user_id = id.split('-') + + project = Project.find(project_id) + user = User.find(user_id) + + new(project, user) + end + def execute(sha) + with_reactive_cache(sha) { |result| result } + end + + def calculate_reactive_cache(sha) config = project.ci_config_for(sha) return {} unless config @@ -12,5 +31,10 @@ module Ci result.valid? ? result.variables_with_data : {} end + + # Required for ReactiveCaching, it is also used in `reactive_cache_worker_finder` + def id + "#{project.id}-#{current_user.id}" + end end end diff --git a/app/services/ci/test_cases_service.rb b/app/services/ci/test_cases_service.rb deleted file mode 100644 index 3139b567571..00000000000 --- a/app/services/ci/test_cases_service.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module Ci - class TestCasesService - MAX_TRACKABLE_FAILURES = 200 - - def execute(build) - return unless Feature.enabled?(:test_failure_history, build.project) - return unless build.has_test_reports? - return unless build.project.default_branch_or_master == build.ref - - test_suite = generate_test_suite_report(build) - - track_failures(build, test_suite) - end - - private - - def generate_test_suite_report(build) - build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new) - end - - def track_failures(build, test_suite) - return if test_suite.failed_count > MAX_TRACKABLE_FAILURES - - test_suite.failed.keys.each_slice(100) do |keys| - Ci::TestCase.transaction do - test_cases = Ci::TestCase.find_or_create_by_batch(build.project, keys) - Ci::TestCaseFailure.insert_all(test_case_failures(test_cases, build)) - end - end - end - - def test_case_failures(test_cases, build) - test_cases.map do |test_case| - { - test_case_id: test_case.id, - build_id: build.id, - failed_at: build.finished_at - } - end - end - end -end diff --git a/app/services/ci/test_failure_history_service.rb b/app/services/ci/test_failure_history_service.rb new file mode 100644 index 00000000000..99a2592ec06 --- /dev/null +++ b/app/services/ci/test_failure_history_service.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module Ci + class TestFailureHistoryService + class Async + attr_reader :service + + def initialize(service) + @service = service + end + + def perform_if_needed + TestFailureHistoryWorker.perform_async(service.pipeline.id) if service.should_track_failures? + end + end + + MAX_TRACKABLE_FAILURES = 200 + + attr_reader :pipeline + delegate :project, to: :pipeline + + def initialize(pipeline) + @pipeline = pipeline + end + + def execute + return unless should_track_failures? + + track_failures + end + + def should_track_failures? + return false unless Feature.enabled?(:test_failure_history, project) + return false unless project.default_branch_or_master == pipeline.ref + + # We fetch for up to MAX_TRACKABLE_FAILURES + 1 builds. So if ever we get + # 201 total number of builds with the assumption that each job has at least + # 1 failed test case, then we have at least 201 failed test cases which exceeds + # the MAX_TRACKABLE_FAILURES of 200. If this is the case, let's early exit so we + # don't have to parse each JUnit report of each of the 201 builds. + failed_builds.length <= MAX_TRACKABLE_FAILURES + end + + def async + Async.new(self) + end + + private + + def failed_builds + @failed_builds ||= pipeline.builds_with_failed_tests(limit: MAX_TRACKABLE_FAILURES + 1) + end + + def track_failures + failed_test_cases = gather_failed_test_cases(failed_builds) + + return if failed_test_cases.size > MAX_TRACKABLE_FAILURES + + failed_test_cases.keys.each_slice(100) do |key_hashes| + Ci::TestCase.transaction do + ci_test_cases = Ci::TestCase.find_or_create_by_batch(project, key_hashes) + failures = test_case_failures(ci_test_cases, failed_test_cases) + + Ci::TestCaseFailure.insert_all(failures) + end + end + end + + def gather_failed_test_cases(failed_builds) + failed_builds.each_with_object({}) do |build, failed_test_cases| + test_suite = generate_test_suite!(build) + test_suite.failed.keys.each do |key| + failed_test_cases[key] = build + end + end + end + + def generate_test_suite!(build) + # Returns an instance of Gitlab::Ci::Reports::TestSuite + build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new) + end + + def test_case_failures(ci_test_cases, failed_test_cases) + ci_test_cases.map do |test_case| + build = failed_test_cases[test_case.key_hash] + + { + test_case_id: test_case.id, + build_id: build.id, + failed_at: build.finished_at + } + end + end + end +end diff --git a/app/services/ci/update_build_state_service.rb b/app/services/ci/update_build_state_service.rb index fb67b0d2355..f01d41d9414 100644 --- a/app/services/ci/update_build_state_service.rb +++ b/app/services/ci/update_build_state_service.rb @@ -82,6 +82,10 @@ module Ci unless checksum.valid? metrics.increment_trace_operation(operation: :invalid) + if checksum.corrupted? + metrics.increment_trace_operation(operation: :corrupted) + end + next unless log_invalid_chunks? ::Gitlab::ErrorTracking.log_exception(InvalidTraceError.new, @@ -89,7 +93,8 @@ module Ci build_id: build.id, state_crc32: checksum.state_crc32, chunks_crc32: checksum.chunks_crc32, - chunks_count: checksum.chunks_count + chunks_count: checksum.chunks_count, + chunks_corrupted: checksum.corrupted? ) end end @@ -151,13 +156,21 @@ module Ci end def has_checksum? - params.dig(:checksum).present? + trace_checksum.present? end def build_running? build_state == 'running' end + def trace_checksum + params.dig(:output, :checksum) || params.dig(:checksum) + end + + def trace_bytesize + params.dig(:output, :bytesize) + end + def pending_state strong_memoize(:pending_state) { ensure_pending_state } end @@ -166,7 +179,8 @@ module Ci build_state = Ci::BuildPendingState.safe_find_or_create_by( build_id: build.id, state: params.fetch(:state), - trace_checksum: params.fetch(:checksum), + trace_checksum: trace_checksum, + trace_bytesize: trace_bytesize, failure_reason: params.dig(:failure_reason) ) diff --git a/app/services/clusters/applications/prometheus_health_check_service.rb b/app/services/clusters/applications/prometheus_health_check_service.rb index e609d9f0b7b..eda47f56e72 100644 --- a/app/services/clusters/applications/prometheus_health_check_service.rb +++ b/app/services/clusters/applications/prometheus_health_check_service.rb @@ -63,8 +63,10 @@ module Clusters def send_notification(project) notification_payload = build_notification_payload(project) - token = project.alerts_service.data.token - Projects::Alerting::NotifyService.new(project, nil, notification_payload).execute(token) + integration = project.alert_management_http_integrations.active.first + + Projects::Alerting::NotifyService.new(project, notification_payload).execute(integration&.token, integration) + @logger.info(message: 'Successfully notified of Prometheus newly unhealthy', cluster_id: @cluster.id, project_id: project.id) end diff --git a/app/services/clusters/aws/authorize_role_service.rb b/app/services/clusters/aws/authorize_role_service.rb index 188c4aebc5f..7ca20289bf7 100644 --- a/app/services/clusters/aws/authorize_role_service.rb +++ b/app/services/clusters/aws/authorize_role_service.rb @@ -29,7 +29,7 @@ module Clusters rescue *ERRORS => e Gitlab::ErrorTracking.track_exception(e) - Response.new(:unprocessable_entity, {}) + Response.new(:unprocessable_entity, response_details(e)) end private @@ -47,6 +47,28 @@ module Clusters def credentials Clusters::Aws::FetchCredentialsService.new(role).execute end + + def response_details(exception) + message = + case exception + when ::Aws::STS::Errors::AccessDenied + _("Access denied: %{error}") % { error: exception.message } + when ::Aws::STS::Errors::ServiceError + _("AWS service error: %{error}") % { error: exception.message } + when ActiveRecord::RecordNotFound + _("Error: Unable to find AWS role for current user") + when ActiveRecord::RecordInvalid + exception.message + when Clusters::Aws::FetchCredentialsService::MissingRoleError + _("Error: No AWS provision role found for user") + when ::Aws::Errors::MissingCredentialsError + _("Error: No AWS credentials were supplied") + else + _('An error occurred while authorizing your role') + end + + { message: message }.compact + end end end end diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb index 96abbb43969..497e676f549 100644 --- a/app/services/clusters/aws/fetch_credentials_service.rb +++ b/app/services/clusters/aws/fetch_credentials_service.rb @@ -30,10 +30,17 @@ module Clusters attr_reader :provider, :region def client - ::Aws::STS::Client.new(credentials: gitlab_credentials, region: region) + ::Aws::STS::Client.new(**client_args) + end + + def client_args + { region: region, credentials: gitlab_credentials }.compact end def gitlab_credentials + # These are not needed for IAM instance profiles + return unless access_key_id.present? && secret_access_key.present? + ::Aws::Credentials.new(access_key_id, secret_access_key) end diff --git a/app/services/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb index a58e9aefcec..76d59cf2159 100644 --- a/app/services/concerns/exclusive_lease_guard.rb +++ b/app/services/concerns/exclusive_lease_guard.rb @@ -21,7 +21,7 @@ module ExclusiveLeaseGuard lease = exclusive_lease.try_obtain unless lease - log_error("Cannot obtain an exclusive lease for #{self.class.name}. There must be another instance already in execution.") + log_error("Cannot obtain an exclusive lease for #{lease_key}. There must be another instance already in execution.") return end diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb index 4f4032e77b9..c1c93aa604e 100644 --- a/app/services/concerns/users/participable_service.rb +++ b/app/services/concerns/users/participable_service.rb @@ -8,10 +8,14 @@ module Users attr_reader :noteable end + private + def noteable_owner return [] unless noteable && noteable.author.present? - [user_as_hash(noteable.author)] + [noteable.author].tap do |users| + preload_status(users) + end end def participants_in_noteable @@ -22,23 +26,29 @@ module Users end def sorted(users) - users.uniq.to_a.compact.sort_by(&:username).map do |user| - user_as_hash(user) + users.uniq.to_a.compact.sort_by(&:username).tap do |users| + preload_status(users) end end def groups - group_counts = GroupMember - .of_groups(current_user.authorized_groups) - .non_request - .count_users_by_group_id + current_user.authorized_groups.with_route.sort_by(&:path) + end - current_user.authorized_groups.with_route.sort_by(&:path).map do |group| - group_as_hash(group, group_counts) - end + def render_participants_as_hash(participants) + participants.map(&method(:participant_as_hash)) end - private + def participant_as_hash(participant) + case participant + when Group + group_as_hash(participant) + when User + user_as_hash(participant) + else + participant + end + end def user_as_hash(user) { @@ -46,12 +56,11 @@ module Users username: user.username, name: user.name, avatar_url: user.avatar_url, - availability: nil + availability: lazy_user_availability(user).itself # calling #itself to avoid returning a BatchLoader instance } - # Return nil for availability for now due to https://gitlab.com/gitlab-org/gitlab/-/issues/285442 end - def group_as_hash(group, group_counts) + def group_as_hash(group) { type: group.class.name, username: group.full_path, @@ -61,5 +70,27 @@ module Users mentionsDisabled: group.mentions_disabled } end + + def group_counts + @group_counts ||= GroupMember + .of_groups(current_user.authorized_groups) + .non_request + .count_users_by_group_id + end + + def preload_status(users) + users.each { |u| lazy_user_availability(u) } + end + + def lazy_user_availability(user) + BatchLoader.for(user.id).batch do |user_ids, loader| + user_ids.each_slice(1_000) do |sliced_user_ids| + UserStatus + .select(:user_id, :availability) + .primary_key_in(sliced_user_ids) + .each { |status| loader.call(status.user_id, status.availability) } + end + end + end end end diff --git a/app/services/container_expiration_policies/cleanup_service.rb b/app/services/container_expiration_policies/cleanup_service.rb index f2bc2beab63..4719c99af6d 100644 --- a/app/services/container_expiration_policies/cleanup_service.rb +++ b/app/services/container_expiration_policies/cleanup_service.rb @@ -20,7 +20,8 @@ module ContainerExpirationPolicies if result[:status] == :success repository.update!( expiration_policy_cleanup_status: :cleanup_unscheduled, - expiration_policy_started_at: nil + expiration_policy_started_at: nil, + expiration_policy_completed_at: Time.zone.now ) success(:finished) else diff --git a/app/services/dependency_proxy/auth_token_service.rb b/app/services/dependency_proxy/auth_token_service.rb new file mode 100644 index 00000000000..16279ed12b0 --- /dev/null +++ b/app/services/dependency_proxy/auth_token_service.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module DependencyProxy + class AuthTokenService < DependencyProxy::BaseService + attr_reader :token + + def initialize(token) + @token = token + end + + def execute + JSONWebToken::HMACToken.decode(token, ::Auth::DependencyProxyAuthenticationService.secret).first + end + + class << self + def decoded_token_payload(token) + self.new(token).execute + end + end + end +end diff --git a/app/services/dependency_proxy/base_service.rb b/app/services/dependency_proxy/base_service.rb index 1b2d4b14a27..944877fd5f9 100644 --- a/app/services/dependency_proxy/base_service.rb +++ b/app/services/dependency_proxy/base_service.rb @@ -2,6 +2,16 @@ module DependencyProxy class BaseService < ::BaseService + class DownloadError < StandardError + attr_reader :http_status + + def initialize(message, http_status) + @http_status = http_status + + super(message) + end + end + private def registry diff --git a/app/services/dependency_proxy/download_blob_service.rb b/app/services/dependency_proxy/download_blob_service.rb index 3c690683bf6..b3548c8a126 100644 --- a/app/services/dependency_proxy/download_blob_service.rb +++ b/app/services/dependency_proxy/download_blob_service.rb @@ -2,16 +2,6 @@ module DependencyProxy class DownloadBlobService < DependencyProxy::BaseService - class DownloadError < StandardError - attr_reader :http_status - - def initialize(message, http_status) - @http_status = http_status - - super(message) - end - end - def initialize(image, blob_sha, token) @image = image @blob_sha = blob_sha diff --git a/app/services/dependency_proxy/find_or_create_manifest_service.rb b/app/services/dependency_proxy/find_or_create_manifest_service.rb new file mode 100644 index 00000000000..6b46f5e4c59 --- /dev/null +++ b/app/services/dependency_proxy/find_or_create_manifest_service.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module DependencyProxy + class FindOrCreateManifestService < DependencyProxy::BaseService + def initialize(group, image, tag, token) + @group = group + @image = image + @tag = tag + @token = token + @file_name = "#{@image}:#{@tag}.json" + @manifest = nil + end + + def execute + @manifest = @group.dependency_proxy_manifests + .find_or_initialize_by_file_name(@file_name) + + head_result = DependencyProxy::HeadManifestService.new(@image, @tag, @token).execute + + return success(manifest: @manifest) if cached_manifest_matches?(head_result) + + pull_new_manifest + respond + rescue Timeout::Error, *Gitlab::HTTP::HTTP_ERRORS + respond + end + + private + + def pull_new_manifest + DependencyProxy::PullManifestService.new(@image, @tag, @token).execute_with_manifest do |new_manifest| + @manifest.update!( + digest: new_manifest[:digest], + file: new_manifest[:file], + size: new_manifest[:file].size + ) + end + end + + def cached_manifest_matches?(head_result) + @manifest && @manifest.digest == head_result[:digest] + end + + def respond + if @manifest.persisted? + success(manifest: @manifest) + else + error('Failed to download the manifest from the external registry', 503) + end + end + end +end diff --git a/app/services/dependency_proxy/head_manifest_service.rb b/app/services/dependency_proxy/head_manifest_service.rb new file mode 100644 index 00000000000..87d9c417c98 --- /dev/null +++ b/app/services/dependency_proxy/head_manifest_service.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module DependencyProxy + class HeadManifestService < DependencyProxy::BaseService + def initialize(image, tag, token) + @image = image + @tag = tag + @token = token + end + + def execute + response = Gitlab::HTTP.head(manifest_url, headers: auth_headers) + + if response.success? + success(digest: response.headers['docker-content-digest']) + else + error(response.body, response.code) + end + rescue Timeout::Error => exception + error(exception.message, 599) + end + + private + + def manifest_url + registry.manifest_url(@image, @tag) + end + end +end diff --git a/app/services/dependency_proxy/pull_manifest_service.rb b/app/services/dependency_proxy/pull_manifest_service.rb index fc54ef85c96..5c804489fd1 100644 --- a/app/services/dependency_proxy/pull_manifest_service.rb +++ b/app/services/dependency_proxy/pull_manifest_service.rb @@ -8,13 +8,25 @@ module DependencyProxy @token = token end - def execute + def execute_with_manifest + raise ArgumentError, 'Block must be provided' unless block_given? + response = Gitlab::HTTP.get(manifest_url, headers: auth_headers) if response.success? - success(manifest: response.body) + file = Tempfile.new + + begin + file.write(response) + file.flush + + yield(success(file: file, digest: response.headers['docker-content-digest'])) + ensure + file.close + file.unlink + end else - error(response.body, response.code) + yield(error(response.body, response.code)) end rescue Timeout::Error => exception error(exception.message, 599) diff --git a/app/services/environments/canary_ingress/update_service.rb b/app/services/environments/canary_ingress/update_service.rb new file mode 100644 index 00000000000..474c3de23d9 --- /dev/null +++ b/app/services/environments/canary_ingress/update_service.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Environments + module CanaryIngress + class UpdateService < ::BaseService + def execute_async(environment) + result = validate(environment) + + return result unless result[:status] == :success + + Environments::CanaryIngress::UpdateWorker.perform_async(environment.id, params) + + success + end + + # This method actually executes the PATCH request to Kubernetes, + # that is used by internal processes i.e. sidekiq worker. + # You should always use `execute_async` to properly validate user's requests. + def execute(environment) + canary_ingress = environment.ingresses&.find(&:canary?) + + unless canary_ingress.present? + return error(_('Canary Ingress does not exist in the environment.')) + end + + if environment.patch_ingress(canary_ingress, patch_data) + success + else + error(_('Failed to update the Canary Ingress.'), :bad_request) + end + end + + private + + def validate(environment) + unless Feature.enabled?(:canary_ingress_weight_control, environment.project, default_enabled: true) + return error(_("Feature flag is not enabled on the environment's project.")) + end + + unless can?(current_user, :update_environment, environment) + return error(_('You do not have permission to update the environment.')) + end + + unless params[:weight].is_a?(Integer) && (0..100).cover?(params[:weight]) + return error(_('Canary weight must be specified and valid range (0..100).')) + end + + if environment.has_running_deployments? + return error(_('There are running deployments on the environment. Please retry later.')) + end + + if ::Gitlab::ApplicationRateLimiter.throttled?(:update_environment_canary_ingress, scope: [environment]) + return error(_("This environment's canary ingress has been updated recently. Please retry later.")) + end + + success + end + + def patch_data + { + metadata: { + annotations: { + Gitlab::Kubernetes::Ingress::ANNOTATION_KEY_CANARY_WEIGHT => params[:weight].to_s + } + } + } + end + end + end +end diff --git a/app/services/feature_flags/create_service.rb b/app/services/feature_flags/create_service.rb index b4ca90f7aae..de3a55d10fc 100644 --- a/app/services/feature_flags/create_service.rb +++ b/app/services/feature_flags/create_service.rb @@ -5,7 +5,6 @@ module FeatureFlags def execute return error('Access Denied', 403) unless can_create? return error('Version is invalid', :bad_request) unless valid_version? - return error('New version feature flags are not enabled for this project', :bad_request) unless flag_version_enabled? ActiveRecord::Base.transaction do feature_flag = project.operations_feature_flags.new(params) @@ -40,13 +39,5 @@ module FeatureFlags def valid_version? !params.key?(:version) || Operations::FeatureFlag.versions.key?(params[:version]) end - - def flag_version_enabled? - params[:version] != 'new_version_flag' || new_version_feature_flags_enabled? - end - - def new_version_feature_flags_enabled? - ::Feature.enabled?(:feature_flags_new_version, project, default_enabled: true) - end end end diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb index ea5b2f401b3..1ca1bfa0c05 100644 --- a/app/services/git/base_hooks_service.rb +++ b/app/services/git/base_hooks_service.rb @@ -135,11 +135,12 @@ module Git # We only need the last commit for the event push, and we don't # need the full deltas either. @event_push_data ||= Gitlab::DataBuilder::Push.build( - push_data_params(commits: commits.last, with_changed_files: false)) + **push_data_params(commits: commits.last, with_changed_files: false) + ) end def push_data - @push_data ||= Gitlab::DataBuilder::Push.build(push_data_params(commits: limited_commits)) + @push_data ||= Gitlab::DataBuilder::Push.build(**push_data_params(commits: limited_commits)) # Dependent code may modify the push data, so return a duplicate each time @push_data.dup diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb index d00ca83441a..4edcff0e3d0 100644 --- a/app/services/git/branch_hooks_service.rb +++ b/app/services/git/branch_hooks_service.rb @@ -118,7 +118,7 @@ module Git commits_to_sync = limited_commits.select { |commit| Atlassian::JiraIssueKeyExtractor.has_keys?(commit.safe_message) }.map(&:sha) if branch_to_sync || commits_to_sync.any? - JiraConnect::SyncBranchWorker.perform_async(project.id, branch_to_sync, commits_to_sync) + JiraConnect::SyncBranchWorker.perform_async(project.id, branch_to_sync, commits_to_sync, Atlassian::JiraConnect::Client.generate_update_sequence_id) end end diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb index 016c31cbccc..52600f5b88f 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -34,7 +34,7 @@ module Groups if @group.save @group.add_owner(current_user) @group.create_namespace_settings - Service.create_from_active_default_integrations(@group, :group_id) if Feature.enabled?(:group_level_integrations, default_enabled: true) + Service.create_from_active_default_integrations(@group, :group_id) end end diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb index aad574aeaf5..e800e546a45 100644 --- a/app/services/groups/transfer_service.rb +++ b/app/services/groups/transfer_service.rb @@ -28,9 +28,11 @@ module Groups Group.transaction do update_group_attributes ensure_ownership + update_integrations end post_update_hooks(@updated_project_ids) + propagate_integrations true end @@ -196,6 +198,17 @@ module Groups raise TransferError, result[:message] unless result[:status] == :success end end + + def update_integrations + @group.services.inherit.delete_all + Service.create_from_active_default_integrations(@group, :group_id) + end + + def propagate_integrations + @group.services.inherit.each do |integration| + PropagateIntegrationWorker.perform_async(integration.id) + end + end end end diff --git a/app/services/import/bitbucket_server_service.rb b/app/services/import/bitbucket_server_service.rb index 86e8215821e..cdb23370ddc 100644 --- a/app/services/import/bitbucket_server_service.rb +++ b/app/services/import/bitbucket_server_service.rb @@ -81,11 +81,9 @@ module Import def blocked_url? Gitlab::UrlBlocker.blocked_url?( url, - { - allow_localhost: allow_local_requests?, - allow_local_network: allow_local_requests?, - schemes: %w(http https) - } + allow_localhost: allow_local_requests?, + allow_local_network: allow_local_requests?, + schemes: %w(http https) ) end diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb index 948dba2d206..847c5eb4397 100644 --- a/app/services/import/github_service.rb +++ b/app/services/import/github_service.rb @@ -31,9 +31,8 @@ module Import project_name, target_namespace, current_user, - access_params, - type: provider - ).execute(extra_project_attrs) + type: provider, + **access_params).execute(extra_project_attrs) end def repo @@ -71,11 +70,9 @@ module Import def blocked_url? Gitlab::UrlBlocker.blocked_url?( url, - { - allow_localhost: allow_local_requests?, - allow_local_network: allow_local_requests?, - schemes: %w(http https) - } + allow_localhost: allow_local_requests?, + allow_local_network: allow_local_requests?, + schemes: %w(http https) ) end diff --git a/app/services/incident_management/incidents/update_severity_service.rb b/app/services/incident_management/incidents/update_severity_service.rb index 5b150f3f02e..faa9277c469 100644 --- a/app/services/incident_management/incidents/update_severity_service.rb +++ b/app/services/incident_management/incidents/update_severity_service.rb @@ -12,7 +12,7 @@ module IncidentManagement end def execute - return unless issuable.incident? + return unless issuable.supports_severity? update_severity! add_system_note diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb index 39471d373f9..d72ca928c34 100644 --- a/app/services/integrations/test/project_service.rb +++ b/app/services/integrations/test/project_service.rb @@ -16,9 +16,7 @@ module Integrations def data strong_memoize(:data) do - next pipeline_events_data if integration.is_a?(::PipelinesEmailService) - - case event + case event || integration.default_test_event when 'push', 'tag_push' push_events_data when 'note', 'confidential_note' @@ -37,8 +35,6 @@ module Integrations deployment_events_data when 'release' releases_events_data - else - push_events_data end end end diff --git a/app/services/issuable/import_csv/base_service.rb b/app/services/issuable/import_csv/base_service.rb index bf5f643a51b..5a2665285de 100644 --- a/app/services/issuable/import_csv/base_service.rb +++ b/app/services/issuable/import_csv/base_service.rb @@ -38,20 +38,19 @@ module Issuable def with_csv_lines csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8) - verify_headers!(csv_data) + validate_headers_presence!(csv_data.lines.first) - csv_parsing_params = { + CSV.new( + csv_data, col_sep: detect_col_sep(csv_data.lines.first), headers: true, header_converters: :symbol - } - - CSV.new(csv_data, csv_parsing_params).each.with_index(2) + ).each.with_index(2) end - def verify_headers!(data) - headers = data.lines.first.downcase - return if headers.include?('title') && headers.include?('description') + def validate_headers_presence!(headers) + headers.downcase! if headers + return if headers && headers.include?('title') && headers.include?('description') raise CSV::MalformedCSVError end diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 978ea6fe9bc..25f319da03b 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -73,22 +73,6 @@ module Issues Milestones::IssuesCountService.new(milestone).delete_cache end - - # Applies label "incident" (creates it if missing) to incident issues. - # Please use in "after" hooks only to ensure we are not appyling - # labels prematurely. - def add_incident_label(issue) - return unless issue.incident? - - label = ::IncidentManagement::CreateIncidentLabelService - .new(project, current_user) - .execute - .payload[:label] - - return if issue.label_ids.include?(label.id) - - issue.labels << label - end end end diff --git a/app/services/issues/clone_service.rb b/app/services/issues/clone_service.rb new file mode 100644 index 00000000000..789da312958 --- /dev/null +++ b/app/services/issues/clone_service.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module Issues + class CloneService < Issuable::Clone::BaseService + CloneError = Class.new(StandardError) + + def execute(issue, target_project, with_notes: false) + @target_project = target_project + @with_notes = with_notes + + unless issue.can_clone?(current_user, target_project) + raise CloneError, s_('CloneIssue|Cannot clone issue due to insufficient permissions!') + end + + if target_project.pending_delete? + raise CloneError, s_('CloneIssue|Cannot clone issue to target project as it is pending deletion.') + end + + super(issue, target_project) + + notify_participants + + queue_copy_designs + + new_entity + end + + private + + attr_reader :target_project + attr_reader :with_notes + + def update_new_entity + # we don't call `super` because we want to be able to decide whether or not to copy all comments over. + update_new_entity_description + update_new_entity_attributes + copy_award_emoji + copy_notes if with_notes + end + + def update_old_entity + # no-op + # The base_service closes the old issue, we don't want that, so we override here so nothing happens. + end + + def create_new_entity + new_params = { + id: nil, + iid: nil, + project: target_project, + author: current_user, + assignee_ids: original_entity.assignee_ids + } + + new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params) + + # Skip creation of system notes for existing attributes of the issue. The system notes of the old + # issue are copied over so we don't want to end up with duplicate notes. + CreateService.new(target_project, current_user, new_params).execute(skip_system_notes: true) + end + + def queue_copy_designs + return unless original_entity.designs.present? + + response = DesignManagement::CopyDesignCollection::QueueService.new( + current_user, + original_entity, + new_entity + ).execute + + log_error(response.message) if response.error? + end + + def notify_participants + notification_service.async.issue_cloned(original_entity, new_entity, current_user) + end + + def add_note_from + SystemNoteService.noteable_cloned(new_entity, target_project, + original_entity, current_user, + direction: :from) + end + + def add_note_to + SystemNoteService.noteable_cloned(original_entity, old_project, + new_entity, current_user, + direction: :to) + end + end +end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index fb7683f940d..44de8eb6389 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -49,6 +49,22 @@ module Issues def user_agent_detail_service UserAgentDetailService.new(@issue, @request) end + + # Applies label "incident" (creates it if missing) to incident issues. + # For use in "after" hooks only to ensure we are not appyling + # labels prematurely. + def add_incident_label(issue) + return unless issue.incident? + + label = ::IncidentManagement::CreateIncidentLabelService + .new(project, current_user) + .execute + .payload[:label] + + return if issue.label_ids.include?(label.id) + + issue.labels << label + end end end diff --git a/app/services/issues/export_csv_service.rb b/app/services/issues/export_csv_service.rb index 1dcdfb9faea..8f513632929 100644 --- a/app/services/issues/export_csv_service.rb +++ b/app/services/issues/export_csv_service.rb @@ -34,7 +34,7 @@ module Issues private def associations_to_preload - %i(author assignees timelogs) + %i(author assignees timelogs milestone) end def header_to_value_hash diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index b9832400302..127ed04cf51 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -9,7 +9,7 @@ module Issues handle_move_between_ids(issue) filter_spam_check_params change_issue_duplicate(issue) - move_issue_to_new_project(issue) || update_task_event(issue) || update(issue) + move_issue_to_new_project(issue) || clone_issue(issue) || update_task_event(issue) || update(issue) end def update(issue) @@ -34,7 +34,6 @@ module Issues end def after_update(issue) - add_incident_label(issue) IssuesChannel.broadcast_to(issue, event: 'updated') if Gitlab::ActionCable::Config.in_app? || Feature.enabled?(:broadcast_issue_updates, issue.project) end @@ -127,6 +126,18 @@ module Issues private + def clone_issue(issue) + target_project = params.delete(:target_clone_project) + with_notes = params.delete(:clone_with_notes) + + return unless target_project && + issue.can_clone?(current_user, target_project) + + # we've pre-empted this from running in #execute, so let's go ahead and update the Issue now. + update(issue) + Issues::CloneService.new(project, current_user).execute(issue, target_project, with_notes: with_notes) + end + def create_merge_request_from_quick_action create_merge_request_params = params.delete(:create_merge_request) return unless create_merge_request_params diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb index 4ed8df0f235..098aae9284c 100644 --- a/app/services/jira/requests/base.rb +++ b/app/services/jira/requests/base.rb @@ -18,14 +18,19 @@ module Jira request end + # We have to add the context_path here because the Jira client is not taking it into account def base_api_url - "/rest/api/#{api_version}" + "#{context_path}/rest/api/#{api_version}" end private attr_reader :jira_service, :project + def context_path + client.options[:context_path].to_s + end + # override this method in the specific request class implementation if a differnt API version is required def api_version JIRA_API_VERSION diff --git a/app/services/jira_connect/sync_service.rb b/app/services/jira_connect/sync_service.rb index f8855fb6deb..b2af284f1f0 100644 --- a/app/services/jira_connect/sync_service.rb +++ b/app/services/jira_connect/sync_service.rb @@ -6,13 +6,15 @@ module JiraConnect self.project = project end - def execute(commits: nil, branches: nil, merge_requests: nil, update_sequence_id: nil) - JiraConnectInstallation.for_project(project).each do |installation| + # Parameters: see Atlassian::JiraConnect::Client#send_info + # Includes: update_sequence_id, commits, branches, merge_requests, pipelines + def execute(**args) + JiraConnectInstallation.for_project(project).flat_map do |installation| client = Atlassian::JiraConnect::Client.new(installation.base_url, installation.shared_secret) - response = client.store_dev_info(project: project, commits: commits, branches: branches, merge_requests: merge_requests, update_sequence_id: update_sequence_id) + responses = client.send_info(project: project, **args) - log_response(response) + responses.each { |r| log_response(r) } end end @@ -29,7 +31,7 @@ module JiraConnect jira_response: response&.to_json } - if response && response['errorMessages'] + if response && (response['errorMessages'] || response['rejectedBuilds'].present?) logger.error(message) else logger.info(message) diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb index 088e6f031c8..3588cda180f 100644 --- a/app/services/members/create_service.rb +++ b/app/services/members/create_service.rb @@ -38,6 +38,8 @@ module Members end end + enqueue_onboarding_progress_action(source) if members.size > errors.size + return success unless errors.any? error(errors.to_sentence) @@ -50,6 +52,10 @@ module Members limit && limit < 0 ? nil : limit end + + def enqueue_onboarding_progress_action(source) + Namespaces::OnboardingUserAddedWorker.perform_async(source.id) + end end end diff --git a/app/services/members/invitation_reminder_email_service.rb b/app/services/members/invitation_reminder_email_service.rb index e589cdc2fa3..688618ec4b4 100644 --- a/app/services/members/invitation_reminder_email_service.rb +++ b/app/services/members/invitation_reminder_email_service.rb @@ -14,8 +14,6 @@ module Members end def execute - return unless experiment_enabled? - reminder_index = days_on_which_to_send_reminders.index(days_after_invitation_sent) return unless reminder_index @@ -24,10 +22,6 @@ module Members private - def experiment_enabled? - Gitlab::Experimentation.enabled_for_attribute?(:invitation_reminders, invitation.invite_email) - end - def days_after_invitation_sent (Date.today - invitation.created_at.to_date).to_i end diff --git a/app/services/merge_requests/after_create_service.rb b/app/services/merge_requests/after_create_service.rb index f0c85ae03c9..fbb9d5fa9dc 100644 --- a/app/services/merge_requests/after_create_service.rb +++ b/app/services/merge_requests/after_create_service.rb @@ -11,6 +11,8 @@ module MergeRequests merge_request.diffs(include_stats: false).write_cache merge_request.create_cross_references!(current_user) + + NamespaceOnboardingAction.create_action(merge_request.target_project.namespace, :merge_request_created) end end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index aa591312c6a..265b211066e 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -58,7 +58,7 @@ module MergeRequests return unless project.jira_subscription_exists? if Atlassian::JiraIssueKeyExtractor.has_keys?(merge_request.title, merge_request.description) - JiraConnect::SyncMergeRequestWorker.perform_async(merge_request.id) + JiraConnect::SyncMergeRequestWorker.perform_async(merge_request.id, Atlassian::JiraConnect::Client.generate_update_sequence_id) end end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 8c069ea5bb0..bff7a43dd7b 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -11,7 +11,7 @@ module MergeRequests params.delete(:target_project_id) params.delete(:source_branch) - if merge_request.closed_without_fork? + if merge_request.closed_or_merged_without_fork? params.delete(:target_branch) params.delete(:force_remove_source_branch) end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index b2826b5c905..9fffb6c372b 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -67,7 +67,7 @@ module Notes track_event(note, current_user) if Feature.enabled?(:notes_create_service_tracking, project) - Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note)) + Gitlab::Tracking.event('Notes::CreateService', 'execute', **tracking_data_for(note)) end if note.for_merge_request? && note.diff_note? && note.start_of_discussion? diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 85113d3ca22..4ff462191fe 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -353,7 +353,7 @@ class NotificationService issue = note.noteable support_bot = User.support_bot - return unless issue.service_desk_reply_to.present? + return unless issue.external_author.present? return unless issue.project.service_desk_enabled? return if note.author == support_bot return unless issue.subscribed?(support_bot, issue.project) @@ -380,6 +380,10 @@ class NotificationService end end + def user_admin_rejection(name, email) + mailer.user_admin_rejection_email(name, email).deliver_later + end + # Members def new_access_request(member) return true unless member.notifiable?(:subscription) @@ -500,6 +504,16 @@ class NotificationService end end + def issue_cloned(issue, new_issue, current_user) + recipients = NotificationRecipients::BuildService.build_recipients(issue, current_user, action: 'cloned') + + recipients.map do |recipient| + email = mailer.issue_cloned_email(recipient.user, issue, new_issue, current_user, recipient.reason) + email.deliver_later + email + end + end + def project_exported(project, current_user) return true unless notifiable?(current_user, :mention, project: project) diff --git a/app/services/onboarding_progress_service.rb b/app/services/onboarding_progress_service.rb new file mode 100644 index 00000000000..ebe7caabdef --- /dev/null +++ b/app/services/onboarding_progress_service.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class OnboardingProgressService + def initialize(namespace) + @namespace = namespace.root_ancestor + end + + def execute(action:) + NamespaceOnboardingAction.create_action(@namespace, action) + end +end diff --git a/app/services/packages/composer/create_package_service.rb b/app/services/packages/composer/create_package_service.rb index 2d2f1568187..0f5429f667e 100644 --- a/app/services/packages/composer/create_package_service.rb +++ b/app/services/packages/composer/create_package_service.rb @@ -16,6 +16,8 @@ module Packages composer_json: composer_json }) end + + created_package end private diff --git a/app/services/packages/conan/create_package_file_service.rb b/app/services/packages/conan/create_package_file_service.rb index 2db5c4e507b..1bde9606492 100644 --- a/app/services/packages/conan/create_package_file_service.rb +++ b/app/services/packages/conan/create_package_file_service.rb @@ -12,7 +12,7 @@ module Packages end def execute - package.package_files.create!( + package_file = package.package_files.build( file: file, size: params['file.size'], file_name: params[:file_name], @@ -25,6 +25,13 @@ module Packages conan_file_type: params[:conan_file_type] } ) + + if params[:build].present? + package_file.package_file_build_infos << package_file.package_file_build_infos.build(pipeline: params[:build].pipeline) + end + + package_file.save! + package_file end end end diff --git a/app/services/packages/create_event_service.rb b/app/services/packages/create_event_service.rb index c4492389da9..f0328ceb08a 100644 --- a/app/services/packages/create_event_service.rb +++ b/app/services/packages/create_event_service.rb @@ -4,7 +4,11 @@ module Packages class CreateEventService < BaseService def execute if Feature.enabled?(:collect_package_events_redis) && redis_event_name - ::Gitlab::UsageDataCounters::HLLRedisCounter.track_event(current_user.id, redis_event_name) + if guest? + ::Gitlab::UsageDataCounters::GuestPackageEventCounter.count(redis_event_name) + else + ::Gitlab::UsageDataCounters::HLLRedisCounter.track_event(current_user.id, redis_event_name) + end end if Feature.enabled?(:collect_package_events) && Gitlab::Database.read_write? @@ -45,5 +49,9 @@ module Packages :guest end end + + def guest? + originator_type == :guest + end end end diff --git a/app/services/packages/create_package_service.rb b/app/services/packages/create_package_service.rb index e3b0ad218e2..fcf252cf971 100644 --- a/app/services/packages/create_package_service.rb +++ b/app/services/packages/create_package_service.rb @@ -8,9 +8,9 @@ module Packages project .packages .with_package_type(package_type) - .safe_find_or_create_by!(name: name, version: version) do |pkg| - pkg.creator = package_creator - yield pkg if block_given? + .safe_find_or_create_by!(name: name, version: version) do |package| + package.creator = package_creator + add_build_info(package) end end @@ -18,7 +18,9 @@ module Packages project .packages .with_package_type(package_type) - .create!(package_attrs(attrs)) + .create!(package_attrs(attrs)) do |package| + add_build_info(package) + end end private @@ -34,5 +36,11 @@ module Packages def package_creator current_user if current_user.is_a?(User) end + + def add_build_info(package) + if params[:build].present? + package.build_infos.new(pipeline: params[:build].pipeline) + end + end end end diff --git a/app/services/packages/generic/create_package_file_service.rb b/app/services/packages/generic/create_package_file_service.rb index f25e8b0ae56..b14b1c193ec 100644 --- a/app/services/packages/generic/create_package_file_service.rb +++ b/app/services/packages/generic/create_package_file_service.rb @@ -18,9 +18,12 @@ module Packages build: params[:build] } - ::Packages::Generic::FindOrCreatePackageService + package = ::Packages::Generic::FindOrCreatePackageService .new(project, current_user, package_params) .execute + + package.build_infos.safe_find_or_create_by!(pipeline: params[:build].pipeline) if params[:build].present? + package end def create_package_file(package) diff --git a/app/services/packages/generic/find_or_create_package_service.rb b/app/services/packages/generic/find_or_create_package_service.rb index 97f774a836b..0a6099e4d35 100644 --- a/app/services/packages/generic/find_or_create_package_service.rb +++ b/app/services/packages/generic/find_or_create_package_service.rb @@ -4,11 +4,7 @@ module Packages module Generic class FindOrCreatePackageService < ::Packages::CreatePackageService def execute - find_or_create_package!(::Packages::Package.package_types['generic']) do |package| - if params[:build].present? - package.build_infos.new(pipeline: params[:build].pipeline) - end - end + find_or_create_package!(::Packages::Package.package_types['generic']) end end end diff --git a/app/services/packages/maven/find_or_create_package_service.rb b/app/services/packages/maven/find_or_create_package_service.rb index a2a61ff8d93..f598b5e7cd4 100644 --- a/app/services/packages/maven/find_or_create_package_service.rb +++ b/app/services/packages/maven/find_or_create_package_service.rb @@ -46,7 +46,7 @@ module Packages .execute end - package.build_infos.create!(pipeline: params[:build].pipeline) if params[:build].present? + package.build_infos.safe_find_or_create_by!(pipeline: params[:build].pipeline) if params[:build].present? package end diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb index c4b75348bba..22396eb7687 100644 --- a/app/services/packages/npm/create_package_service.rb +++ b/app/services/packages/npm/create_package_service.rb @@ -17,10 +17,6 @@ module Packages def create_npm_package! package = create_package!(:npm, name: name, version: version) - if build.present? - package.build_infos.create!(pipeline: build.pipeline) - end - ::Packages::CreatePackageFileService.new(package, file_params).execute ::Packages::CreateDependencyService.new(package, package_dependencies).execute ::Packages::Npm::CreateTagService.new(package, dist_tag).execute @@ -50,10 +46,6 @@ module Packages params[:versions][version] end - def build - params[:build] - end - def dist_tag params['dist-tags'].each_key.first end diff --git a/app/services/packages/pypi/create_package_service.rb b/app/services/packages/pypi/create_package_service.rb index c49efca0fc5..cb8d9559dc9 100644 --- a/app/services/packages/pypi/create_package_service.rb +++ b/app/services/packages/pypi/create_package_service.rb @@ -19,6 +19,8 @@ module Packages Packages::Pypi::Metadatum.upsert(meta.attributes) ::Packages::CreatePackageFileService.new(created_package, file_params).execute + + created_package end end @@ -32,6 +34,7 @@ module Packages def file_params { + build: params[:build], file: params[:content], file_name: params[:content].original_filename, file_md5: params[:md5_digest], diff --git a/app/services/pages/legacy_storage_lease.rb b/app/services/pages/legacy_storage_lease.rb new file mode 100644 index 00000000000..3f42fc8c63b --- /dev/null +++ b/app/services/pages/legacy_storage_lease.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Pages + module LegacyStorageLease + extend ActiveSupport::Concern + + include ::ExclusiveLeaseGuard + + LEASE_TIMEOUT = 1.hour + + # override method from exclusive lease guard to guard it by feature flag + # TODO: just remove this method after testing this in production + # https://gitlab.com/gitlab-org/gitlab/-/issues/282464 + def try_obtain_lease + return yield unless Feature.enabled?(:pages_use_legacy_storage_lease, project, default_enabled: true) + + super + end + + def lease_key + "pages_legacy_storage:#{project.id}" + end + + def lease_timeout + LEASE_TIMEOUT + end + end +end diff --git a/app/services/pages/zip_directory_service.rb b/app/services/pages/zip_directory_service.rb new file mode 100644 index 00000000000..a27ad5fda46 --- /dev/null +++ b/app/services/pages/zip_directory_service.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module Pages + class ZipDirectoryService + InvalidArchiveError = Class.new(RuntimeError) + InvalidEntryError = Class.new(RuntimeError) + + PUBLIC_DIR = 'public' + + def initialize(input_dir) + @input_dir = File.realpath(input_dir) + @output_file = File.join(@input_dir, "@migrated.zip") # '@' to avoid any name collision with groups or projects + end + + def execute + FileUtils.rm_f(@output_file) + + count = 0 + ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| + write_entry(zipfile, PUBLIC_DIR) + count = zipfile.entries.count + end + + [@output_file, count] + end + + private + + def write_entry(zipfile, zipfile_path) + disk_file_path = File.join(@input_dir, zipfile_path) + + unless valid_path?(disk_file_path) + # archive without public directory is completelly unusable + raise InvalidArchiveError if zipfile_path == PUBLIC_DIR + + # archive with invalid entry will just have this entry missing + raise InvalidEntryError + end + + case File.lstat(disk_file_path).ftype + when 'directory' + recursively_zip_directory(zipfile, disk_file_path, zipfile_path) + when 'file', 'link' + zipfile.add(zipfile_path, disk_file_path) + else + raise InvalidEntryError + end + rescue InvalidEntryError => e + Gitlab::ErrorTracking.track_exception(e, input_dir: @input_dir, disk_file_path: disk_file_path) + end + + def recursively_zip_directory(zipfile, disk_file_path, zipfile_path) + zipfile.mkdir(zipfile_path) + + entries = Dir.entries(disk_file_path) - %w[. ..] + entries = entries.map { |entry| File.join(zipfile_path, entry) } + + write_entries(zipfile, entries) + end + + def write_entries(zipfile, entries) + entries.each do |zipfile_path| + write_entry(zipfile, zipfile_path) + end + end + + # that should never happen, but we want to be safer + # in theory without this we would allow to use symlinks + # to pack any directory on disk + # it isn't possible because SafeZip doesn't extract such archives + def valid_path?(disk_file_path) + realpath = File.realpath(disk_file_path) + + realpath == File.join(@input_dir, PUBLIC_DIR) || + realpath.start_with?(File.join(@input_dir, PUBLIC_DIR + "/")) + # happens if target of symlink isn't there + rescue => e + Gitlab::ErrorTracking.track_exception(e, input_dir: @input_dir, disk_file_path: disk_file_path) + + false + end + end +end diff --git a/app/services/post_receive_service.rb b/app/services/post_receive_service.rb index 79b613f6a88..bd9588844ad 100644 --- a/app/services/post_receive_service.rb +++ b/app/services/post_receive_service.rb @@ -40,6 +40,8 @@ class PostReceiveService response.add_basic_message(redirect_message) response.add_basic_message(project_created_message) + + record_onboarding_progress end response @@ -90,6 +92,10 @@ class PostReceiveService banner&.message end + + def record_onboarding_progress + NamespaceOnboardingAction.create_action(project.namespace, :git_write) + end end PostReceiveService.prepend_if_ee('EE::PostReceiveService') diff --git a/app/services/projects/alerting/notify_service.rb b/app/services/projects/alerting/notify_service.rb index ab8f53a3757..014fb0e3ed3 100644 --- a/app/services/projects/alerting/notify_service.rb +++ b/app/services/projects/alerting/notify_service.rb @@ -2,10 +2,16 @@ module Projects module Alerting - class NotifyService < BaseService + class NotifyService + include BaseServiceUtility include Gitlab::Utils::StrongMemoize include ::IncidentManagement::Settings + def initialize(project, payload) + @project = project + @payload = payload + end + def execute(token, integration = nil) @integration = integration @@ -24,7 +30,7 @@ module Projects private - attr_reader :integration + attr_reader :project, :payload, :integration def process_alert if alert.persisted? @@ -101,7 +107,7 @@ module Projects def incoming_payload strong_memoize(:incoming_payload) do - Gitlab::AlertManagement::Payload.parse(project, params.to_h) + Gitlab::AlertManagement::Payload.parse(project, payload.to_h) end end @@ -110,7 +116,7 @@ module Projects end def valid_payload_size? - Gitlab::Utils::DeepSize.new(params).valid? + Gitlab::Utils::DeepSize.new(payload).valid? end def active_integration? diff --git a/app/services/projects/container_repository/delete_tags_service.rb b/app/services/projects/container_repository/delete_tags_service.rb index 505ddaf50e3..410cf6c624e 100644 --- a/app/services/projects/container_repository/delete_tags_service.rb +++ b/app/services/projects/container_repository/delete_tags_service.rb @@ -36,6 +36,7 @@ module Projects def log_response(response) log_data = LOG_DATA_BASE.merge( container_repository_id: @container_repository.id, + project_id: @container_repository.project_id, message: 'deleted tags', deleted_tags_count: response[:deleted]&.size ).compact diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index 1cd81fe37c7..228115d72b8 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -14,7 +14,7 @@ module Projects groups + project_members - participants.uniq + render_participants_as_hash(participants.uniq) end def project_members diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb index 8ad4f59373d..93165a58470 100644 --- a/app/services/projects/prometheus/alerts/notify_service.rb +++ b/app/services/projects/prometheus/alerts/notify_service.rb @@ -3,7 +3,7 @@ module Projects module Prometheus module Alerts - class NotifyService < BaseService + class NotifyService include Gitlab::Utils::StrongMemoize include ::IncidentManagement::Settings @@ -17,28 +17,35 @@ module Projects SUPPORTED_VERSION = '4' - def execute(token, _integration = nil) + def initialize(project, payload) + @project = project + @payload = payload + end + + def execute(token, integration = nil) return bad_request unless valid_payload_size? - return unprocessable_entity unless self.class.processable?(params) - return unauthorized unless valid_alert_manager_token?(token) + return unprocessable_entity unless self.class.processable?(payload) + return unauthorized unless valid_alert_manager_token?(token, integration) process_prometheus_alerts ServiceResponse.success end - def self.processable?(params) + def self.processable?(payload) # Workaround for https://gitlab.com/gitlab-org/gitlab/-/issues/220496 - return false unless params + return false unless payload - REQUIRED_PAYLOAD_KEYS.subset?(params.keys.to_set) && - params['version'] == SUPPORTED_VERSION + REQUIRED_PAYLOAD_KEYS.subset?(payload.keys.to_set) && + payload['version'] == SUPPORTED_VERSION end private + attr_reader :project, :payload + def valid_payload_size? - Gitlab::Utils::DeepSize.new(params).valid? + Gitlab::Utils::DeepSize.new(payload).valid? end def firings @@ -50,12 +57,12 @@ module Projects end def alerts - params['alerts'] + payload['alerts'] end - def valid_alert_manager_token?(token) + def valid_alert_manager_token?(token, integration) valid_for_manual?(token) || - valid_for_alerts_endpoint?(token) || + valid_for_alerts_endpoint?(token, integration) || valid_for_managed?(token) end @@ -70,11 +77,10 @@ module Projects end end - def valid_for_alerts_endpoint?(token) - return false unless project.alerts_service_activated? + def valid_for_alerts_endpoint?(token, integration) + return false unless integration&.active? - # Here we are enforcing the existence of the token - compare_token(token, project.alerts_service.token) + compare_token(token, integration.token) end def valid_for_managed?(token) @@ -122,7 +128,7 @@ module Projects def process_prometheus_alerts alerts.each do |alert| AlertManagement::ProcessPrometheusAlertService - .new(project, nil, alert.to_h) + .new(project, alert.to_h) .execute end end diff --git a/app/services/projects/schedule_bulk_repository_shard_moves_service.rb b/app/services/projects/schedule_bulk_repository_shard_moves_service.rb new file mode 100644 index 00000000000..dd49910207f --- /dev/null +++ b/app/services/projects/schedule_bulk_repository_shard_moves_service.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Projects + # Tries to schedule a move for every project with repositories on the source shard + class ScheduleBulkRepositoryShardMovesService + include BaseServiceUtility + + def execute(source_storage_name, destination_storage_name = nil) + shard = Shard.find_by_name!(source_storage_name) + + ProjectRepository.for_shard(shard).each_batch(column: :project_id) do |relation| + Project.id_in(relation.select(:project_id)).each do |project| + project.with_lock do + next if project.repository_storage != source_storage_name + + storage_move = project.repository_storage_moves.build( + source_storage_name: source_storage_name, + destination_storage_name: destination_storage_name + ) + + unless storage_move.schedule + log_info("Project #{project.full_path} (#{project.id}) was skipped: #{storage_move.errors.full_messages.to_sentence}") + end + end + end + end + + success + end + + def self.enqueue(source_storage_name, destination_storage_name = nil) + ::ProjectScheduleBulkRepositoryShardMovesWorker.perform_async(source_storage_name, destination_storage_name) + end + end +end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 5178c76f0fc..1574c90d2ac 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -59,7 +59,7 @@ module Projects raise TransferError.new(s_("TransferProject|Root namespace can't be updated if project has NPM packages")) end - attempt_transfer_transaction + proceed_to_transfer end # rubocop: enable CodeReuse/ActiveRecord @@ -67,7 +67,7 @@ module Projects new_namespace.root_ancestor == project.namespace.root_ancestor end - def attempt_transfer_transaction + def proceed_to_transfer Project.transaction do project.expire_caches_before_rename(@old_path) @@ -87,6 +87,8 @@ module Projects # Move uploads move_project_uploads(project) + update_integrations + project.old_path_with_namespace = @old_path update_repository_configuration(@new_path) @@ -214,6 +216,11 @@ module Projects project.shared_runners_enabled = false end end + + def update_integrations + project.services.inherit.delete_all + Service.create_from_active_default_integrations(project, :project_id) + end end end diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index b9c579a130f..53872c67f49 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -4,6 +4,9 @@ module Projects class UpdatePagesService < BaseService InvalidStateError = Class.new(StandardError) FailedToExtractError = Class.new(StandardError) + ExclusiveLeaseTaken = Class.new(StandardError) + + include ::Pages::LegacyStorageLease BLOCK_SIZE = 32.kilobytes PUBLIC_DIR = 'public' @@ -109,6 +112,17 @@ module Projects end def deploy_page!(archive_public_path) + deployed = try_obtain_lease do + deploy_page_unsafe!(archive_public_path) + true + end + + unless deployed + raise ExclusiveLeaseTaken, "Failed to deploy pages - other deployment is in progress" + end + end + + def deploy_page_unsafe!(archive_public_path) # Do atomic move of pages # Move and removal may not be atomic, but they are significantly faster then extracting and removal # 1. We move deployed public to previous public path (file removal is slow) @@ -125,8 +139,6 @@ module Projects end def create_pages_deployment(artifacts_path, build) - return unless Feature.enabled?(:zip_pages_deployments, project, default_enabled: true) - # we're using the full archive and pages daemon needs to read it # so we want the total count from entries, not only "public/" directory # because it better approximates work we need to do before we can serve the site diff --git a/app/services/releases/base_service.rb b/app/services/releases/base_service.rb index 38ef80ced56..d0e1577bd8d 100644 --- a/app/services/releases/base_service.rb +++ b/app/services/releases/base_service.rb @@ -11,8 +11,6 @@ module Releases @project, @current_user, @params = project, user, params.dup end - delegate :repository, to: :project - def tag_name params[:tag] end @@ -39,22 +37,18 @@ module Releases end end - def existing_tag - strong_memoize(:existing_tag) do - repository.find_tag(tag_name) - end - end - - def tag_exist? - existing_tag.present? - end - def repository strong_memoize(:repository) do project.repository end end + def existing_tag + strong_memoize(:existing_tag) do + repository.find_tag(tag_name) + end + end + def milestones return [] unless param_for_milestone_titles_provided? @@ -78,7 +72,7 @@ module Releases end def param_for_milestone_titles_provided? - params.key?(:milestones) + !!params[:milestones] end def execute_hooks(release, action = 'create') diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb index deefe559d5d..11fdbaf3169 100644 --- a/app/services/releases/create_service.rb +++ b/app/services/releases/create_service.rb @@ -10,7 +10,7 @@ module Releases # should be found before the creation of new tag # because tag creation can spawn new pipeline # which won't have any data for evidence yet - evidence_pipeline = find_evidence_pipeline + evidence_pipeline = Releases::EvidencePipelineFinder.new(project, params).execute tag = ensure_tag @@ -78,26 +78,10 @@ module Releases ) end - def find_evidence_pipeline - # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245 - return params[:evidence_pipeline] if params[:evidence_pipeline] - - sha = existing_tag&.dereferenced_target&.sha - sha ||= repository.commit(ref)&.sha - - return unless sha - - project.ci_pipelines.for_sha(sha).last - end - def create_evidence!(release, pipeline) - return if release.historical_release? + return if release.historical_release? || release.upcoming_release? - if release.upcoming_release? - CreateEvidenceWorker.perform_at(release.released_at, release.id, pipeline&.id) - else - CreateEvidenceWorker.perform_async(release.id, pipeline&.id) - end + ::Releases::CreateEvidenceWorker.perform_async(release.id, pipeline&.id) end end end diff --git a/app/services/resource_events/change_labels_service.rb b/app/services/resource_events/change_labels_service.rb index dc23f727079..ddf3b05ac10 100644 --- a/app/services/resource_events/change_labels_service.rb +++ b/app/services/resource_events/change_labels_service.rb @@ -24,6 +24,8 @@ module ResourceEvents Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, labels) # rubocop:disable Gitlab/BulkInsert resource.expire_note_etag_cache + + Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_label_changed_action(author: user) if resource.is_a?(Issue) end private diff --git a/app/services/service_desk_settings/update_service.rb b/app/services/service_desk_settings/update_service.rb index c837b75f439..32d1c5c1c87 100644 --- a/app/services/service_desk_settings/update_service.rb +++ b/app/services/service_desk_settings/update_service.rb @@ -5,7 +5,7 @@ module ServiceDeskSettings def execute settings = ServiceDeskSetting.safe_find_or_create_by!(project_id: project.id) - unless ::Feature.enabled?(:service_desk_custom_address, project) + unless ::Feature.enabled?(:service_desk_custom_address, project, default_enabled: true) params.delete(:project_key) end diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb index 2fbeaf4405c..8ab1193b04f 100644 --- a/app/services/submit_usage_ping_service.rb +++ b/app/services/submit_usage_ping_service.rb @@ -43,8 +43,6 @@ class SubmitUsagePingService private def save_raw_usage_data(usage_data) - return unless Feature.enabled?(:save_raw_usage_data) - RawUsageData.safe_find_or_create_by(recorded_at: usage_data[:recorded_at]) do |record| record.payload = usage_data end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 0d369c23b57..881a139437a 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class SystemHooksService + BUILDER_DRIVEN_EVENT_DATA_AVAILABLE_FOR_CLASSES = [GroupMember].freeze + def execute_hooks_for(model, event) data = build_event_data(model, event) @@ -20,6 +22,9 @@ class SystemHooksService private def build_event_data(model, event) + # return entire event data from its builder class, if available. + return builder_driven_event_data(model, event) if builder_driven_event_data_available?(model) + data = { event_name: build_event_name(model, event), created_at: model.created_at&.xmlschema, @@ -62,8 +67,6 @@ class SystemHooksService old_full_path: model.full_path_before_last_save ) end - when GroupMember - data.merge!(group_member_data(model)) end data @@ -75,10 +78,6 @@ class SystemHooksService return "user_add_to_team" if event == :create return "user_remove_from_team" if event == :destroy return "user_update_for_team" if event == :update - when GroupMember - return 'user_add_to_group' if event == :create - return 'user_remove_from_group' if event == :destroy - return 'user_update_for_group' if event == :update else "#{model.class.name.downcase}_#{event}" end @@ -128,19 +127,6 @@ class SystemHooksService } end - def group_member_data(model) - { - group_name: model.group.name, - group_path: model.group.path, - group_id: model.group.id, - user_username: model.user.username, - user_name: model.user.name, - user_email: model.user.email, - user_id: model.user.id, - group_access: model.human_access - } - end - def user_data(model) { name: model.name, @@ -149,6 +135,17 @@ class SystemHooksService username: model.username } end + + def builder_driven_event_data_available?(model) + model.class.in?(BUILDER_DRIVEN_EVENT_DATA_AVAILABLE_FOR_CLASSES) + end + + def builder_driven_event_data(model, event) + case model + when GroupMember + Gitlab::HookData::GroupMemberBuilder.new(model).build(event) + end + end end SystemHooksService.prepend_if_ee('EE::SystemHooksService') diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index eacc88f98a3..58f72e9badc 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -226,6 +226,10 @@ module SystemNoteService ::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).noteable_moved(noteable_ref, direction) end + def noteable_cloned(noteable, project, noteable_ref, author, direction:) + ::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).noteable_cloned(noteable_ref, direction) + end + def mark_duplicate_issue(noteable, project, author, canonical_issue) ::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).mark_duplicate_issue(canonical_issue) end diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb index 7a73af0a81a..b344b240a07 100644 --- a/app/services/system_notes/issuables_service.rb +++ b/app/services/system_notes/issuables_service.rb @@ -242,6 +242,29 @@ module SystemNotes create_note(NoteSummary.new(noteable, project, author, body, action: 'moved')) end + # Called when noteable has been cloned + # + # noteable_ref - Referenced noteable + # direction - symbol, :to or :from + # + # Example Note text: + # + # "cloned to some_namespace/project_new#11" + # + # Returns the created Note object + def noteable_cloned(noteable_ref, direction) + unless [:to, :from].include?(direction) + raise ArgumentError, "Invalid direction `#{direction}`" + end + + cross_reference = noteable_ref.to_reference(project) + body = "cloned #{direction} #{cross_reference}" + + issue_activity_counter.track_issue_cloned_action(author: author) if noteable.is_a?(Issue) && direction == :to + + create_note(NoteSummary.new(noteable, project, author, body, action: 'cloned')) + end + # Called when the confidentiality changes # # Example Note text: diff --git a/app/services/upload_service.rb b/app/services/upload_service.rb index 403944557a2..ba6ead41836 100644 --- a/app/services/upload_service.rb +++ b/app/services/upload_service.rb @@ -6,16 +6,18 @@ class UploadService end def execute - return unless @file && @file.size <= max_attachment_size + return unless file && file.size <= max_attachment_size - uploader = @uploader_class.new(@model, nil, @uploader_context) - uploader.store!(@file) + uploader = uploader_class.new(model, nil, **uploader_context) + uploader.store!(file) uploader end private + attr_reader :model, :file, :uploader_class, :uploader_context + def max_attachment_size Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i end diff --git a/app/services/users/approve_service.rb b/app/services/users/approve_service.rb index 27668e9430e..debd1e8cd17 100644 --- a/app/services/users/approve_service.rb +++ b/app/services/users/approve_service.rb @@ -7,8 +7,9 @@ module Users end def execute(user) - return error(_('You are not allowed to approve a user')) unless allowed? - return error(_('The user you are trying to approve is not pending an approval')) unless approval_required?(user) + return error(_('You are not allowed to approve a user'), :forbidden) unless allowed? + return error(_('The user you are trying to approve is not pending an approval'), :conflict) if user.active? + return error(_('The user you are trying to approve is not pending an approval'), :conflict) unless approval_required?(user) if user.activate # Resends confirmation email if the user isn't confirmed yet. @@ -18,9 +19,9 @@ module Users DeviseMailer.user_admin_approval(user).deliver_later after_approve_hook(user) - success + success(message: 'Success', http_status: :created) else - error(user.errors.full_messages.uniq.join('. ')) + error(user.errors.full_messages.uniq.join('. '), :unprocessable_entity) end end diff --git a/app/services/users/reject_service.rb b/app/services/users/reject_service.rb new file mode 100644 index 00000000000..dd72547c688 --- /dev/null +++ b/app/services/users/reject_service.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Users + class RejectService < BaseService + def initialize(current_user) + @current_user = current_user + end + + def execute(user) + return error(_('You are not allowed to reject a user')) unless allowed? + return error(_('This user does not have a pending request')) unless user.blocked_pending_approval? + + user.delete_async(deleted_by: current_user, params: { hard_delete: true }) + + NotificationService.new.user_admin_rejection(user.name, user.email) + + success + end + + private + + attr_reader :current_user + + def allowed? + can?(current_user, :reject_user) + end + end +end diff --git a/app/services/users/set_status_service.rb b/app/services/users/set_status_service.rb index 356c8782af1..a907937070f 100644 --- a/app/services/users/set_status_service.rb +++ b/app/services/users/set_status_service.rb @@ -14,10 +14,10 @@ module Users def execute return false unless can?(current_user, :update_user_status, target_user) - if params[:emoji].present? || params[:message].present? || params[:availability].present? - set_status - else + if status_cleared? remove_status + else + set_status end end @@ -25,8 +25,7 @@ module Users def set_status params[:emoji] = UserStatus::DEFAULT_EMOJI if params[:emoji].blank? - params.delete(:availability) if params[:availability].blank? - return false if params[:availability].present? && UserStatus.availabilities.keys.exclude?(params[:availability]) + params[:availability] = UserStatus.availabilities[:not_set] unless new_user_availability user_status.update(params) end @@ -38,5 +37,15 @@ module Users def user_status target_user.status || target_user.build_status end + + def status_cleared? + params[:emoji].blank? && + params[:message].blank? && + (new_user_availability.blank? || new_user_availability == UserStatus.availabilities[:not_set]) + end + + def new_user_availability + UserStatus.availabilities[params[:availability]] + end end end diff --git a/app/services/users/validate_otp_service.rb b/app/services/users/validate_otp_service.rb index a9ce7959aea..c8a9f217d22 100644 --- a/app/services/users/validate_otp_service.rb +++ b/app/services/users/validate_otp_service.rb @@ -2,10 +2,14 @@ module Users class ValidateOtpService < BaseService + include ::Gitlab::Auth::Otp::Fortinet + def initialize(current_user) @current_user = current_user - @strategy = if Feature.enabled?(:forti_authenticator, current_user) + @strategy = if forti_authenticator_enabled?(current_user) ::Gitlab::Auth::Otp::Strategies::FortiAuthenticator.new(current_user) + elsif forti_token_cloud_enabled?(current_user) + ::Gitlab::Auth::Otp::Strategies::FortiTokenCloud.new(current_user) else ::Gitlab::Auth::Otp::Strategies::Devise.new(current_user) end -- cgit v1.2.3