diff options
Diffstat (limited to 'app/services')
26 files changed, 338 insertions, 38 deletions
diff --git a/app/services/ci/create_cluster_service.rb b/app/services/ci/create_cluster_service.rb new file mode 100644 index 00000000000..f7ee0e468e2 --- /dev/null +++ b/app/services/ci/create_cluster_service.rb @@ -0,0 +1,15 @@ +module Ci + class CreateClusterService < BaseService + def execute(access_token) + params['gcp_machine_type'] ||= GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE + + cluster_params = + params.merge(user: current_user, + gcp_token: access_token) + + project.create_cluster(cluster_params).tap do |cluster| + ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted? + end + end + end +end diff --git a/app/services/ci/extract_sections_from_build_trace_service.rb b/app/services/ci/extract_sections_from_build_trace_service.rb new file mode 100644 index 00000000000..75f9e0f897d --- /dev/null +++ b/app/services/ci/extract_sections_from_build_trace_service.rb @@ -0,0 +1,30 @@ +module Ci + class ExtractSectionsFromBuildTraceService < BaseService + def execute(build) + return false unless build.trace_sections.empty? + + Gitlab::Database.bulk_insert(BuildTraceSection.table_name, extract_sections(build)) + true + end + + private + + def find_or_create_name(name) + project.build_trace_section_names.find_or_create_by!(name: name) + rescue ActiveRecord::RecordInvalid + project.build_trace_section_names.find_by!(name: name) + end + + def extract_sections(build) + build.trace.extract_sections.map do |attr| + name = attr.delete(:name) + name_record = find_or_create_name(name) + + attr.merge( + build_id: build.id, + project_id: project.id, + section_name_id: name_record.id) + end + end + end +end diff --git a/app/services/ci/fetch_gcp_operation_service.rb b/app/services/ci/fetch_gcp_operation_service.rb new file mode 100644 index 00000000000..0b68e4d6ea9 --- /dev/null +++ b/app/services/ci/fetch_gcp_operation_service.rb @@ -0,0 +1,17 @@ +module Ci + class FetchGcpOperationService + def execute(cluster) + api_client = + GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil) + + operation = api_client.projects_zones_operations( + cluster.gcp_project_id, + cluster.gcp_cluster_zone, + cluster.gcp_operation_id) + + yield(operation) if block_given? + rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e + return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}") + end + end +end diff --git a/app/services/ci/fetch_kubernetes_token_service.rb b/app/services/ci/fetch_kubernetes_token_service.rb new file mode 100644 index 00000000000..44da87cb00c --- /dev/null +++ b/app/services/ci/fetch_kubernetes_token_service.rb @@ -0,0 +1,72 @@ +## +# TODO: +# Almost components in this class were copied from app/models/project_services/kubernetes_service.rb +# We should dry up those classes not to repeat the same code. +# Maybe we should have a special facility (e.g. lib/kubernetes_api) to maintain all Kubernetes API caller. +module Ci + class FetchKubernetesTokenService + attr_reader :api_url, :ca_pem, :username, :password + + def initialize(api_url, ca_pem, username, password) + @api_url = api_url + @ca_pem = ca_pem + @username = username + @password = password + end + + def execute + read_secrets.each do |secret| + name = secret.dig('metadata', 'name') + if /default-token/ =~ name + token_base64 = secret.dig('data', 'token') + return Base64.decode64(token_base64) if token_base64 + end + end + + nil + end + + private + + def read_secrets + kubeclient = build_kubeclient! + + kubeclient.get_secrets.as_json + rescue KubeException => err + raise err unless err.error_code == 404 + [] + end + + def build_kubeclient!(api_path: 'api', api_version: 'v1') + raise "Incomplete settings" unless api_url && username && password + + ::Kubeclient::Client.new( + join_api_url(api_path), + api_version, + auth_options: { username: username, password: password }, + ssl_options: kubeclient_ssl_options, + http_proxy_uri: ENV['http_proxy'] + ) + end + + def join_api_url(api_path) + url = URI.parse(api_url) + prefix = url.path.sub(%r{/+\z}, '') + + url.path = [prefix, api_path].join("/") + + url.to_s + end + + def kubeclient_ssl_options + 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 + end +end diff --git a/app/services/ci/finalize_cluster_creation_service.rb b/app/services/ci/finalize_cluster_creation_service.rb new file mode 100644 index 00000000000..347875c5697 --- /dev/null +++ b/app/services/ci/finalize_cluster_creation_service.rb @@ -0,0 +1,33 @@ +module Ci + class FinalizeClusterCreationService + def execute(cluster) + api_client = + GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil) + + begin + gke_cluster = api_client.projects_zones_clusters_get( + cluster.gcp_project_id, + cluster.gcp_cluster_zone, + cluster.gcp_cluster_name) + rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e + return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}") + end + + endpoint = gke_cluster.endpoint + api_url = 'https://' + endpoint + ca_cert = Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate) + username = gke_cluster.master_auth.username + password = gke_cluster.master_auth.password + + kubernetes_token = Ci::FetchKubernetesTokenService.new( + api_url, ca_cert, username, password).execute + + unless kubernetes_token + return cluster.make_errored!('Failed to get a default token of kubernetes') + end + + Ci::IntegrateClusterService.new.execute( + cluster, endpoint, ca_cert, kubernetes_token, username, password) + end + end +end diff --git a/app/services/ci/integrate_cluster_service.rb b/app/services/ci/integrate_cluster_service.rb new file mode 100644 index 00000000000..d123ce8d26b --- /dev/null +++ b/app/services/ci/integrate_cluster_service.rb @@ -0,0 +1,26 @@ +module Ci + class IntegrateClusterService + def execute(cluster, endpoint, ca_cert, token, username, password) + Gcp::Cluster.transaction do + cluster.update!( + enabled: true, + endpoint: endpoint, + ca_cert: ca_cert, + kubernetes_token: token, + username: username, + password: password, + service: cluster.project.find_or_initialize_service('kubernetes'), + status_event: :make_created) + + cluster.service.update!( + active: true, + api_url: cluster.api_url, + ca_pem: ca_cert, + namespace: cluster.project_namespace, + token: token) + end + rescue ActiveRecord::RecordInvalid => e + cluster.make_errored!("Failed to integrate cluster into kubernetes_service: #{e.message}") + end + end +end diff --git a/app/services/ci/provision_cluster_service.rb b/app/services/ci/provision_cluster_service.rb new file mode 100644 index 00000000000..52d80b01813 --- /dev/null +++ b/app/services/ci/provision_cluster_service.rb @@ -0,0 +1,36 @@ +module Ci + class ProvisionClusterService + def execute(cluster) + api_client = + GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil) + + begin + operation = api_client.projects_zones_clusters_create( + cluster.gcp_project_id, + cluster.gcp_cluster_zone, + cluster.gcp_cluster_name, + cluster.gcp_cluster_size, + machine_type: cluster.gcp_machine_type) + rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e + return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}") + end + + unless operation.status == 'RUNNING' || operation.status == 'PENDING' + return cluster.make_errored!("Operation status is unexpected; #{operation.status_message}") + end + + cluster.gcp_operation_id = api_client.parse_operation_id(operation.self_link) + + unless cluster.gcp_operation_id + return cluster.make_errored!('Can not find operation_id from self_link') + end + + if cluster.make_creating + WaitForClusterCreationWorker.perform_in( + WaitForClusterCreationWorker::INITIAL_INTERVAL, cluster.id) + else + return cluster.make_errored!("Failed to update cluster record; #{cluster.errors}") + end + end + end +end diff --git a/app/services/ci/update_cluster_service.rb b/app/services/ci/update_cluster_service.rb new file mode 100644 index 00000000000..70d88fca660 --- /dev/null +++ b/app/services/ci/update_cluster_service.rb @@ -0,0 +1,22 @@ +module Ci + class UpdateClusterService < BaseService + def execute(cluster) + Gcp::Cluster.transaction do + cluster.update!(params) + + if params['enabled'] == 'true' + cluster.service.update!( + active: true, + api_url: cluster.api_url, + ca_pem: cluster.ca_cert, + namespace: cluster.project_namespace, + token: cluster.kubernetes_token) + else + cluster.service.update!(active: false) + end + end + rescue ActiveRecord::RecordInvalid => e + cluster.errors.add(:base, e.message) + end + end +end diff --git a/app/services/emails/base_service.rb b/app/services/emails/base_service.rb index 7f591c89411..5bbceeb3b3f 100644 --- a/app/services/emails/base_service.rb +++ b/app/services/emails/base_service.rb @@ -1,9 +1,8 @@ module Emails class BaseService - def initialize(current_user, opts) - @current_user = current_user - @user = opts.delete(:user) - @email = opts[:email] + def initialize(current_user, params = {}) + @current_user, @params = current_user, params.dup + @user = params.delete(:user) end end end diff --git a/app/services/emails/confirm_service.rb b/app/services/emails/confirm_service.rb new file mode 100644 index 00000000000..b5301bf2b82 --- /dev/null +++ b/app/services/emails/confirm_service.rb @@ -0,0 +1,7 @@ +module Emails + class ConfirmService < ::Emails::BaseService + def execute(email) + email.resend_confirmation_instructions + end + end +end diff --git a/app/services/emails/create_service.rb b/app/services/emails/create_service.rb index b6491ee9804..94a841af7c3 100644 --- a/app/services/emails/create_service.rb +++ b/app/services/emails/create_service.rb @@ -1,7 +1,7 @@ module Emails class CreateService < ::Emails::BaseService - def execute - @user.emails.create(email: @email) + def execute(extra_params = {}) + @user.emails.create(@params.merge(extra_params)) end end end diff --git a/app/services/emails/destroy_service.rb b/app/services/emails/destroy_service.rb index 44011cc36c8..1ed131fe326 100644 --- a/app/services/emails/destroy_service.rb +++ b/app/services/emails/destroy_service.rb @@ -1,7 +1,7 @@ module Emails class DestroyService < ::Emails::BaseService - def execute - update_secondary_emails! if Email.find_by_email!(@email).destroy + def execute(email) + email.destroy && update_secondary_emails! end private diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 12604e7eb5d..d61a342ebad 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -43,6 +43,10 @@ class IssuableBaseService < BaseService SystemNoteService.change_time_spent(issuable, issuable.project, current_user) end + def create_discussion_lock_note(issuable) + SystemNoteService.discussion_lock(issuable, current_user) + end + def filter_params(issuable) ability_name = :"admin_#{issuable.to_ability_name}" @@ -57,6 +61,7 @@ class IssuableBaseService < BaseService params.delete(:due_date) params.delete(:canonical_issue_id) params.delete(:project) + params.delete(:discussion_locked) end filter_assignee(issuable) @@ -236,6 +241,7 @@ class IssuableBaseService < BaseService handle_common_system_notes(issuable, old_labels: old_labels) end + change_discussion_lock(issuable) handle_changes( issuable, old_labels: old_labels, @@ -249,7 +255,7 @@ class IssuableBaseService < BaseService invalidate_cache_counts(issuable, users: affected_assignees.compact) after_update(issuable) issuable.create_new_cross_references!(current_user) - execute_hooks(issuable, 'update') + execute_hooks(issuable, 'update', old_labels: old_labels, old_assignees: old_assignees) issuable.update_project_counter_caches if update_project_counters end @@ -294,6 +300,12 @@ class IssuableBaseService < BaseService end end + def change_discussion_lock(issuable) + if issuable.previous_changes.include?('discussion_locked') + create_discussion_lock_note(issuable) + end + end + def toggle_award(issuable) award = params.delete(:emoji_award) if award diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 4c198fc96ea..735257c4779 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -1,10 +1,10 @@ module Issues class BaseService < ::IssuableBaseService - def hook_data(issue, action) - issue_data = issue.to_hook_data(current_user) - issue_url = Gitlab::UrlBuilder.build(issue) - issue_data[:object_attributes].merge!(url: issue_url, action: action) - issue_data + def hook_data(issue, action, old_labels: [], old_assignees: []) + hook_data = issue.to_hook_data(current_user, old_labels: old_labels, old_assignees: old_assignees) + hook_data[:object_attributes][:action] = action + + hook_data end def reopen_service @@ -22,8 +22,8 @@ module Issues issue, issue.project, current_user, old_assignees) end - def execute_hooks(issue, action = 'open') - issue_data = hook_data(issue, action) + def execute_hooks(issue, action = 'open', old_labels: [], old_assignees: []) + issue_data = hook_data(issue, action, old_labels: old_labels, old_assignees: old_assignees) hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks issue.project.execute_hooks(issue_data, hooks_scope) issue.project.execute_services(issue_data, hooks_scope) diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index b4ca3966505..e0339ddf9bb 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -34,7 +34,7 @@ module Issues if issue.assignees != old_assignees create_assignee_note(issue, old_assignees) notification_service.reassigned_issue(issue, current_user, old_assignees) - todo_service.reassigned_issue(issue, current_user) + todo_service.reassigned_issue(issue, current_user, old_assignees) end if issue.previous_changes.include?('confidential') diff --git a/app/services/keys/last_used_service.rb b/app/services/keys/last_used_service.rb index 066f3246158..dbd79f7da55 100644 --- a/app/services/keys/last_used_service.rb +++ b/app/services/keys/last_used_service.rb @@ -16,6 +16,8 @@ module Keys end def update? + return false if ::Gitlab::Database.read_only? + last_used = key.last_used_at return false if last_used && (Time.zone.now - last_used) <= TIMEOUT diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 35ccff26262..112606a82d7 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -18,19 +18,19 @@ module MergeRequests super if changed_title end - def hook_data(merge_request, action, oldrev = nil) - hook_data = merge_request.to_hook_data(current_user) - hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request) + def hook_data(merge_request, action, old_rev: nil, old_labels: [], old_assignees: []) + hook_data = merge_request.to_hook_data(current_user, old_labels: old_labels, old_assignees: old_assignees) hook_data[:object_attributes][:action] = action - if oldrev && !Gitlab::Git.blank_ref?(oldrev) - hook_data[:object_attributes][:oldrev] = oldrev + if old_rev && !Gitlab::Git.blank_ref?(old_rev) + hook_data[:object_attributes][:oldrev] = old_rev end + hook_data end - def execute_hooks(merge_request, action = 'open', oldrev = nil) + def execute_hooks(merge_request, action = 'open', old_rev: nil, old_labels: [], old_assignees: []) if merge_request.project - merge_data = hook_data(merge_request, action, oldrev) + merge_data = hook_data(merge_request, action, old_rev: old_rev, old_labels: old_labels, old_assignees: old_assignees) merge_request.project.execute_hooks(merge_data, :merge_request_hooks) merge_request.project.execute_services(merge_data, :merge_request_hooks) end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index bc4a13cf4bc..fc100580c4f 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -166,7 +166,7 @@ module MergeRequests # Call merge request webhook with update branches def execute_mr_web_hooks merge_requests_for_source_branch.each do |merge_request| - execute_hooks(merge_request, 'update', @oldrev) + execute_hooks(merge_request, 'update', old_rev: @oldrev) end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index e2a80db06a6..8d5da459882 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -31,13 +31,6 @@ class NotificationService end end - # Always notify user about email added to profile - def new_email(email) - if email.user&.can?(:receive_notifications) - mailer.new_email_email(email.id).deliver_later - end - end - # When create an issue we should send an email to: # # * issue assignee if their notification level is not Disabled diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 54eb75ab9bf..19d75ff2efa 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -22,6 +22,13 @@ module Projects Projects::UnlinkForkService.new(project, current_user).execute + # The project is not necessarily a fork, so update the fork network originating + # from this project + if fork_network = project.root_of_fork_network + fork_network.update(root_project: nil, + deleted_root_project_name: project.full_name) + end + attempt_destroy_transaction(project) system_hook_service.execute_hooks_for(project, :destroy) diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index ad67e68a86a..eb5cce5ab98 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -23,11 +23,31 @@ module Projects refresh_forks_count + link_fork_network(new_project) + new_project end private + def fork_network + if @project.fork_network + @project.fork_network + elsif forked_from_project = @project.forked_from_project + # TODO: remove this case when all background migrations have completed + # this only happens when a project had a `forked_project_link` that was + # not migrated to the `fork_network` relation + forked_from_project.fork_network || forked_from_project.create_root_of_fork_network + else + @project.create_root_of_fork_network + end + end + + def link_fork_network(new_project) + fork_network.fork_network_members.create(project: new_project, + forked_from_project: @project) + end + def refresh_forks_count Projects::ForksCountService.new(@project).refresh_cache end diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index f30b40423c8..abe414d0c05 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -16,6 +16,7 @@ module Projects refresh_forks_count(@project.forked_from_project) @project.forked_project_link.destroy + @project.fork_network_member.destroy end def refresh_forks_count(project) diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index a077b3584b0..955d934838b 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -458,7 +458,7 @@ module QuickActions target_branch_param.strip end command :target_branch do |branch_name| - @updates[:target_branch] = branch_name if project.repository.branch_names.include?(branch_name) + @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name) end desc 'Move issue from one column of the board to another' diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 1f66a2668f9..a52dce6cb4b 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -591,6 +591,13 @@ module SystemNoteService create_note(NoteSummary.new(noteable, project, author, body, action: 'duplicate')) end + def discussion_lock(issuable, author) + action = issuable.discussion_locked? ? 'locked' : 'unlocked' + body = "#{action} this #{issuable.class.to_s.titleize.downcase}" + + create_note(NoteSummary.new(issuable, issuable.project, author, body, action: action)) + end + private def notes_for_mentioner(mentioner, noteable, notes) diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 6ee96d6a0f8..b6125cafa83 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -43,8 +43,8 @@ class TodoService # # * create a pending todo for new assignee if issue is assigned # - def reassigned_issue(issue, current_user) - create_assignment_todo(issue, current_user) + def reassigned_issue(issue, current_user, old_assignees = []) + create_assignment_todo(issue, current_user, old_assignees) end # When create a merge request we should: @@ -254,10 +254,11 @@ class TodoService create_mention_todos(project, target, author, note, skip_users) end - def create_assignment_todo(issuable, author) + def create_assignment_todo(issuable, author, old_assignees = []) if issuable.assignees.any? + assignees = issuable.assignees - old_assignees attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED) - create_todos(issuable.assignees, attributes) + create_todos(assignees, attributes) end end diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb index ab532a1fdcf..5803404c3c8 100644 --- a/app/services/users/activity_service.rb +++ b/app/services/users/activity_service.rb @@ -14,7 +14,7 @@ module Users private def record_activity - Gitlab::UserActivities.record(@author.id) + Gitlab::UserActivities.record(@author.id) if Gitlab::Database.read_write? Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@author.id} (username: #{@author.username})") end |