summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/api/commits.rb16
-rw-r--r--lib/api/entities.rb26
-rw-r--r--lib/api/features.rb1
-rw-r--r--lib/api/helpers.rb8
-rw-r--r--lib/api/helpers/internal_helpers.rb8
-rw-r--r--lib/api/helpers/pagination.rb75
-rw-r--r--lib/api/helpers/runner.rb2
-rw-r--r--lib/api/internal.rb9
-rw-r--r--lib/api/issues.rb19
-rw-r--r--lib/api/merge_requests.rb6
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/project_milestones.rb17
-rw-r--r--lib/api/project_templates.rb5
-rw-r--r--lib/api/projects.rb8
-rw-r--r--lib/api/services.rb23
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/banzai/filter/relative_link_filter.rb5
-rw-r--r--lib/bitbucket_server/connection.rb1
-rw-r--r--lib/feature.rb10
-rw-r--r--lib/gitlab/auth/omniauth_identity_linker_base.rb6
-rw-r--r--lib/gitlab/chat.rb10
-rw-r--r--lib/gitlab/chat/command.rb94
-rw-r--r--lib/gitlab/chat/output.rb93
-rw-r--r--lib/gitlab/chat/responder.rb22
-rw-r--r--lib/gitlab/chat/responder/base.rb40
-rw-r--r--lib/gitlab/chat/responder/slack.rb80
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb5
-rw-r--r--lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb8
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml64
-rw-r--r--lib/gitlab/danger/helper.rb8
-rw-r--r--lib/gitlab/diff/file.rb4
-rw-r--r--lib/gitlab/etag_caching/router.rb8
-rw-r--r--lib/gitlab/git/repository.rb6
-rw-r--r--lib/gitlab/gitaly_client.rb2
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb19
-rw-r--r--lib/gitlab/graphql/authorize.rb15
-rw-r--r--lib/gitlab/graphql/authorize/authorize_resource.rb17
-rw-r--r--lib/gitlab/graphql/authorize/instrumentation.rb28
-rw-r--r--lib/gitlab/import_export/import_export.yml2
-rw-r--r--lib/gitlab/import_export/merge_request_parser.rb11
-rw-r--r--lib/gitlab/kubernetes/helm.rb4
-rw-r--r--lib/gitlab/lfs_token.rb13
-rw-r--r--lib/gitlab/project_template.rb8
-rw-r--r--lib/gitlab/shell.rb5
-rw-r--r--lib/gitlab/slash_commands/application_help.rb25
-rw-r--r--lib/gitlab/slash_commands/command.rb3
-rw-r--r--lib/gitlab/slash_commands/presenters/error.rb17
-rw-r--r--lib/gitlab/slash_commands/presenters/run.rb33
-rw-r--r--lib/gitlab/slash_commands/run.rb44
-rw-r--r--lib/gitlab/tracing.rb9
-rw-r--r--lib/gitlab/usage_data.rb12
-rw-r--r--lib/sentry/client.rb2
53 files changed, 777 insertions, 185 deletions
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 9d23daafe95..8defc59224d 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -323,6 +323,22 @@ module API
present paginate(commit.merge_requests), with: Entities::MergeRequestBasic
end
+
+ desc "Get a commit's GPG signature" do
+ success Entities::CommitSignature
+ end
+ params do
+ requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
+ end
+ get ':id/repository/commits/:sha/signature', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
+ commit = user_project.commit(params[:sha])
+ not_found! 'Commit' unless commit
+
+ signature = commit.signature
+ not_found! 'GPG Signature' unless signature
+
+ present signature, with: Entities::CommitSignature
+ end
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index af788a4ed73..7c035990fb0 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -392,6 +392,13 @@ module API
expose :project_id
end
+ class CommitSignature < Grape::Entity
+ expose :gpg_key_id
+ expose :gpg_key_primary_keyid, :gpg_key_user_name, :gpg_key_user_email
+ expose :verification_status
+ expose :gpg_key_subkey_id
+ end
+
class BasicRef < Grape::Entity
expose :type, :name
end
@@ -550,6 +557,15 @@ module API
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue|
issue
end
+
+ expose :merge_requests_count do |issue, options|
+ if options[:issuable_metadata]
+ # Avoids an N+1 query when metadata is included
+ options[:issuable_metadata][issue.id].merge_requests_count
+ else
+ issue.merge_requests_closing_issues.count
+ end
+ end
end
class Issue < IssueBasic
@@ -1369,13 +1385,9 @@ module API
class GitInfo < Grape::Entity
expose :repo_url, :ref, :sha, :before_sha
- expose :ref_type do |model|
- if model.tag
- 'tag'
- else
- 'branch'
- end
- end
+ expose :ref_type
+ expose :refspecs
+ expose :git_depth, as: :depth
end
class RunnerInfo < Grape::Entity
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 835aac05905..4dc1834c644 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -42,6 +42,7 @@ module API
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
optional :feature_group, type: String, desc: 'A Feature group name'
optional :user, type: String, desc: 'A GitLab username'
+ optional :group, type: String, desc: "A GitLab group's path, such as 'gitlab-org'"
optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce'
end
post ':name' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 2eb7b04711a..54cd4cd9cdb 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -299,6 +299,12 @@ module API
items.search(text)
end
+ def order_options_with_tie_breaker
+ order_options = { params[:order_by] => params[:sort] }
+ order_options['id'] ||= 'desc'
+ order_options
+ end
+
# error helpers
def forbidden!(reason = nil)
@@ -393,7 +399,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def reorder_projects(projects)
- projects.reorder(params[:order_by] => params[:sort])
+ projects.reorder(order_options_with_tie_breaker)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 4eaaca96b49..fe78049af87 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -81,6 +81,14 @@ module API
Gitlab::GlRepository.gl_repository(project, wiki?)
end
+ def gl_project_path
+ if wiki?
+ project.wiki.full_path
+ else
+ project.full_path
+ end
+ end
+
# Return the repository depending on whether we want the wiki or the
# regular repository
def repository
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index de59c915d66..d00e61678b5 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -13,6 +13,33 @@ module API
strategy.new(self).paginate(relation)
end
+ class Base
+ private
+
+ def per_page
+ @per_page ||= params[:per_page]
+ end
+
+ def base_request_uri
+ @base_request_uri ||= URI.parse(request.url).tap do |uri|
+ uri.host = Gitlab.config.gitlab.host
+ uri.port = nil
+ end
+ end
+
+ def build_page_url(query_params:)
+ base_request_uri.tap do |uri|
+ uri.query = query_params
+ end.to_s
+ end
+
+ def page_href(next_page_params = {})
+ query_params = params.merge(**next_page_params, per_page: per_page).to_query
+
+ build_page_url(query_params: query_params)
+ end
+ end
+
class KeysetPaginationInfo
attr_reader :relation, :request_context
@@ -85,7 +112,7 @@ module API
end
end
- class KeysetPaginationStrategy
+ class KeysetPaginationStrategy < Base
attr_reader :request_context
delegate :params, :header, :request, to: :request_context
@@ -141,10 +168,6 @@ module API
]
end
- def per_page
- params[:per_page]
- end
-
def add_default_pagination_headers
header 'X-Per-Page', per_page.to_s
end
@@ -154,22 +177,12 @@ module API
header 'Link', link_for('next', next_page_params)
end
- def page_href(next_page_params)
- request_url = request.url.split('?').first
- request_params = params.dup
- request_params[:per_page] = per_page
-
- request_params.merge!(next_page_params) if next_page_params
-
- "#{request_url}?#{request_params.to_query}"
- end
-
def link_for(rel, next_page_params)
%(<#{page_href(next_page_params)}>; rel="#{rel}")
end
end
- class DefaultPaginationStrategy
+ class DefaultPaginationStrategy < Base
attr_reader :request_context
delegate :params, :header, :request, to: :request_context
@@ -198,15 +211,13 @@ module API
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def add_default_order(relation)
if relation.is_a?(ActiveRecord::Relation) && relation.order_values.empty?
- relation = relation.order(:id)
+ relation = relation.order(:id) # rubocop: disable CodeReuse/ActiveRecord
end
relation
end
- # rubocop: enable CodeReuse/ActiveRecord
def add_pagination_headers(paginated_data)
header 'X-Per-Page', paginated_data.limit_value.to_s
@@ -222,27 +233,13 @@ module API
end
def pagination_links(paginated_data)
- request_url = request.url.split('?').first
- request_params = params.clone
- request_params[:per_page] = paginated_data.limit_value
-
- links = []
-
- request_params[:page] = paginated_data.prev_page
- links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") if request_params[:page]
-
- request_params[:page] = paginated_data.next_page
- links << %(<#{request_url}?#{request_params.to_query}>; rel="next") if request_params[:page]
-
- request_params[:page] = 1
- links << %(<#{request_url}?#{request_params.to_query}>; rel="first")
-
- unless data_without_counts?(paginated_data)
- request_params[:page] = total_pages(paginated_data)
- links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
- end
+ [].tap do |links|
+ links << %(<#{page_href(page: paginated_data.prev_page)}>; rel="prev") if paginated_data.prev_page
+ links << %(<#{page_href(page: paginated_data.next_page)}>; rel="next") if paginated_data.next_page
+ links << %(<#{page_href(page: 1)}>; rel="first")
- links.join(', ')
+ links << %(<#{page_href(page: total_pages(paginated_data))}>; rel="last") unless data_without_counts?(paginated_data)
+ end.join(', ')
end
def total_pages(paginated_data)
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 16df8e830e1..ff73a49d5e8 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -26,7 +26,7 @@ module API
end
def get_runner_ip
- { ip_address: request.env["HTTP_X_FORWARDED_FOR"] || request.ip }
+ { ip_address: env["action_dispatch.remote_ip"].to_s || request.ip }
end
def current_runner
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 9488b3469d9..70b32f7d758 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -77,6 +77,7 @@ module API
when ::Gitlab::GitAccessResult::Success
payload = {
gl_repository: gl_repository,
+ gl_project_path: gl_project_path,
gl_id: Gitlab::GlId.gl_id(user),
gl_username: user&.username,
git_config_options: [],
@@ -117,13 +118,7 @@ module API
raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!")
end
- token_handler = Gitlab::LfsToken.new(actor)
-
- {
- username: token_handler.actor_name,
- lfs_token: token_handler.token,
- repository_http_path: project.http_url_to_repo
- }
+ Gitlab::LfsToken.new(actor).authentication_payload(project.http_url_to_repo)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index b3636c98550..f43f4d961d6 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -29,8 +29,7 @@ module API
issues = IssuesFinder.new(current_user, args).execute
.preload(:assignees, :labels, :notes, :timelogs, :project, :author, :closed_by)
-
- issues.reorder(args[:order_by] => args[:sort])
+ issues.reorder(order_options_with_tie_breaker)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -55,6 +54,7 @@ module API
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all],
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
+ optional :confidential, type: Boolean, desc: 'Filter confidential or public issues'
use :pagination
use :issues_params_ee
@@ -304,19 +304,14 @@ module API
get ':id/issues/:issue_iid/related_merge_requests' do
issue = find_project_issue(params[:issue_iid])
- merge_request_iids = ::Issues::ReferencedMergeRequestsService.new(user_project, current_user)
+ merge_requests = ::Issues::ReferencedMergeRequestsService.new(user_project, current_user)
.execute(issue)
.flatten
- .map(&:iid)
-
- merge_requests =
- if merge_request_iids.present?
- MergeRequestsFinder.new(current_user, project_id: user_project.id, iids: merge_request_iids).execute
- else
- MergeRequest.none
- end
- present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
+ present paginate(::Kaminari.paginate_array(merge_requests)),
+ with: Entities::MergeRequestBasic,
+ current_user: current_user,
+ project: user_project
end
desc 'List merge requests closing issue' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index df46b4446ff..03f6684226f 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -38,7 +38,7 @@ module API
args[:scope] = args[:scope].underscore if args[:scope]
merge_requests = MergeRequestsFinder.new(current_user, args).execute
- .reorder(args[:order_by] => args[:sort])
+ .reorder(order_options_with_tie_breaker)
merge_requests = paginate(merge_requests)
.preload(:source_project, :target_project)
@@ -369,11 +369,11 @@ module API
merge_request.update(squash: params[:squash]) if params[:squash]
- merge_params = {
+ merge_params = HashWithIndifferentAccess.new(
commit_message: params[:merge_commit_message],
squash_commit_message: params[:squash_commit_message],
should_remove_source_branch: params[:should_remove_source_branch]
- }
+ )
if merge_when_pipeline_succeeds && merge_request.head_pipeline && merge_request.head_pipeline.active?
::MergeRequests::MergeWhenPipelineSucceedsService
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 1bdf7aeb119..f7bd092ce50 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -39,7 +39,7 @@ module API
# at the DB query level (which we cannot in that case), the current
# page can have less elements than :per_page even if
# there's more than one page.
- raw_notes = noteable.notes.with_metadata.reorder(params[:order_by] => params[:sort])
+ raw_notes = noteable.notes.with_metadata.reorder(order_options_with_tie_breaker)
notes =
# paginate() only works with a relation. This could lead to a
# mismatch between the pagination headers info and the actual notes
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
index da31bcb8dac..ca24742b7a3 100644
--- a/lib/api/project_milestones.rb
+++ b/lib/api/project_milestones.rb
@@ -98,6 +98,23 @@ module API
milestone_issuables_for(user_project, :merge_request)
end
+
+ desc 'Promote a milestone to group milestone' do
+ detail 'This feature was introduced in GitLab 11.9'
+ end
+ post ':id/milestones/:milestone_id/promote' do
+ begin
+ authorize! :admin_milestone, user_project
+ authorize! :admin_milestone, user_project.group
+
+ milestone = user_project.milestones.find(params[:milestone_id])
+ Milestones::PromoteService.new(user_project, current_user).execute(milestone)
+
+ status(200)
+ rescue Milestones::PromoteService::PromoteMilestoneError => error
+ render_api_error!(error.message, 400)
+ end
+ end
end
end
end
diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb
index d05ddad7466..119902a189c 100644
--- a/lib/api/project_templates.rb
+++ b/lib/api/project_templates.rb
@@ -36,7 +36,10 @@ module API
optional :project, type: String, desc: 'The project name to use when expanding placeholders in the template. Only affects licenses'
optional :fullname, type: String, desc: 'The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses'
end
- get ':id/templates/:type/:name', requirements: { name: /[\w\.-]+/ } do
+ # The regex is needed to ensure a period (e.g. agpl-3.0)
+ # isn't confused with a format type. We also need to allow encoded
+ # values (e.g. C%2B%2B for C++), so allow % and + as well.
+ get ':id/templates/:type/:name', requirements: { name: /[\w%.+-]+/ } do
template = TemplateFinder
.build(params[:type], user_project, name: params[:name])
.execute
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 6a93ef9f3ad..b23fe6cd4e7 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -258,6 +258,8 @@ module API
end
params do
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
+ optional :path, type: String, desc: 'The path that will be assigned to the fork'
+ optional :name, type: String, desc: 'The name that will be assigned to the fork'
end
post ':id/fork' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42284')
@@ -386,7 +388,11 @@ module API
desc 'Get languages in project repository'
get ':id/languages' do
- user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h
+ if user_project.repository_languages.present?
+ user_project.repository_languages.map { |l| [l.name, l.share] }.to_h
+ else
+ user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h
+ end
end
desc 'Remove a project'
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 163c7505a65..bda6be51553 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -431,7 +431,7 @@ module API
{
required: false,
name: :jira_issue_transition_id,
- type: Integer,
+ type: String,
desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
}
],
@@ -592,6 +592,26 @@ module API
desc: 'The description of the tracker'
}
],
+ 'youtrack' => [
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'The issues URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'The description of the tracker'
+ }
+ ],
'slack' => [
CHAT_NOTIFICATION_SETTINGS,
CHAT_NOTIFICATION_FLAGS,
@@ -665,6 +685,7 @@ module API
PrometheusService,
PushoverService,
RedmineService,
+ YoutrackService,
SlackService,
MattermostService,
MicrosoftTeamsService,
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 8ce09a8881b..7d88880d412 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -26,7 +26,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def reorder_users(users)
if params[:order_by] && params[:sort]
- users.reorder(params[:order_by] => params[:sort])
+ users.reorder(order_options_with_tie_breaker)
else
users
end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 93e6d6470f1..2745905c5ff 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -150,7 +150,10 @@ module Banzai
end
def uri_type(path)
- @uri_types[path] ||= current_commit.uri_type(path)
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58011
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ @uri_types[path] ||= current_commit.uri_type(path)
+ end
end
def current_commit
diff --git a/lib/bitbucket_server/connection.rb b/lib/bitbucket_server/connection.rb
index 9c14b26c65a..fbd451efb23 100644
--- a/lib/bitbucket_server/connection.rb
+++ b/lib/bitbucket_server/connection.rb
@@ -77,6 +77,7 @@ module BitbucketServer
private
def check_errors!(response)
+ return if ActionDispatch::Response::NO_CONTENT_CODES.include?(response.code)
raise ConnectionError, "Response is not valid JSON" unless response.parsed_response.is_a?(Hash)
return if response.code >= 200 && response.code < 300
diff --git a/lib/feature.rb b/lib/feature.rb
index e59cd70f822..749c861d740 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -111,11 +111,11 @@ class Feature
end
def gate_specified?
- %i(user project feature_group).any? { |key| params.key?(key) }
+ %i(user project group feature_group).any? { |key| params.key?(key) }
end
def targets
- [feature_group, user, project].compact
+ [feature_group, user, project, group].compact
end
private
@@ -139,5 +139,11 @@ class Feature
Project.find_by_full_path(params[:project])
end
+
+ def group
+ return unless params.key?(:group)
+
+ Group.find_by_full_path(params[:group])
+ end
end
end
diff --git a/lib/gitlab/auth/omniauth_identity_linker_base.rb b/lib/gitlab/auth/omniauth_identity_linker_base.rb
index 253445570f2..c620fc5d6bd 100644
--- a/lib/gitlab/auth/omniauth_identity_linker_base.rb
+++ b/lib/gitlab/auth/omniauth_identity_linker_base.rb
@@ -12,7 +12,7 @@ module Gitlab
end
def link
- save if identity.new_record?
+ save if unlinked?
end
def changed?
@@ -35,6 +35,10 @@ module Gitlab
@changed = identity.save
end
+ def unlinked?
+ identity.new_record?
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def identity
@identity ||= current_user.identities
diff --git a/lib/gitlab/chat.rb b/lib/gitlab/chat.rb
new file mode 100644
index 00000000000..23d4fb36b66
--- /dev/null
+++ b/lib/gitlab/chat.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ # Returns `true` if Chatops is available for the current instance.
+ def self.available?
+ ::Feature.enabled?(:chatops, default_enabled: true)
+ end
+ end
+end
diff --git a/lib/gitlab/chat/command.rb b/lib/gitlab/chat/command.rb
new file mode 100644
index 00000000000..49b7dcf4bbe
--- /dev/null
+++ b/lib/gitlab/chat/command.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ # Class for scheduling chat pipelines.
+ #
+ # A Command takes care of creating a `Ci::Pipeline` with all the data
+ # necessary to execute a chat command. This includes data such as the chat
+ # data (e.g. the response URL) and any environment variables that should be
+ # exposed to the chat command.
+ class Command
+ include Utils::StrongMemoize
+
+ attr_reader :project, :chat_name, :name, :arguments, :response_url,
+ :channel
+
+ # project - The Project to schedule the command for.
+ # chat_name - The ChatName belonging to the user that scheduled the
+ # command.
+ # name - The name of the chat command to run.
+ # arguments - The arguments (as a String) to pass to the command.
+ # channel - The channel the message was sent from.
+ # response_url - The URL to send the response back to.
+ def initialize(project:, chat_name:, name:, arguments:, channel:, response_url:)
+ @project = project
+ @chat_name = chat_name
+ @name = name
+ @arguments = arguments
+ @channel = channel
+ @response_url = response_url
+ end
+
+ # Tries to create a new pipeline.
+ #
+ # This method will return a pipeline that _may_ be persisted, or `nil` if
+ # the pipeline could not be created.
+ def try_create_pipeline
+ return unless valid?
+
+ create_pipeline
+ end
+
+ def create_pipeline
+ service = ::Ci::CreatePipelineService.new(
+ project,
+ chat_name.user,
+ ref: branch,
+ sha: commit,
+ chat_data: {
+ chat_name_id: chat_name.id,
+ command: name,
+ arguments: arguments,
+ response_url: response_url
+ }
+ )
+
+ service.execute(:chat) do |pipeline|
+ build_environment_variables(pipeline)
+ build_chat_data(pipeline)
+ end
+ end
+
+ # pipeline - The `Ci::Pipeline` to create the environment variables for.
+ def build_environment_variables(pipeline)
+ pipeline.variables.build(
+ [{ key: 'CHAT_INPUT', value: arguments },
+ { key: 'CHAT_CHANNEL', value: channel }]
+ )
+ end
+
+ # pipeline - The `Ci::Pipeline` to create the chat data for.
+ def build_chat_data(pipeline)
+ pipeline.build_chat_data(
+ chat_name_id: chat_name.id,
+ response_url: response_url
+ )
+ end
+
+ def valid?
+ branch && commit
+ end
+
+ def branch
+ strong_memoize(:branch) { project.default_branch }
+ end
+
+ def commit
+ strong_memoize(:commit) do
+ project.commit(branch)&.id if branch
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat/output.rb b/lib/gitlab/chat/output.rb
new file mode 100644
index 00000000000..411b1555a7d
--- /dev/null
+++ b/lib/gitlab/chat/output.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ # Class for gathering and formatting the output of a `Ci::Build`.
+ class Output
+ attr_reader :build
+
+ MissingBuildSectionError = Class.new(StandardError)
+
+ # The primary trace section to look for.
+ PRIMARY_SECTION = 'chat_reply'
+
+ # The backup trace section in case the primary one could not be found.
+ FALLBACK_SECTION = 'build_script'
+
+ # build - The `Ci::Build` to obtain the output from.
+ def initialize(build)
+ @build = build
+ end
+
+ # Returns a `String` containing the output of the build.
+ #
+ # The output _does not_ include the command that was executed.
+ def to_s
+ offset, length = read_offset_and_length
+
+ trace.read do |stream|
+ stream.seek(offset)
+
+ output = stream
+ .stream
+ .read(length)
+ .force_encoding(Encoding.default_external)
+
+ without_executed_command_line(output)
+ end
+ end
+
+ # Returns the offset to seek to and the number of bytes to read relative
+ # to the offset.
+ def read_offset_and_length
+ section = find_build_trace_section(PRIMARY_SECTION) ||
+ find_build_trace_section(FALLBACK_SECTION)
+
+ unless section
+ raise(
+ MissingBuildSectionError,
+ "The build_script trace section could not be found for build #{build.id}"
+ )
+ end
+
+ length = section[:byte_end] - section[:byte_start]
+
+ [section[:byte_start], length]
+ end
+
+ # Removes the line containing the executed command from the build output.
+ #
+ # output - A `String` containing the output of a trace section.
+ def without_executed_command_line(output)
+ # If `output.split("\n")` produces an empty Array then the slicing that
+ # follows it will produce a nil. For example:
+ #
+ # "\n".split("\n") # => []
+ # "\n".split("\n")[1..-1] # => nil
+ #
+ # To work around this we only "join" if we're given an Array.
+ if (converted = output.split("\n")[1..-1])
+ converted.join("\n")
+ else
+ ''
+ end
+ end
+
+ # Returns the trace section for the given name, or `nil` if the section
+ # could not be found.
+ #
+ # name - The name of the trace section to find.
+ def find_build_trace_section(name)
+ trace_sections.find { |s| s[:name] == name }
+ end
+
+ def trace_sections
+ @trace_sections ||= trace.extract_sections
+ end
+
+ def trace
+ @trace ||= build.trace
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat/responder.rb b/lib/gitlab/chat/responder.rb
new file mode 100644
index 00000000000..6267fbc20e2
--- /dev/null
+++ b/lib/gitlab/chat/responder.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ module Responder
+ # Returns an instance of the responder to use for generating chat
+ # responses.
+ #
+ # This method will return `nil` if no formatter is available for the given
+ # build.
+ #
+ # build - A `Ci::Build` that executed a chat command.
+ def self.responder_for(build)
+ service = build.pipeline.chat_data&.chat_name&.service
+
+ if (responder = service.try(:chat_responder))
+ responder.new(build)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat/responder/base.rb b/lib/gitlab/chat/responder/base.rb
new file mode 100644
index 00000000000..f1ad0e36793
--- /dev/null
+++ b/lib/gitlab/chat/responder/base.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ module Responder
+ class Base
+ attr_reader :build
+
+ # build - The `Ci::Build` that was executed.
+ def initialize(build)
+ @build = build
+ end
+
+ def pipeline
+ build.pipeline
+ end
+
+ def project
+ pipeline.project
+ end
+
+ def success(*)
+ raise NotImplementedError, 'You must implement #success(output)'
+ end
+
+ def failure
+ raise NotImplementedError, 'You must implement #failure'
+ end
+
+ def send_response(output)
+ raise NotImplementedError, 'You must implement #send_response(output)'
+ end
+
+ def scheduled_output
+ raise NotImplementedError, 'You must implement #scheduled_output'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat/responder/slack.rb b/lib/gitlab/chat/responder/slack.rb
new file mode 100644
index 00000000000..0cf02c92a67
--- /dev/null
+++ b/lib/gitlab/chat/responder/slack.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ module Responder
+ class Slack < Responder::Base
+ SUCCESS_COLOR = '#B3ED8E'
+ FAILURE_COLOR = '#FF5640'
+ RESPONSE_TYPE = :in_channel
+
+ # Slack breaks messages apart if they're around 4 KB in size. We use a
+ # slightly smaller limit here to account for user mentions.
+ MESSAGE_SIZE_LIMIT = 3.5.kilobytes
+
+ # Sends a response back to Slack
+ #
+ # output - The output to send back to Slack, as a Hash.
+ def send_response(output)
+ Gitlab::HTTP.post(
+ pipeline.chat_data.response_url,
+ {
+ headers: { Accept: 'application/json' },
+ body: output.to_json
+ }
+ )
+ end
+
+ # Sends the output for a build that completed successfully.
+ #
+ # output - The output produced by the chat command.
+ def success(output)
+ return if output.empty?
+
+ send_response(
+ text: message_text(limit_output(output)),
+ response_type: RESPONSE_TYPE
+ )
+ end
+
+ # Sends the output for a build that failed.
+ def failure
+ send_response(
+ text: message_text("<#{build_url}|Sorry, the build failed!>"),
+ response_type: RESPONSE_TYPE
+ )
+ end
+
+ # Returns the output to send back after a command has been scheduled.
+ def scheduled_output
+ # We return an empty message so that Slack still shows the input
+ # command, without polluting the channel with standard "The job has
+ # been scheduled" (or similar) responses.
+ { text: '' }
+ end
+
+ private
+
+ def limit_output(output)
+ if output.bytesize <= MESSAGE_SIZE_LIMIT
+ output
+ else
+ "<#{build_url}|The output is too large to be sent back directly!>"
+ end
+ end
+
+ def mention_user
+ "<@#{pipeline.chat_data.chat_name.chat_id}>"
+ end
+
+ def message_text(output)
+ "#{mention_user}: #{output}"
+ end
+
+ def build_url
+ ::Gitlab::Routing.url_helpers.project_build_url(project, build)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index 41632211374..164a4634d84 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -12,6 +12,8 @@ module Gitlab
ref: @command.ref,
sha: @command.sha,
before_sha: @command.before_sha,
+ source_sha: @command.source_sha,
+ target_sha: @command.target_sha,
tag: @command.tag_exists?,
trigger_requests: Array(@command.trigger_request),
user: @command.current_user,
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index e62d547d862..7b77e86feae 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -7,10 +7,11 @@ module Gitlab
module Chain
Command = Struct.new(
:source, :project, :current_user,
- :origin_ref, :checkout_sha, :after_sha, :before_sha,
+ :origin_ref, :checkout_sha, :after_sha, :before_sha, :source_sha, :target_sha,
:trigger_request, :schedule, :merge_request,
:ignore_skip_ci, :save_incompleted,
- :seeds_block, :variables_attributes, :push_options
+ :seeds_block, :variables_attributes, :push_options,
+ :chat_data
) do
include Gitlab::Utils::StrongMemoize
diff --git a/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb b/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb
index 0f687a4ce9b..1e09b417311 100644
--- a/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb
+++ b/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb
@@ -6,7 +6,13 @@ module Gitlab
module Chain
class RemoveUnwantedChatJobs < Chain::Base
def perform!
- # to be overriden in EE
+ return unless pipeline.config_processor && pipeline.chat?
+
+ # When scheduling a chat pipeline we only want to run the build
+ # that matches the chat command.
+ pipeline.config_processor.jobs.select! do |name, _|
+ name.to_s == command.chat_data[:command].to_s
+ end
end
def break?
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index e369d26f22f..c0d4d4400b3 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -48,12 +48,15 @@ variables:
POSTGRES_PASSWORD: testing-password
POSTGRES_ENABLED: "true"
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
+ POSTGRES_VERSION: 9.6.2
- KUBERNETES_VERSION: 1.11.6
- HELM_VERSION: 2.12.2
+ KUBERNETES_VERSION: 1.11.7
+ HELM_VERSION: 2.12.3
DOCKER_DRIVER: overlay2
+ ROLLOUT_RESOURCE_TYPE: deployment
+
stages:
- build
- test
@@ -71,12 +74,11 @@ stages:
build:
stage: build
- image: docker:stable-git
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image/master:stable"
services:
- - docker:stable-dind
+ - docker:stable-dind
script:
- - setup_docker
- - build
+ - /build/build.sh
only:
- branches
@@ -488,7 +490,6 @@ rollout 100%:
export DATABASE_URL=${DATABASE_URL-$auto_database_url}
export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
export CI_APPLICATION_TAG=$CI_COMMIT_SHA
- export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE=$KUBE_NAMESPACE
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
@@ -698,6 +699,7 @@ rollout 100%:
--set postgresql.postgresUser="$POSTGRES_USER" \
--set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
--set postgresql.postgresDatabase="$POSTGRES_DB" \
+ --set postgresql.imageTag="$POSTGRES_VERSION" \
--set application.initializeCommand="$DB_INITIALIZE" \
--namespace="$KUBE_NAMESPACE" \
"$name" \
@@ -740,7 +742,7 @@ rollout 100%:
chart/
fi
- kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/$name"
+ kubectl rollout status -n "$KUBE_NAMESPACE" -w "$ROLLOUT_RESOURCE_TYPE/$name"
}
function scale() {
@@ -826,7 +828,7 @@ rollout 100%:
# Function to ensure backwards compatibility with AUTO_DEVOPS_DOMAIN
function ensure_kube_ingress_base_domain() {
- if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then
+ if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ] && [ -n "$AUTO_DEVOPS_DOMAIN" ] ; then
export KUBE_INGRESS_BASE_DOMAIN=$AUTO_DEVOPS_DOMAIN
fi
}
@@ -847,50 +849,6 @@ rollout 100%:
fi
}
- function build() {
- registry_login
-
- if [[ -f Dockerfile ]]; then
- echo "Building Dockerfile-based application..."
- docker build \
- --build-arg HTTP_PROXY="$HTTP_PROXY" \
- --build-arg http_proxy="$http_proxy" \
- --build-arg HTTPS_PROXY="$HTTPS_PROXY" \
- --build-arg https_proxy="$https_proxy" \
- --build-arg FTP_PROXY="$FTP_PROXY" \
- --build-arg ftp_proxy="$ftp_proxy" \
- --build-arg NO_PROXY="$NO_PROXY" \
- --build-arg no_proxy="$no_proxy" \
- -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
- else
- echo "Building Heroku-based application using gliderlabs/herokuish docker image..."
- docker run -i \
- -e BUILDPACK_URL \
- -e HTTP_PROXY \
- -e http_proxy \
- -e HTTPS_PROXY \
- -e https_proxy \
- -e FTP_PROXY \
- -e ftp_proxy \
- -e NO_PROXY \
- -e no_proxy \
- --name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build
- docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
- docker rm "$CI_CONTAINER_NAME" >/dev/null
- echo ""
-
- echo "Configuring $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG docker image..."
- docker create --expose 5000 --env PORT=5000 --name="$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" /bin/herokuish procfile start web
- docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
- docker rm "$CI_CONTAINER_NAME" >/dev/null
- echo ""
- fi
-
- echo "Pushing to GitLab Container Registry..."
- docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
- echo ""
- }
-
function initialize_tiller() {
echo "Checking Tiller..."
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index b8428637e19..d3c86fdb629 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -95,6 +95,7 @@ module Gitlab
CATEGORY_LABELS = {
docs: "~Documentation",
+ none: "",
qa: "~QA"
}.freeze
@@ -105,13 +106,13 @@ module Gitlab
%r{\A(ee/)?app/(assets|views)/} => :frontend,
%r{\A(ee/)?public/} => :frontend,
- %r{\A(ee/)?spec/javascripts/} => :frontend,
+ %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
%r{\A(ee/)?vendor/assets/} => :frontend,
%r{\A(jest\.config\.js|package\.json|yarn\.lock)\z} => :frontend,
%r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
%r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
- %r{\A(ee/)?spec/(?!javascripts)[^/]+} => :backend,
+ %r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
%r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
%r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
%r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile|\.gitlab-ci\.yml)\z} => :backend,
@@ -120,6 +121,9 @@ module Gitlab
%r{\A(ee/)?db/} => :database,
%r{\A(ee/)?qa/} => :qa,
+ # Files that don't fit into any category are marked with :none
+ %r{\A(ee/)?changelogs/} => :none,
+
# Fallbacks in case the above patterns miss anything
%r{\.rb\z} => :backend,
%r{\.(md|txt)\z} => :docs,
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index e410d5a8333..c9d89d56884 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -293,6 +293,10 @@ module Gitlab
end
end
+ def viewer
+ rich_viewer || simple_viewer
+ end
+
def simple_viewer
@simple_viewer ||= simple_viewer_class.new(self)
end
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
index 08e30214b46..0891f79198d 100644
--- a/lib/gitlab/etag_caching/router.rb
+++ b/lib/gitlab/etag_caching/router.rb
@@ -52,6 +52,14 @@ module Gitlab
Gitlab::EtagCaching::Router::Route.new(
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/environments\.json\z),
'environments'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/import/github/realtime_changes\.json\z),
+ 'realtime_changes_import_github'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/import/gitea/realtime_changes\.json\z),
+ 'realtime_changes_import_gitea'
)
].freeze
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 593a3676519..aea132a3dd9 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -556,6 +556,12 @@ module Gitlab
tags.find { |tag| tag.name == name }
end
+ def merge_to_ref(user, source_sha, branch, target_ref, message)
+ wrapped_gitaly_errors do
+ gitaly_operation_client.user_merge_to_ref(user, source_sha, branch, target_ref, message)
+ end
+ end
+
def merge(user, source_sha, target_branch, message, &block)
wrapped_gitaly_errors do
gitaly_operation_client.user_merge_branch(user, source_sha, target_branch, message, &block)
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 0ab53f8f706..5aeedb0f50d 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -28,7 +28,7 @@ module Gitlab
PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'
- MAXIMUM_GITALY_CALLS = 35
+ MAXIMUM_GITALY_CALLS = 30
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
MUTEX = Mutex.new
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 22d2d149e65..d172c798da2 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -100,6 +100,25 @@ module Gitlab
end
end
+ def user_merge_to_ref(user, source_sha, branch, target_ref, message)
+ request = Gitaly::UserMergeToRefRequest.new(
+ repository: @gitaly_repo,
+ source_sha: source_sha,
+ branch: encode_binary(branch),
+ target_ref: encode_binary(target_ref),
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ message: message
+ )
+
+ response = GitalyClient.call(@repository.storage, :operation_service, :user_merge_to_ref, request)
+
+ if pre_receive_error = response.pre_receive_error.presence
+ raise Gitlab::Git::PreReceiveError, pre_receive_error
+ end
+
+ response.commit_id
+ end
+
def user_merge_branch(user, source_sha, target_branch, message)
request_enum = QueueEnumerator.new
response_enum = GitalyClient.call(
diff --git a/lib/gitlab/graphql/authorize.rb b/lib/gitlab/graphql/authorize.rb
index 5e48bf9043d..f62813db82c 100644
--- a/lib/gitlab/graphql/authorize.rb
+++ b/lib/gitlab/graphql/authorize.rb
@@ -10,21 +10,6 @@ module Gitlab
def self.use(schema_definition)
schema_definition.instrument(:field, Instrumentation.new)
end
-
- def required_permissions
- # If the `#authorize` call is used on multiple classes, we add the
- # permissions specified on a subclass, to the ones that were specified
- # on it's superclass.
- @required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions)
- superclass.required_permissions.dup
- else
- []
- end
- end
-
- def authorize(*permissions)
- required_permissions.concat(permissions)
- end
end
end
end
diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb
index a56c4f6368d..b367a97105c 100644
--- a/lib/gitlab/graphql/authorize/authorize_resource.rb
+++ b/lib/gitlab/graphql/authorize/authorize_resource.rb
@@ -6,8 +6,21 @@ module Gitlab
module AuthorizeResource
extend ActiveSupport::Concern
- included do
- extend Gitlab::Graphql::Authorize
+ class_methods do
+ def required_permissions
+ # If the `#authorize` call is used on multiple classes, we add the
+ # permissions specified on a subclass, to the ones that were specified
+ # on it's superclass.
+ @required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions)
+ superclass.required_permissions.dup
+ else
+ []
+ end
+ end
+
+ def authorize(*permissions)
+ required_permissions.concat(permissions)
+ end
end
def find_object(*args)
diff --git a/lib/gitlab/graphql/authorize/instrumentation.rb b/lib/gitlab/graphql/authorize/instrumentation.rb
index d638d2b43ee..593da8471dd 100644
--- a/lib/gitlab/graphql/authorize/instrumentation.rb
+++ b/lib/gitlab/graphql/authorize/instrumentation.rb
@@ -6,19 +6,15 @@ module Gitlab
class Instrumentation
# Replace the resolver for the field with one that will only return the
# resolved object if the permissions check is successful.
- #
- # Collections are not supported. Apply permissions checks for those at the
- # database level instead, to avoid loading superfluous data from the DB
def instrument(_type, field)
- field_definition = field.metadata[:type_class]
- return field unless field_definition.respond_to?(:required_permissions)
- return field if field_definition.required_permissions.empty?
+ required_permissions = Array.wrap(field.metadata[:authorize])
+ return field if required_permissions.empty?
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], field_definition.required_permissions)
+ checker = build_checker(ctx[:current_user], required_permissions)
if resolved_obj.respond_to?(:then)
resolved_obj.then(&checker)
@@ -35,10 +31,22 @@ module Gitlab
private
def build_checker(current_user, abilities)
- proc do |obj|
+ lambda do |value|
# Load the elements if they weren't loaded by BatchLoader yet
- obj = obj.sync if obj.respond_to?(:sync)
- obj if abilities.all? { |ability| Ability.allowed?(current_user, ability, obj) }
+ 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
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 099677a791c..fa54fc17d95 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -28,7 +28,6 @@ project_tree:
- notes:
:author
- releases:
- - :author
- :links
- project_members:
- :user
@@ -133,7 +132,6 @@ excluded_attributes:
- :external_diff
- :stored_externally
- :external_diff_store
- - :st_diffs
merge_request_diff_files:
- :diff
- :external_diff_offset
diff --git a/lib/gitlab/import_export/merge_request_parser.rb b/lib/gitlab/import_export/merge_request_parser.rb
index 040a70d6775..deb2f59f05f 100644
--- a/lib/gitlab/import_export/merge_request_parser.rb
+++ b/lib/gitlab/import_export/merge_request_parser.rb
@@ -20,6 +20,17 @@ module Gitlab
create_target_branch unless branch_exists?(@merge_request.target_branch)
end
+ # The merge_request_diff associated with the current @merge_request might
+ # be invalid. Than means, when the @merge_request object is saved, the
+ # @merge_request.merge_request_diff won't. This can leave the merge request
+ # in an invalid state, because a merge request must have an associated
+ # merge request diff.
+ # In this change, if the associated merge request diff is invalid, we set
+ # it to nil. This change, in association with the after callback
+ # :ensure_merge_request_diff in the MergeRequest class, makes that
+ # when the merge request is going to be created and it doesn't have
+ # one, a default one will be generated.
+ @merge_request.merge_request_diff = nil unless @merge_request.merge_request_diff&.valid?
@merge_request
end
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index bbac15c7710..42c4745ff98 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -3,8 +3,8 @@
module Gitlab
module Kubernetes
module Helm
- HELM_VERSION = '2.12.2'.freeze
- KUBECTL_VERSION = '1.11.0'.freeze
+ HELM_VERSION = '2.12.3'.freeze
+ KUBECTL_VERSION = '1.11.7'.freeze
NAMESPACE = 'gitlab-managed-apps'.freeze
SERVICE_ACCOUNT = 'tiller'.freeze
CLUSTER_ROLE_BINDING = 'tiller-admin'.freeze
diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb
index 26b81847d37..31e6fc9d8c7 100644
--- a/lib/gitlab/lfs_token.rb
+++ b/lib/gitlab/lfs_token.rb
@@ -30,8 +30,8 @@ module Gitlab
end
end
- def token(expire_time: DEFAULT_EXPIRE_TIME)
- HMACToken.new(actor).token(expire_time)
+ def token
+ HMACToken.new(actor).token(DEFAULT_EXPIRE_TIME)
end
def token_valid?(token_to_check)
@@ -47,6 +47,15 @@ module Gitlab
user? ? :lfs_token : :lfs_deploy_token
end
+ def authentication_payload(repository_http_path)
+ {
+ username: actor_name,
+ lfs_token: token,
+ repository_http_path: repository_http_path,
+ expires_in: DEFAULT_EXPIRE_TIME
+ }
+ end
+
private # rubocop:disable Lint/UselessAccessModifier
class HMACToken
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index ef656e5b2ce..29c511524a2 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -28,11 +28,17 @@ module Gitlab
ProjectTemplate.new('rails', 'Ruby on Rails', _('Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/rails', 'illustrations/logos/rails.svg'),
ProjectTemplate.new('spring', 'Spring', _('Includes an MVC structure, mvnw and pom.xml to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/spring', 'illustrations/logos/spring.svg'),
ProjectTemplate.new('express', 'NodeJS Express', _('Includes an MVC structure to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/express', 'illustrations/logos/express.svg'),
+ ProjectTemplate.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore', 'illustrations/logos/dotnet.svg'),
ProjectTemplate.new('hugo', 'Pages/Hugo', _('Everything you need to create a GitLab Pages site using Hugo.'), 'https://gitlab.com/pages/hugo'),
ProjectTemplate.new('jekyll', 'Pages/Jekyll', _('Everything you need to create a GitLab Pages site using Jekyll.'), 'https://gitlab.com/pages/jekyll'),
ProjectTemplate.new('plainhtml', 'Pages/Plain HTML', _('Everything you need to create a GitLab Pages site using plain HTML.'), 'https://gitlab.com/pages/plain-html'),
ProjectTemplate.new('gitbook', 'Pages/GitBook', _('Everything you need to create a GitLab Pages site using GitBook.'), 'https://gitlab.com/pages/gitbook'),
- ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo')
+ ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo'),
+ ProjectTemplate.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo', 'illustrations/logos/netlify.svg'),
+ ProjectTemplate.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll', 'illustrations/logos/netlify.svg'),
+ ProjectTemplate.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html', 'illustrations/logos/netlify.svg'),
+ ProjectTemplate.new('nfgitbook', 'Netlify/GitBook', _('A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfgitbook', 'illustrations/logos/netlify.svg'),
+ ProjectTemplate.new('nfhexo', 'Netlify/Hexo', _('A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhexo', 'illustrations/logos/netlify.svg')
].freeze
class << self
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 1153e69d3de..c7d8dfcd495 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -280,7 +280,10 @@ module Gitlab
# add_namespace("default", "gitlab")
#
def add_namespace(storage, name)
- Gitlab::GitalyClient::NamespaceService.new(storage).add(name)
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58012
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ Gitlab::GitalyClient::NamespaceService.new(storage).add(name)
+ end
rescue GRPC::InvalidArgument => e
raise ArgumentError, e.message
end
diff --git a/lib/gitlab/slash_commands/application_help.rb b/lib/gitlab/slash_commands/application_help.rb
new file mode 100644
index 00000000000..0ea7554ba64
--- /dev/null
+++ b/lib/gitlab/slash_commands/application_help.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ class ApplicationHelp < BaseCommand
+ def initialize(params)
+ @params = params
+ end
+
+ def execute
+ Gitlab::SlashCommands::Presenters::Help.new(commands).present(trigger, params[:text])
+ end
+
+ private
+
+ def trigger
+ "#{params[:command]} [project name or alias]"
+ end
+
+ def commands
+ Gitlab::SlashCommands::Command.commands
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb
index 474c09b9c4d..7c963fcf38a 100644
--- a/lib/gitlab/slash_commands/command.rb
+++ b/lib/gitlab/slash_commands/command.rb
@@ -9,7 +9,8 @@ module Gitlab
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove,
- Gitlab::SlashCommands::Deploy
+ Gitlab::SlashCommands::Deploy,
+ Gitlab::SlashCommands::Run
]
end
diff --git a/lib/gitlab/slash_commands/presenters/error.rb b/lib/gitlab/slash_commands/presenters/error.rb
new file mode 100644
index 00000000000..442f8796338
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/error.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ module Presenters
+ class Error < Presenters::Base
+ def initialize(message)
+ @message = message
+ end
+
+ def message
+ ephemeral_response(text: @message)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/presenters/run.rb b/lib/gitlab/slash_commands/presenters/run.rb
new file mode 100644
index 00000000000..c4bbc231464
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/run.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ module Presenters
+ class Run < Presenters::Base
+ # rubocop: disable CodeReuse/ActiveRecord
+ def present(pipeline)
+ build = pipeline.builds.take
+
+ if build && (responder = Chat::Responder.responder_for(build))
+ in_channel_response(responder.scheduled_output)
+ else
+ unsupported_chat_service
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def unsupported_chat_service
+ ephemeral_response(text: 'Sorry, this chat service is currently not supported by GitLab ChatOps.')
+ end
+
+ def failed_to_schedule(command)
+ ephemeral_response(
+ text: 'The command could not be scheduled. Make sure that your ' \
+ 'project has a .gitlab-ci.yml that defines a job with the ' \
+ "name #{command.inspect}"
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/run.rb b/lib/gitlab/slash_commands/run.rb
new file mode 100644
index 00000000000..10a545e28ac
--- /dev/null
+++ b/lib/gitlab/slash_commands/run.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ # Slash command for triggering chatops jobs.
+ class Run < BaseCommand
+ def self.match(text)
+ /\Arun\s+(?<command>\S+)(\s+(?<arguments>.+))?\z/.match(text)
+ end
+
+ def self.help_message
+ 'run <command> <arguments>'
+ end
+
+ def self.available?(project)
+ Chat.available? && project.builds_enabled?
+ end
+
+ def self.allowed?(project, user)
+ can?(user, :create_pipeline, project)
+ end
+
+ def execute(match)
+ command = Chat::Command.new(
+ project: project,
+ chat_name: chat_name,
+ name: match[:command],
+ arguments: match[:arguments],
+ channel: params[:channel_id],
+ response_url: params[:response_url]
+ )
+
+ presenter = Gitlab::SlashCommands::Presenters::Run.new
+ pipeline = command.try_create_pipeline
+
+ if pipeline&.persisted?
+ presenter.present(pipeline)
+ else
+ presenter.failed_to_schedule(command.name)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing.rb b/lib/gitlab/tracing.rb
index 0d9b0be1c8e..29517591c51 100644
--- a/lib/gitlab/tracing.rb
+++ b/lib/gitlab/tracing.rb
@@ -27,10 +27,11 @@ module Gitlab
def self.tracing_url
return unless tracing_url_enabled?
- tracing_url_template % {
- correlation_id: Gitlab::CorrelationId.current_id.to_s,
- service: Gitlab.process_name
- }
+ # Avoid using `format` since it can throw TypeErrors
+ # which we want to avoid on unsanitised env var input
+ tracing_url_template.to_s
+ .gsub(/\{\{\s*correlation_id\s*\}\}/, Gitlab::CorrelationId.current_id.to_s)
+ .gsub(/\{\{\s*service\s*\}\}/, Gitlab.process_name)
end
end
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index a65f4a8639c..0101ccc046a 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -64,12 +64,12 @@ module Gitlab
group_clusters_disabled: count(::Clusters::Cluster.disabled.group_type),
clusters_platforms_gke: count(::Clusters::Cluster.gcp_installed.enabled),
clusters_platforms_user: count(::Clusters::Cluster.user_provided.enabled),
- clusters_applications_helm: count(::Clusters::Applications::Helm.installed),
- clusters_applications_ingress: count(::Clusters::Applications::Ingress.installed),
- clusters_applications_cert_managers: count(::Clusters::Applications::CertManager.installed),
- clusters_applications_prometheus: count(::Clusters::Applications::Prometheus.installed),
- clusters_applications_runner: count(::Clusters::Applications::Runner.installed),
- clusters_applications_knative: count(::Clusters::Applications::Knative.installed),
+ clusters_applications_helm: count(::Clusters::Applications::Helm.available),
+ clusters_applications_ingress: count(::Clusters::Applications::Ingress.available),
+ clusters_applications_cert_managers: count(::Clusters::Applications::CertManager.available),
+ clusters_applications_prometheus: count(::Clusters::Applications::Prometheus.available),
+ clusters_applications_runner: count(::Clusters::Applications::Runner.available),
+ clusters_applications_knative: count(::Clusters::Applications::Knative.available),
in_review_folder: count(::Environment.in_review_folder),
groups: count(Group),
issues: count(Issue),
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
index 4187014d49e..49ec196b103 100644
--- a/lib/sentry/client.rb
+++ b/lib/sentry/client.rb
@@ -54,7 +54,7 @@ module Sentry
def handle_response(response)
unless response.code == 200
- raise Client::Error, "Sentry response error: #{response.code}"
+ raise Client::Error, "Sentry response status code: #{response.code}"
end
response.as_json