diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-01-18 19:00:14 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-01-18 19:00:14 +0000 |
commit | 05f0ebba3a2c8ddf39e436f412dc2ab5bf1353b2 (patch) | |
tree | 11d0f2a6ec31c7793c184106cedc2ded3d9a2cc5 /app/services | |
parent | ec73467c23693d0db63a797d10194da9e72a74af (diff) | |
download | gitlab-ce-05f0ebba3a2c8ddf39e436f412dc2ab5bf1353b2.tar.gz |
Add latest changes from gitlab-org/gitlab@15-8-stable-eev15.8.0-rc42
Diffstat (limited to 'app/services')
78 files changed, 565 insertions, 934 deletions
diff --git a/app/services/achievements/base_service.rb b/app/services/achievements/base_service.rb new file mode 100644 index 00000000000..0a8e6ee3c78 --- /dev/null +++ b/app/services/achievements/base_service.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Achievements + class BaseService < ::BaseContainerService + def initialize(namespace:, current_user: nil, params: {}) + @namespace = namespace + super(container: namespace, current_user: current_user, params: params) + end + + private + + def allowed? + current_user&.can?(:admin_achievement, @namespace) + end + + def error(message) + ServiceResponse.error(message: Array(message)) + end + end +end diff --git a/app/services/achievements/create_service.rb b/app/services/achievements/create_service.rb new file mode 100644 index 00000000000..2843df6c191 --- /dev/null +++ b/app/services/achievements/create_service.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Achievements + class CreateService < BaseService + def execute + return error_no_permissions unless allowed? + + achievement = Achievements::Achievement.create(params.merge(namespace_id: @namespace.id)) + + return error_creating(achievement) unless achievement.persisted? + + ServiceResponse.success(payload: achievement) + end + + private + + def error_no_permissions + error('You have insufficient permissions to create achievements for this namespace') + end + + def error_creating(achievement) + error(achievement&.errors&.full_messages || 'Failed to create achievement') + end + end +end diff --git a/app/services/boards/base_items_list_service.rb b/app/services/boards/base_items_list_service.rb index 2a9cbb83cc4..bf68aee2c1f 100644 --- a/app/services/boards/base_items_list_service.rb +++ b/app/services/boards/base_items_list_service.rb @@ -18,7 +18,6 @@ module Boards # TODO: eliminate need for SQL literal fragment columns = Arel.sql(fields.values_at(*keys).join(', ')) results = item_model.where(id: collection_ids) - results = query_additions(results, required_fields) results = results.select(columns) Hash[keys.zip(results.pluck(columns).flatten)] @@ -27,11 +26,6 @@ module Boards private - # override if needed - def query_additions(items, required_fields) - items - end - def collection_ids @collection_ids ||= init_collection.select(item_model.arel_table[:id]) end diff --git a/app/services/bulk_imports/create_service.rb b/app/services/bulk_imports/create_service.rb index 124b5964232..35a35e7b7c9 100644 --- a/app/services/bulk_imports/create_service.rb +++ b/app/services/bulk_imports/create_service.rb @@ -36,6 +36,8 @@ module BulkImports end def execute + validate! + bulk_import = create_bulk_import Gitlab::Tracking.event(self.class.name, 'create', label: 'bulk_import_group') @@ -43,7 +45,8 @@ module BulkImports BulkImportWorker.perform_async(bulk_import.id) ServiceResponse.success(payload: bulk_import) - rescue ActiveRecord::RecordInvalid => e + + rescue ActiveRecord::RecordInvalid, BulkImports::Error, BulkImports::NetworkError => e ServiceResponse.error( message: e.message, http_status: :unprocessable_entity @@ -52,6 +55,11 @@ module BulkImports private + def validate! + client.validate_instance_version! + client.validate_import_scopes! + end + def create_bulk_import BulkImport.transaction do bulk_import = BulkImport.create!( @@ -70,7 +78,8 @@ module BulkImports source_type: entity[:source_type], source_full_path: entity[:source_full_path], destination_slug: entity[:destination_slug], - destination_namespace: entity[:destination_namespace] + destination_namespace: entity[:destination_namespace], + migrate_projects: Gitlab::Utils.to_boolean(entity[:migrate_projects], default: true) ) end diff --git a/app/services/captcha/captcha_verification_service.rb b/app/services/captcha/captcha_verification_service.rb index 3ed8ea12f3a..b7b699f7108 100644 --- a/app/services/captcha/captcha_verification_service.rb +++ b/app/services/captcha/captcha_verification_service.rb @@ -5,7 +5,7 @@ module Captcha # Encapsulates logic of checking captchas. # class CaptchaVerificationService - include Recaptcha::Verify + include Recaptcha::Adapters::ControllerMethods # Currently the only value that is used out of the request by the reCAPTCHA library # is 'remote_ip'. Therefore, we just create a struct to avoid passing the full request @@ -45,7 +45,7 @@ module Captcha attr_reader :spam_params - # The recaptcha library's Recaptcha::Verify#verify_recaptcha method requires that + # The recaptcha library's Recaptcha::Adapters::ControllerMethods#verify_recaptcha method requires that # 'request' be a readable attribute - it doesn't support passing it as an options argument. attr_reader :request end diff --git a/app/services/chat_names/authorize_user_service.rb b/app/services/chat_names/authorize_user_service.rb index 6c28a1cea7e..32714831fb8 100644 --- a/app/services/chat_names/authorize_user_service.rb +++ b/app/services/chat_names/authorize_user_service.rb @@ -4,8 +4,7 @@ module ChatNames class AuthorizeUserService include Gitlab::Routing - def initialize(integration, params) - @integration = integration + def initialize(params) @params = params end @@ -29,7 +28,6 @@ module ChatNames def chat_name_params { - integration_id: @integration.id, team_id: @params[:team_id], team_domain: @params[:team_domain], chat_id: @params[:user_id], diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 9c3cc803587..eb25aeaf5a5 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -157,6 +157,13 @@ module Ci duration >= LOG_MAX_CREATION_THRESHOLD end + + l.log_when do |observations| + pipeline_includes_count = observations['pipeline_includes_count'] + next false unless pipeline_includes_count + + pipeline_includes_count.to_i > Gitlab::Ci::Config::External::Context::MAX_INCLUDES + end end end end diff --git a/app/services/ci/job_artifacts/create_service.rb b/app/services/ci/job_artifacts/create_service.rb index ee9982cf3ab..6e2ba76682f 100644 --- a/app/services/ci/job_artifacts/create_service.rb +++ b/app/services/ci/job_artifacts/create_service.rb @@ -92,7 +92,8 @@ module Ci file: artifacts_file, file_type: params[:artifact_type], file_format: params[:artifact_format], - file_sha256: artifacts_file.sha256 + file_sha256: artifacts_file.sha256, + accessibility: accessibility(params) ) ) @@ -102,7 +103,8 @@ module Ci file: metadata_file, file_type: :metadata, file_format: :gzip, - file_sha256: metadata_file.sha256 + file_sha256: metadata_file.sha256, + accessibility: accessibility(params) ) ) end @@ -110,6 +112,10 @@ module Ci [artifact, artifact_metadata] end + def accessibility(params) + params[:accessibility] || 'public' + end + def parse_artifact(artifact) case artifact.file_type when 'dotenv' then parse_dotenv_artifact(artifact) diff --git a/app/services/ci/job_artifacts/delete_service.rb b/app/services/ci/job_artifacts/delete_service.rb index c9d590eccc4..fc5c6b12389 100644 --- a/app/services/ci/job_artifacts/delete_service.rb +++ b/app/services/ci/job_artifacts/delete_service.rb @@ -26,8 +26,7 @@ module Ci if result.fetch(:status) == :success ServiceResponse.success(payload: { - destroyed_artifacts_count: result.fetch(:destroyed_artifacts_count), - statistics_updates: result.fetch(:statistics_updates) + destroyed_artifacts_count: result.fetch(:destroyed_artifacts_count) }) else ServiceResponse.error(message: result.fetch(:message)) diff --git a/app/services/ci/job_artifacts/destroy_associations_service.rb b/app/services/ci/job_artifacts/destroy_associations_service.rb index 794d24eadf2..fd3e69a5913 100644 --- a/app/services/ci/job_artifacts/destroy_associations_service.rb +++ b/app/services/ci/job_artifacts/destroy_associations_service.rb @@ -2,27 +2,32 @@ module Ci module JobArtifacts + # This class is used by Ci::JobArtifact's FastDestroyAll implementation. + # Ci::JobArtifact.begin_fast_destroy instantiates this service and calls #destroy_records. + # This will set @statistics_updates instance variables. + # The same instance is passed to Ci::JobArtifact.finalize_fast_destroy, which then calls + # #update_statistics, using @statistics_updates set by #destroy_records. class DestroyAssociationsService BATCH_SIZE = 100 def initialize(job_artifacts_relation) @job_artifacts_relation = job_artifacts_relation - @statistics = {} + @statistics_updates = {} end def destroy_records @job_artifacts_relation.each_batch(of: BATCH_SIZE) do |relation| service = Ci::JobArtifacts::DestroyBatchService.new(relation, pick_up_at: Time.current) result = service.execute(update_stats: false) - updates = result[:statistics_updates] - - @statistics.merge!(updates) { |_key, oldval, newval| newval + oldval } + @statistics_updates.merge!(result[:statistics_updates]) do |_project, existing_updates, new_updates| + existing_updates.concat(new_updates) + end end end def update_statistics - @statistics.each do |project, delta| - project.increment_statistic_value(Ci::JobArtifact.project_statistics_name, delta) + @statistics_updates.each do |project, increments| + ProjectStatistics.bulk_increment_statistic(project, Ci::JobArtifact.project_statistics_name, increments) end end end diff --git a/app/services/ci/job_artifacts/destroy_batch_service.rb b/app/services/ci/job_artifacts/destroy_batch_service.rb index e0307d9bd53..7cb1be95a3e 100644 --- a/app/services/ci/job_artifacts/destroy_batch_service.rb +++ b/app/services/ci/job_artifacts/destroy_batch_service.rb @@ -46,14 +46,13 @@ module Ci after_batch_destroy_hook(@job_artifacts) - # This is executed outside of the transaction because it depends on Redis update_project_statistics! if update_stats + increment_monitoring_statistics(artifacts_count, artifacts_bytes) Gitlab::Ci::Artifacts::Logger.log_deleted(@job_artifacts, 'Ci::JobArtifacts::DestroyBatchService#execute') - success(destroyed_artifacts_count: artifacts_count, - statistics_updates: affected_project_statistics) + success(destroyed_artifacts_count: artifacts_count, statistics_updates: statistics_updates_per_project) end # rubocop: enable CodeReuse/ActiveRecord @@ -74,17 +73,18 @@ module Ci # using ! here since this can't be called inside a transaction def update_project_statistics! - affected_project_statistics.each do |project, delta| - project.increment_statistic_value(Ci::JobArtifact.project_statistics_name, delta) + statistics_updates_per_project.each do |project, increments| + ProjectStatistics.bulk_increment_statistic(project, Ci::JobArtifact.project_statistics_name, increments) end end - def affected_project_statistics - strong_memoize(:affected_project_statistics) do - artifacts_by_project = @job_artifacts.group_by(&:project) - artifacts_by_project.each.with_object({}) do |(project, artifacts), accumulator| - delta = -artifacts.sum { |artifact| artifact.size.to_i } - accumulator[project] = delta + def statistics_updates_per_project + strong_memoize(:statistics_updates_per_project) do + result = Hash.new { |updates, project| updates[project] = [] } + + @job_artifacts.each_with_object(result) do |job_artifact, result| + increment = Gitlab::Counters::Increment.new(amount: -job_artifact.size.to_i, ref: job_artifact.id) + result[job_artifact.project] << increment end end end diff --git a/app/services/clusters/aws/authorize_role_service.rb b/app/services/clusters/aws/authorize_role_service.rb deleted file mode 100644 index 7ca20289bf7..00000000000 --- a/app/services/clusters/aws/authorize_role_service.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Aws - class AuthorizeRoleService - attr_reader :user - - Response = Struct.new(:status, :body) - - ERRORS = [ - ActiveRecord::RecordInvalid, - ActiveRecord::RecordNotFound, - Clusters::Aws::FetchCredentialsService::MissingRoleError, - ::Aws::Errors::MissingCredentialsError, - ::Aws::STS::Errors::ServiceError - ].freeze - - def initialize(user, params:) - @user = user - @role_arn = params[:role_arn] - @region = params[:region] - end - - def execute - ensure_role_exists! - update_role_arn! - - Response.new(:ok, credentials) - rescue *ERRORS => e - Gitlab::ErrorTracking.track_exception(e) - - Response.new(:unprocessable_entity, response_details(e)) - end - - private - - attr_reader :role, :role_arn, :region - - def ensure_role_exists! - @role = ::Aws::Role.find_by_user_id!(user.id) - end - - def update_role_arn! - role.update!(role_arn: role_arn, region: region) - end - - 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 deleted file mode 100644 index e38852c7ec7..00000000000 --- a/app/services/clusters/aws/fetch_credentials_service.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Aws - class FetchCredentialsService - attr_reader :provision_role - - MissingRoleError = Class.new(StandardError) - - def initialize(provision_role, provider: nil) - @provision_role = provision_role - @provider = provider - @region = provider&.region || provision_role&.region || Clusters::Providers::Aws::DEFAULT_REGION - end - - def execute - raise MissingRoleError, 'AWS provisioning role not configured' unless provision_role.present? - - ::Aws::AssumeRoleCredentials.new( - client: client, - role_arn: provision_role.role_arn, - role_session_name: session_name, - external_id: provision_role.role_external_id, - policy: session_policy - ).credentials - end - - private - - attr_reader :provider, :region - - def client - ::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 - - def access_key_id - Gitlab::CurrentSettings.eks_access_key_id - end - - def secret_access_key - Gitlab::CurrentSettings.eks_secret_access_key - end - - ## - # If we haven't created a provider record yet, - # we restrict ourselves to read-only access so - # that we can safely expose credentials to the - # frontend (to be used when populating the - # creation form). - def session_policy - if provider.nil? - File.read(read_only_policy) - end - end - - def read_only_policy - Rails.root.join('vendor', 'aws', 'iam', "eks_cluster_read_only_policy.json") - end - - def session_name - if provider.present? - "gitlab-eks-cluster-#{provider.cluster_id}-user-#{provision_role.user_id}" - else - "gitlab-eks-autofill-user-#{provision_role.user_id}" - end - end - end - end -end diff --git a/app/services/clusters/aws/finalize_creation_service.rb b/app/services/clusters/aws/finalize_creation_service.rb deleted file mode 100644 index 54f07e1d44c..00000000000 --- a/app/services/clusters/aws/finalize_creation_service.rb +++ /dev/null @@ -1,139 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Aws - class FinalizeCreationService - include Gitlab::Utils::StrongMemoize - - attr_reader :provider - - delegate :cluster, to: :provider - - def execute(provider) - @provider = provider - - configure_provider - create_gitlab_service_account! - configure_platform_kubernetes - configure_node_authentication! - - cluster.save! - rescue ::Aws::CloudFormation::Errors::ServiceError => e - log_service_error(e.class.name, provider.id, e.message) - provider.make_errored!(s_('ClusterIntegration|Failed to fetch CloudFormation stack: %{message}') % { message: e.message }) - rescue Kubeclient::HttpError => e - log_service_error(e.class.name, provider.id, e.message) - provider.make_errored!(s_('ClusterIntegration|Failed to run Kubeclient: %{message}') % { message: e.message }) - rescue ActiveRecord::RecordInvalid => e - log_service_error(e.class.name, provider.id, e.message) - provider.make_errored!(s_('ClusterIntegration|Failed to configure EKS provider: %{message}') % { message: e.message }) - end - - private - - def create_gitlab_service_account! - Clusters::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator( - kube_client, - rbac: true - ).execute - end - - def configure_provider - provider.status_event = :make_created - end - - def configure_platform_kubernetes - cluster.build_platform_kubernetes( - api_url: cluster_endpoint, - ca_cert: cluster_certificate, - token: request_kubernetes_token) - end - - def request_kubernetes_token - Clusters::Kubernetes::FetchKubernetesTokenService.new( - kube_client, - Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, - Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE - ).execute - end - - def kube_client - @kube_client ||= build_kube_client!( - cluster_endpoint, - cluster_certificate - ) - end - - def build_kube_client!(api_url, ca_pem) - raise "Incomplete settings" unless api_url - - Gitlab::Kubernetes::KubeClient.new( - api_url, - auth_options: kubeclient_auth_options, - ssl_options: kubeclient_ssl_options(ca_pem), - http_proxy_uri: ENV['http_proxy'] - ) - end - - def kubeclient_auth_options - { bearer_token: Kubeclient::AmazonEksCredentials.token(provider.credentials, cluster.name) } - end - - def kubeclient_ssl_options(ca_pem) - opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER } - - if ca_pem.present? - opts[:cert_store] = OpenSSL::X509::Store.new - opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem)) - end - - opts - end - - def cluster_stack - @cluster_stack ||= provider.api_client.describe_stacks(stack_name: provider.cluster.name).stacks.first - end - - def stack_output_value(key) - cluster_stack.outputs.detect { |output| output.output_key == key }.output_value - end - - def node_instance_role_arn - stack_output_value('NodeInstanceRole') - end - - def cluster_endpoint - strong_memoize(:cluster_endpoint) do - stack_output_value('ClusterEndpoint') - end - end - - def cluster_certificate - strong_memoize(:cluster_certificate) do - Base64.decode64(stack_output_value('ClusterCertificate')) - end - end - - def configure_node_authentication! - kube_client.create_config_map(node_authentication_config) - end - - def node_authentication_config - Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth.new(node_instance_role_arn).generate - end - - def logger - @logger ||= Gitlab::Kubernetes::Logger.build - end - - def log_service_error(exception, provider_id, message) - logger.error( - exception: exception.class.name, - service: self.class.name, - provider_id: provider_id, - message: message - ) - end - end - end -end diff --git a/app/services/clusters/aws/provision_service.rb b/app/services/clusters/aws/provision_service.rb deleted file mode 100644 index b454a7a5f59..00000000000 --- a/app/services/clusters/aws/provision_service.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Aws - class ProvisionService - attr_reader :provider - - def execute(provider) - @provider = provider - - configure_provider_credentials - provision_cluster - - if provider.make_creating - WaitForClusterCreationWorker.perform_in( - Clusters::Aws::VerifyProvisionStatusService::INITIAL_INTERVAL, - provider.cluster_id - ) - else - provider.make_errored!("Failed to update provider record; #{provider.errors.full_messages}") - end - rescue Clusters::Aws::FetchCredentialsService::MissingRoleError - provider.make_errored!('Amazon role is not configured') - rescue ::Aws::Errors::MissingCredentialsError - provider.make_errored!('Amazon credentials are not configured') - rescue ::Aws::STS::Errors::ServiceError => e - provider.make_errored!("Amazon authentication failed; #{e.message}") - rescue ::Aws::CloudFormation::Errors::ServiceError => e - provider.make_errored!("Amazon CloudFormation request failed; #{e.message}") - end - - private - - def provision_role - provider.created_by_user&.aws_role - end - - def credentials - @credentials ||= Clusters::Aws::FetchCredentialsService.new( - provision_role, - provider: provider - ).execute - end - - def configure_provider_credentials - provider.update!( - access_key_id: credentials.access_key_id, - secret_access_key: credentials.secret_access_key, - session_token: credentials.session_token - ) - end - - def provision_cluster - provider.api_client.create_stack( - stack_name: provider.cluster.name, - template_body: stack_template, - parameters: parameters, - capabilities: ["CAPABILITY_IAM"] - ) - end - - def parameters - [ - parameter('ClusterName', provider.cluster.name), - parameter('ClusterRole', provider.role_arn), - parameter('KubernetesVersion', provider.kubernetes_version), - parameter('ClusterControlPlaneSecurityGroup', provider.security_group_id), - parameter('VpcId', provider.vpc_id), - parameter('Subnets', provider.subnet_ids.join(',')), - parameter('NodeAutoScalingGroupDesiredCapacity', provider.num_nodes.to_s), - parameter('NodeInstanceType', provider.instance_type), - parameter('KeyName', provider.key_name) - ] - end - - def parameter(key, value) - { parameter_key: key, parameter_value: value } - end - - def stack_template - File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml')) - end - end - end -end diff --git a/app/services/clusters/aws/verify_provision_status_service.rb b/app/services/clusters/aws/verify_provision_status_service.rb deleted file mode 100644 index 99532662bc4..00000000000 --- a/app/services/clusters/aws/verify_provision_status_service.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Aws - class VerifyProvisionStatusService - attr_reader :provider - - INITIAL_INTERVAL = 5.minutes - POLL_INTERVAL = 1.minute - TIMEOUT = 30.minutes - - def execute(provider) - @provider = provider - - case cluster_stack.stack_status - when 'CREATE_IN_PROGRESS' - continue_creation - when 'CREATE_COMPLETE' - finalize_creation - else - provider.make_errored!("Unexpected status; #{cluster_stack.stack_status}") - end - rescue ::Aws::CloudFormation::Errors::ServiceError => e - provider.make_errored!("Amazon CloudFormation request failed; #{e.message}") - end - - private - - def cluster_stack - @cluster_stack ||= provider.api_client.describe_stacks(stack_name: provider.cluster.name).stacks.first - end - - def continue_creation - if timeout_threshold.future? - WaitForClusterCreationWorker.perform_in(POLL_INTERVAL, provider.cluster_id) - else - provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT }) - end - end - - def timeout_threshold - cluster_stack.creation_time + TIMEOUT - end - - def finalize_creation - Clusters::Aws::FinalizeCreationService.new.execute(provider) - end - end - end -end diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index cb2de8b943c..4c7384806ad 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -24,9 +24,7 @@ module Clusters return cluster if cluster.errors.present? - cluster.tap do |cluster| - cluster.save && ClusterProvisionWorker.perform_async(cluster.id) - end + cluster.tap(&:save) end private diff --git a/app/services/clusters/gcp/fetch_operation_service.rb b/app/services/clusters/gcp/fetch_operation_service.rb deleted file mode 100644 index 6c648b443a0..00000000000 --- a/app/services/clusters/gcp/fetch_operation_service.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - class FetchOperationService - def execute(provider) - operation = provider.api_client.projects_zones_operations( - provider.gcp_project_id, - provider.zone, - provider.operation_id) - - yield(operation) if block_given? - rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e - logger.error( - exception: e.class.name, - service: self.class.name, - provider_id: provider.id, - message: e.message - ) - - provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") - end - - private - - def logger - @logger ||= Gitlab::Kubernetes::Logger.build - end - end - end -end diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb deleted file mode 100644 index 73d6fc4dc8f..00000000000 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - class FinalizeCreationService - attr_reader :provider - - def execute(provider) - @provider = provider - - configure_provider - create_gitlab_service_account! - configure_kubernetes - configure_pre_installed_knative if provider.knative_pre_installed? - cluster.save! - rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e - log_service_error(e.class.name, provider.id, e.message) - provider.make_errored!(s_('ClusterIntegration|Failed to request to Google Cloud Platform: %{message}') % { message: e.message }) - rescue Kubeclient::HttpError => e - log_service_error(e.class.name, provider.id, e.message) - provider.make_errored!(s_('ClusterIntegration|Failed to run Kubeclient: %{message}') % { message: e.message }) - rescue ActiveRecord::RecordInvalid => e - log_service_error(e.class.name, provider.id, e.message) - provider.make_errored!(s_('ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}') % { message: e.message }) - end - - private - - def create_gitlab_service_account! - Clusters::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator( - kube_client, - rbac: create_rbac_cluster? - ).execute - end - - def configure_provider - provider.endpoint = gke_cluster.endpoint - provider.status_event = :make_created - end - - def configure_kubernetes - cluster.platform_type = :kubernetes - cluster.build_platform_kubernetes( - api_url: 'https://' + gke_cluster.endpoint, - ca_cert: Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate), - authorization_type: authorization_type, - token: request_kubernetes_token) - end - - def configure_pre_installed_knative - knative = cluster.build_application_knative( - hostname: 'example.com' - ) - knative.make_pre_installed! - end - - def request_kubernetes_token - Clusters::Kubernetes::FetchKubernetesTokenService.new( - kube_client, - Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, - Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE - ).execute - end - - def authorization_type - create_rbac_cluster? ? 'rbac' : 'abac' - end - - def create_rbac_cluster? - !provider.legacy_abac? - end - - def kube_client - @kube_client ||= build_kube_client!( - 'https://' + gke_cluster.endpoint, - Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate) - ) - end - - def build_kube_client!(api_url, ca_pem) - raise "Incomplete settings" unless api_url - - Gitlab::Kubernetes::KubeClient.new( - api_url, - auth_options: { bearer_token: provider.access_token }, - ssl_options: kubeclient_ssl_options(ca_pem), - http_proxy_uri: ENV['http_proxy'] - ) - end - - def kubeclient_ssl_options(ca_pem) - opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER } - - if ca_pem.present? - opts[:cert_store] = OpenSSL::X509::Store.new - opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem)) - end - - opts - end - - def gke_cluster - @gke_cluster ||= provider.api_client.projects_zones_clusters_get( - provider.gcp_project_id, - provider.zone, - cluster.name) - end - - def cluster - @cluster ||= provider.cluster - end - - def logger - @logger ||= Gitlab::Kubernetes::Logger.build - end - - def log_service_error(exception, provider_id, message) - logger.error( - exception: exception.class.name, - service: self.class.name, - provider_id: provider_id, - message: message - ) - end - end - end -end diff --git a/app/services/clusters/gcp/provision_service.rb b/app/services/clusters/gcp/provision_service.rb deleted file mode 100644 index 7dc2d3c32f1..00000000000 --- a/app/services/clusters/gcp/provision_service.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - class ProvisionService - CLOUD_RUN_ADDONS = %i[http_load_balancing istio_config cloud_run_config].freeze - - attr_reader :provider - - def execute(provider) - @provider = provider - - get_operation_id do |operation_id| - if provider.make_creating(operation_id) - WaitForClusterCreationWorker.perform_in( - Clusters::Gcp::VerifyProvisionStatusService::INITIAL_INTERVAL, - provider.cluster_id) - else - provider.make_errored!("Failed to update provider record; #{provider.errors}") - end - end - end - - private - - def get_operation_id - enable_addons = provider.cloud_run? ? CLOUD_RUN_ADDONS : [] - - operation = provider.api_client.projects_zones_clusters_create( - provider.gcp_project_id, - provider.zone, - provider.cluster.name, - provider.num_nodes, - machine_type: provider.machine_type, - legacy_abac: provider.legacy_abac, - enable_addons: enable_addons - ) - - unless operation.status == 'PENDING' || operation.status == 'RUNNING' - return provider.make_errored!("Operation status is unexpected; #{operation.status_message}") - end - - operation_id = provider.api_client.parse_operation_id(operation.self_link) - - unless operation_id - return provider.make_errored!('Can not find operation_id from self_link') - end - - yield(operation_id) - - rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e - provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") - end - end - end -end diff --git a/app/services/clusters/gcp/verify_provision_status_service.rb b/app/services/clusters/gcp/verify_provision_status_service.rb deleted file mode 100644 index ddb2832aae6..00000000000 --- a/app/services/clusters/gcp/verify_provision_status_service.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - class VerifyProvisionStatusService - attr_reader :provider - - INITIAL_INTERVAL = 2.minutes - EAGER_INTERVAL = 10.seconds - TIMEOUT = 20.minutes - - def execute(provider) - @provider = provider - - request_operation do |operation| - case operation.status - when 'PENDING', 'RUNNING' - continue_creation(operation) - when 'DONE' - finalize_creation - else - provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}") - end - end - end - - private - - def continue_creation(operation) - if elapsed_time_from_creation(operation) < TIMEOUT - WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id) - else - provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT }) - end - end - - def elapsed_time_from_creation(operation) - Time.current.utc - operation.start_time.to_time.utc - end - - def finalize_creation - Clusters::Gcp::FinalizeCreationService.new.execute(provider) - end - - def request_operation(&blk) - Clusters::Gcp::FetchOperationService.new.execute(provider, &blk) - end - end - end -end diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb index ae1e1d1e66c..b3427697052 100644 --- a/app/services/concerns/integrations/project_test_data.rb +++ b/app/services/concerns/integrations/project_test_data.rb @@ -2,8 +2,14 @@ module Integrations module ProjectTestData + NoDataError = Class.new(ArgumentError) + private + def no_data_error(msg) + raise NoDataError, msg + end + def push_events_data Gitlab::DataBuilder::Push.build_sample(project, current_user) end @@ -11,7 +17,7 @@ module Integrations def note_events_data note = NotesFinder.new(current_user, project: project, target: project, sort: 'id_desc').execute.first - return { error: s_('TestHooks|Ensure the project has notes.') } unless note.present? + no_data_error(s_('TestHooks|Ensure the project has notes.')) unless note.present? Gitlab::DataBuilder::Note.build(note, current_user) end @@ -19,7 +25,7 @@ module Integrations def issues_events_data issue = IssuesFinder.new(current_user, project_id: project.id, sort: 'created_desc').execute.first - return { error: s_('TestHooks|Ensure the project has issues.') } unless issue.present? + no_data_error(s_('TestHooks|Ensure the project has issues.')) unless issue.present? issue.to_hook_data(current_user) end @@ -27,7 +33,7 @@ module Integrations def merge_requests_events_data merge_request = MergeRequestsFinder.new(current_user, project_id: project.id, sort: 'created_desc').execute.first - return { error: s_('TestHooks|Ensure the project has merge requests.') } unless merge_request.present? + no_data_error(s_('TestHooks|Ensure the project has merge requests.')) unless merge_request.present? merge_request.to_hook_data(current_user) end @@ -35,7 +41,7 @@ module Integrations def job_events_data build = Ci::JobsFinder.new(current_user: current_user, project: project).execute.first - return { error: s_('TestHooks|Ensure the project has CI jobs.') } unless build.present? + no_data_error(s_('TestHooks|Ensure the project has CI jobs.')) unless build.present? Gitlab::DataBuilder::Build.build(build) end @@ -43,7 +49,7 @@ module Integrations def pipeline_events_data pipeline = Ci::PipelinesFinder.new(project, current_user, order_by: 'id', sort: 'desc').execute.first - return { error: s_('TestHooks|Ensure the project has CI pipelines.') } unless pipeline.present? + no_data_error(s_('TestHooks|Ensure the project has CI pipelines.')) unless pipeline.present? Gitlab::DataBuilder::Pipeline.build(pipeline) end @@ -51,9 +57,7 @@ module Integrations def wiki_page_events_data page = project.wiki.list_pages(limit: 1).first - if !project.wiki_enabled? || page.blank? - return { error: s_('TestHooks|Ensure the wiki is enabled and has pages.') } - end + no_data_error(s_('TestHooks|Ensure the wiki is enabled and has pages.')) if !project.wiki_enabled? || page.blank? Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create') end @@ -61,7 +65,7 @@ module Integrations def deployment_events_data deployment = DeploymentsFinder.new(project: project, order_by: 'created_at', sort: 'desc').execute.first - return { error: s_('TestHooks|Ensure the project has deployments.') } unless deployment.present? + no_data_error(s_('TestHooks|Ensure the project has deployments.')) unless deployment.present? Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.current) end @@ -69,7 +73,7 @@ module Integrations def releases_events_data release = ReleasesFinder.new(project, current_user, order_by: :created_at, sort: :desc).execute.first - return { error: s_('TestHooks|Ensure the project has releases.') } unless release.present? + no_data_error(s_('TestHooks|Ensure the project has releases.')) unless release.present? release.to_hook_data('create') end diff --git a/app/services/design_management/save_designs_service.rb b/app/services/design_management/save_designs_service.rb index 64537293e65..ea5675c6ddd 100644 --- a/app/services/design_management/save_designs_service.rb +++ b/app/services/design_management/save_designs_service.rb @@ -113,7 +113,7 @@ module DesignManagement def file_content(file, full_path) transformer = ::Lfs::FileTransformer.new(project, repository, target_branch) - transformer.new_file(full_path, file.to_io).content + transformer.new_file(full_path, file.to_io, detect_content_type: Feature.enabled?(:design_management_allow_dangerous_images, project)).content end # Returns the latest blobs for the designs as a Hash of `{ Design => Blob }` diff --git a/app/services/discussions/resolve_service.rb b/app/services/discussions/resolve_service.rb index baf14aa8a03..54fc452ac85 100644 --- a/app/services/discussions/resolve_service.rb +++ b/app/services/discussions/resolve_service.rb @@ -17,7 +17,8 @@ module Discussions def execute discussions.each(&method(:resolve_discussion)) - process_auto_merge + + after_resolve_cleanup end private @@ -67,9 +68,19 @@ module Discussions end end - def process_auto_merge + def after_resolve_cleanup return unless merge_request return unless @resolved_count > 0 + + send_graphql_triggers + process_auto_merge + end + + def send_graphql_triggers + GraphqlTriggers.merge_request_merge_status_updated(merge_request) + end + + def process_auto_merge return unless discussions_ready_to_merge? AutoMergeProcessWorker.perform_async(merge_request.id) diff --git a/app/services/discussions/unresolve_service.rb b/app/services/discussions/unresolve_service.rb index fbd96ceafe7..f6685b79a33 100644 --- a/app/services/discussions/unresolve_service.rb +++ b/app/services/discussions/unresolve_service.rb @@ -12,10 +12,27 @@ module Discussions end def execute + @all_discussions_resolved_before = merge_request ? @discussion.noteable.discussions_resolved? : false + @discussion.unresolve! + send_graphql_triggers + Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter .track_unresolve_thread_action(user: @user) end + + private + + def merge_request + @discussion.noteable if @discussion.for_merge_request? + end + strong_memoize_attr :merge_request + + def send_graphql_triggers + return unless merge_request && @all_discussions_resolved_before + + GraphqlTriggers.merge_request_merge_status_updated(merge_request) + end end end diff --git a/app/services/draft_notes/publish_service.rb b/app/services/draft_notes/publish_service.rb index a82a6e22a5a..fab7a227e7d 100644 --- a/app/services/draft_notes/publish_service.rb +++ b/app/services/draft_notes/publish_service.rb @@ -34,7 +34,12 @@ module DraftNotes created_notes = draft_notes.map do |draft_note| draft_note.review = review - create_note_from_draft(draft_note, skip_capture_diff_note_position: true, skip_keep_around_commits: true) + create_note_from_draft( + draft_note, + skip_capture_diff_note_position: true, + skip_keep_around_commits: true, + skip_merge_status_trigger: true + ) end capture_diff_note_positions(created_notes) @@ -43,16 +48,18 @@ module DraftNotes set_reviewed notification_service.async.new_review(review) MergeRequests::ResolvedDiscussionNotificationService.new(project: project, current_user: current_user).execute(merge_request) + GraphqlTriggers.merge_request_merge_status_updated(merge_request) end - def create_note_from_draft(draft, skip_capture_diff_note_position: false, skip_keep_around_commits: false) + def create_note_from_draft(draft, skip_capture_diff_note_position: false, skip_keep_around_commits: false, skip_merge_status_trigger: false) # Make sure the diff file is unfolded in order to find the correct line # codes. draft.diff_file&.unfold_diff_lines(draft.original_position) note_params = draft.publish_params.merge(skip_keep_around_commits: skip_keep_around_commits) note = Notes::CreateService.new(draft.project, draft.author, note_params).execute( - skip_capture_diff_note_position: skip_capture_diff_note_position + skip_capture_diff_note_position: skip_capture_diff_note_position, + skip_merge_status_trigger: skip_merge_status_trigger ) set_discussion_resolve_status(note, draft) diff --git a/app/services/environments/stop_stale_service.rb b/app/services/environments/stop_stale_service.rb new file mode 100644 index 00000000000..7b7032cb670 --- /dev/null +++ b/app/services/environments/stop_stale_service.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Environments + class StopStaleService < BaseService + def execute + return ServiceResponse.error(message: 'Before date must be provided') unless params[:before].present? + + return ServiceResponse.error(message: 'Unauthorized') unless can?(current_user, :stop_environment, project) + + Environment.available + .deployed_and_updated_before(project.id, params[:before]) + .without_protected(project) + .in_batches(of: 100) do |env_batch| # rubocop:disable Cop/InBatches + Environments::AutoStopWorker.bulk_perform_async_with_contexts( + env_batch, + arguments_proc: ->(environment) { environment.id }, + context_proc: ->(environment) { { project: project } } + ) + end + + ServiceResponse.success(message: 'Successfully requested stop for all stale environments') + end + end +end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 1055f5ff088..8f722de2019 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -9,7 +9,7 @@ module Files git_user = Gitlab::Git::User.from_gitlab(current_user) if current_user.present? - @author_email = params[:author_email] || git_user&.email + @author_email = commit_email(git_user) @author_name = params[:author_name] || git_user&.name @commit_message = params[:commit_message] @last_commit_sha = params[:last_commit_sha] @@ -33,5 +33,18 @@ module Files last_commit.sha != commit_id end + + private + + def commit_email(git_user) + return params[:author_email] if params[:author_email].present? + return unless current_user + + namespace_commit_email = current_user.namespace_commit_email_for_project(@start_project) + + return namespace_commit_email.email.email if namespace_commit_email + + git_user.email + end end end diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb index 71dd9501648..6087efce9fd 100644 --- a/app/services/git/branch_hooks_service.rb +++ b/app/services/git/branch_hooks_service.rb @@ -165,14 +165,11 @@ module Git end def signature_types - types = [ + [ ::CommitSignatures::GpgSignature, - ::CommitSignatures::X509CommitSignature + ::CommitSignatures::X509CommitSignature, + ::CommitSignatures::SshSignature ] - - types.push(::CommitSignatures::SshSignature) if Feature.enabled?(:ssh_commit_signatures, project) - - types end def unsigned_commit_shas(commits) diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb index bd54b48c5f4..2d88283661c 100644 --- a/app/services/groups/import_export/export_service.rb +++ b/app/services/groups/import_export/export_service.rb @@ -71,7 +71,7 @@ module Groups end def tree_exporter - tree_exporter_class.new( + Gitlab::ImportExport::Group::TreeSaver.new( group: group, current_user: current_user, shared: shared, @@ -79,18 +79,6 @@ module Groups ) end - def tree_exporter_class - if ndjson? - Gitlab::ImportExport::Group::TreeSaver - else - Gitlab::ImportExport::Group::LegacyTreeSaver - end - end - - def ndjson? - ::Feature.enabled?(:group_export_ndjson, group&.parent) - end - def version_saver Gitlab::ImportExport::VersionSaver.new(shared: shared) end diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb index ac181245986..15948ab82a2 100644 --- a/app/services/groups/import_export/import_service.rb +++ b/app/services/groups/import_export/import_service.rb @@ -29,7 +29,7 @@ module Groups def execute Gitlab::Tracking.event(self.class.name, 'create', label: 'import_group_from_file') - if valid_user_permissions? && import_file && restorers.all?(&:restore) + if valid_user_permissions? && import_file && valid_import_file? && restorers.all?(&:restore) notify_success Gitlab::Tracking.event( @@ -75,25 +75,11 @@ module Groups def tree_restorer @tree_restorer ||= - if ndjson? - Gitlab::ImportExport::Group::TreeRestorer.new( - user: current_user, - shared: shared, - group: group - ) - else - Gitlab::ImportExport::Group::LegacyTreeRestorer.new( - user: current_user, - shared: shared, - group: group, - group_hash: nil - ) - end - end - - def ndjson? - ::Feature.enabled?(:group_import_ndjson, group&.parent) && - File.exist?(File.join(shared.export_path, 'tree/groups/_all.ndjson')) + Gitlab::ImportExport::Group::TreeRestorer.new( + user: current_user, + shared: shared, + group: group + ) end def remove_import_file @@ -115,6 +101,14 @@ module Groups end end + def valid_import_file? + return true if File.exist?(File.join(shared.export_path, 'tree/groups/_all.ndjson')) + + shared.error(::Gitlab::ImportExport::Error.incompatible_import_file_error) + + false + end + def notify_success @logger.info( group_id: group.id, diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb index 6fbf7daeb81..0a9705181ba 100644 --- a/app/services/groups/transfer_service.rb +++ b/app/services/groups/transfer_service.rb @@ -34,6 +34,7 @@ module Groups update_integrations remove_issue_contacts(old_root_ancestor_id, was_root_group) update_crm_objects(was_root_group) + remove_namespace_commit_emails(was_root_group) end post_update_hooks(@updated_project_ids, old_root_ancestor_id) @@ -279,6 +280,10 @@ module Groups Gitlab::EventStore.publish(event) end + + def remove_namespace_commit_emails(was_root_group) + Users::NamespaceCommitEmail.delete_for_namespace(@group) if was_root_group + end end end diff --git a/app/services/ide/schemas_config_service.rb b/app/services/ide/schemas_config_service.rb index a013a4679b5..5292f15dc37 100644 --- a/app/services/ide/schemas_config_service.rb +++ b/app/services/ide/schemas_config_service.rb @@ -33,9 +33,7 @@ module Ide end def predefined_schemas - return PREDEFINED_SCHEMAS if Feature.enabled?(:schema_linting) - - [] + PREDEFINED_SCHEMAS end def get_cached(url) diff --git a/app/services/import/github/gists_import_service.rb b/app/services/import/github/gists_import_service.rb index df1bbe306e7..e57430916fa 100644 --- a/app/services/import/github/gists_import_service.rb +++ b/app/services/import/github/gists_import_service.rb @@ -3,16 +3,20 @@ module Import module Github class GistsImportService < ::BaseService - def initialize(user, params) + def initialize(user, client, params) @current_user = user @params = params + @client = client end def execute return error('Import already in progress', 422) if import_status.started? + check_user_token start_import success + rescue Octokit::Unauthorized + error('Access denied to the GitHub account.', 401) end private @@ -29,6 +33,10 @@ module Import Gitlab::GithubGistsImport::StartImportWorker.perform_async(current_user.id, encrypted_token) import_status.start! end + + def check_user_token + @client.octokit.user.present? + end end end end diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb index 2378a4b11b1..b30c344723d 100644 --- a/app/services/import/github_service.rb +++ b/app/services/import/github_service.rb @@ -46,12 +46,8 @@ module Import @project_name ||= params[:new_name].presence || repo[:name] end - def namespace_path - @namespace_path ||= params[:target_namespace].presence || current_user.namespace_path - end - def target_namespace - @target_namespace ||= find_or_create_namespace(namespace_path, current_user.namespace_path) + @target_namespace ||= Namespace.find_by_full_path(target_namespace_path) end def extra_project_attrs @@ -104,6 +100,8 @@ module Import def validate_context if blocked_url? log_and_return_error("Invalid URL: #{url}", _("Invalid URL: %{url}") % { url: url }, :bad_request) + elsif target_namespace.nil? + error(_('Namespace or group to import repository into does not exist.'), :unprocessable_entity) elsif !authorized? error(_('This namespace has already been taken. Choose a different one.'), :unprocessable_entity) elsif oversized? @@ -111,6 +109,12 @@ module Import end end + def target_namespace_path + raise ArgumentError, 'Target namespace is required' if params[:target_namespace].blank? + + params[:target_namespace] + end + def log_error(exception) Gitlab::GithubImport::Logger.error( message: 'Import failed due to a GitHub error', diff --git a/app/services/integrations/test/base_service.rb b/app/services/integrations/test/base_service.rb index a8a027092d5..6291f2dfbaa 100644 --- a/app/services/integrations/test/base_service.rb +++ b/app/services/integrations/test/base_service.rb @@ -21,9 +21,9 @@ module Integrations return error('Testing not available for this event') end - return error(data[:error]) if data[:error].present? - integration.test(data) + rescue ArgumentError => e + error(e.message) end private diff --git a/app/services/issuable/discussions_list_service.rb b/app/services/issuable/discussions_list_service.rb index 1e5e37e4e1b..10e7660289b 100644 --- a/app/services/issuable/discussions_list_service.rb +++ b/app/services/issuable/discussions_list_service.rb @@ -19,7 +19,7 @@ module Issuable return Note.none unless can_read_issuable_notes? notes = NotesFinder.new(current_user, params.merge({ target: issuable, project: issuable.project })) - .execute.with_web_entity_associations.inc_relations_for_view.fresh + .execute.with_web_entity_associations.inc_relations_for_view(issuable).fresh if paginator paginated_discussions_by_type = paginator.records.group_by(&:table_name) @@ -49,7 +49,6 @@ module Issuable def paginator return if params[:per_page].blank? - return if issuable.instance_of?(MergeRequest) && Feature.disabled?(:paginated_mr_discussions, issuable.project) strong_memoize(:paginator) do issuable diff --git a/app/services/issuable_links/create_service.rb b/app/services/issuable_links/create_service.rb index 2e9775af8c2..f244f54b25f 100644 --- a/app/services/issuable_links/create_service.rb +++ b/app/services/issuable_links/create_service.rb @@ -121,7 +121,7 @@ module IssuableLinks end def target_issuable_type - :issue + 'issue' end def create_notes(issuable_link) diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 10407e99715..553fb6e2ac9 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -97,6 +97,16 @@ module Issues hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks issue.project.execute_hooks(issue_data, hooks_scope) issue.project.execute_integrations(issue_data, hooks_scope) + + execute_incident_hooks(issue, issue_data) if issue.incident? + end + + # We can remove this code after proposal in + # https://gitlab.com/gitlab-org/gitlab/-/issues/367550#proposal is updated. + def execute_incident_hooks(issue, issue_data) + issue_data[:object_kind] = 'incident' + issue_data[:event_type] = 'incident' + issue.project.execute_integrations(issue_data, :incident_hooks) end def update_project_counter_caches?(issue) @@ -117,7 +127,7 @@ module Issues override :allowed_create_params def allowed_create_params(params) - super(params).except(:issue_type, :work_item_type_id, :work_item_type) + super(params).except(:work_item_type_id, :work_item_type) end end end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index afad8d0c6bf..f6a1db2dcaa 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -24,6 +24,9 @@ module Issues return error(_('Operation not allowed'), 403) unless @current_user.can?(authorization_action, @project) @issue = @build_service.execute + # issue_type is set in BuildService, so we can delete it from params, in later phase + # it can be set also from quick actions - in that case work_item_id is synced later again + params.delete(:issue_type) handle_move_between_ids(@issue) @@ -68,6 +71,7 @@ module Issues handle_escalation_status_change(issue) create_timeline_event(issue) try_to_associate_contacts(issue) + change_additional_attributes(issue) super end @@ -127,6 +131,15 @@ module Issues set_crm_contacts(issue, contacts) end + + override :change_additional_attributes + def change_additional_attributes(issue) + super + + # issue_type can be still set through quick actions, in that case + # we have to make sure to re-sync work_item_type with it + issue.work_item_type_id = find_work_item_type_id(params[:issue_type]) if params[:issue_type] + end end end diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb index 69d33e1c873..a02fce552cf 100644 --- a/app/services/lfs/file_transformer.rb +++ b/app/services/lfs/file_transformer.rb @@ -29,11 +29,11 @@ module Lfs @branch_name = branch_name end - def new_file(file_path, file_content, encoding: nil) + def new_file(file_path, file_content, encoding: nil, detect_content_type: false) if project.lfs_enabled? && lfs_file?(file_path) file_content = parse_file_content(file_content, encoding: encoding) lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content) - lfs_object = create_lfs_object!(lfs_pointer_file, file_content) + lfs_object = create_lfs_object!(lfs_pointer_file, file_content, detect_content_type) link_lfs_object!(lfs_object) @@ -63,9 +63,17 @@ module Lfs end # rubocop: disable CodeReuse/ActiveRecord - def create_lfs_object!(lfs_pointer_file, file_content) + def create_lfs_object!(lfs_pointer_file, file_content, detect_content_type) LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object| - lfs_object.file = CarrierWaveStringFile.new(file_content) + lfs_object.file = if detect_content_type && (content_type = Gitlab::Utils::MimeType.from_string(file_content)) + CarrierWaveStringFile.new_file( + file_content: file_content, + filename: '', + content_type: content_type + ) + else + CarrierWaveStringFile.new(file_content) + end end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb index 51f9492ec91..5e73d7a957b 100644 --- a/app/services/members/approve_access_request_service.rb +++ b/app/services/members/approve_access_request_service.rb @@ -6,7 +6,7 @@ module Members validate_access!(access_requester) unless skip_authorization access_requester.access_level = params[:access_level] if params[:access_level] - access_requester.accept_request + access_requester.accept_request(current_user) after_execute(member: access_requester, skip_log_audit_event: skip_log_audit_event) diff --git a/app/services/members/creator_service.rb b/app/services/members/creator_service.rb index f59a3ed77eb..2d378a64c02 100644 --- a/app/services/members/creator_service.rb +++ b/app/services/members/creator_service.rb @@ -225,7 +225,7 @@ module Members access_level: access_level) .execute( member, - skip_authorization: ldap, + skip_authorization: ldap || skip_authorization?, skip_log_audit_event: ldap ) end diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index 5afc13701e0..24c5b12b335 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -13,24 +13,48 @@ module Members end @skip_auth = skip_authorization - last_owner = true + + if a_group_owner?(member) + process_destroy_of_group_owner_member(member, skip_subresources, unassign_issuables) + else + destroy_member(member) + destroy_data_related_to_member(member, skip_subresources, unassign_issuables) + end + + member + end + + private + + def process_destroy_of_group_owner_member(member, skip_subresources, unassign_issuables) + # Deleting 2 different group owners via the API in quick succession could lead to + # wrong results for the `last_owner?` check due to race conditions. To prevent this + # we wrap both the last_owner? check and the deletes of owners within a lock. + last_group_owner = true in_lock("delete_members:#{member.source.class}:#{member.source.id}", sleep_sec: 0.1.seconds) do - break if member.is_a?(GroupMember) && member.source.last_owner?(member.user) + break if member.source.last_owner?(member.user) - last_owner = false - member.destroy + last_group_owner = false + destroy_member(member) end - unless last_owner - member.user&.invalidate_cache_counts - delete_member_associations(member, skip_subresources, unassign_issuables) - end + # deletion of related data does not have to be within the lock. + destroy_data_related_to_member(member, skip_subresources, unassign_issuables) unless last_group_owner + end - member + def destroy_member(member) + member.destroy end - private + def destroy_data_related_to_member(member, skip_subresources, unassign_issuables) + member.user&.invalidate_cache_counts + delete_member_associations(member, skip_subresources, unassign_issuables) + end + + def a_group_owner?(member) + member.is_a?(GroupMember) && member.owner? + end def delete_member_associations(member, skip_subresources, unassign_issuables) if member.request? && member.user != current_user diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb index 0e6b02f7a80..b2c0fffc12d 100644 --- a/app/services/members/update_service.rb +++ b/app/services/members/update_service.rb @@ -11,10 +11,9 @@ module Members [member.id, { human_access: member.human_access, expires_at: member.expires_at }] end - if Feature.enabled?(:bulk_update_membership_roles, current_user) - multiple_members_update(members, permission, old_access_level_expiry_map) - else - single_member_update(members.first, permission, old_access_level_expiry_map) + updated_members = update_members(members, permission) + Member.transaction do + updated_members.each { |member| post_update(member, permission, old_access_level_expiry_map) } end prepare_response(members) @@ -22,35 +21,22 @@ module Members private - def single_member_update(member, permission, old_access_level_expiry_map) + def update_members(members, permission) + # `filter_map` avoids the `post_update` call for the member that resulted in no change + Member.transaction do + members.filter_map { |member| update_member(member, permission) } + end + rescue ActiveRecord::RecordInvalid + [] + end + + def update_member(member, permission) raise Gitlab::Access::AccessDeniedError unless has_update_permissions?(member, permission) member.attributes = params - return success(member: member) unless member.changed? - - post_update(member, permission, old_access_level_expiry_map) if member.save - end - - def multiple_members_update(members, permission, old_access_level_expiry_map) - begin - updated_members = - Member.transaction do - # Using `next` with `filter_map` avoids the `post_update` call for the member that resulted in no change - members.filter_map do |member| - raise Gitlab::Access::AccessDeniedError unless has_update_permissions?(member, permission) - - member.attributes = params - next unless member.changed? - - member.save! - member - end - end - rescue ActiveRecord::RecordInvalid - return - end + return unless member.changed? - updated_members.each { |member| post_update(member, permission, old_access_level_expiry_map) } + member.tap(&:save!) end def post_update(member, permission, old_access_level_expiry_map) @@ -62,18 +48,13 @@ module Members end def prepare_response(members) - errored_member = members.detect { |member| member.errors.any? } - if errored_member.present? - return error(errored_member.errors.full_messages.to_sentence, pass_back: { member: errored_member }) + errored_members = members.select { |member| member.errors.any? } + if errored_members.present? + error_message = errored_members.flat_map { |member| member.errors.full_messages }.uniq.to_sentence + return error(error_message, pass_back: { members: errored_members }) end - # TODO: Remove the :member key when removing the bulk_update_membership_roles FF and update where it's used. - # https://gitlab.com/gitlab-org/gitlab/-/issues/373257 - if members.one? - success(member: members.first) - else - success(members: members) - end + success(members: members) end def has_update_permissions?(member, permission) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 468cadb03c7..f6cbe889128 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -94,6 +94,10 @@ module MergeRequests private + def refresh_pipelines_on_merge_requests(merge_request, allow_duplicate: false) + create_pipeline_for(merge_request, current_user, async: true, allow_duplicate: allow_duplicate) + end + def enqueue_jira_connect_messages_for(merge_request) return unless project.jira_subscription_exists? @@ -184,16 +188,18 @@ module MergeRequests merge_request, merge_request.project, current_user, old_reviewers) end - def create_pipeline_for(merge_request, user, async: false) + def create_pipeline_for(merge_request, user, async: false, allow_duplicate: false) + create_pipeline_params = params.slice(:push_options).merge(allow_duplicate: allow_duplicate) + if async MergeRequests::CreatePipelineWorker.perform_async( project.id, user.id, merge_request.id, - params.slice(:push_options).deep_stringify_keys) + create_pipeline_params.deep_stringify_keys) else MergeRequests::CreatePipelineService - .new(project: project, current_user: user, params: params.slice(:push_options)) + .new(project: project, current_user: user, params: create_pipeline_params) .execute(merge_request) end end diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb index 1c4e1784b34..792f1728b88 100644 --- a/app/services/merge_requests/rebase_service.rb +++ b/app/services/merge_requests/rebase_service.rb @@ -6,6 +6,19 @@ module MergeRequests attr_reader :merge_request, :rebase_error + def validate(merge_request) + return error_response(_('Source branch does not exist')) unless + merge_request.source_branch_exists? + + return error_response(_('Cannot push to source branch')) unless + user_access.can_push_to_branch?(merge_request.source_branch) + + return error_response(_('Source branch is protected from force push')) unless + merge_request.permits_force_push? + + ServiceResponse.success + end + def execute(merge_request, skip_ci: false) @merge_request = merge_request @skip_ci = skip_ci @@ -40,5 +53,13 @@ module MergeRequests REBASE_ERROR end end + + def user_access + Gitlab::UserAccess.new(current_user, container: project) + end + + def error_response(message) + ServiceResponse.error(message: message) + end end end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 533d0052fb8..ce49d5dd43c 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -162,10 +162,6 @@ module MergeRequests @outdate_service ||= Suggestions::OutdateService.new end - def refresh_pipelines_on_merge_requests(merge_request) - create_pipeline_for(merge_request, current_user, async: true) - end - def abort_auto_merges(merge_request) abort_auto_merge(merge_request, 'source branch was updated') end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 745647b727c..a273b853c0d 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -169,6 +169,8 @@ module MergeRequests merge_request.target_branch ) + refresh_pipelines_on_merge_requests(merge_request, allow_duplicate: true) + abort_auto_merge(merge_request, 'target branch was changed') end diff --git a/app/services/ml/experiment_tracking/candidate_repository.rb b/app/services/ml/experiment_tracking/candidate_repository.rb index 1dbeb30145b..f1fd93d7816 100644 --- a/app/services/ml/experiment_tracking/candidate_repository.rb +++ b/app/services/ml/experiment_tracking/candidate_repository.rb @@ -14,9 +14,10 @@ module Ml ::Ml::Candidate.with_project_id_and_iid(project.id, iid) end - def create!(experiment, start_time, tags = nil) + def create!(experiment, start_time, tags = nil, name = nil) candidate = experiment.candidates.create!( user: user, + name: candidate_name(name, tags), start_time: start_time || 0 ) @@ -85,6 +86,13 @@ module Ml entity_class.insert_all(entities, returning: false) unless entities.empty? end + + def candidate_name(name, tags) + return name if name.present? + return unless tags.present? + + tags.detect { |t| t[:key] == 'mlflow.runName' }&.dig(:value) + end end end end diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb index cc5c81cf280..e6766273441 100644 --- a/app/services/notes/build_service.rb +++ b/app/services/notes/build_service.rb @@ -35,7 +35,7 @@ module Notes note.author = current_user parent_confidential = discussion&.confidential? - can_set_confidential = can?(current_user, :mark_note_as_confidential, note) + can_set_confidential = can?(current_user, :mark_note_as_internal, note) return discussion_not_found if parent_confidential && !can_set_confidential diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 555d60dc291..5f05b613288 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -4,7 +4,7 @@ module Notes class CreateService < ::Notes::BaseService include IncidentManagement::UsageData - def execute(skip_capture_diff_note_position: false) + def execute(skip_capture_diff_note_position: false, skip_merge_status_trigger: false) note = Notes::BuildService.new(project, current_user, params.except(:merge_request_diff_head_sha)).execute # n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/37440 @@ -34,7 +34,13 @@ module Notes end end - when_saved(note, skip_capture_diff_note_position: skip_capture_diff_note_position) if note_saved + if note_saved + when_saved( + note, + skip_capture_diff_note_position: skip_capture_diff_note_position, + skip_merge_status_trigger: skip_merge_status_trigger + ) + end end note @@ -72,15 +78,21 @@ module Notes end end - def when_saved(note, skip_capture_diff_note_position: false) + def when_saved(note, skip_capture_diff_note_position: false, skip_merge_status_trigger: false) todo_service.new_note(note, current_user) clear_noteable_diffs_cache(note) Suggestions::CreateService.new(note).execute increment_usage_counter(note) track_event(note, current_user) - if !skip_capture_diff_note_position && note.for_merge_request? && note.diff_note? && note.start_of_discussion? - Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion) + if note.for_merge_request? && note.start_of_discussion? + if !skip_capture_diff_note_position && note.diff_note? + Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion) + end + + if !skip_merge_status_trigger && note.to_be_resolved? + GraphqlTriggers.merge_request_merge_status_updated(note.noteable) + end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 550bd6d4c55..777d02c590d 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -91,10 +91,10 @@ class NotificationService end # Notify the user when at least one of their personal access tokens has expired today - def access_token_expired(user) + def access_token_expired(user, token_names = []) return unless user.can?(:receive_notifications) - mailer.access_token_expired_email(user).deliver_later + mailer.access_token_expired_email(user, token_names).deliver_later end # Notify the user when one of their personal access tokens is revoked diff --git a/app/services/pages_domains/create_service.rb b/app/services/pages_domains/create_service.rb index 1f771ca3a05..17194fbe5e4 100644 --- a/app/services/pages_domains/create_service.rb +++ b/app/services/pages_domains/create_service.rb @@ -24,6 +24,7 @@ module PagesDomains project_id: project.id, namespace_id: project.namespace_id, root_namespace_id: project.root_namespace.id, + domain_id: domain.id, domain: domain.domain } ) diff --git a/app/services/pages_domains/delete_service.rb b/app/services/pages_domains/delete_service.rb index af69e1845a9..89e598acee0 100644 --- a/app/services/pages_domains/delete_service.rb +++ b/app/services/pages_domains/delete_service.rb @@ -22,6 +22,7 @@ module PagesDomains project_id: project.id, namespace_id: project.namespace_id, root_namespace_id: project.root_namespace.id, + domain_id: domain.id, domain: domain.domain } ) diff --git a/app/services/pages_domains/retry_acme_order_service.rb b/app/services/pages_domains/retry_acme_order_service.rb index 6251c9d3615..01647a8ecf5 100644 --- a/app/services/pages_domains/retry_acme_order_service.rb +++ b/app/services/pages_domains/retry_acme_order_service.rb @@ -30,6 +30,7 @@ module PagesDomains project_id: domain.project.id, namespace_id: domain.project.namespace_id, root_namespace_id: domain.project.root_namespace.id, + domain_id: domain.id, domain: domain.domain } ) diff --git a/app/services/pages_domains/update_service.rb b/app/services/pages_domains/update_service.rb index b038aaa95b6..1887441d8b8 100644 --- a/app/services/pages_domains/update_service.rb +++ b/app/services/pages_domains/update_service.rb @@ -24,6 +24,7 @@ module PagesDomains project_id: project.id, namespace_id: project.namespace_id, root_namespace_id: project.root_namespace.id, + domain_id: domain.id, domain: domain.domain } ) diff --git a/app/services/personal_access_tokens/revoke_service.rb b/app/services/personal_access_tokens/revoke_service.rb index bb5edc27340..237c95bc456 100644 --- a/app/services/personal_access_tokens/revoke_service.rb +++ b/app/services/personal_access_tokens/revoke_service.rb @@ -4,13 +4,17 @@ module PersonalAccessTokens class RevokeService < BaseService attr_reader :token, :current_user, :group - VALID_SOURCES = %w[secret_detection].freeze + VALID_SOURCES = %i[self secret_detection].freeze def initialize(current_user = nil, token: nil, group: nil, source: nil) @current_user = current_user @token = token @group = group @source = source + + @source = :self if @current_user && !@source + + raise ArgumentError unless VALID_SOURCES.include?(@source) end def execute @@ -36,22 +40,21 @@ module PersonalAccessTokens end def revocation_permitted? - if current_user + case @source + when :self Ability.allowed?(current_user, :revoke_token, token) + when :secret_detection + true else - source && VALID_SOURCES.include?(source) + false end end - def source - current_user&.username || @source - end - def log_event Gitlab::AppLogger.info( class: self.class.name, message: "PAT Revoked", - revoked_by: source, + revoked_by: current_user&.username || @source, revoked_for: token.user.username, token_id: token.id) end diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index ae5aae87a77..11437ad90fc 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -23,7 +23,7 @@ module Projects MergeRequestsFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title]) end - def commands(noteable, type) + def commands(noteable) return [] unless noteable && current_user QuickActions::InterpretService.new(project, current_user).available_commands(noteable) diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index a4b473f35c6..d3313526eaf 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -22,6 +22,12 @@ module Projects end def execute + params[:wiki_enabled] = params[:wiki_access_level] if params[:wiki_access_level] + params[:builds_enabled] = params[:builds_access_level] if params[:builds_access_level] + params[:snippets_enabled] = params[:builds_access_level] if params[:snippets_access_level] + params[:merge_requests_enabled] = params[:merge_requests_access_level] if params[:merge_requests_access_level] + params[:issues_enabled] = params[:issues_access_level] if params[:issues_access_level] + if create_from_template? return ::Projects::CreateFromTemplateService.new(current_user, params).execute end diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index 967a1e990b2..e6ccae0a22b 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -166,7 +166,7 @@ module Projects .then do |(import_url, resolved_host)| next '' if resolved_host.nil? || !import_url.scheme.in?(%w[http https]) - import_url.host.to_s + import_url.hostname.to_s end end diff --git a/app/services/projects/refresh_build_artifacts_size_statistics_service.rb b/app/services/projects/refresh_build_artifacts_size_statistics_service.rb index 8e006dc8c34..f11083d6c04 100644 --- a/app/services/projects/refresh_build_artifacts_size_statistics_service.rb +++ b/app/services/projects/refresh_build_artifacts_size_statistics_service.rb @@ -2,28 +2,31 @@ module Projects class RefreshBuildArtifactsSizeStatisticsService - BATCH_SIZE = 1000 + BATCH_SIZE = 500 + REFRESH_INTERVAL_SECONDS = 0.1 def execute refresh = Projects::BuildArtifactsSizeRefresh.process_next_refresh! - return unless refresh + + return unless refresh&.running? batch = refresh.next_batch(limit: BATCH_SIZE).to_a if batch.any? - # We are doing the sum in ruby because the query takes too long when done in SQL - total_artifacts_size = batch.sum { |artifact| artifact.size.to_i } + increments = batch.map do |artifact| + Gitlab::Counters::Increment.new(amount: artifact.size.to_i, ref: artifact.id) + end Projects::BuildArtifactsSizeRefresh.transaction do # Mark the refresh ready for another worker to pick up and process the next batch refresh.requeue!(batch.last.id) - refresh.project.statistics.increment_counter(:build_artifacts_size, total_artifacts_size) + ProjectStatistics.bulk_increment_statistic(refresh.project, :build_artifacts_size, increments) end + + sleep REFRESH_INTERVAL_SECONDS else - # Remove the refresh job from the table if there are no more - # remaining job artifacts to calculate for the given project. - refresh.destroy! + refresh.schedule_finalize! end refresh diff --git a/app/services/repositories/housekeeping_service.rb b/app/services/repositories/housekeeping_service.rb index e12d69807f2..f314e210442 100644 --- a/app/services/repositories/housekeeping_service.rb +++ b/app/services/repositories/housekeeping_service.rb @@ -10,7 +10,7 @@ module Repositories class HousekeepingService < BaseService # Timeout set to 24h LEASE_TIMEOUT = 86400 - PACK_REFS_PERIOD = 6 + GC_PERIOD = 200 class LeaseTaken < StandardError def to_s @@ -74,21 +74,13 @@ module Repositories if pushes_since_gc % gc_period == 0 :gc - elsif pushes_since_gc % full_repack_period == 0 - :full_repack - elsif pushes_since_gc % repack_period == 0 - :incremental_repack else - :pack_refs + :incremental_repack end end def period_match? - if Feature.enabled?(:optimized_housekeeping) - pushes_since_gc % repack_period == 0 - else - [gc_period, full_repack_period, repack_period, PACK_REFS_PERIOD].any? { |period| pushes_since_gc % period == 0 } - end + [gc_period, repack_period].any? { |period| pushes_since_gc % period == 0 } end def housekeeping_enabled? @@ -96,11 +88,7 @@ module Repositories end def gc_period - Gitlab::CurrentSettings.housekeeping_gc_period - end - - def full_repack_period - Gitlab::CurrentSettings.housekeeping_full_repack_period + GC_PERIOD end def repack_period diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 403a2f077b0..b4344a009b2 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -112,6 +112,25 @@ class SearchService false end + def global_search_enabled_for_scope? + case params[:scope] + when 'blobs' + Feature.enabled?(:global_search_code_tab, current_user, type: :ops) + when 'commits' + Feature.enabled?(:global_search_commits_tab, current_user, type: :ops) + when 'issues' + Feature.enabled?(:global_search_issues_tab, current_user, type: :ops) + when 'merge_requests' + Feature.enabled?(:global_search_merge_requests_tab, current_user, type: :ops) + when 'wiki_blobs' + Feature.enabled?(:global_search_wiki_tab, current_user, type: :ops) + when 'users' + Feature.enabled?(:global_search_users_tab, current_user, type: :ops) + else + true + end + end + private def page diff --git a/app/services/security/ci_configuration/base_create_service.rb b/app/services/security/ci_configuration/base_create_service.rb index 7f3b66d40e1..aaa850fde39 100644 --- a/app/services/security/ci_configuration/base_create_service.rb +++ b/app/services/security/ci_configuration/base_create_service.rb @@ -3,7 +3,7 @@ module Security module CiConfiguration class BaseCreateService - attr_reader :branch_name, :current_user, :project + attr_reader :branch_name, :current_user, :project, :name def initialize(project, current_user) @project = project @@ -41,8 +41,18 @@ module Security end def existing_gitlab_ci_content - @gitlab_ci_yml ||= project.ci_config_for(project.repository.root_ref_sha) + root_ref = root_ref_sha(project) + return if root_ref.nil? + + @gitlab_ci_yml ||= project.ci_config_for(root_ref) YAML.safe_load(@gitlab_ci_yml) if @gitlab_ci_yml + rescue Psych::BadAlias + raise Gitlab::Graphql::Errors::MutationError, + ".gitlab-ci.yml with aliases/anchors is not supported. Please change the CI configuration manually." + rescue Psych::Exception => e + Gitlab::AppLogger.error("Failed to process existing .gitlab-ci.yml: #{e.message}") + raise Gitlab::Graphql::Errors::MutationError, + "#{name} merge request creation mutation failed" end def successful_change_path @@ -61,6 +71,15 @@ module Security self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s ) end + + def root_ref_sha(project) + project.repository.root_ref_sha + rescue StandardError => e + # this might fail on the very first commit, + # and unfortunately it raises a StandardError + Gitlab::ErrorTracking.track_exception(e, project_id: project.id) + nil + end end end end diff --git a/app/services/security/ci_configuration/container_scanning_create_service.rb b/app/services/security/ci_configuration/container_scanning_create_service.rb index da2f1ac0981..4dfd05451ad 100644 --- a/app/services/security/ci_configuration/container_scanning_create_service.rb +++ b/app/services/security/ci_configuration/container_scanning_create_service.rb @@ -21,6 +21,10 @@ module Security def description _('Configure Container Scanning in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings) to customize Container Scanning settings.') end + + def name + 'Container Scanning' + end end end end diff --git a/app/services/security/ci_configuration/dependency_scanning_create_service.rb b/app/services/security/ci_configuration/dependency_scanning_create_service.rb index b11eccc680c..66dd76c4b5d 100644 --- a/app/services/security/ci_configuration/dependency_scanning_create_service.rb +++ b/app/services/security/ci_configuration/dependency_scanning_create_service.rb @@ -21,6 +21,10 @@ module Security def description _('Configure Dependency Scanning in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings) to customize Dependency Scanning settings.') end + + def name + 'Dependency Scanning' + end end end end diff --git a/app/services/security/ci_configuration/sast_create_service.rb b/app/services/security/ci_configuration/sast_create_service.rb index d78e22f1fe1..643cb7f89dc 100644 --- a/app/services/security/ci_configuration/sast_create_service.rb +++ b/app/services/security/ci_configuration/sast_create_service.rb @@ -20,13 +20,7 @@ module Security end def action - existing_content = begin - existing_gitlab_ci_content # this can fail on the very first commit - rescue StandardError - nil - end - - Security::CiConfiguration::SastBuildAction.new(project.auto_devops_enabled?, params, existing_content, project.ci_config_path).generate + Security::CiConfiguration::SastBuildAction.new(project.auto_devops_enabled?, params, existing_gitlab_ci_content, project.ci_config_path).generate end def next_branch @@ -40,6 +34,10 @@ module Security def description _('Configure SAST in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.') end + + def name + 'SAST' + end end end end diff --git a/app/services/security/ci_configuration/sast_iac_create_service.rb b/app/services/security/ci_configuration/sast_iac_create_service.rb index fbc65484216..61bbebd77d0 100644 --- a/app/services/security/ci_configuration/sast_iac_create_service.rb +++ b/app/services/security/ci_configuration/sast_iac_create_service.rb @@ -21,6 +21,10 @@ module Security def description _('Configure SAST IaC in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST IaC settings.') end + + def name + 'SAST IaC' + end end end end diff --git a/app/services/security/ci_configuration/secret_detection_create_service.rb b/app/services/security/ci_configuration/secret_detection_create_service.rb index ca5138b6ed6..792fe4986e9 100644 --- a/app/services/security/ci_configuration/secret_detection_create_service.rb +++ b/app/services/security/ci_configuration/secret_detection_create_service.rb @@ -21,6 +21,10 @@ module Security def description _('Configure Secret Detection in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings) to customize Secret Detection settings.') end + + def name + 'Secret Detection' + end end end end diff --git a/app/services/service_ping/submit_service.rb b/app/services/service_ping/submit_service.rb index da2a51562f8..6d39174b6c7 100644 --- a/app/services/service_ping/submit_service.rb +++ b/app/services/service_ping/submit_service.rb @@ -42,20 +42,20 @@ module ServicePing { metadata: { uuid: service_ping_payload[:uuid], - metrics: metrics_collection_time(service_ping_payload) + metrics: metrics_collection_metadata(service_ping_payload) } } end - def metrics_collection_time(payload, parents = []) + def metrics_collection_metadata(payload, parents = []) return [] unless payload.is_a?(Hash) payload.flat_map do |key, metric_value| key_path = parents.dup.append(key) if metric_value.respond_to?(:duration) - { name: key_path.join('.'), time_elapsed: metric_value.duration } + { name: key_path.join('.'), time_elapsed: metric_value.duration, error: metric_value.error }.compact else - metrics_collection_time(metric_value, key_path) + metrics_collection_metadata(metric_value, key_path) end end end diff --git a/app/services/service_response.rb b/app/services/service_response.rb index 848f90e7f25..da4773ab9c7 100644 --- a/app/services/service_response.rb +++ b/app/services/service_response.rb @@ -26,22 +26,22 @@ class ServiceResponse self.reason = reason end - def track_exception(as: StandardError, **extra_data) - if error? - e = as.new(message) - Gitlab::ErrorTracking.track_exception(e, extra_data) + def log_and_raise_exception(as: StandardError, **extra_data) + error_tracking(as) do |ex| + Gitlab::ErrorTracking.log_and_raise_exception(ex, extra_data) end + end - self + def track_exception(as: StandardError, **extra_data) + error_tracking(as) do |ex| + Gitlab::ErrorTracking.track_exception(ex, extra_data) + end end def track_and_raise_exception(as: StandardError, **extra_data) - if error? - e = as.new(message) - Gitlab::ErrorTracking.track_and_raise_exception(e, extra_data) + error_tracking(as) do |ex| + Gitlab::ErrorTracking.track_and_raise_exception(ex, extra_data) end - - self end def [](key) @@ -73,4 +73,13 @@ class ServiceResponse private attr_writer :status, :message, :http_status, :payload, :reason + + def error_tracking(error_klass) + if error? + ex = error_klass.new(message) + yield ex + end + + self + end end diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb index b41a9959c13..3f2949a53ba 100644 --- a/app/services/test_hooks/base_service.rb +++ b/app/services/test_hooks/base_service.rb @@ -16,9 +16,16 @@ module TestHooks trigger_key = hook.class.triggers.key(trigger.to_sym) return error('Testing not available for this hook') if trigger_key.nil? || data.blank? + return error(data[:error]) if data[:error].present? hook.execute(data, trigger_key, force: true) + rescue ArgumentError => e + error(e.message) + end + + def error(message) + ServiceResponse.error(message: message) end end end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 9ae31f8ac58..bfd1e55507c 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -220,7 +220,7 @@ class TodoService create_todos(reviewers, attributes, project.namespace, project) end - def create_member_access_request(member) + def create_member_access_request_todos(member) source = member.source attributes = attributes_for_access_request_todos(source, member.user, Todo::MEMBER_ACCESS_REQUESTED) @@ -433,7 +433,12 @@ class TodoService note: note } - attributes[:group_id] = source.id unless source.instance_of? Project + if source.instance_of? Project + attributes[:project_id] = source.id + attributes[:group_id] = source.group.id if source.group.present? + else + attributes[:group_id] = source.id + end attributes end diff --git a/app/services/users/block_service.rb b/app/services/users/block_service.rb index 37921c477b4..0715e299e87 100644 --- a/app/services/users/block_service.rb +++ b/app/services/users/block_service.rb @@ -20,8 +20,14 @@ module Users private + # overridden by EE module def after_block_hook(user) - # overridden by EE module + custom_attribute = { + user_id: user.id, + key: UserCustomAttribute::BLOCKED_BY, + value: "#{current_user.username}/#{current_user.id}+#{Time.current}" + } + UserCustomAttribute.upsert_custom_attributes([custom_attribute]) end end end diff --git a/app/services/users/signup_service.rb b/app/services/users/signup_service.rb index 1087ae76216..9eb1e75988c 100644 --- a/app/services/users/signup_service.rb +++ b/app/services/users/signup_service.rb @@ -12,9 +12,9 @@ module Users inject_validators if @user.save - success + ServiceResponse.success else - error(@user.errors.full_messages.join('. ')) + ServiceResponse.error(message: @user.errors.full_messages.join('. ')) end end diff --git a/app/services/users/unblock_service.rb b/app/services/users/unblock_service.rb new file mode 100644 index 00000000000..1302395662f --- /dev/null +++ b/app/services/users/unblock_service.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Users + class UnblockService < BaseService + def initialize(current_user) + @current_user = current_user + end + + def execute(user) + if user.activate + after_unblock_hook(user) + ServiceResponse.success(payload: { user: user }) + else + ServiceResponse.error(message: user.errors.full_messages) + end + end + + private + + def after_unblock_hook(user) + custom_attribute = { + user_id: user.id, + key: UserCustomAttribute::UNBLOCKED_BY, + value: "#{current_user.username}/#{current_user.id}+#{Time.current}" + } + UserCustomAttribute.upsert_custom_attributes([custom_attribute]) + end + end +end diff --git a/app/services/work_items/parent_links/create_service.rb b/app/services/work_items/parent_links/create_service.rb index e7906f1fcdd..288ca152f93 100644 --- a/app/services/work_items/parent_links/create_service.rb +++ b/app/services/work_items/parent_links/create_service.rb @@ -46,7 +46,7 @@ module WorkItems end def target_issuable_type - issuable.issue_type == 'issue' ? 'task' : issuable.issue_type + 'work item' end def issuables_not_found_message |