summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn T Skarbek <jtslear@gmail.com>2019-04-04 08:33:59 -0400
committerJohn T Skarbek <jtslear@gmail.com>2019-04-04 08:33:59 -0400
commit7de4c89396a739eee90db3eebd75818e8cc4b9ef (patch)
tree4a2ae49d83f26eab743d5c0b6073b6e9f3d69e63
parent83ab2898ed37abfd4bf7f92000a9899957811f33 (diff)
parent2fdc7c1fd6884cf120bf82cb870f66d1bf73811a (diff)
downloadgitlab-ce-7de4c89396a739eee90db3eebd75818e8cc4b9ef.tar.gz
Merge remote-tracking branch 'origin/master' into 11-10-stable
-rw-r--r--.gitlab/issue_templates/Feature proposal.md4
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/controllers/projects/repositories_controller.rb2
-rw-r--r--app/graphql/types/ci/pipeline_type.rb6
-rw-r--r--app/graphql/types/issue_type.rb12
-rw-r--r--app/graphql/types/merge_request_type.rb8
-rw-r--r--app/graphql/types/milestone_type.rb2
-rw-r--r--app/graphql/types/project_type.rb14
-rw-r--r--app/graphql/types/query_type.rb3
-rw-r--r--app/graphql/types/user_type.rb2
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/models/repository.rb5
-rw-r--r--app/presenters/ci/bridge_presenter.rb9
-rw-r--r--app/services/clusters/applications/base_helm_service.rb12
-rw-r--r--app/services/groups/base_service.rb6
-rw-r--r--app/services/groups/create_service.rb2
-rw-r--r--app/services/groups/update_service.rb1
-rw-r--r--app/services/merge_requests/base_service.rb20
-rw-r--r--app/views/projects/buttons/_download.html.haml36
-rw-r--r--app/views/projects/buttons/_download_links.html.haml9
-rw-r--r--changelogs/unreleased/24704-download-repository-path.yml5
-rw-r--r--changelogs/unreleased/48090-filter-sensitive-metric-labels.yml5
-rw-r--r--changelogs/unreleased/54417-graphql-type-authorization.yml5
-rw-r--r--changelogs/unreleased/55964-fix-email-encoding.yml5
-rw-r--r--changelogs/unreleased/add_backtrace_to_kubernetes_log.yml5
-rw-r--r--config/initializers/graphql.rb3
-rw-r--r--config/initializers/premailer.rb3
-rw-r--r--db/post_migrate/20190325111602_rename_v2_root_namespaces.rb27
-rw-r--r--doc/ci/variables/where_variables_can_be_used.md28
-rw-r--r--doc/development/api_graphql_styleguide.md140
-rw-r--r--doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.pngbin6491 -> 19986 bytes
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md8
-rw-r--r--doc/user/project/repository/img/download_source_code.pngbin0 -> 61467 bytes
-rw-r--r--doc/user/project/repository/index.md20
-rw-r--r--doc/user/reserved_names.md1
-rw-r--r--lib/api/groups.rb1
-rw-r--r--lib/api/users.rb1
-rw-r--r--lib/gitlab/git/repository.rb7
-rw-r--r--lib/gitlab/graphql/authorize/authorize_field_service.rb94
-rw-r--r--lib/gitlab/graphql/authorize/instrumentation.rb44
-rw-r--r--lib/gitlab/graphql/errors.rb1
-rw-r--r--lib/gitlab/metrics/transaction.rb15
-rw-r--r--lib/gitlab/path_regex.rb1
-rw-r--r--lib/gitlab/workhorse.rb38
-rw-r--r--locale/gitlab.pot16
-rw-r--r--spec/features/commits/user_uses_quick_actions_spec.rb23
-rw-r--r--spec/features/merge_request/user_uses_quick_actions_spec.rb70
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/show/download_buttons_spec.rb3
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb2
-rw-r--r--spec/graphql/features/authorization_spec.rb221
-rw-r--r--spec/graphql/types/issue_type_spec.rb2
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb11
-rw-r--r--spec/graphql/types/milestone_type_spec.rb9
-rw-r--r--spec/graphql/types/project_type_spec.rb12
-rw-r--r--spec/graphql/types/query_type_spec.rb4
-rw-r--r--spec/graphql/types/user_type_spec.rb9
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb11
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb (renamed from spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb)22
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb229
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb2
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb31
-rw-r--r--spec/mailers/notify_spec.rb13
-rw-r--r--spec/presenters/ci/bridge_presenter_spec.rb15
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb22
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb72
-rw-r--r--spec/services/clusters/applications/patch_service_spec.rb70
-rw-r--r--spec/services/clusters/applications/upgrade_service_spec.rb70
-rw-r--r--spec/services/merge_requests/create_service_spec.rb13
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb11
-rw-r--r--spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb47
-rw-r--r--spec/support/shared_examples/services/base_helm_service_shared_examples.rb32
78 files changed, 1130 insertions, 570 deletions
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index b4007c1ba7b..8a49715e0e8 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -24,6 +24,10 @@ Personas can be found at https://about.gitlab.com/handbook/marketing/product-mar
<!-- See the Feature Change Documentation Workflow https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html
Add all known Documentation Requirements here, per https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#documentation-requirements -->
+### Testing
+
+<!-- What risks does this change pose? How might it affect the quality of the product? What additional test coverage or changes to tests will be needed? Will it require cross-browser testing? See the test engineering process for further guidelines: https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/ -->
+
### What does success look like, and how can we measure that?
<!-- Define both the success metrics and acceptance criteria. Note that success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this. -->
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 034552a83ee..34aae156b19 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.30.0
+1.31.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index d127a0ff9f1..a2f28f43be3 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.3.3
+8.4.0
diff --git a/Gemfile b/Gemfile
index c0815c4a2a6..a3a56de8099 100644
--- a/Gemfile
+++ b/Gemfile
@@ -419,7 +419,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 1.13.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 1.19.0', require: 'gitaly'
gem 'grpc', '~> 1.15.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 4ebcc6c81b2..e8053ada8b2 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -281,7 +281,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (1.13.0)
+ gitaly-proto (1.19.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
@@ -1017,7 +1017,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.13.0)
+ gitaly-proto (~> 1.19.0)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
gitlab-markup (~> 1.7.0)
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index b90db135b4a..efcd35a2e0e 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -287,7 +287,7 @@
list-style: none;
padding: 0 1px;
- a,
+ a:not(.btn),
button,
.menu-item {
@include dropdown-link;
@@ -351,6 +351,10 @@
// Expects up to 3 digits on the badge
margin-right: 40px;
}
+
+ .dropdown-menu-content {
+ padding: $dropdown-item-padding-y $dropdown-item-padding-x;
+ }
}
.droplab-dropdown {
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 4eeaeb860ee..3b4215b766e 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -23,7 +23,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
append_sha = false if @filename == shortname
end
- send_git_archive @repository, ref: @ref, format: params[:format], append_sha: append_sha
+ send_git_archive @repository, ref: @ref, path: params[:path], format: params[:format], append_sha: append_sha
rescue => ex
logger.error("#{self.class.name}: #{ex}")
git_not_found!
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index 18696293b97..de7d6570a3e 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -3,10 +3,12 @@
module Types
module Ci
class PipelineType < BaseObject
- expose_permissions Types::PermissionTypes::Ci::Pipeline
-
graphql_name 'Pipeline'
+ authorize :read_pipeline
+
+ expose_permissions Types::PermissionTypes::Ci::Pipeline
+
field :id, GraphQL::ID_TYPE, null: false
field :iid, GraphQL::ID_TYPE, null: false
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 5ad3ea52930..adb137dfee3 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -2,10 +2,12 @@
module Types
class IssueType < BaseObject
- expose_permissions Types::PermissionTypes::Issue
-
graphql_name 'Issue'
+ authorize :read_issue
+
+ expose_permissions Types::PermissionTypes::Issue
+
present_using IssuePresenter
field :iid, GraphQL::ID_TYPE, null: false
@@ -15,16 +17,14 @@ module Types
field :author, Types::UserType,
null: false,
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find },
- authorize: :read_user
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find }
field :assignees, Types::UserType.connection_type, null: true
field :labels, Types::LabelType.connection_type, null: true
field :milestone, Types::MilestoneType,
null: true,
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find },
- authorize: :read_milestone
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
field :due_date, Types::TimeType, null: true
field :confidential, GraphQL::BOOLEAN_TYPE, null: false
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 1ed27a14e33..120ffe0dfde 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -2,12 +2,14 @@
module Types
class MergeRequestType < BaseObject
+ graphql_name 'MergeRequest'
+
+ authorize :read_merge_request
+
expose_permissions Types::PermissionTypes::MergeRequest
present_using MergeRequestPresenter
- graphql_name 'MergeRequest'
-
field :id, GraphQL::ID_TYPE, null: false
field :iid, GraphQL::ID_TYPE, null: false
field :title, GraphQL::STRING_TYPE, null: false
@@ -48,7 +50,7 @@ module Types
field :downvotes, GraphQL::INT_TYPE, null: false
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false
- field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline, authorize: :read_pipeline
+ field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline
field :pipelines, Types::Ci::PipelineType.connection_type,
resolver: Resolvers::MergeRequestPipelinesResolver
end
diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb
index af31b572c9a..2772fbec86f 100644
--- a/app/graphql/types/milestone_type.rb
+++ b/app/graphql/types/milestone_type.rb
@@ -4,6 +4,8 @@ module Types
class MilestoneType < BaseObject
graphql_name 'Milestone'
+ authorize :read_milestone
+
field :description, GraphQL::STRING_TYPE, null: true
field :title, GraphQL::STRING_TYPE, null: false
field :state, GraphQL::STRING_TYPE, null: false
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index b96c2f3afb2..fbb4eddd13c 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -2,10 +2,12 @@
module Types
class ProjectType < BaseObject
- expose_permissions Types::PermissionTypes::Project
-
graphql_name 'Project'
+ authorize :read_project
+
+ expose_permissions Types::PermissionTypes::Project
+
field :id, GraphQL::ID_TYPE, null: false
field :full_path, GraphQL::ID_TYPE, null: false
@@ -67,14 +69,12 @@ module Types
field :merge_requests,
Types::MergeRequestType.connection_type,
null: true,
- resolver: Resolvers::MergeRequestsResolver,
- authorize: :read_merge_request
+ resolver: Resolvers::MergeRequestsResolver
field :merge_request,
Types::MergeRequestType,
null: true,
- resolver: Resolvers::MergeRequestsResolver.single,
- authorize: :read_merge_request
+ resolver: Resolvers::MergeRequestsResolver.single
field :issues,
Types::IssueType.connection_type,
@@ -88,7 +88,7 @@ module Types
field :pipelines,
Types::Ci::PipelineType.connection_type,
- null: false,
+ null: true,
resolver: Resolvers::ProjectPipelinesResolver
end
end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 472fe5d6ec2..0f655ab9d03 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -7,8 +7,7 @@ module Types
field :project, Types::ProjectType,
null: true,
resolver: Resolvers::ProjectResolver,
- description: "Find a project",
- authorize: :read_project
+ description: "Find a project"
field :metadata, Types::MetadataType,
null: true,
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index a13e65207df..6b53554314b 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -4,6 +4,8 @@ module Types
class UserType < BaseObject
graphql_name 'User'
+ authorize :read_user
+
present_using UserPresenter
field :name, GraphQL::STRING_TYPE, null: false
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index f2abb241753..009dd70c2c9 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -299,6 +299,10 @@ module ProjectsHelper
}.to_json
end
+ def directory?
+ @path.present?
+ end
+
private
def get_project_nav_tabs(project, current_user)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 574ce12b309..51ab2247a03 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -299,13 +299,14 @@ class Repository
end
end
- def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:)
+ def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:, path: nil)
raw_repository.archive_metadata(
ref,
storage_path,
project.path,
format,
- append_sha: append_sha
+ append_sha: append_sha,
+ path: path
)
end
diff --git a/app/presenters/ci/bridge_presenter.rb b/app/presenters/ci/bridge_presenter.rb
new file mode 100644
index 00000000000..ee11cffe355
--- /dev/null
+++ b/app/presenters/ci/bridge_presenter.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Ci
+ class BridgePresenter < CommitStatusPresenter
+ def detailed_status
+ @detailed_status ||= subject.detailed_status(user)
+ end
+ end
+end
diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb
index c38b2656260..adaa68b1efb 100644
--- a/app/services/clusters/applications/base_helm_service.rb
+++ b/app/services/clusters/applications/base_helm_service.rb
@@ -13,16 +13,20 @@ module Clusters
def log_error(error)
meta = {
- exception: error.class.name,
error_code: error.respond_to?(:error_code) ? error.error_code : nil,
service: self.class.name,
app_id: app.id,
project_ids: app.cluster.project_ids,
- group_ids: app.cluster.group_ids,
- message: error.message
+ group_ids: app.cluster.group_ids
}
- logger.error(meta)
+ logger_meta = meta.merge(
+ exception: error.class.name,
+ message: error.message,
+ backtrace: Gitlab::Profiler.clean_backtrace(error.backtrace)
+ )
+
+ logger.error(logger_meta)
Gitlab::Sentry.track_acceptable_exception(error, extra: meta)
end
diff --git a/app/services/groups/base_service.rb b/app/services/groups/base_service.rb
index 8c8acce5ca5..019cd047ae9 100644
--- a/app/services/groups/base_service.rb
+++ b/app/services/groups/base_service.rb
@@ -7,5 +7,11 @@ module Groups
def initialize(group, user, params = {})
@group, @current_user, @params = group, user, params.dup
end
+
+ private
+
+ def remove_unallowed_params
+ # overridden in EE
+ end
end
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index 99ead467f74..74aad3b1c94 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -8,6 +8,8 @@ module Groups
end
def execute
+ remove_unallowed_params
+
@group = Group.new(params)
after_build_hook(@group, params)
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 787445180f0..73e1e00dc33 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -6,6 +6,7 @@ module Groups
def execute
reject_parent_id!
+ remove_unallowed_params
return false unless valid_visibility_level_change?(group, params[:visibility_level])
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 3e208241da5..f968e3693da 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -55,15 +55,7 @@ module MergeRequests
end
def create_pipeline_for(merge_request, user)
- return unless Feature.enabled?(:ci_merge_request_pipeline,
- merge_request.source_project,
- default_enabled: true)
-
- ##
- # UpdateMergeRequestsWorker could be retried by an exception.
- # MR pipelines should not be recreated in such case.
- return if merge_request.merge_request_pipeline_exists?
- return if merge_request.has_no_commits?
+ return unless can_create_pipeline_for?(merge_request)
create_detached_merge_request_pipeline(merge_request, user)
end
@@ -80,6 +72,16 @@ module MergeRequests
end
end
+ def can_create_pipeline_for?(merge_request)
+ ##
+ # UpdateMergeRequestsWorker could be retried by an exception.
+ # pipelines for merge request should not be recreated in such case.
+ return false if merge_request.merge_request_pipeline_exists?
+ return false if merge_request.has_no_commits?
+
+ true
+ end
+
def can_use_merge_request_ref?(merge_request)
Feature.enabled?(:ci_use_merge_request_ref, project, default_enabled: true) &&
!merge_request.for_fork?
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 4eb53faa6ff..acd63de2277 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -8,30 +8,20 @@
%span.sr-only= _('Select Archive Format')
= sprite_icon("arrow-down")
%ul.dropdown-menu.dropdown-menu-right{ role: 'menu' }
- %li.dropdown-header
- #{ _('Source code') }
- %li
- = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'zip'), rel: 'nofollow', download: '' do
- %span= _('Download zip')
- %li
- = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.gz'), rel: 'nofollow', download: '' do
- %span= _('Download tar.gz')
- %li
- = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.bz2'), rel: 'nofollow', download: '' do
- %span= _('Download tar.bz2')
- %li
- = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar'), rel: 'nofollow', download: '' do
- %span= _('Download tar')
-
+ %li.dropdown-bold-header= _('Download source code')
+ %li.dropdown-menu-content
+ = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: nil
+ - if directory?
+ %li.separator
+ %li.dropdown-bold-header= _('Download this directory')
+ %li.dropdown-menu-content
+ = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: @path
- if pipeline && pipeline.latest_builds_with_artifacts.any?
- %li.dropdown-header Artifacts
+ %li.separator
+ %li.dropdown-bold-header= _('Download artifacts')
- unless pipeline.latest?
- - latest_pipeline = project.pipeline_for(ref)
- %li
- .unclickable= ci_status_for_statuseable(latest_pipeline)
- %li.dropdown-header Previous Artifacts
+ %span.unclickable= ci_status_for_statuseable(project.pipeline_for(ref))
+ %li.dropdown-header= _('Previous Artifacts')
- pipeline.latest_builds_with_artifacts.each do |job|
%li
- = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
- %span
- #{s_('DownloadArtifacts|Download')} '#{job.name}'
+ = link_to job.name, latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: ''
diff --git a/app/views/projects/buttons/_download_links.html.haml b/app/views/projects/buttons/_download_links.html.haml
new file mode 100644
index 00000000000..47a1704f946
--- /dev/null
+++ b/app/views/projects/buttons/_download_links.html.haml
@@ -0,0 +1,9 @@
+%ul
+ %li.d-inline-block.m-0.p-0
+ = link_to 'zip', project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: 'zip'), rel: 'nofollow', download: '', class: 'btn btn-primary btn-xs'
+ %li.d-inline-block.m-0.p-0
+ = link_to 'tar.gz', project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: 'tar.gz'), rel: 'nofollow', download: '', class: 'btn btn-xs'
+ %li.d-inline-block.m-0.p-0
+ = link_to 'tar.bz2', project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: 'tar.bz2'), rel: 'nofollow', download: '', class: 'btn btn-xs'
+ %li.d-inline-block.m-0.p-0
+ = link_to 'tar', project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: 'tar'), rel: 'nofollow', download: '', class: 'btn btn-xs'
diff --git a/changelogs/unreleased/24704-download-repository-path.yml b/changelogs/unreleased/24704-download-repository-path.yml
new file mode 100644
index 00000000000..ff3082bec45
--- /dev/null
+++ b/changelogs/unreleased/24704-download-repository-path.yml
@@ -0,0 +1,5 @@
+---
+title: Download a folder from repository
+merge_request: 26532
+author: kiameisomabes
+type: added
diff --git a/changelogs/unreleased/48090-filter-sensitive-metric-labels.yml b/changelogs/unreleased/48090-filter-sensitive-metric-labels.yml
new file mode 100644
index 00000000000..e588fa79619
--- /dev/null
+++ b/changelogs/unreleased/48090-filter-sensitive-metric-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Remove `path` and `branch` labels from metrics
+merge_request: 26744
+author:
+type: fixed
diff --git a/changelogs/unreleased/54417-graphql-type-authorization.yml b/changelogs/unreleased/54417-graphql-type-authorization.yml
new file mode 100644
index 00000000000..528b58a858a
--- /dev/null
+++ b/changelogs/unreleased/54417-graphql-type-authorization.yml
@@ -0,0 +1,5 @@
+---
+title: GraphQL Types can be made to always authorize access to resources of that Type
+merge_request: 25724
+author:
+type: added
diff --git a/changelogs/unreleased/55964-fix-email-encoding.yml b/changelogs/unreleased/55964-fix-email-encoding.yml
new file mode 100644
index 00000000000..2195a853702
--- /dev/null
+++ b/changelogs/unreleased/55964-fix-email-encoding.yml
@@ -0,0 +1,5 @@
+---
+title: Fix notfication emails having wrong encoding
+merge_request: 26931
+author:
+type: fixed
diff --git a/changelogs/unreleased/add_backtrace_to_kubernetes_log.yml b/changelogs/unreleased/add_backtrace_to_kubernetes_log.yml
new file mode 100644
index 00000000000..26b8ac4b1ef
--- /dev/null
+++ b/changelogs/unreleased/add_backtrace_to_kubernetes_log.yml
@@ -0,0 +1,5 @@
+---
+title: Show error backtrace when logging errors to kubernetes.log
+merge_request: 25726
+author:
+type: other
diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb
index e653556231d..f1bc289f1f0 100644
--- a/config/initializers/graphql.rb
+++ b/config/initializers/graphql.rb
@@ -1,4 +1,7 @@
# frozen_string_literal: true
+GraphQL::ObjectType.accepts_definitions(authorize: GraphQL::Define.assign_metadata_key(:authorize))
GraphQL::Field.accepts_definitions(authorize: GraphQL::Define.assign_metadata_key(:authorize))
+
+GraphQL::Schema::Object.accepts_definition(:authorize)
GraphQL::Schema::Field.accepts_definition(:authorize)
diff --git a/config/initializers/premailer.rb b/config/initializers/premailer.rb
index cb00d3cfe95..87f8e67ef1c 100644
--- a/config/initializers/premailer.rb
+++ b/config/initializers/premailer.rb
@@ -4,5 +4,6 @@ Premailer::Rails.config.merge!(
preserve_styles: true,
remove_comments: true,
remove_ids: false,
- remove_scripts: false
+ remove_scripts: false,
+ output_encoding: 'US-ASCII'
)
diff --git a/db/post_migrate/20190325111602_rename_v2_root_namespaces.rb b/db/post_migrate/20190325111602_rename_v2_root_namespaces.rb
new file mode 100644
index 00000000000..8571bb82fa0
--- /dev/null
+++ b/db/post_migrate/20190325111602_rename_v2_root_namespaces.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RenameV2RootNamespaces < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+ include Gitlab::Database::RenameReservedPathsMigration::V1
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ # We're taking over the /v2 namespace as it necessary for Docker client to
+ # work with GitLab as Dependency proxy for containers.
+ def up
+ disable_statement_timeout do
+ rename_root_paths 'v2'
+ end
+ end
+
+ def down
+ disable_statement_timeout do
+ revert_renames
+ end
+ end
+end
diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md
index ceca4af1bee..1218ac0b071 100644
--- a/doc/ci/variables/where_variables_can_be_used.md
+++ b/doc/ci/variables/where_variables_can_be_used.md
@@ -15,26 +15,26 @@ There are two places defined variables can be used. On the:
### `.gitlab-ci.yml` file
-| Definition | Can be expanded? | Expansion place | Description |
-|--------------------------------------|-------------------|-----------------|--------------|
-| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).<ul><li>Supported: all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules)</li><li>Not supported: variables defined in Runner's `config.toml` and variables created in job's `script`</li></ul> |
-| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support: <ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> |
-| `variables` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `image` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `services:[]` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `services:[]:name` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment |
-| `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
-| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`.<br/>Not supported:<ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> |
+| Definition | Can be expanded? | Expansion place | Description |
+|:-------------------------------------------|:-----------------|:-----------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).<br/><br/>Supported are all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules).<br/><br/>Not supported are variables defined in Runner's `config.toml` and variables created in job's `script`. |
+| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
+| `variables` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `image` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `services:[]` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `services:[]:name` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment |
+| `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
+| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
### `config.toml` file
NOTE: **Note:**
You can read more about `config.toml` in the [Runner's docs](https://docs.gitlab.com/runner/configuration/advanced-configuration.html).
-| Definition | Can be expanded? | Description |
-|--------------------------------------|------------------|-------------|
+| Definition | Can be expanded? | Description |
+|:-------------------------------------|:-----------------|:---------------------------------------------------------------------------------------------------------------------------------------------|
| `runners.environment` | yes | The variable expansion is made by the Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
| `runners.kubernetes.pod_labels` | yes | The Variable expansion is made by the Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
| `runners.kubernetes.pod_annotations` | yes | The Variable expansion is made by the Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 501092ff2aa..8d2bfff3a5d 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -9,38 +9,6 @@ can be shared.
It is also possible to add a `private_token` to the querystring, or
add a `HTTP_PRIVATE_TOKEN` header.
-### Authorization
-
-Fields can be authorized using the same abilities used in the Rails
-app. This can be done by supplying the `authorize` option:
-
-```ruby
-module Types
- class QueryType < BaseObject
- graphql_name 'Query'
-
- field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver, authorize: :read_project
- end
-end
-```
-
-Fields can be authorized against multiple abilities, in which case all
-ability checks must pass. This requires explicitly passing a block to `field`:
-
-```ruby
-field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do
- authorize [:read_project, :another_ability]
-end
-```
-
-The object found by the resolve call is used for authorization.
-
-TIP: **Tip:**
-When authorizing collections, try to load only what the currently
-authenticated user is allowed to view with our existing finders first.
-This minimizes database queries and unnecessary authorization checks of
-the loaded records.
-
## Types
When exposing a model through the GraphQL API, we do so by creating a
@@ -197,6 +165,114 @@ end
policies at once. The fields for these will all have be non-nullable
booleans with a default description.
+## Authorization
+
+Authorizations can be applied to both types and fields using the same
+abilities as in the Rails app.
+
+If the:
+
+- Currently authenticated user fails the authorization, the authorized
+resource will be returned as `null`.
+- Resource is part of a collection, the collection will be filtered to
+exclude the objects that the user's authorization checks failed against.
+
+TIP: **Tip:**
+Try to load only what the currently authenticated user is allowed to
+view with our existing finders first, without relying on authorization
+to filter the records. This minimizes database queries and unnecessary
+authorization checks of the loaded records.
+
+### Type authorization
+
+Authorize a type by passing an ability to the `authorize` method. All
+fields with the same type will be authorized by checking that the
+currently authenticated user has the required ability.
+
+For example, the following authorization ensures that the currently
+authenticated user can only see projects that they have the
+`read_project` ability for (so long as the project is returned in a
+field that uses `Types::ProjectType`):
+
+```ruby
+module Types
+ class ProjectType < BaseObject
+ authorize :read_project
+ end
+end
+```
+
+You can also authorize against multiple abilities, in which case all of
+the ability checks must pass.
+
+For example, the following authorization ensures that the currently
+authenticated user must have `read_project` and `another_ability`
+abilities to see a project:
+
+```ruby
+module Types
+ class ProjectType < BaseObject
+ authorize [:read_project, :another_ability]
+ end
+end
+```
+
+### Field authorization
+
+Fields can be authorized with the `authorize` option.
+
+For example, the following authorization ensures that the currently
+authenticated user must have the `owner_access` ability to see the
+project:
+
+```ruby
+module Types
+ class MyType < BaseObject
+ field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver, authorize: :owner_access
+ end
+end
+```
+
+Fields can also be authorized against multiple abilities, in which case
+all of ability checks must pass. **Note:** This requires explicitly
+passing a block to `field`:
+
+```ruby
+module Types
+ class MyType < BaseObject
+ field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do
+ authorize [:owner_access, :another_ability]
+ end
+ end
+end
+```
+
+NOTE: **Note:** If the field's type already [has a particular
+authorization](#type-authorization) then there is no need to add that
+same authorization to the field.
+
+### Type and Field authorizations together
+
+Authorizations are cumulative, so where authorizations are defined on
+a field, and also on the field's type, then the currently authenticated
+user would need to pass all ability checks.
+
+In the following simplified example the currently authenticated user
+would need both `first_permission` and `second_permission` abilities in
+order to see the author of the issue.
+
+```ruby
+class UserType
+ authorize :first_permission
+end
+```
+
+```ruby
+class IssueType
+ field :author, UserType, authorize: :second_permission
+end
+```
+
## Resolvers
To find objects to display in a field, we can add resolvers to
diff --git a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png
index 2a2101719ba..ea3aff59aa1 100644
--- a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png
+++ b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png
Binary files differ
diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index f8af71ab46b..1477e35dca8 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -35,11 +35,11 @@ You need to have jobs configured to enable this feature.
You can prevent merge requests from being merged if their pipeline did not succeed
or if there are discussions to be resolved.
-Navigate to your project's settings page, select the
-**Only allow merge requests to be merged if the pipeline succeeds** check box and
-hit **Save** for the changes to take effect.
+Navigate to your project's settings page and expand the **Merge requests** section.
+In the **Merge checks** subsection, select the **Pipelines must succeed** check
+box and hit **Save** for the changes to take effect.
-![Only allow merge if pipeline succeeds settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png)
+![Pipelines must succeed settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png)
From now on, every time the pipeline fails you will not be able to merge the
merge request from the UI, until you make all relevant jobs pass.
diff --git a/doc/user/project/repository/img/download_source_code.png b/doc/user/project/repository/img/download_source_code.png
new file mode 100644
index 00000000000..17f2cb4b3e8
--- /dev/null
+++ b/doc/user/project/repository/img/download_source_code.png
Binary files differ
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 22d912cd9d1..718566a539f 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -241,4 +241,24 @@ Projects that contain a `.xcodeproj` or `.xcworkspace` directory can now be clon
in Xcode using the new **Open in Xcode** button, located next to the Git URL
used for cloning your project. The button is only shown on macOS.
+## Download Source Code
+
+Source code stored in the repository can be downloaded.
+
+By clicking the download icon, a dropdown will open with links to download the following:
+
+![Download source code](img/download_source_code.png)
+
+- **Source Code:**
+ This allows users to download the source code on branch they're currently
+ viewing. Available zip, tar, tar.gz and tar.bz2.
+- **Directory:**
+ > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24704) in GitLab 11.10
+
+ Only shows up when viewing a sub-directory. This allows users to download
+ the specific directory they're currently viewing. Also available in zip, tar,
+ tar.gz and tar.bz2.
+- **Artifacts:**
+ This allows users to download the artifacts of the latest CI build.
+
[jupyter]: https://jupyter.org
diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md
index 9aa81e33fc0..9e8475d8294 100644
--- a/doc/user/reserved_names.md
+++ b/doc/user/reserved_names.md
@@ -83,6 +83,7 @@ Currently the following names are reserved as top level groups:
- unsubscribes
- uploads
- users
+- v2
These group names are unavailable as subgroup names:
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 9fcf476f537..ad16f26f5cc 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -26,6 +26,7 @@ module API
optional :ldap_cn, type: String, desc: 'LDAP Common Name'
optional :ldap_access, type: Integer, desc: 'A valid access level'
optional :shared_runners_minutes_limit, type: Integer, desc: '(admin-only) Pipeline minutes quota for this group'
+ optional :extra_shared_runners_minutes_limit, type: Integer, desc: '(admin-only) Extra pipeline minutes quota for this group'
all_or_none_of :ldap_cn, :ldap_access
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 776329622e2..2f23e33bd4a 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -54,6 +54,7 @@ module API
if Gitlab.ee?
optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user'
+ optional :extra_shared_runners_minutes_limit, type: Integer, desc: '(admin-only) Extra pipeline minutes quota for this user'
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 7d6851a4b8d..c33d243330d 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -231,12 +231,12 @@ module Gitlab
end
end
- def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:)
+ def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:, path: nil)
ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref)
return {} if commit.nil?
- prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha)
+ prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha, path: path)
{
'ArchivePrefix' => prefix,
@@ -248,13 +248,14 @@ module Gitlab
# This is both the filename of the archive (missing the extension) and the
# name of the top-level member of the archive under which all files go
- def archive_prefix(ref, sha, project_path, append_sha:)
+ def archive_prefix(ref, sha, project_path, append_sha:, path:)
append_sha = (ref != sha) if append_sha.nil?
formatted_ref = ref.tr('/', '-')
prefix_segments = [project_path, formatted_ref]
prefix_segments << sha if append_sha
+ prefix_segments << path.tr('/', '-').gsub(%r{^/|/$}, '') if path
prefix_segments.join('-')
end
diff --git a/lib/gitlab/graphql/authorize/authorize_field_service.rb b/lib/gitlab/graphql/authorize/authorize_field_service.rb
new file mode 100644
index 00000000000..f3ca82ec697
--- /dev/null
+++ b/lib/gitlab/graphql/authorize/authorize_field_service.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Authorize
+ class AuthorizeFieldService
+ def initialize(field)
+ @field = field
+ @old_resolve_proc = @field.resolve_proc
+ end
+
+ def authorizations?
+ authorizations.present?
+ end
+
+ def authorized_resolve
+ proc do |obj, args, ctx|
+ resolved_obj = @old_resolve_proc.call(obj, args, ctx)
+ checker = build_checker(ctx[:current_user])
+
+ if resolved_obj.respond_to?(:then)
+ resolved_obj.then(&checker)
+ else
+ checker.call(resolved_obj)
+ end
+ end
+ end
+
+ private
+
+ def authorizations
+ @authorizations ||= (type_authorizations + field_authorizations).uniq
+ end
+
+ # Returns any authorize metadata from the return type of @field
+ def type_authorizations
+ type = @field.type
+
+ # When the return type of @field is a collection, find the singular type
+ if type.get_field('edges')
+ type = node_type_for_relay_connection(type)
+ elsif type.list?
+ type = node_type_for_basic_connection(type)
+ end
+
+ Array.wrap(type.metadata[:authorize])
+ end
+
+ # Returns any authorize metadata from @field
+ def field_authorizations
+ Array.wrap(@field.metadata[:authorize])
+ end
+
+ def build_checker(current_user)
+ lambda do |value|
+ # Load the elements if they were not loaded by BatchLoader yet
+ value = value.sync if value.respond_to?(:sync)
+
+ check = lambda do |object|
+ authorizations.all? do |ability|
+ Ability.allowed?(current_user, ability, object)
+ end
+ end
+
+ case value
+ when Array, ActiveRecord::Relation
+ value.select(&check)
+ else
+ value if check.call(value)
+ end
+ end
+ end
+
+ # Returns the singular type for relay connections.
+ # This will be the type class of edges.node
+ def node_type_for_relay_connection(type)
+ type = type.get_field('edges').type.unwrap.get_field('node')&.type
+
+ if type.nil?
+ raise Gitlab::Graphql::Errors::ConnectionDefinitionError,
+ 'Connection Type must conform to the Relay Cursor Connections Specification'
+ end
+
+ type
+ end
+
+ # Returns the singular type for basic connections, for example `[Types::ProjectType]`
+ def node_type_for_basic_connection(type)
+ type.unwrap
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/authorize/instrumentation.rb b/lib/gitlab/graphql/authorize/instrumentation.rb
index 593da8471dd..15ecc3b04f0 100644
--- a/lib/gitlab/graphql/authorize/instrumentation.rb
+++ b/lib/gitlab/graphql/authorize/instrumentation.rb
@@ -7,46 +7,12 @@ module Gitlab
# Replace the resolver for the field with one that will only return the
# resolved object if the permissions check is successful.
def instrument(_type, field)
- required_permissions = Array.wrap(field.metadata[:authorize])
- return field if required_permissions.empty?
+ service = AuthorizeFieldService.new(field)
- old_resolver = field.resolve_proc
-
- new_resolver = -> (obj, args, ctx) do
- resolved_obj = old_resolver.call(obj, args, ctx)
- checker = build_checker(ctx[:current_user], required_permissions)
-
- if resolved_obj.respond_to?(:then)
- resolved_obj.then(&checker)
- else
- checker.call(resolved_obj)
- end
- end
-
- field.redefine do
- resolve(new_resolver)
- end
- end
-
- private
-
- def build_checker(current_user, abilities)
- lambda do |value|
- # Load the elements if they weren't loaded by BatchLoader yet
- value = value.sync if value.respond_to?(:sync)
-
- check = lambda do |object|
- abilities.all? do |ability|
- Ability.allowed?(current_user, ability, object)
- end
- end
-
- case value
- when Array
- value.select(&check)
- else
- value if check.call(value)
- end
+ if service.authorizations?
+ field.redefine { resolve(service.authorized_resolve) }
+ else
+ field
end
end
end
diff --git a/lib/gitlab/graphql/errors.rb b/lib/gitlab/graphql/errors.rb
index fe74549e322..bcbba72e017 100644
--- a/lib/gitlab/graphql/errors.rb
+++ b/lib/gitlab/graphql/errors.rb
@@ -6,6 +6,7 @@ module Gitlab
BaseError = Class.new(GraphQL::ExecutionError)
ArgumentError = Class.new(BaseError)
ResourceNotAvailable = Class.new(BaseError)
+ ConnectionDefinitionError = Class.new(BaseError)
end
end
end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index e91803ecd62..d7986685c65 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -8,6 +8,8 @@ module Gitlab
# base labels shared among all transactions
BASE_LABELS = { controller: nil, action: nil }.freeze
+ # labels that potentially contain sensitive information and will be filtered
+ FILTERED_LABELS = [:branch, :path].freeze
THREAD_KEY = :_gitlab_metrics_transaction
@@ -64,7 +66,7 @@ module Gitlab
end
def add_metric(series, values, tags = {})
- @metrics << Metric.new("#{::Gitlab::Metrics.series_prefix}#{series}", values, tags)
+ @metrics << Metric.new("#{::Gitlab::Metrics.series_prefix}#{series}", values, filter_tags(tags))
end
# Tracks a business level event
@@ -75,8 +77,9 @@ module Gitlab
# event_name - The name of the event (e.g. "git_push").
# tags - A set of tags to attach to the event.
def add_event(event_name, tags = {})
- self.class.transaction_metric(event_name, :counter, prefix: 'event_', use_feature_flag: true, tags: tags).increment(tags.merge(labels))
- @metrics << Metric.new(EVENT_SERIES, { count: 1 }, tags.merge(event: event_name), :event)
+ filtered_tags = filter_tags(tags)
+ self.class.transaction_metric(event_name, :counter, prefix: 'event_', use_feature_flag: true, tags: filtered_tags).increment(filtered_tags.merge(labels))
+ @metrics << Metric.new(EVENT_SERIES, { count: 1 }, filtered_tags.merge(event: event_name), :event)
end
# Returns a MethodCall object for the given name.
@@ -164,6 +167,12 @@ module Gitlab
end
end
end
+
+ private
+
+ def filter_tags(tags)
+ tags.without(*FILTERED_LABELS)
+ end
end
end
end
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 3c888be0710..a07b1246bee 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -57,6 +57,7 @@ module Gitlab
unsubscribes
uploads
users
+ v2
].freeze
# This list should contain all words following `/*namespace_id/:project_id` in
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 5d5a867c9ab..83eabb8d674 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -63,13 +63,26 @@ module Gitlab
]
end
- def send_git_archive(repository, ref:, format:, append_sha:)
+ def send_git_archive(repository, ref:, format:, append_sha:, path: nil)
format ||= 'tar.gz'
format = format.downcase
- params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha)
- raise "Repository or ref not found" if params.empty?
+ metadata = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha, path: path)
- params['GitalyServer'] = gitaly_server_hash(repository)
+ raise "Repository or ref not found" if metadata.empty?
+
+ params = {
+ 'GitalyServer' => gitaly_server_hash(repository),
+ 'ArchivePath' => metadata['ArchivePath'],
+ 'GetArchiveRequest' => encode_binary(
+ Gitaly::GetArchiveRequest.new(
+ repository: repository.gitaly_repository,
+ commit_id: metadata['CommitId'],
+ prefix: metadata['ArchivePrefix'],
+ format: archive_format(format),
+ path: path.presence || ""
+ ).to_proto
+ )
+ }
# If present DisableCache must be a Boolean. Otherwise workhorse ignores it.
params['DisableCache'] = true if git_archive_cache_disabled?
@@ -220,6 +233,10 @@ module Gitlab
Base64.urlsafe_encode64(JSON.dump(hash))
end
+ def encode_binary(binary)
+ Base64.urlsafe_encode64(binary)
+ end
+
def gitaly_server_hash(repository)
{
address: Gitlab::GitalyClient.address(repository.project.repository_storage),
@@ -238,6 +255,19 @@ module Gitlab
def git_archive_cache_disabled?
ENV['WORKHORSE_ARCHIVE_CACHE_DISABLED'].present? || Feature.enabled?(:workhorse_archive_cache_disabled)
end
+
+ def archive_format(format)
+ case format
+ when "tar.bz2", "tbz", "tbz2", "tb2", "bz2"
+ Gitaly::GetArchiveRequest::Format::TAR_BZ2
+ when "tar"
+ Gitaly::GetArchiveRequest::Format::TAR
+ when "zip"
+ Gitaly::GetArchiveRequest::Format::ZIP
+ else
+ Gitaly::GetArchiveRequest::Format::TAR_GZ
+ end
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f9c411642c7..9de34dd92ea 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3013,19 +3013,10 @@ msgstr ""
msgid "Download asset"
msgstr ""
-msgid "Download tar"
+msgid "Download source code"
msgstr ""
-msgid "Download tar.bz2"
-msgstr ""
-
-msgid "Download tar.gz"
-msgstr ""
-
-msgid "Download zip"
-msgstr ""
-
-msgid "DownloadArtifacts|Download"
+msgid "Download this directory"
msgstr ""
msgid "DownloadCommit|Email Patches"
@@ -6028,6 +6019,9 @@ msgstr ""
msgid "Preview payload"
msgstr ""
+msgid "Previous Artifacts"
+msgstr ""
+
msgid "Prioritize"
msgstr ""
diff --git a/spec/features/commits/user_uses_quick_actions_spec.rb b/spec/features/commits/user_uses_quick_actions_spec.rb
index 9a4b7bd2444..4b7e7465df1 100644
--- a/spec/features/commits/user_uses_quick_actions_spec.rb
+++ b/spec/features/commits/user_uses_quick_actions_spec.rb
@@ -22,27 +22,6 @@ describe 'Commit > User uses quick actions', :js do
let(:tag_message) { 'Stable release' }
let(:truncated_commit_sha) { Commit.truncate_sha(commit.sha) }
- it 'tags this commit' do
- add_note("/tag #{tag_name} #{tag_message}")
-
- expect(page).to have_content 'Commands applied'
- expect(page).to have_content "tagged commit #{truncated_commit_sha}"
- expect(page).to have_content tag_name
-
- visit project_tag_path(project, tag_name)
- expect(page).to have_content tag_name
- expect(page).to have_content tag_message
- expect(page).to have_content truncated_commit_sha
- end
-
- describe 'preview', :js do
- it 'removes quick action from note and explains it' do
- preview_note("/tag #{tag_name} #{tag_message}")
-
- expect(page).not_to have_content '/tag'
- expect(page).to have_content %{Tags this commit to #{tag_name} with "#{tag_message}"}
- expect(page).to have_content tag_name
- end
- end
+ it_behaves_like 'tag quick action'
end
end
diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb
index a2b5859bd1e..56774896795 100644
--- a/spec/features/merge_request/user_uses_quick_actions_spec.rb
+++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb
@@ -56,6 +56,8 @@ describe 'Merge request > User uses quick actions', :js do
project.add_maintainer(user)
end
+ it_behaves_like 'merge quick action'
+
describe 'toggling the WIP prefix in the title from note' do
context 'when the current user can toggle the WIP prefix' do
before do
@@ -103,74 +105,6 @@ describe 'Merge request > User uses quick actions', :js do
end
end
- describe 'merging the MR from the note' do
- context 'when the current user can merge the MR' do
- before do
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'merges the MR' do
- add_note("/merge")
-
- expect(page).to have_content 'Commands applied'
-
- expect(merge_request.reload).to be_merged
- end
- end
-
- context 'when the head diff changes in the meanwhile' do
- before do
- merge_request.source_branch = 'another_branch'
- merge_request.save
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'does not merge the MR' do
- add_note("/merge")
-
- expect(page).not_to have_content 'Your commands have been executed!'
-
- expect(merge_request.reload).not_to be_merged
- end
- end
-
- context 'when the current user cannot merge the MR' do
- before do
- project.add_guest(guest)
- sign_in(guest)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'does not merge the MR' do
- add_note("/merge")
-
- expect(page).not_to have_content 'Your commands have been executed!'
-
- expect(merge_request.reload).not_to be_merged
- end
- end
- end
-
- describe 'adding a due date from note' do
- before do
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it_behaves_like 'due quick action not available'
- end
-
- describe 'removing a due date from note' do
- before do
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it_behaves_like 'remove_due_date action not available'
- end
-
describe '/target_branch command in merge request' do
let(:another_project) { create(:project, :public, :repository) }
let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index c8dc72a34ec..3e75890725e 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -35,7 +35,7 @@ describe 'Download buttons in branches page' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, 'binary-encoding/download', job: 'build')
- expect(page).to have_link "Download '#{build.name}'", href: href
+ expect(page).to have_link build.name, href: href
end
end
end
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index 03cb3530e2b..111972a6b00 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -30,7 +30,7 @@ describe 'Projects > Files > Download buttons in files tree' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
- expect(page).to have_link "Download '#{build.name}'", href: href
+ expect(page).to have_link build.name, href: href
end
end
end
diff --git a/spec/features/projects/show/download_buttons_spec.rb b/spec/features/projects/show/download_buttons_spec.rb
index 3a2dcc5aa55..fee5f8001b0 100644
--- a/spec/features/projects/show/download_buttons_spec.rb
+++ b/spec/features/projects/show/download_buttons_spec.rb
@@ -35,11 +35,10 @@ describe 'Projects > Show > Download buttons' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
- expect(page).to have_link "Download '#{build.name}'", href: href
+ expect(page).to have_link build.name, href: href
end
it 'download links have download attribute' do
- expect(page).to have_selector('a', text: 'Download')
page.all('a', text: 'Download').each do |link|
expect(link[:download]).to eq ''
end
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index fbfd8cee7aa..4c8ec53836a 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -36,7 +36,7 @@ describe 'Download buttons in tags page' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, "#{tag}/download", job: 'build')
- expect(page).to have_link "Download '#{build.name}'", href: href
+ expect(page).to have_link build.name, href: href
end
end
end
diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb
index a229d29afa6..f863c4444b8 100644
--- a/spec/graphql/features/authorization_spec.rb
+++ b/spec/graphql/features/authorization_spec.rb
@@ -5,61 +5,192 @@ require 'spec_helper'
describe 'Gitlab::Graphql::Authorization' do
set(:user) { create(:user) }
+ let(:permission_single) { :foo }
+ let(:permission_collection) { [:foo, :bar] }
let(:test_object) { double(name: 'My name') }
- let(:object_type) { object_type_class }
- let(:query_type) { query_type_class(object_type, test_object) }
- let(:schema) { schema_class(query_type) }
+ let(:query_string) { '{ object() { name } }' }
+ let(:result) { execute_query(query_type)['data'] }
- let(:execute) do
- schema.execute(
- query_string,
- context: { current_user: user },
- variables: {}
- )
+ subject { result['object'] }
+
+ shared_examples 'authorization with a single permission' do
+ it 'returns the protected field when user has permission' do
+ permit(permission_single)
+
+ expect(subject).to eq('name' => test_object.name)
+ end
+
+ it 'returns nil when user is not authorized' do
+ expect(subject).to be_nil
+ end
end
- let(:result) { execute['data'] }
+ shared_examples 'authorization with a collection of permissions' do
+ it 'returns the protected field when user has all permissions' do
+ permit(*permission_collection)
+
+ expect(subject).to eq('name' => test_object.name)
+ end
+
+ it 'returns nil when user only has one of the permissions' do
+ permit(permission_collection.first)
+
+ expect(subject).to be_nil
+ end
+
+ it 'returns nil when user only has none of the permissions' do
+ expect(subject).to be_nil
+ end
+ end
before do
# By default, disallow all permissions.
allow(Ability).to receive(:allowed?).and_return(false)
end
- describe 'authorizing with a single permission' do
- let(:query_string) { '{ singlePermission() { name } }' }
+ describe 'Field authorizations' do
+ let(:type) { type_factory }
- subject { result['singlePermission'] }
+ describe 'with a single permission' do
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_single
+ end
+ end
+
+ include_examples 'authorization with a single permission'
+ end
+
+ describe 'with a collection of permissions' do
+ let(:query_type) do
+ permissions = permission_collection
+ query_factory do |qt|
+ qt.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } do
+ authorize permissions
+ end
+ end
+ end
- it 'should return the protected field when user has permission' do
- permit(:foo)
+ include_examples 'authorization with a collection of permissions'
+ end
+ end
- expect(subject['name']).to eq(test_object.name)
+ describe 'Type authorizations' do
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }
+ end
end
- it 'should return nil when user is not authorized' do
- expect(subject).to be_nil
+ describe 'with a single permission' do
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_single
+ end
+ end
+
+ include_examples 'authorization with a single permission'
+ end
+
+ describe 'with a collection of permissions' do
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_collection
+ end
+ end
+
+ include_examples 'authorization with a collection of permissions'
end
end
- describe 'authorizing with an Array of permissions' do
- let(:query_string) { '{ permissionCollection() { name } }' }
+ describe 'type and field authorizations together' do
+ let(:permission_1) { permission_collection.first }
+ let(:permission_2) { permission_collection.last }
- subject { result['permissionCollection'] }
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_1
+ end
+ end
- it 'should return the protected field when user has all permissions' do
- permit(:foo, :bar)
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_2
+ end
+ end
- expect(subject['name']).to eq(test_object.name)
+ include_examples 'authorization with a collection of permissions'
+ end
+
+ describe 'type authorizations when applied to a relay connection' do
+ let(:query_string) { '{ object() { edges { node { name } } } }' }
+
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_single
+ end
+ end
+
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object] }
+ end
end
- it 'should return nil when user only has one of the permissions' do
- permit(:foo)
+ subject { result.dig('object', 'edges') }
- expect(subject).to be_nil
+ it 'returns the protected field when user has permission' do
+ permit(permission_single)
+
+ expect(subject).not_to be_empty
+ expect(subject.first['node']).to eq('name' => test_object.name)
end
- it 'should return nil when user only has none of the permissions' do
- expect(subject).to be_nil
+ it 'returns nil when user is not authorized' do
+ expect(subject).to be_empty
+ end
+ end
+
+ describe 'type authorizations when applied to a basic connection' do
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_single
+ end
+ end
+
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, [type], null: true, resolve: ->(obj, args, ctx) { [test_object] }
+ end
+ end
+
+ subject { result['object'].first }
+
+ include_examples 'authorization with a single permission'
+ end
+
+ describe 'when connections do not follow the correct specification' do
+ let(:query_string) { '{ object() { edges { node { name }} } }' }
+
+ let(:type) do
+ bad_node = type_factory do |type|
+ type.graphql_name 'BadNode'
+ type.field :bad_node, GraphQL::STRING_TYPE, null: true
+ end
+
+ type_factory do |type|
+ type.field :edges, [bad_node], null: true
+ end
+ end
+
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type, null: true
+ end
+ end
+
+ it 'throws an error' do
+ expect { result }.to raise_error(Gitlab::Graphql::Errors::ConnectionDefinitionError)
end
end
@@ -71,36 +202,34 @@ describe 'Gitlab::Graphql::Authorization' do
end
end
- def object_type_class
+ def type_factory
Class.new(Types::BaseObject) do
- graphql_name 'TestObject'
+ graphql_name 'TestType'
field :name, GraphQL::STRING_TYPE, null: true
+
+ yield(self) if block_given?
end
end
- def query_type_class(type, object)
+ def query_factory
Class.new(Types::BaseObject) do
graphql_name 'TestQuery'
- field :single_permission, type,
- null: true,
- authorize: :foo,
- resolve: ->(obj, args, ctx) { object }
-
- field :permission_collection, type,
- null: true,
- resolve: ->(obj, args, ctx) { object } do
- authorize [:foo, :bar]
- end
+ yield(self) if block_given?
end
end
- def schema_class(query)
- Class.new(GraphQL::Schema) do
+ def execute_query(query_type)
+ schema = Class.new(GraphQL::Schema) do
use Gitlab::Graphql::Authorize
-
- query(query)
+ query(query_type)
end
+
+ schema.execute(
+ query_string,
+ context: { current_user: user },
+ variables: {}
+ )
end
end
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
index 63a07647a60..dc37b15001f 100644
--- a/spec/graphql/types/issue_type_spec.rb
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -4,4 +4,6 @@ describe GitlabSchema.types['Issue'] do
it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Issue) }
it { expect(described_class.graphql_name).to eq('Issue') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_issue) }
end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index c369953e3ea..89c12879074 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -3,14 +3,9 @@ require 'spec_helper'
describe GitlabSchema.types['MergeRequest'] do
it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::MergeRequest) }
- describe 'head pipeline' do
- it 'has a head pipeline field' do
- expect(described_class).to have_graphql_field(:head_pipeline)
- end
+ it { expect(described_class).to require_graphql_authorizations(:read_merge_request) }
- it 'authorizes the field' do
- expect(described_class.fields['headPipeline'])
- .to require_graphql_authorizations(:read_pipeline)
- end
+ describe 'nested head pipeline' do
+ it { expect(described_class).to have_graphql_field(:head_pipeline) }
end
end
diff --git a/spec/graphql/types/milestone_type_spec.rb b/spec/graphql/types/milestone_type_spec.rb
new file mode 100644
index 00000000000..f7ee79eae9f
--- /dev/null
+++ b/spec/graphql/types/milestone_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['Milestone'] do
+ it { expect(described_class.graphql_name).to eq('Milestone') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_milestone) }
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index e8f1c84f8d6..e0ad09bdf22 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -5,19 +5,11 @@ describe GitlabSchema.types['Project'] do
it { expect(described_class.graphql_name).to eq('Project') }
+ it { expect(described_class).to require_graphql_authorizations(:read_project) }
+
describe 'nested merge request' do
it { expect(described_class).to have_graphql_field(:merge_requests) }
it { expect(described_class).to have_graphql_field(:merge_request) }
-
- it 'authorizes the merge request' do
- expect(described_class.fields['mergeRequest'])
- .to require_graphql_authorizations(:read_merge_request)
- end
-
- it 'authorizes the merge requests' do
- expect(described_class.fields['mergeRequests'])
- .to require_graphql_authorizations(:read_merge_request)
- end
end
describe 'nested issues' do
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index 07c61ea7647..69e3ea8a4a9 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -15,10 +15,6 @@ describe GitlabSchema.types['Query'] do
is_expected.to have_graphql_type(Types::ProjectType)
is_expected.to have_graphql_resolver(Resolvers::ProjectResolver)
end
-
- it 'authorizes with read_project' do
- is_expected.to require_graphql_authorizations(:read_project)
- end
end
describe 'metadata field' do
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
new file mode 100644
index 00000000000..8134cc13eb4
--- /dev/null
+++ b/spec/graphql/types/user_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['User'] do
+ it { expect(described_class.graphql_name).to eq('User') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_user) }
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 8ba6862392c..fc8f590068a 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -152,13 +152,14 @@ describe Gitlab::Git::Repository, :seed_helper do
let(:append_sha) { true }
let(:ref) { 'master' }
let(:format) { nil }
+ let(:path) { nil }
let(:expected_extension) { 'tar.gz' }
let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" }
let(:expected_path) { File.join(storage_path, cache_key, expected_filename) }
let(:expected_prefix) { "gitlab-git-test-#{ref}-#{SeedRepo::LastCommit::ID}" }
- subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha) }
+ subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha, path: path) }
it 'sets CommitId to the commit SHA' do
expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID)
@@ -176,6 +177,14 @@ describe Gitlab::Git::Repository, :seed_helper do
expect(metadata['ArchivePath']).to eq(expected_path)
end
+ context 'path is set' do
+ let(:path) { 'foo/bar' }
+
+ it 'appends the path to the prefix' do
+ expect(metadata['ArchivePrefix']).to eq("#{expected_prefix}-foo-bar")
+ end
+ end
+
context 'append_sha varies archive path and filename' do
where(:append_sha, :ref, :expected_prefix) do
sha = SeedRepo::LastCommit::ID
diff --git a/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
index cf3a8bcc8b4..ce320a2bdb0 100644
--- a/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
@@ -2,13 +2,17 @@
require 'spec_helper'
-describe Gitlab::Graphql::Authorize::Instrumentation do
+# Also see spec/graphql/features/authorization_spec.rb for
+# integration tests of AuthorizeFieldService
+describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
describe '#build_checker' do
let(:current_user) { double(:current_user) }
let(:abilities) { [double(:first_ability), double(:last_ability)] }
let(:checker) do
- described_class.new.__send__(:build_checker, current_user, abilities)
+ service = described_class.new(double(resolve_proc: proc {}))
+ allow(service).to receive(:authorizations).and_return(abilities)
+ service.__send__(:build_checker, current_user)
end
it 'returns a checker which checks for a single object' do
@@ -56,12 +60,14 @@ describe Gitlab::Graphql::Authorize::Instrumentation do
.to contain_exactly(allowed)
end
end
+ end
- def spy_ability_check_for(ability, object, passed: true)
- expect(Ability)
- .to receive(:allowed?)
- .with(current_user, ability, object)
- .and_return(passed)
- end
+ private
+
+ def spy_ability_check_for(ability, object, passed: true)
+ expect(Ability)
+ .to receive(:allowed?)
+ .with(current_user, ability, object)
+ .and_return(passed)
end
end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
new file mode 100644
index 00000000000..e70fde09edd
--- /dev/null
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -0,0 +1,229 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Transaction do
+ let(:transaction) { described_class.new }
+ let(:metric) { transaction.metrics[0] }
+
+ let(:sensitive_tags) do
+ {
+ path: 'private',
+ branch: 'sensitive'
+ }
+ end
+
+ shared_examples 'tag filter' do |sane_tags|
+ it 'filters potentially sensitive tags' do
+ expect(metric.tags).to eq(sane_tags)
+ end
+ end
+
+ describe '#duration' do
+ it 'returns the duration of a transaction in seconds' do
+ transaction.run { }
+
+ expect(transaction.duration).to be > 0
+ end
+ end
+
+ describe '#allocated_memory' do
+ it 'returns the allocated memory in bytes' do
+ transaction.run { 'a' * 32 }
+
+ expect(transaction.allocated_memory).to be_a_kind_of(Numeric)
+ end
+ end
+
+ describe '#run' do
+ it 'yields the supplied block' do
+ expect { |b| transaction.run(&b) }.to yield_control
+ end
+
+ it 'stores the transaction in the current thread' do
+ transaction.run do
+ expect(described_class.current).to eq(transaction)
+ end
+ end
+
+ it 'removes the transaction from the current thread upon completion' do
+ transaction.run { }
+
+ expect(described_class.current).to be_nil
+ end
+ end
+
+ describe '#add_metric' do
+ it 'adds a metric to the transaction' do
+ transaction.add_metric('foo', value: 1)
+
+ expect(metric.series).to eq('rails_foo')
+ expect(metric.tags).to eq({})
+ expect(metric.values).to eq(value: 1)
+ end
+
+ context 'with sensitive tags' do
+ before do
+ transaction
+ .add_metric('foo', { value: 1 }, **sensitive_tags.merge(sane: 'yes'))
+ end
+
+ it_behaves_like 'tag filter', sane: 'yes'
+ end
+ end
+
+ describe '#method_call_for' do
+ it 'returns a MethodCall' do
+ method = transaction.method_call_for('Foo#bar', :Foo, '#bar')
+
+ expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall)
+ end
+ end
+
+ describe '#increment' do
+ it 'increments a counter' do
+ transaction.increment(:time, 1)
+ transaction.increment(:time, 2)
+
+ values = metric_values(time: 3)
+
+ expect(transaction).to receive(:add_metric)
+ .with('transactions', values, {})
+
+ transaction.track_self
+ end
+ end
+
+ describe '#set' do
+ it 'sets a value' do
+ transaction.set(:number, 10)
+
+ values = metric_values(number: 10)
+
+ expect(transaction).to receive(:add_metric)
+ .with('transactions', values, {})
+
+ transaction.track_self
+ end
+ end
+
+ describe '#finish' do
+ it 'tracks the transaction details and submits them to Sidekiq' do
+ expect(transaction).to receive(:track_self)
+ expect(transaction).to receive(:submit)
+
+ transaction.finish
+ end
+ end
+
+ describe '#track_self' do
+ it 'adds a metric for the transaction itself' do
+ values = metric_values
+
+ expect(transaction).to receive(:add_metric)
+ .with('transactions', values, {})
+
+ transaction.track_self
+ end
+ end
+
+ describe '#submit' do
+ it 'submits the metrics to Sidekiq' do
+ transaction.track_self
+
+ expect(Gitlab::Metrics).to receive(:submit_metrics)
+ .with([an_instance_of(Hash)])
+
+ transaction.submit
+ end
+
+ it 'adds the action as a tag for every metric' do
+ allow(transaction)
+ .to receive(:labels)
+ .and_return(controller: 'Foo', action: 'bar')
+
+ transaction.track_self
+
+ hash = {
+ series: 'rails_transactions',
+ tags: { action: 'Foo#bar' },
+ values: metric_values,
+ timestamp: a_kind_of(Integer)
+ }
+
+ expect(Gitlab::Metrics).to receive(:submit_metrics)
+ .with([hash])
+
+ transaction.submit
+ end
+
+ it 'does not add an action tag for events' do
+ allow(transaction)
+ .to receive(:labels)
+ .and_return(controller: 'Foo', action: 'bar')
+
+ transaction.add_event(:meow)
+
+ hash = {
+ series: 'events',
+ tags: { event: :meow },
+ values: { count: 1 },
+ timestamp: a_kind_of(Integer)
+ }
+
+ expect(Gitlab::Metrics).to receive(:submit_metrics)
+ .with([hash])
+
+ transaction.submit
+ end
+ end
+
+ describe '#add_event' do
+ it 'adds a metric' do
+ transaction.add_event(:meow)
+
+ expect(metric).to be_an_instance_of(Gitlab::Metrics::Metric)
+ end
+
+ it "does not prefix the metric's series name" do
+ transaction.add_event(:meow)
+
+ expect(metric.series).to eq(described_class::EVENT_SERIES)
+ end
+
+ it 'tracks a counter for every event' do
+ transaction.add_event(:meow)
+
+ expect(metric.values).to eq(count: 1)
+ end
+
+ it 'tracks the event name' do
+ transaction.add_event(:meow)
+
+ expect(metric.tags).to eq(event: :meow)
+ end
+
+ it 'allows tracking of custom tags' do
+ transaction.add_event(:meow, animal: 'cat')
+
+ expect(metric.tags).to eq(event: :meow, animal: 'cat')
+ end
+
+ context 'with sensitive tags' do
+ before do
+ transaction.add_event(:meow, **sensitive_tags.merge(sane: 'yes'))
+ end
+
+ it_behaves_like 'tag filter', event: :meow, sane: 'yes'
+ end
+ end
+
+ private
+
+ def metric_values(opts = {})
+ {
+ duration: 0.0,
+ allocated_memory: a_kind_of(Numeric)
+ }.merge(opts)
+ end
+end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 312e5e55af8..71e69a0d418 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -100,7 +100,7 @@ describe Gitlab::PathRegex do
end
let(:ee_top_level_words) do
- ['unsubscribes']
+ %w(unsubscribes v2)
end
let(:files_in_public) do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index d88086b01b1..fed7834e2a9 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -16,20 +16,12 @@ describe Gitlab::Workhorse do
let(:ref) { 'master' }
let(:format) { 'zip' }
let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path }
- let(:base_params) { repository.archive_metadata(ref, storage_path, format, append_sha: nil) }
- let(:gitaly_params) do
- base_params.merge(
- 'GitalyServer' => {
- 'address' => Gitlab::GitalyClient.address(project.repository_storage),
- 'token' => Gitlab::GitalyClient.token(project.repository_storage)
- },
- 'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys
- )
- end
+ let(:path) { 'some/path' }
+ let(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: nil, path: path) }
let(:cache_disabled) { false }
subject do
- described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil)
+ described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil, path: path)
end
before do
@@ -41,7 +33,22 @@ describe Gitlab::Workhorse do
expect(key).to eq('Gitlab-Workhorse-Send-Data')
expect(command).to eq('git-archive')
- expect(params).to include(gitaly_params)
+ expect(params).to eq({
+ 'GitalyServer' => {
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ },
+ 'ArchivePath' => metadata['ArchivePath'],
+ 'GetArchiveRequest' => Base64.urlsafe_encode64(
+ Gitaly::GetArchiveRequest.new(
+ repository: repository.gitaly_repository,
+ commit_id: metadata['CommitId'],
+ prefix: metadata['ArchivePrefix'],
+ format: Gitaly::GetArchiveRequest::Format::ZIP,
+ path: path
+ ).to_proto
+ )
+ }.deep_stringify_keys)
end
context 'when archive caching is disabled' do
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 3c8897ed37c..5fa1369c00a 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -30,6 +30,19 @@ describe Notify do
description: 'My awesome description!')
end
+ describe 'with HTML-encoded entities' do
+ before do
+ described_class.test_email('test@test.com', 'Subject', 'Some body with &mdash;').deliver
+ end
+
+ subject { ActionMailer::Base.deliveries.last }
+
+ it 'retains 7bit encoding' do
+ expect(subject.body.ascii_only?).to eq(true)
+ expect(subject.body.encoding).to eq('7bit')
+ end
+ end
+
context 'for a project' do
shared_examples 'an assignee email' do
it 'is sent to the assignee as the author' do
diff --git a/spec/presenters/ci/bridge_presenter_spec.rb b/spec/presenters/ci/bridge_presenter_spec.rb
new file mode 100644
index 00000000000..986818a7b9e
--- /dev/null
+++ b/spec/presenters/ci/bridge_presenter_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Ci::BridgePresenter do
+ set(:project) { create(:project) }
+ set(:pipeline) { create(:ci_pipeline, project: project) }
+ set(:bridge) { create(:ci_bridge, pipeline: pipeline, status: :failed) }
+
+ subject(:presenter) do
+ described_class.new(bridge)
+ end
+
+ it 'presents information about recoverable state' do
+ expect(presenter).to be_recoverable
+ end
+end
diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
index 19446ce1cf8..1cca89d31d7 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -33,14 +33,22 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
end
- shared_examples 'error logging' do
+ shared_examples 'error handling' do
context 'when installation raises a Kubeclient::HttpError' do
let(:cluster) { create(:cluster, :provided_by_user, :project) }
+ let(:logger) { service.send(:logger) }
+ let(:error) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) }
before do
application.update!(cluster: cluster)
- expect(service).to receive(:installation_phase).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
+ expect(service).to receive(:installation_phase).and_raise(error)
+ end
+
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'Unauthorized' }
+ let(:error_code) { 401 }
end
it 'shows the response code from the error' do
@@ -49,12 +57,6 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
expect(application).to be_errored.or(be_update_errored)
expect(application.status_reason).to eq('Kubernetes error: 401')
end
-
- it 'should log error' do
- expect(service.send(:logger)).to receive(:error)
-
- service.execute
- end
end
end
@@ -66,7 +68,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
context 'when application is updating' do
let(:application) { create(:clusters_applications_helm, :updating) }
- include_examples 'error logging'
+ include_examples 'error handling'
RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
@@ -127,7 +129,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
context 'when application is installing' do
- include_examples 'error logging'
+ include_examples 'error handling'
RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index 018d9822d3e..db0c33a95b2 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -39,51 +39,34 @@ describe Clusters::Applications::InstallService do
expect(helm_client).to receive(:install).with(install_command).and_raise(error)
end
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'system failure' }
+ let(:error_code) { 500 }
+ end
+
it 'make the application errored' do
service.execute
expect(application).to be_errored
expect(application.status_reason).to match('Kubernetes error: 500')
end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::InstallService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::InstallService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- service.execute
- end
end
context 'a non kubernetes error happens' do
let(:application) { create(:clusters_applications_helm, :scheduled) }
- let(:error) { StandardError.new("something bad happened") }
+ let(:error) { StandardError.new('something bad happened') }
before do
expect(application).to receive(:make_installing!).once.and_raise(error)
end
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'StandardError' }
+ let(:error_message) { 'something bad happened' }
+ let(:error_code) { nil }
+ end
+
it 'make the application errored' do
expect(helm_client).not_to receive(:install)
@@ -92,35 +75,6 @@ describe Clusters::Applications::InstallService do
expect(application).to be_errored
expect(application.status_reason).to eq("Can't start installation process.")
end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::InstallService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::InstallService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- service.execute
- end
end
end
end
diff --git a/spec/services/clusters/applications/patch_service_spec.rb b/spec/services/clusters/applications/patch_service_spec.rb
index d4ee3243b84..10b1379a127 100644
--- a/spec/services/clusters/applications/patch_service_spec.rb
+++ b/spec/services/clusters/applications/patch_service_spec.rb
@@ -41,47 +41,30 @@ describe Clusters::Applications::PatchService do
expect(helm_client).to receive(:update).with(update_command).and_raise(error)
end
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'system failure' }
+ let(:error_code) { 500 }
+ end
+
it 'make the application errored' do
service.execute
expect(application).to be_update_errored
expect(application.status_reason).to match('Kubernetes error: 500')
end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::PatchService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::PatchService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- service.execute
- end
end
context 'a non kubernetes error happens' do
let(:application) { create(:clusters_applications_knative, :scheduled) }
let(:error) { StandardError.new('something bad happened') }
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'StandardError' }
+ let(:error_message) { 'something bad happened' }
+ let(:error_code) { nil }
+ end
+
before do
expect(application).to receive(:make_updating!).once.and_raise(error)
end
@@ -94,35 +77,6 @@ describe Clusters::Applications::PatchService do
expect(application).to be_update_errored
expect(application.status_reason).to eq("Can't start update process.")
end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::PatchService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::PatchService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- service.execute
- end
end
end
end
diff --git a/spec/services/clusters/applications/upgrade_service_spec.rb b/spec/services/clusters/applications/upgrade_service_spec.rb
index 1822fc38dbd..dd2e6e94e4f 100644
--- a/spec/services/clusters/applications/upgrade_service_spec.rb
+++ b/spec/services/clusters/applications/upgrade_service_spec.rb
@@ -41,41 +41,18 @@ describe Clusters::Applications::UpgradeService do
expect(helm_client).to receive(:update).with(install_command).and_raise(error)
end
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'system failure' }
+ let(:error_code) { 500 }
+ end
+
it 'make the application errored' do
service.execute
expect(application).to be_update_errored
expect(application.status_reason).to match('Kubernetes error: 500')
end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::UpgradeService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::UpgradeService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- service.execute
- end
end
context 'a non kubernetes error happens' do
@@ -86,6 +63,12 @@ describe Clusters::Applications::UpgradeService do
expect(application).to receive(:make_updating!).once.and_raise(error)
end
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'StandardError' }
+ let(:error_message) { 'something bad happened' }
+ let(:error_code) { nil }
+ end
+
it 'make the application errored' do
expect(helm_client).not_to receive(:update)
@@ -94,35 +77,6 @@ describe Clusters::Applications::UpgradeService do
expect(application).to be_update_errored
expect(application.status_reason).to eq("Can't start upgrade process.")
end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::UpgradeService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::UpgradeService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- service.execute
- end
end
end
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 55e7b46248b..dc5d1cf2f04 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -263,19 +263,6 @@ describe MergeRequests::CreateService do
expect(merge_request.actual_head_pipeline).to be_merge_request_event
end
end
-
- context "when the 'ci_merge_request_pipeline' feature flag is disabled" do
- before do
- stub_feature_flags(ci_merge_request_pipeline: false)
- end
-
- it 'does not create a detached merge request pipeline' do
- expect(merge_request).to be_persisted
-
- merge_request.reload
- expect(merge_request.merge_request_pipelines.count).to eq(0)
- end
- end
end
context "when .gitlab-ci.yml does not have merge_requests keywords" do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 25cbac6d7ee..5cf3577f01f 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -230,17 +230,6 @@ describe MergeRequests::RefreshService do
end.not_to change { @merge_request.merge_request_pipelines.count }
end
end
-
- context "when the 'ci_merge_request_pipeline' feature flag is disabled" do
- before do
- stub_feature_flags(ci_merge_request_pipeline: false)
- end
-
- it 'does not create a detached merge request pipeline' do
- expect { subject }
- .not_to change { @merge_request.merge_request_pipelines.count }
- end
- end
end
context "when .gitlab-ci.yml does not have merge_requests keywords" do
diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
index 4604d867507..b337a1c18d8 100644
--- a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
@@ -1,4 +1,28 @@
# frozen_string_literal: true
shared_examples 'tag quick action' do
+ context "post note to existing commit" do
+ it 'tags this commit' do
+ add_note("/tag #{tag_name} #{tag_message}")
+
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "tagged commit #{truncated_commit_sha}"
+ expect(page).to have_content tag_name
+
+ visit project_tag_path(project, tag_name)
+ expect(page).to have_content tag_name
+ expect(page).to have_content tag_message
+ expect(page).to have_content truncated_commit_sha
+ end
+ end
+
+ context 'preview', :js do
+ it 'removes quick action from note and explains it' do
+ preview_note("/tag #{tag_name} #{tag_message}")
+
+ expect(page).not_to have_content '/tag'
+ expect(page).to have_content %{Tags this commit to #{tag_name} with "#{tag_message}"}
+ expect(page).to have_content tag_name
+ end
+ end
end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
index 31d88183f0d..c454ddc4bba 100644
--- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
@@ -1,4 +1,51 @@
# frozen_string_literal: true
shared_examples 'merge quick action' do
+ context 'when the current user can merge the MR' do
+ before do
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'merges the MR' do
+ add_note("/merge")
+
+ expect(page).to have_content 'Commands applied'
+
+ expect(merge_request.reload).to be_merged
+ end
+ end
+
+ context 'when the head diff changes in the meanwhile' do
+ before do
+ merge_request.source_branch = 'another_branch'
+ merge_request.save
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
+
+ context 'when the current user cannot merge the MR' do
+ before do
+ project.add_guest(guest)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
end
diff --git a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb
new file mode 100644
index 00000000000..78a8e49fd76
--- /dev/null
+++ b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+shared_examples 'logs kubernetes errors' do
+ let(:error_hash) do
+ {
+ service: service.class.name,
+ app_id: application.id,
+ project_ids: application.cluster.project_ids,
+ group_ids: [],
+ error_code: error_code
+ }
+ end
+
+ let(:logger_hash) do
+ error_hash.merge(
+ exception: error_name,
+ message: error_message,
+ backtrace: instance_of(Array)
+ )
+ end
+
+ it 'logs into kubernetes.log and Sentry' do
+ expect(service.send(:logger)).to receive(:error).with(logger_hash)
+
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
+ error,
+ extra: hash_including(error_hash)
+ )
+
+ service.execute
+ end
+end