Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-04-20 21:38:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-04-20 21:38:24 +0300
commit983a0bba5d2a042c4a3bbb22432ec192c7501d82 (patch)
treeb153cd387c14ba23bd5a07514c7c01fddf6a78a0 /app/services
parenta2bddee2cdb38673df0e004d5b32d9f77797de64 (diff)
Add latest changes from gitlab-org/gitlab@12-10-stable-ee
Diffstat (limited to 'app/services')
-rw-r--r--app/services/auto_merge/base_service.rb8
-rw-r--r--app/services/auto_merge/merge_when_pipeline_succeeds_service.rb4
-rw-r--r--app/services/auto_merge_service.rb37
-rw-r--r--app/services/clusters/applications/base_service.rb12
-rw-r--r--app/services/concerns/deploy_token_methods.rb8
-rw-r--r--app/services/emails/destroy_service.rb2
-rw-r--r--app/services/git/branch_push_service.rb2
-rw-r--r--app/services/git/process_ref_changes_service.rb8
-rw-r--r--app/services/groups/deploy_tokens/create_service.rb6
-rw-r--r--app/services/groups/import_export/import_service.rb10
-rw-r--r--app/services/groups/transfer_service.rb22
-rw-r--r--app/services/issues/export_csv_service.rb77
-rw-r--r--app/services/jira_import/start_import_service.rb8
-rw-r--r--app/services/merge_requests/merge_orchestration_service.rb40
-rw-r--r--app/services/merge_requests/pushed_branches_service.rb32
-rw-r--r--app/services/merge_requests/update_service.rb19
-rw-r--r--app/services/metrics/dashboard/transient_embed_service.rb5
-rw-r--r--app/services/personal_access_tokens/create_service.rb31
-rw-r--r--app/services/pod_logs/base_service.rb6
-rw-r--r--app/services/pod_logs/elasticsearch_service.rb21
-rw-r--r--app/services/pod_logs/kubernetes_service.rb19
-rw-r--r--app/services/projects/deploy_tokens/create_service.rb6
-rw-r--r--app/services/resources/create_access_token_service.rb111
-rw-r--r--app/services/snippets/create_service.rb4
-rw-r--r--app/services/terraform/remote_state_handler.rb77
-rw-r--r--app/services/users/build_service.rb12
26 files changed, 524 insertions, 63 deletions
diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb
index e08b4ac2260..1de2f31f87c 100644
--- a/app/services/auto_merge/base_service.rb
+++ b/app/services/auto_merge/base_service.rb
@@ -49,6 +49,14 @@ module AutoMerge
end
end
+ def available_for?(merge_request)
+ strong_memoize("available_for_#{merge_request.id}") do
+ merge_request.can_be_merged_by?(current_user) &&
+ merge_request.mergeable_state?(skip_ci_check: true) &&
+ yield
+ end
+ end
+
private
def strategy
diff --git a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
index 7c0e9228b28..9ae5bd1b5ec 100644
--- a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
+++ b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
@@ -30,7 +30,9 @@ module AutoMerge
end
def available_for?(merge_request)
- merge_request.actual_head_pipeline&.active?
+ super do
+ merge_request.actual_head_pipeline&.active?
+ end
end
end
end
diff --git a/app/services/auto_merge_service.rb b/app/services/auto_merge_service.rb
index eee227be202..c5cbcc7c93b 100644
--- a/app/services/auto_merge_service.rb
+++ b/app/services/auto_merge_service.rb
@@ -1,23 +1,26 @@
# frozen_string_literal: true
class AutoMergeService < BaseService
+ include Gitlab::Utils::StrongMemoize
+
STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds'
STRATEGIES = [STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS].freeze
class << self
- def all_strategies
+ def all_strategies_ordered_by_preference
STRATEGIES
end
def get_service_class(strategy)
- return unless all_strategies.include?(strategy)
+ return unless all_strategies_ordered_by_preference.include?(strategy)
"::AutoMerge::#{strategy.camelize}Service".constantize
end
end
- def execute(merge_request, strategy)
- service = get_service_instance(strategy)
+ def execute(merge_request, strategy = nil)
+ strategy ||= preferred_strategy(merge_request)
+ service = get_service_instance(merge_request, strategy)
return :failed unless service&.available_for?(merge_request)
@@ -27,37 +30,47 @@ class AutoMergeService < BaseService
def update(merge_request)
return :failed unless merge_request.auto_merge_enabled?
- get_service_instance(merge_request.auto_merge_strategy).update(merge_request)
+ strategy = merge_request.auto_merge_strategy
+ get_service_instance(merge_request, strategy).update(merge_request)
end
def process(merge_request)
return unless merge_request.auto_merge_enabled?
- get_service_instance(merge_request.auto_merge_strategy).process(merge_request)
+ strategy = merge_request.auto_merge_strategy
+ get_service_instance(merge_request, strategy).process(merge_request)
end
def cancel(merge_request)
return error("Can't cancel the automatic merge", 406) unless merge_request.auto_merge_enabled?
- get_service_instance(merge_request.auto_merge_strategy).cancel(merge_request)
+ strategy = merge_request.auto_merge_strategy
+ get_service_instance(merge_request, strategy).cancel(merge_request)
end
def abort(merge_request, reason)
return error("Can't abort the automatic merge", 406) unless merge_request.auto_merge_enabled?
- get_service_instance(merge_request.auto_merge_strategy).abort(merge_request, reason)
+ strategy = merge_request.auto_merge_strategy
+ get_service_instance(merge_request, strategy).abort(merge_request, reason)
end
def available_strategies(merge_request)
- self.class.all_strategies.select do |strategy|
- get_service_instance(strategy).available_for?(merge_request)
+ self.class.all_strategies_ordered_by_preference.select do |strategy|
+ get_service_instance(merge_request, strategy).available_for?(merge_request)
end
end
+ def preferred_strategy(merge_request)
+ available_strategies(merge_request).first
+ end
+
private
- def get_service_instance(strategy)
- self.class.get_service_class(strategy)&.new(project, current_user, params)
+ def get_service_instance(merge_request, strategy)
+ strong_memoize("service_instance_#{merge_request.id}_#{strategy}") do
+ self.class.get_service_class(strategy)&.new(project, current_user, params)
+ end
end
end
diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb
index bd4ce693085..86b48b5228d 100644
--- a/app/services/clusters/applications/base_service.rb
+++ b/app/services/clusters/applications/base_service.rb
@@ -35,6 +35,18 @@ module Clusters
application.modsecurity_mode = params[:modsecurity_mode] || 0
end
+ if application.has_attribute?(:host)
+ application.host = params[:host]
+ end
+
+ if application.has_attribute?(:protocol)
+ application.protocol = params[:protocol]
+ end
+
+ if application.has_attribute?(:port)
+ application.port = params[:port]
+ end
+
if application.respond_to?(:oauth_application)
application.oauth_application = create_oauth_application(application, request)
end
diff --git a/app/services/concerns/deploy_token_methods.rb b/app/services/concerns/deploy_token_methods.rb
index c875342a07c..f59a50d6878 100644
--- a/app/services/concerns/deploy_token_methods.rb
+++ b/app/services/concerns/deploy_token_methods.rb
@@ -14,4 +14,12 @@ module DeployTokenMethods
deploy_token.destroy
end
+
+ def create_deploy_token_payload_for(deploy_token)
+ if deploy_token.persisted?
+ success(deploy_token: deploy_token, http_status: :created)
+ else
+ error(deploy_token.errors.full_messages.to_sentence, :bad_request, pass_back: { deploy_token: deploy_token })
+ end
+ end
end
diff --git a/app/services/emails/destroy_service.rb b/app/services/emails/destroy_service.rb
index a0b43ad3d08..6e671f52d57 100644
--- a/app/services/emails/destroy_service.rb
+++ b/app/services/emails/destroy_service.rb
@@ -13,7 +13,7 @@ module Emails
user.update_secondary_emails!
end
- result[:status] == 'success'
+ result[:status] == :success
end
end
end
diff --git a/app/services/git/branch_push_service.rb b/app/services/git/branch_push_service.rb
index da45bcc7eaa..5c1ee981d0c 100644
--- a/app/services/git/branch_push_service.rb
+++ b/app/services/git/branch_push_service.rb
@@ -36,6 +36,8 @@ module Git
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
def enqueue_update_mrs
+ return if params[:merge_request_branches]&.exclude?(branch_name)
+
UpdateMergeRequestsWorker.perform_async(
project.id,
current_user.id,
diff --git a/app/services/git/process_ref_changes_service.rb b/app/services/git/process_ref_changes_service.rb
index 387cd29d69d..6d1ff97016b 100644
--- a/app/services/git/process_ref_changes_service.rb
+++ b/app/services/git/process_ref_changes_service.rb
@@ -42,6 +42,7 @@ module Git
push_service_class = push_service_class_for(ref_type)
create_bulk_push_event = changes.size > Gitlab::CurrentSettings.push_event_activities_limit
+ merge_request_branches = merge_request_branches_for(changes)
changes.each do |change|
push_service_class.new(
@@ -49,6 +50,7 @@ module Git
current_user,
change: change,
push_options: params[:push_options],
+ merge_request_branches: merge_request_branches,
create_pipelines: change[:index] < PIPELINE_PROCESS_LIMIT || Feature.enabled?(:git_push_create_all_pipelines, project),
execute_project_hooks: execute_project_hooks,
create_push_event: !create_bulk_push_event
@@ -71,5 +73,11 @@ module Git
Git::BranchPushService
end
+
+ def merge_request_branches_for(changes)
+ return if Feature.disabled?(:refresh_only_existing_merge_requests_on_push, default_enabled: true)
+
+ @merge_requests_branches ||= MergeRequests::PushedBranchesService.new(project, current_user, changes: changes).execute
+ end
end
end
diff --git a/app/services/groups/deploy_tokens/create_service.rb b/app/services/groups/deploy_tokens/create_service.rb
index 81f761eb61d..aee423659ef 100644
--- a/app/services/groups/deploy_tokens/create_service.rb
+++ b/app/services/groups/deploy_tokens/create_service.rb
@@ -8,11 +8,7 @@ module Groups
def execute
deploy_token = create_deploy_token_for(@group, params)
- if deploy_token.persisted?
- success(deploy_token: deploy_token, http_status: :created)
- else
- error(deploy_token.errors.full_messages.to_sentence, :bad_request)
- end
+ create_deploy_token_payload_for(deploy_token)
end
end
end
diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb
index 548a4a98dc1..f62b9d3c8a6 100644
--- a/app/services/groups/import_export/import_service.rb
+++ b/app/services/groups/import_export/import_service.rb
@@ -33,10 +33,12 @@ module Groups
end
def restorer
- @restorer ||= Gitlab::ImportExport::Group::TreeRestorer.new(user: @current_user,
- shared: @shared,
- group: @group,
- group_hash: nil)
+ @restorer ||= Gitlab::ImportExport::Group::LegacyTreeRestorer.new(
+ user: @current_user,
+ shared: @shared,
+ group: @group,
+ group_hash: nil
+ )
end
def remove_import_file
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index 4e7875e0491..fe3ab884302 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -2,15 +2,6 @@
module Groups
class TransferService < Groups::BaseService
- ERROR_MESSAGES = {
- database_not_supported: s_('TransferGroup|Database is not supported.'),
- namespace_with_same_path: s_('TransferGroup|The parent group already has a subgroup with the same path.'),
- group_is_already_root: s_('TransferGroup|Group is already a root group.'),
- same_parent_as_current: s_('TransferGroup|Group is already associated to the parent group.'),
- invalid_policies: s_("TransferGroup|You don't have enough permissions."),
- group_contains_images: s_('TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again.')
- }.freeze
-
TransferError = Class.new(StandardError)
attr_reader :error, :new_parent_group
@@ -124,7 +115,18 @@ module Groups
end
def raise_transfer_error(message)
- raise TransferError, ERROR_MESSAGES[message]
+ raise TransferError, localized_error_messages[message]
+ end
+
+ def localized_error_messages
+ {
+ database_not_supported: s_('TransferGroup|Database is not supported.'),
+ namespace_with_same_path: s_('TransferGroup|The parent group already has a subgroup with the same path.'),
+ group_is_already_root: s_('TransferGroup|Group is already a root group.'),
+ same_parent_as_current: s_('TransferGroup|Group is already associated to the parent group.'),
+ invalid_policies: s_("TransferGroup|You don't have enough permissions."),
+ group_contains_images: s_('TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again.')
+ }.freeze
end
end
end
diff --git a/app/services/issues/export_csv_service.rb b/app/services/issues/export_csv_service.rb
new file mode 100644
index 00000000000..1dcdfb9faea
--- /dev/null
+++ b/app/services/issues/export_csv_service.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Issues
+ class ExportCsvService
+ include Gitlab::Routing.url_helpers
+ include GitlabRoutingHelper
+
+ # Target attachment size before base64 encoding
+ TARGET_FILESIZE = 15000000
+
+ attr_reader :project
+
+ def initialize(issues_relation, project)
+ @issues = issues_relation
+ @labels = @issues.labels_hash
+ @project = project
+ end
+
+ def csv_data
+ csv_builder.render(TARGET_FILESIZE)
+ end
+
+ def email(user)
+ Notify.issues_csv_email(user, project, csv_data, csv_builder.status).deliver_now
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def csv_builder
+ @csv_builder ||=
+ CsvBuilder.new(@issues.preload(associations_to_preload), header_to_value_hash)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ def associations_to_preload
+ %i(author assignees timelogs)
+ end
+
+ def header_to_value_hash
+ {
+ 'Issue ID' => 'iid',
+ 'URL' => -> (issue) { issue_url(issue) },
+ 'Title' => 'title',
+ 'State' => -> (issue) { issue.closed? ? 'Closed' : 'Open' },
+ 'Description' => 'description',
+ 'Author' => 'author_name',
+ 'Author Username' => -> (issue) { issue.author&.username },
+ 'Assignee' => -> (issue) { issue.assignees.map(&:name).join(', ') },
+ 'Assignee Username' => -> (issue) { issue.assignees.map(&:username).join(', ') },
+ 'Confidential' => -> (issue) { issue.confidential? ? 'Yes' : 'No' },
+ 'Locked' => -> (issue) { issue.discussion_locked? ? 'Yes' : 'No' },
+ 'Due Date' => -> (issue) { issue.due_date&.to_s(:csv) },
+ 'Created At (UTC)' => -> (issue) { issue.created_at&.to_s(:csv) },
+ 'Updated At (UTC)' => -> (issue) { issue.updated_at&.to_s(:csv) },
+ 'Closed At (UTC)' => -> (issue) { issue.closed_at&.to_s(:csv) },
+ 'Milestone' => -> (issue) { issue.milestone&.title },
+ 'Weight' => -> (issue) { issue.weight },
+ 'Labels' => -> (issue) { issue_labels(issue) },
+ 'Time Estimate' => ->(issue) { issue.time_estimate.to_s(:csv) },
+ 'Time Spent' => -> (issue) { issue_time_spent(issue) }
+ }
+ end
+
+ def issue_labels(issue)
+ @labels[issue.id].sort.join(',').presence
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def issue_time_spent(issue)
+ issue.timelogs.map(&:time_spent).sum
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
+
+Issues::ExportCsvService.prepend_if_ee('EE::Issues::ExportCsvService')
diff --git a/app/services/jira_import/start_import_service.rb b/app/services/jira_import/start_import_service.rb
index e8d9e6734bd..de4e490281f 100644
--- a/app/services/jira_import/start_import_service.rb
+++ b/app/services/jira_import/start_import_service.rb
@@ -62,12 +62,12 @@ module JiraImport
end
def validate
- return build_error_response(_('Jira import feature is disabled.')) unless project.jira_issues_import_feature_flag_enabled?
- return build_error_response(_('You do not have permissions to run the import.')) unless user.can?(:admin_project, project)
- return build_error_response(_('Cannot import because issues are not available in this project.')) unless project.feature_available?(:issues, user)
- return build_error_response(_('Jira integration not configured.')) unless project.jira_service&.active?
+ project.validate_jira_import_settings!(user: user)
+
return build_error_response(_('Unable to find Jira project to import data from.')) if jira_project_key.blank?
return build_error_response(_('Jira import is already running.')) if import_in_progress?
+ rescue Projects::ImportService::Error => e
+ build_error_response(e.message)
end
def build_error_response(message)
diff --git a/app/services/merge_requests/merge_orchestration_service.rb b/app/services/merge_requests/merge_orchestration_service.rb
new file mode 100644
index 00000000000..24341ef1145
--- /dev/null
+++ b/app/services/merge_requests/merge_orchestration_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MergeOrchestrationService < ::BaseService
+ def execute(merge_request)
+ return unless can_merge?(merge_request)
+
+ merge_request.update(merge_error: nil)
+
+ if can_merge_automatically?(merge_request)
+ auto_merge_service.execute(merge_request)
+ else
+ merge_request.merge_async(current_user.id, params)
+ end
+ end
+
+ def can_merge?(merge_request)
+ can_merge_automatically?(merge_request) || can_merge_immediately?(merge_request)
+ end
+
+ def preferred_auto_merge_strategy(merge_request)
+ auto_merge_service.preferred_strategy(merge_request)
+ end
+
+ private
+
+ def can_merge_immediately?(merge_request)
+ merge_request.can_be_merged_by?(current_user) &&
+ merge_request.mergeable_state?
+ end
+
+ def can_merge_automatically?(merge_request)
+ auto_merge_service.available_strategies(merge_request).any?
+ end
+
+ def auto_merge_service
+ @auto_merge_service ||= AutoMergeService.new(project, current_user, params)
+ end
+ end
+end
diff --git a/app/services/merge_requests/pushed_branches_service.rb b/app/services/merge_requests/pushed_branches_service.rb
new file mode 100644
index 00000000000..afcf0f7678a
--- /dev/null
+++ b/app/services/merge_requests/pushed_branches_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class PushedBranchesService < MergeRequests::BaseService
+ include ::Gitlab::Utils::StrongMemoize
+
+ # Skip moving this logic into models since it's too specific
+ # rubocop: disable CodeReuse/ActiveRecord
+ def execute
+ return [] if branch_names.blank?
+
+ source_branches = project.source_of_merge_requests.opened
+ .from_source_branches(branch_names).pluck(:source_branch)
+
+ target_branches = project.merge_requests.opened
+ .by_target_branch(branch_names).distinct.pluck(:target_branch)
+
+ source_branches.concat(target_branches).to_set
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ def branch_names
+ strong_memoize(:branch_names) do
+ params[:changes].map do |change|
+ Gitlab::Git.branch_name(change[:ref])
+ end.compact
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 1516e33a7c6..2d33e87bf4b 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -79,14 +79,21 @@ module MergeRequests
def merge_from_quick_action(merge_request)
last_diff_sha = params.delete(:merge)
- return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
- merge_request.update(merge_error: nil)
-
- if merge_request.head_pipeline_active?
- AutoMergeService.new(project, current_user, { sha: last_diff_sha }).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ if Feature.enabled?(:merge_orchestration_service, merge_request.project, default_enabled: true)
+ MergeRequests::MergeOrchestrationService
+ .new(project, current_user, { sha: last_diff_sha })
+ .execute(merge_request)
else
- merge_request.merge_async(current_user.id, { sha: last_diff_sha })
+ return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
+
+ merge_request.update(merge_error: nil)
+
+ if merge_request.head_pipeline_active?
+ AutoMergeService.new(project, current_user, { sha: last_diff_sha }).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ else
+ merge_request.merge_async(current_user.id, { sha: last_diff_sha })
+ end
end
end
diff --git a/app/services/metrics/dashboard/transient_embed_service.rb b/app/services/metrics/dashboard/transient_embed_service.rb
index 035707dceb9..ce81f337e47 100644
--- a/app/services/metrics/dashboard/transient_embed_service.rb
+++ b/app/services/metrics/dashboard/transient_embed_service.rb
@@ -30,6 +30,11 @@ module Metrics
def sequence
[STAGES::EndpointInserter]
end
+
+ override :identifiers
+ def identifiers
+ Digest::SHA256.hexdigest(params[:embed_json])
+ end
end
end
end
diff --git a/app/services/personal_access_tokens/create_service.rb b/app/services/personal_access_tokens/create_service.rb
new file mode 100644
index 00000000000..ff9bb7d6802
--- /dev/null
+++ b/app/services/personal_access_tokens/create_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module PersonalAccessTokens
+ class CreateService < BaseService
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @params = params.dup
+ end
+
+ def execute
+ personal_access_token = current_user.personal_access_tokens.create(params.slice(*allowed_params))
+
+ if personal_access_token.persisted?
+ ServiceResponse.success(payload: { personal_access_token: personal_access_token })
+ else
+ ServiceResponse.error(message: personal_access_token.errors.full_messages.to_sentence)
+ end
+ end
+
+ private
+
+ def allowed_params
+ [
+ :name,
+ :impersonation,
+ :scopes,
+ :expires_at
+ ]
+ end
+ end
+end
diff --git a/app/services/pod_logs/base_service.rb b/app/services/pod_logs/base_service.rb
index 8cc8fb913a2..2451ab8e0ce 100644
--- a/app/services/pod_logs/base_service.rb
+++ b/app/services/pod_logs/base_service.rb
@@ -62,13 +62,11 @@ module PodLogs
end
def get_raw_pods(result)
- result[:raw_pods] = cluster.kubeclient.get_pods(namespace: namespace)
-
- success(result)
+ raise NotImplementedError
end
def get_pod_names(result)
- result[:pods] = result[:raw_pods].map(&:metadata).map(&:name)
+ result[:pods] = result[:raw_pods].map { |p| p[:name] }
success(result)
end
diff --git a/app/services/pod_logs/elasticsearch_service.rb b/app/services/pod_logs/elasticsearch_service.rb
index 0a5185999ab..aac0fa424ca 100644
--- a/app/services/pod_logs/elasticsearch_service.rb
+++ b/app/services/pod_logs/elasticsearch_service.rb
@@ -23,6 +23,23 @@ module PodLogs
super + %i(cursor)
end
+ def get_raw_pods(result)
+ client = cluster&.application_elastic_stack&.elasticsearch_client
+ return error(_('Unable to connect to Elasticsearch')) unless client
+
+ result[:raw_pods] = ::Gitlab::Elasticsearch::Logs::Pods.new(client).pods(namespace)
+
+ success(result)
+ rescue Elasticsearch::Transport::Transport::ServerError => e
+ ::Gitlab::ErrorTracking.track_exception(e)
+
+ error(_('Elasticsearch returned status code: %{status_code}') % {
+ # ServerError is the parent class of exceptions named after HTTP status codes, eg: "Elasticsearch::Transport::Transport::Errors::NotFound"
+ # there is no method on the exception other than the class name to determine the type of error encountered.
+ status_code: e.class.name.split('::').last
+ })
+ end
+
def check_times(result)
result[:start_time] = params['start_time'] if params.key?('start_time') && Time.iso8601(params['start_time'])
result[:end_time] = params['end_time'] if params.key?('end_time') && Time.iso8601(params['end_time'])
@@ -48,7 +65,7 @@ module PodLogs
client = cluster&.application_elastic_stack&.elasticsearch_client
return error(_('Unable to connect to Elasticsearch')) unless client
- response = ::Gitlab::Elasticsearch::Logs.new(client).pod_logs(
+ response = ::Gitlab::Elasticsearch::Logs::Lines.new(client).pod_logs(
namespace,
pod_name: result[:pod_name],
container_name: result[:container_name],
@@ -69,7 +86,7 @@ module PodLogs
# there is no method on the exception other than the class name to determine the type of error encountered.
status_code: e.class.name.split('::').last
})
- rescue ::Gitlab::Elasticsearch::Logs::InvalidCursor
+ rescue ::Gitlab::Elasticsearch::Logs::Lines::InvalidCursor
error(_('Invalid cursor value provided'))
end
end
diff --git a/app/services/pod_logs/kubernetes_service.rb b/app/services/pod_logs/kubernetes_service.rb
index 31e26912c73..0a8072a9037 100644
--- a/app/services/pod_logs/kubernetes_service.rb
+++ b/app/services/pod_logs/kubernetes_service.rb
@@ -21,6 +21,17 @@ module PodLogs
private
+ def get_raw_pods(result)
+ result[:raw_pods] = cluster.kubeclient.get_pods(namespace: namespace).map do |pod|
+ {
+ name: pod.metadata.name,
+ container_names: pod.spec.containers.map(&:name)
+ }
+ end
+
+ success(result)
+ end
+
def check_pod_name(result)
# If pod_name is not received as parameter, get the pod logs of the first
# pod of this namespace.
@@ -43,11 +54,11 @@ module PodLogs
end
def check_container_name(result)
- pod_details = result[:raw_pods].find { |p| p.metadata.name == result[:pod_name] }
- containers = pod_details.spec.containers.map(&:name)
+ pod_details = result[:raw_pods].find { |p| p[:name] == result[:pod_name] }
+ container_names = pod_details[:container_names]
# select first container if not specified
- result[:container_name] ||= containers.first
+ result[:container_name] ||= container_names.first
unless result[:container_name]
return error(_('No containers available'))
@@ -58,7 +69,7 @@ module PodLogs
' %{max_length} chars' % { max_length: K8S_NAME_MAX_LENGTH }))
end
- unless containers.include?(result[:container_name])
+ unless container_names.include?(result[:container_name])
return error(_('Container does not exist'))
end
diff --git a/app/services/projects/deploy_tokens/create_service.rb b/app/services/projects/deploy_tokens/create_service.rb
index 2e71650b066..592198ef241 100644
--- a/app/services/projects/deploy_tokens/create_service.rb
+++ b/app/services/projects/deploy_tokens/create_service.rb
@@ -8,11 +8,7 @@ module Projects
def execute
deploy_token = create_deploy_token_for(@project, params)
- if deploy_token.persisted?
- success(deploy_token: deploy_token, http_status: :created)
- else
- error(deploy_token.errors.full_messages.to_sentence, :bad_request)
- end
+ create_deploy_token_payload_for(deploy_token)
end
end
end
diff --git a/app/services/resources/create_access_token_service.rb b/app/services/resources/create_access_token_service.rb
new file mode 100644
index 00000000000..fd3c8d78e58
--- /dev/null
+++ b/app/services/resources/create_access_token_service.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+module Resources
+ class CreateAccessTokenService < BaseService
+ attr_accessor :resource_type, :resource
+
+ def initialize(resource_type, resource, user, params = {})
+ @resource_type = resource_type
+ @resource = resource
+ @current_user = user
+ @params = params.dup
+ end
+
+ def execute
+ return unless feature_enabled?
+ return error("User does not have permission to create #{resource_type} Access Token") unless has_permission_to_create?
+
+ # We skip authorization by default, since the user creating the bot is not an admin
+ # and project/group bot users are not created via sign-up
+ user = create_user
+
+ return error(user.errors.full_messages.to_sentence) unless user.persisted?
+ return error("Failed to provide maintainer access") unless provision_access(resource, user)
+
+ token_response = create_personal_access_token(user)
+
+ if token_response.success?
+ success(token_response.payload[:personal_access_token])
+ else
+ error(token_response.message)
+ end
+ end
+
+ private
+
+ def feature_enabled?
+ ::Feature.enabled?(:resource_access_token, resource)
+ end
+
+ def has_permission_to_create?
+ case resource_type
+ when 'project'
+ can?(current_user, :admin_project, resource)
+ when 'group'
+ can?(current_user, :admin_group, resource)
+ else
+ false
+ end
+ end
+
+ def create_user
+ Users::CreateService.new(current_user, default_user_params).execute(skip_authorization: true)
+ end
+
+ def default_user_params
+ {
+ name: params[:name] || "#{resource.name.to_s.humanize} bot",
+ email: generate_email,
+ username: generate_username,
+ user_type: "#{resource_type}_bot".to_sym
+ }
+ end
+
+ def generate_username
+ base_username = "#{resource_type}_#{resource.id}_bot"
+
+ uniquify.string(base_username) { |s| User.find_by_username(s) }
+ end
+
+ def generate_email
+ email_pattern = "#{resource_type}#{resource.id}_bot%s@example.com"
+
+ uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s|
+ User.find_by_email(s)
+ end
+ end
+
+ def uniquify
+ Uniquify.new
+ end
+
+ def create_personal_access_token(user)
+ PersonalAccessTokens::CreateService.new(user, personal_access_token_params).execute
+ end
+
+ def personal_access_token_params
+ {
+ name: "#{resource_type}_bot",
+ impersonation: false,
+ scopes: params[:scopes] || default_scopes,
+ expires_at: params[:expires_at] || nil
+ }
+ end
+
+ def default_scopes
+ Gitlab::Auth::API_SCOPES + Gitlab::Auth::REPOSITORY_SCOPES + Gitlab::Auth.registry_scopes - [:read_user]
+ end
+
+ def provision_access(resource, user)
+ resource.add_maintainer(user)
+ end
+
+ def error(message)
+ ServiceResponse.error(message: message)
+ end
+
+ def success(access_token)
+ ServiceResponse.success(payload: { access_token: access_token })
+ end
+ end
+end
diff --git a/app/services/snippets/create_service.rb b/app/services/snippets/create_service.rb
index 0b74bd77e28..155013db344 100644
--- a/app/services/snippets/create_service.rb
+++ b/app/services/snippets/create_service.rb
@@ -38,9 +38,7 @@ module Snippets
private
def save_and_commit
- snippet_saved = @snippet.with_transaction_returning_status do
- @snippet.save && @snippet.store_mentions!
- end
+ snippet_saved = @snippet.save
if snippet_saved && Feature.enabled?(:version_snippets, current_user)
create_repository
diff --git a/app/services/terraform/remote_state_handler.rb b/app/services/terraform/remote_state_handler.rb
new file mode 100644
index 00000000000..5bb6f6a1dee
--- /dev/null
+++ b/app/services/terraform/remote_state_handler.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Terraform
+ class RemoteStateHandler < BaseService
+ include Gitlab::OptimisticLocking
+
+ StateLockedError = Class.new(StandardError)
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def find_with_lock
+ raise ArgumentError unless params[:name].present?
+
+ state = Terraform::State.find_by(project: project, name: params[:name])
+ raise ActiveRecord::RecordNotFound.new("Couldn't find state") unless state
+
+ retry_optimistic_lock(state) { |state| yield state } if state && block_given?
+ state
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def create_or_find!
+ raise ArgumentError unless params[:name].present?
+
+ Terraform::State.create_or_find_by(project: project, name: params[:name])
+ end
+
+ def handle_with_lock
+ retrieve_with_lock do |state|
+ raise StateLockedError unless lock_matches?(state)
+
+ yield state if block_given?
+
+ state.save! unless state.destroyed?
+ end
+ end
+
+ def lock!
+ raise ArgumentError if params[:lock_id].blank?
+
+ retrieve_with_lock do |state|
+ raise StateLockedError if state.locked?
+
+ state.lock_xid = params[:lock_id]
+ state.locked_by_user = current_user
+ state.locked_at = Time.now
+
+ state.save!
+ end
+ end
+
+ def unlock!
+ retrieve_with_lock do |state|
+ # force-unlock does not pass ID, so we ignore it if it is missing
+ raise StateLockedError unless params[:lock_id].nil? || lock_matches?(state)
+
+ state.lock_xid = nil
+ state.locked_by_user = nil
+ state.locked_at = nil
+
+ state.save!
+ end
+ end
+
+ private
+
+ def retrieve_with_lock
+ create_or_find!.tap { |state| retry_optimistic_lock(state) { |state| yield state } }
+ end
+
+ def lock_matches?(state)
+ return true if state.lock_xid.nil? && params[:lock_id].nil?
+
+ ActiveSupport::SecurityUtils
+ .secure_compare(state.lock_xid.to_s, params[:lock_id].to_s)
+ end
+ end
+end
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index 6f9f307c322..3938d675596 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -81,7 +81,8 @@ module Users
:private_profile,
:organization,
:location,
- :public_email
+ :public_email,
+ :user_type
]
end
@@ -95,7 +96,8 @@ module Users
:first_name,
:last_name,
:password,
- :username
+ :username,
+ :user_type
]
end
@@ -127,6 +129,8 @@ module Users
user_params[:external] = user_external?
end
+ user_params.delete(:user_type) unless project_bot?(user_params[:user_type])
+
user_params
end
@@ -137,6 +141,10 @@ module Users
def user_external?
user_default_internal_regex_instance.match(params[:email]).nil?
end
+
+ def project_bot?(user_type)
+ user_type&.to_sym == :project_bot
+ end
end
end