diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2017-04-13 15:08:52 +0200 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2017-04-13 15:08:52 +0200 |
commit | a8231ea1befd803fb5892ea3e6679219f5d7d8e5 (patch) | |
tree | a5bfe0cec13735bb36ba010c7dbacfaf13ba135f /app/services | |
parent | 57f8f2d7ff851fc4f5d1c81a28a023855f1985b7 (diff) | |
parent | 37ab389139a21a8ab10ddbbddec1b61f720b27ab (diff) | |
download | gitlab-ce-a8231ea1befd803fb5892ea3e6679219f5d7d8e5.tar.gz |
Merge branch 'master' into feature/gb/manual-actions-protected-branches-permissions
* master: (641 commits)
Revert "Fix registry for projects with uppercases in path"
Fix registry for projects with uppercases in path
Move event icons into events_helper
Reset New branch button when issue state changes
Add link to environments on kubernetes.md
Indent system notes on desktop screens
Improve webpack-dev-server compatibility with non-localhost setups.
Add changelog entry
Fix recent searches icon alignment in Safari
Use preload to avoid Rails using JOIN
Fix NUMBER_OF_TRUNCATED_DIFF_LINES re-definition error
Prepare for zero downtime migrations
Fix filtered search input width for IE
Fix the `gitlab:gitlab_shell:check` task
Fixed random failures with Poll spec
Include CONTRIBUTING.md file when importing .gitlab-ci.yml templates
Let uses hide verbose output by default
Separate examples for each other
Collapse similar sibling scenarios
Use empty_project for resources that are independent of the repo
...
Conflicts:
app/views/projects/ci/builds/_build.html.haml
Diffstat (limited to 'app/services')
19 files changed, 256 insertions, 69 deletions
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index db82b8f6c30..5e151b0f044 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -17,6 +17,7 @@ module Auth end def self.full_access_token(*names) + names = names.flatten registry = Gitlab.config.registry token = JSONWebToken::RSAToken.new(registry.key) token.issuer = registry.issuer @@ -37,13 +38,13 @@ module Auth private def authorized_token(*accesses) - token = JSONWebToken::RSAToken.new(registry.key) - token.issuer = registry.issuer - token.audience = params[:service] - token.subject = current_user.try(:username) - token.expire_time = self.class.token_expire_at - token[:access] = accesses.compact - token + JSONWebToken::RSAToken.new(registry.key).tap do |token| + token.issuer = registry.issuer + token.audience = params[:service] + token.subject = current_user.try(:username) + token.expire_time = self.class.token_expire_at + token[:access] = accesses.compact + end end def scope @@ -55,20 +56,43 @@ module Auth def process_scope(scope) type, name, actions = scope.split(':', 3) actions = actions.split(',') + path = ContainerRegistry::Path.new(name) + return unless type == 'repository' - process_repository_access(type, name, actions) + process_repository_access(type, path, actions) end - def process_repository_access(type, name, actions) - requested_project = Project.find_by_full_path(name) + def process_repository_access(type, path, actions) + return unless path.valid? + + requested_project = path.repository_project + return unless requested_project actions = actions.select do |action| can_access?(requested_project, action) end - { type: type, name: name, actions: actions } if actions.present? + return unless actions.present? + + # At this point user/build is already authenticated. + # + ensure_container_repository!(path, actions) + + { type: type, name: path.to_s, actions: actions } + end + + ## + # Because we do not have two way communication with registry yet, + # we create a container repository image resource when push to the + # registry is successfuly authorized. + # + def ensure_container_repository!(path, actions) + return if path.has_repository? + return unless actions.include?('push') + + ContainerRepository.create_from_path!(path) end def can_access?(requested_project, requested_action) @@ -101,6 +125,11 @@ module Auth can?(current_user, :read_container_image, requested_project) end + ## + # We still support legacy pipeline triggers which do not have associated + # actor. New permissions model and new triggers are always associated with + # an actor, so this should be improved in 10.0 version of GitLab. + # def build_can_push?(requested_project) # Build can push only to the project from which it originates has_authentication_ability?(:build_create_container_image) && @@ -113,14 +142,11 @@ module Auth end def error(code, status:, message: '') - { - errors: [{ code: code, message: message }], - http_status: status - } + { errors: [{ code: code, message: message }], http_status: status } end def has_authentication_ability?(capability) - (@authentication_abilities || []).include?(capability) + @authentication_abilities.to_a.include?(capability) end end end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 38a85e9fc42..21350be5557 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -53,6 +53,8 @@ module Ci .execute(pipeline) end + cancel_pending_pipelines if project.auto_cancel_pending_pipelines? + pipeline.tap(&:process!) end @@ -63,6 +65,22 @@ module Ci pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i end + def cancel_pending_pipelines + Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables| + cancelables.find_each do |cancelable| + cancelable.auto_cancel_running(pipeline) + end + end + end + + def auto_cancelable_pipelines + project.pipelines + .where(ref: pipeline.ref) + .where.not(id: pipeline.id) + .where.not(sha: project.repository.sha_from_ref(pipeline.ref)) + .created_or_pending + end + def commit @commit ||= project.commit(origin_sha || origin_ref) end diff --git a/app/services/ci/expire_pipeline_cache_service.rb b/app/services/ci/expire_pipeline_cache_service.rb new file mode 100644 index 00000000000..91d9c1d2ba1 --- /dev/null +++ b/app/services/ci/expire_pipeline_cache_service.rb @@ -0,0 +1,51 @@ +module Ci + class ExpirePipelineCacheService < BaseService + attr_reader :pipeline + + def execute(pipeline) + @pipeline = pipeline + store = Gitlab::EtagCaching::Store.new + + store.touch(project_pipelines_path) + store.touch(commit_pipelines_path) if pipeline.commit + store.touch(new_merge_request_pipelines_path) + merge_requests_pipelines_paths.each { |path| store.touch(path) } + + Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(@pipeline) + end + + private + + def project_pipelines_path + Gitlab::Routing.url_helpers.namespace_project_pipelines_path( + project.namespace, + project, + format: :json) + end + + def commit_pipelines_path + Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path( + project.namespace, + project, + pipeline.commit.id, + format: :json) + end + + def new_merge_request_pipelines_path + Gitlab::Routing.url_helpers.new_namespace_project_merge_request_path( + project.namespace, + project, + format: :json) + end + + def merge_requests_pipelines_paths + pipeline.merge_requests.collect do |merge_request| + Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path( + project.namespace, + project, + merge_request, + format: :json) + end + end + end +end diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb index f72ddbf690c..ecc6173a96a 100644 --- a/app/services/ci/retry_pipeline_service.rb +++ b/app/services/ci/retry_pipeline_service.rb @@ -7,9 +7,7 @@ module Ci raise Gitlab::Access::AccessDeniedError end - pipeline.builds.latest.failed_or_canceled.find_each do |build| - next unless build.retryable? - + pipeline.retryable_builds.find_each do |build| Ci::RetryBuildService.new(project, current_user) .reprocess(build) end diff --git a/app/services/concerns/issues/resolve_discussions.rb b/app/services/concerns/issues/resolve_discussions.rb index 297c7d696c3..910a2a15e5d 100644 --- a/app/services/concerns/issues/resolve_discussions.rb +++ b/app/services/concerns/issues/resolve_discussions.rb @@ -21,11 +21,11 @@ module Issues @discussions_to_resolve ||= if discussion_to_resolve_id discussion_or_nil = merge_request_to_resolve_discussions_of - .find_diff_discussion(discussion_to_resolve_id) + .find_discussion(discussion_to_resolve_id) Array(discussion_or_nil) else merge_request_to_resolve_discussions_of - .resolvable_discussions + .discussions_to_be_resolved end end end diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index 11a045f4c31..38a113caec7 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -11,7 +11,7 @@ class DeleteBranchService < BaseService return error('Cannot remove HEAD branch', 405) end - if project.protected_branch?(branch_name) + if ProtectedBranch.protected?(project, branch_name) return error('Protected branch cant be removed', 405) end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index bc7431c89a8..45411c779cc 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -127,7 +127,7 @@ class GitPushService < BaseService project.change_head(branch_name) # Set protection on the default branch if configured - if current_application_settings.default_branch_protection != PROTECTION_NONE && !@project.protected_branch?(@project.default_branch) + if current_application_settings.default_branch_protection != PROTECTION_NONE && !ProtectedBranch.protected?(@project, @project.default_branch) params = { name: @project.default_branch, diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb index 77bced4bd5c..3a4f7b159f1 100644 --- a/app/services/issues/build_service.rb +++ b/app/services/issues/build_service.rb @@ -35,14 +35,19 @@ module Issues end def item_for_discussion(discussion) - first_note = discussion.first_note_to_resolve || discussion.first_note + first_note_to_resolve = discussion.first_note_to_resolve || discussion.first_note + + is_very_first_note = first_note_to_resolve == discussion.first_note + action = is_very_first_note ? "started" : "commented on" + + note_url = Gitlab::UrlBuilder.build(first_note_to_resolve) + other_note_count = discussion.notes.size - 1 - note_url = Gitlab::UrlBuilder.build(first_note) - discussion_info = "- [ ] #{first_note.author.to_reference} commented on a [discussion](#{note_url}): " + discussion_info = "- [ ] #{first_note_to_resolve.author.to_reference} #{action} a [discussion](#{note_url}): " discussion_info << " (+#{other_note_count} #{'comment'.pluralize(other_note_count)})" if other_note_count > 0 - note_without_block_quotes = Banzai::Filter::BlockquoteFenceFilter.new(first_note.note).call + note_without_block_quotes = Banzai::Filter::BlockquoteFenceFilter.new(first_note_to_resolve.note).call spaces = ' ' * 4 quote = note_without_block_quotes.lines.map { |line| "#{spaces}> #{line}" }.join diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb new file mode 100644 index 00000000000..ea7cacc956c --- /dev/null +++ b/app/services/notes/build_service.rb @@ -0,0 +1,25 @@ +module Notes + class BuildService < ::BaseService + def execute + in_reply_to_discussion_id = params.delete(:in_reply_to_discussion_id) + + if project && in_reply_to_discussion_id.present? + discussion = project.notes.find_discussion(in_reply_to_discussion_id) + + unless discussion + note = Note.new + note.errors.add(:base, 'Discussion to reply to cannot be found') + return note + end + + params.merge!(discussion.reply_attributes) + end + + note = Note.new(params) + note.project = project + note.author = current_user + + note + end + end +end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 61d66a26932..f3954f6f8c4 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -1,12 +1,10 @@ module Notes - class CreateService < BaseService + class CreateService < ::BaseService def execute merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha) - note = Note.new(params) - note.project = project - note.author = current_user - note.system = false + note = Notes::BuildService.new(project, current_user, params).execute + return note unless note.valid? # We execute commands (extracted from `params[:note]`) on the noteable # **before** we save the note because if the note consists of commands diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index a7142d5950e..06d8d143231 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -31,16 +31,16 @@ module Projects project.team.truncate project.destroy! - unless remove_registry_tags - raise_error('Failed to remove project container registry. Please try again or contact administrator') + unless remove_legacy_registry_tags + raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.') end unless remove_repository(repo_path) - raise_error('Failed to remove project repository. Please try again or contact administrator') + raise_error('Failed to remove project repository. Please try again or contact administrator.') end unless remove_repository(wiki_path) - raise_error('Failed to remove wiki repository. Please try again or contact administrator') + raise_error('Failed to remove wiki repository. Please try again or contact administrator.') end end @@ -68,10 +68,16 @@ module Projects end end - def remove_registry_tags + ## + # This method makes sure that we correctly remove registry tags + # for legacy image repository (when repository path equals project path). + # + def remove_legacy_registry_tags return true unless Gitlab.config.registry.enabled - project.container_registry_repository.delete_tags + ContainerRepository.build_root_repository(project).tap do |repository| + return repository.has_tags? ? repository.delete_tags! : true + end end def raise_error(message) diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb index 89d8ba60134..4b3337a5c9d 100644 --- a/app/services/protected_branches/update_service.rb +++ b/app/services/protected_branches/update_service.rb @@ -1,13 +1,10 @@ module ProtectedBranches class UpdateService < BaseService - attr_reader :protected_branch - def execute(protected_branch) raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project) - @protected_branch = protected_branch - @protected_branch.update(params) - @protected_branch + protected_branch.update(params) + protected_branch end end end diff --git a/app/services/protected_tags/create_service.rb b/app/services/protected_tags/create_service.rb new file mode 100644 index 00000000000..faba7865a17 --- /dev/null +++ b/app/services/protected_tags/create_service.rb @@ -0,0 +1,11 @@ +module ProtectedTags + class CreateService < BaseService + attr_reader :protected_tag + + def execute + raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project) + + project.protected_tags.create(params) + end + end +end diff --git a/app/services/protected_tags/update_service.rb b/app/services/protected_tags/update_service.rb new file mode 100644 index 00000000000..aea6a48968d --- /dev/null +++ b/app/services/protected_tags/update_service.rb @@ -0,0 +1,10 @@ +module ProtectedTags + class UpdateService < BaseService + def execute(protected_tag) + raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project) + + protected_tag.update(params) + protected_tag + end + end +end diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index 595653ea58a..49d45ec9dbd 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -7,6 +7,8 @@ module SlashCommands # Takes a text and interprets the commands that are extracted from it. # Returns the content without commands, and hash of changes to be applied to a record. def execute(content, issuable) + return [content, {}] unless current_user.can?(:use_slash_commands) + @issuable = issuable @updates = {} diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 35cfcc3682e..c9e25c7aaa2 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -228,12 +228,10 @@ module SystemNoteService def discussion_continued_in_issue(discussion, project, author, issue) body = "created #{issue.to_reference} to continue this discussion" + note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body) - note_params = discussion.reply_attributes.merge(project: project, author: author, note: body) - note_params[:type] = note_params.delete(:note_type) - - note = Note.create(note_params.merge(system: true)) - note.system_note_metadata = SystemNoteMetadata.new({ action: 'discussion' }) + note = Note.create(note_attributes.merge(system: true)) + note.system_note_metadata = SystemNoteMetadata.new(action: 'discussion') note end diff --git a/app/services/users/create_service.rb b/app/services/users/create_service.rb index a847a71a66a..93ca7b1141a 100644 --- a/app/services/users/create_service.rb +++ b/app/services/users/create_service.rb @@ -11,7 +11,7 @@ module Users user = User.new(build_user_params) - if current_user&.is_admin? + if current_user&.admin? if params[:reset_password] @reset_token = user.generate_reset_token params[:force_random_password] = true @@ -47,7 +47,7 @@ module Users private def can_create_user? - (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.is_admin? + (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.admin? end # Allowed params for creating a user (admins only) @@ -94,7 +94,7 @@ module Users end def build_user_params - if current_user&.is_admin? + if current_user&.admin? user_params = params.slice(*admin_create_params) user_params[:created_by_id] = current_user&.id diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index a3b32a71a64..ba58b174cc0 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -26,7 +26,7 @@ module Users ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute end - move_issues_to_ghost_user(user) + MigrateToGhostUserService.new(user).execute # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing namespace = user.namespace @@ -35,22 +35,5 @@ module Users user_data end - - private - - def move_issues_to_ghost_user(user) - # Block the user before moving issues to prevent a data race. - # If the user creates an issue after `move_issues_to_ghost_user` - # runs and before the user is destroyed, the destroy will fail with - # an exception. We block the user so that issues can't be created - # after `move_issues_to_ghost_user` runs and before the destroy happens. - user.block - - ghost_user = User.ghost - - user.issues.update_all(author_id: ghost_user.id) - - user.reload - end end end diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb new file mode 100644 index 00000000000..1e1ed1791ec --- /dev/null +++ b/app/services/users/migrate_to_ghost_user_service.rb @@ -0,0 +1,59 @@ +# When a user is destroyed, some of their associated records are +# moved to a "Ghost User", to prevent these associated records from +# being destroyed. +# +# For example, all the issues/MRs a user has created are _not_ destroyed +# when the user is destroyed. +module Users + class MigrateToGhostUserService + extend ActiveSupport::Concern + + attr_reader :ghost_user, :user + + def initialize(user) + @user = user + end + + def execute + # Block the user before moving records to prevent a data race. + # For example, if the user creates an issue after `migrate_issues` + # runs and before the user is destroyed, the destroy will fail with + # an exception. + user.block + + user.transaction do + @ghost_user = User.ghost + + migrate_issues + migrate_merge_requests + migrate_notes + migrate_abuse_reports + migrate_award_emoji + end + + user.reload + end + + private + + def migrate_issues + user.issues.update_all(author_id: ghost_user.id) + end + + def migrate_merge_requests + user.merge_requests.update_all(author_id: ghost_user.id) + end + + def migrate_notes + user.notes.update_all(author_id: ghost_user.id) + end + + def migrate_abuse_reports + user.reported_abuse_reports.update_all(reporter_id: ghost_user.id) + end + + def migrate_award_emoji + user.award_emoji.update_all(user_id: ghost_user.id) + end + end +end |