summaryrefslogtreecommitdiff
path: root/lib/api/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api/helpers')
-rw-r--r--lib/api/helpers/authentication.rb5
-rw-r--r--lib/api/helpers/caching.rb137
-rw-r--r--lib/api/helpers/common_helpers.rb4
-rw-r--r--lib/api/helpers/graphql_helpers.rb4
-rw-r--r--lib/api/helpers/notes_helpers.rb6
-rw-r--r--lib/api/helpers/packages/conan/api_helpers.rb23
-rw-r--r--lib/api/helpers/packages/dependency_proxy_helpers.rb2
-rw-r--r--lib/api/helpers/packages_helpers.rb3
-rw-r--r--lib/api/helpers/runner.rb9
-rw-r--r--lib/api/helpers/services_helpers.rb10
-rw-r--r--lib/api/helpers/variables_helpers.rb27
11 files changed, 206 insertions, 24 deletions
diff --git a/lib/api/helpers/authentication.rb b/lib/api/helpers/authentication.rb
index a6cfe930190..da11f07485b 100644
--- a/lib/api/helpers/authentication.rb
+++ b/lib/api/helpers/authentication.rb
@@ -52,6 +52,11 @@ module API
token&.user
end
+ def ci_build_from_namespace_inheritable
+ token = token_from_namespace_inheritable
+ token if token.is_a?(::Ci::Build)
+ end
+
private
def find_token_from_raw_credentials(token_types, raw)
diff --git a/lib/api/helpers/caching.rb b/lib/api/helpers/caching.rb
new file mode 100644
index 00000000000..d0f22109879
--- /dev/null
+++ b/lib/api/helpers/caching.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+# Grape helpers for caching.
+#
+# This module helps introduce standardised caching into the Grape API
+# in a similar manner to the standard Grape DSL.
+
+module API
+ module Helpers
+ module Caching
+ # @return [ActiveSupport::Duration]
+ DEFAULT_EXPIRY = 1.day
+
+ # @return [ActiveSupport::Cache::Store]
+ def cache
+ Rails.cache
+ end
+
+ # This is functionally equivalent to the standard `#present` used in
+ # Grape endpoints, but the JSON for the object, or for each object of
+ # a collection, will be cached.
+ #
+ # With a collection all the keys will be fetched in a single call and the
+ # Entity rendered for those missing from the cache, which are then written
+ # back into it.
+ #
+ # Both the single object, and all objects inside a collection, must respond
+ # to `#cache_key`.
+ #
+ # To override the Grape formatter we return a custom wrapper in
+ # `Gitlab::Json::PrecompiledJson` which tells the `Gitlab::Json::GrapeFormatter`
+ # to export the string without conversion.
+ #
+ # A cache context can be supplied to add more context to the cache key. This
+ # defaults to including the `current_user` in every key for safety, unless overridden.
+ #
+ # @param obj_or_collection [Object, Enumerable<Object>] the object or objects to render
+ # @param with [Grape::Entity] the entity to use for rendering
+ # @param cache_context [Proc] a proc to call for each object to provide more context to the cache key
+ # @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry
+ # @param presenter_args [Hash] keyword arguments to be passed to the entity
+ # @return [Gitlab::Json::PrecompiledJson]
+ def present_cached(obj_or_collection, with:, cache_context: -> (_) { current_user.cache_key }, expires_in: DEFAULT_EXPIRY, **presenter_args)
+ json =
+ if obj_or_collection.is_a?(Enumerable)
+ cached_collection(
+ obj_or_collection,
+ presenter: with,
+ presenter_args: presenter_args,
+ context: cache_context,
+ expires_in: expires_in
+ )
+ else
+ cached_object(
+ obj_or_collection,
+ presenter: with,
+ presenter_args: presenter_args,
+ context: cache_context,
+ expires_in: expires_in
+ )
+ end
+
+ body Gitlab::Json::PrecompiledJson.new(json)
+ end
+
+ private
+
+ # Optionally uses a `Proc` to add context to a cache key
+ #
+ # @param object [Object] must respond to #cache_key
+ # @param context [Proc] a proc that will be called with the object as an argument, and which should return a
+ # string or array of strings to be combined into the cache key
+ # @return [String]
+ def contextual_cache_key(object, context)
+ return object.cache_key if context.nil?
+
+ [object.cache_key, context.call(object)].flatten.join(":")
+ end
+
+ # Used for fetching or rendering a single object
+ #
+ # @param object [Object] the object to render
+ # @param presenter [Grape::Entity]
+ # @param presenter_args [Hash] keyword arguments to be passed to the entity
+ # @param context [Proc]
+ # @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry
+ # @return [String]
+ def cached_object(object, presenter:, presenter_args:, context:, expires_in:)
+ cache.fetch(contextual_cache_key(object, context), expires_in: expires_in) do
+ Gitlab::Json.dump(presenter.represent(object, **presenter_args).as_json)
+ end
+ end
+
+ # Used for fetching or rendering multiple objects
+ #
+ # @param objects [Enumerable<Object>] the objects to render
+ # @param presenter [Grape::Entity]
+ # @param presenter_args [Hash] keyword arguments to be passed to the entity
+ # @param context [Proc]
+ # @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry
+ # @return [Array<String>]
+ def cached_collection(collection, presenter:, presenter_args:, context:, expires_in:)
+ json = fetch_multi(collection, context: context, expires_in: expires_in) do |obj|
+ Gitlab::Json.dump(presenter.represent(obj, **presenter_args).as_json)
+ end
+
+ json.values
+ end
+
+ # An adapted version of ActiveSupport::Cache::Store#fetch_multi.
+ #
+ # The original method only provides the missing key to the block,
+ # not the missing object, so we have to create a map of cache keys
+ # to the objects to allow us to pass the object to the missing value
+ # block.
+ #
+ # The result is that this is functionally identical to `#fetch`.
+ def fetch_multi(*objs, context:, **kwargs)
+ objs.flatten!
+ map = multi_key_map(objs, context: context)
+
+ cache.fetch_multi(*map.keys, **kwargs) do |key|
+ yield map[key]
+ end
+ end
+
+ # @param objects [Enumerable<Object>] objects which _must_ respond to `#cache_key`
+ # @param context [Proc] a proc that can be called to help generate each cache key
+ # @return [Hash]
+ def multi_key_map(objects, context:)
+ objects.index_by do |object|
+ contextual_cache_key(object, context)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/common_helpers.rb b/lib/api/helpers/common_helpers.rb
index a44fd4b0a5b..8940cf87f82 100644
--- a/lib/api/helpers/common_helpers.rb
+++ b/lib/api/helpers/common_helpers.rb
@@ -32,6 +32,10 @@ module API
end
end.compact.to_set
end
+
+ def endpoint_id
+ "#{request.request_method} #{route.origin}"
+ end
end
end
end
diff --git a/lib/api/helpers/graphql_helpers.rb b/lib/api/helpers/graphql_helpers.rb
index 3ddef0c16b3..4f7f85bd69d 100644
--- a/lib/api/helpers/graphql_helpers.rb
+++ b/lib/api/helpers/graphql_helpers.rb
@@ -6,8 +6,8 @@ module API
# against the graphql API. Helper code for the graphql server implementation
# should be in app/graphql/ or lib/gitlab/graphql/
module GraphqlHelpers
- def run_graphql!(query:, context: {}, transform: nil)
- result = GitlabSchema.execute(query, context: context)
+ def run_graphql!(query:, context: {}, variables: nil, transform: nil)
+ result = GitlabSchema.execute(query, variables: variables, context: context)
if transform
transform.call(result)
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index 71a18524104..cb938bc8a14 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -116,7 +116,7 @@ module API
end
def create_note(noteable, opts)
- whitelist_query_limiting
+ disable_query_limiting
authorize!(:create_note, noteable)
parent = noteable_parent(noteable)
@@ -144,8 +144,8 @@ module API
present discussion, with: Entities::Discussion
end
- def whitelist_query_limiting
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/211538')
+ def disable_query_limiting
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/211538')
end
end
end
diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb
index d5f5448fd42..b18f52b5be6 100644
--- a/lib/api/helpers/packages/conan/api_helpers.rb
+++ b/lib/api/helpers/packages/conan/api_helpers.rb
@@ -14,7 +14,8 @@ module API
package,
current_user,
project,
- conan_package_reference: params[:conan_package_reference]
+ conan_package_reference: params[:conan_package_reference],
+ id: params[:id]
)
render_api_error!("No recipe manifest found", 404) if yield(presenter).empty?
@@ -31,19 +32,15 @@ module API
end
def recipe_upload_urls
- { upload_urls: Hash[
- file_names.select(&method(:recipe_file?)).map do |file_name|
- [file_name, build_recipe_file_upload_url(file_name)]
- end
- ] }
+ { upload_urls: file_names.select(&method(:recipe_file?)).to_h do |file_name|
+ [file_name, build_recipe_file_upload_url(file_name)]
+ end }
end
def package_upload_urls
- { upload_urls: Hash[
- file_names.select(&method(:package_file?)).map do |file_name|
- [file_name, build_package_file_upload_url(file_name)]
- end
- ] }
+ { upload_urls: file_names.select(&method(:package_file?)).to_h do |file_name|
+ [file_name, build_package_file_upload_url(file_name)]
+ end }
end
def recipe_file?(file_name)
@@ -212,10 +209,8 @@ module API
end
def find_personal_access_token
- personal_access_token = find_personal_access_token_from_conan_jwt ||
+ find_personal_access_token_from_conan_jwt ||
find_personal_access_token_from_http_basic_auth
-
- personal_access_token
end
def find_user_from_job_token
diff --git a/lib/api/helpers/packages/dependency_proxy_helpers.rb b/lib/api/helpers/packages/dependency_proxy_helpers.rb
index 577ba97d68a..989c4e1761b 100644
--- a/lib/api/helpers/packages/dependency_proxy_helpers.rb
+++ b/lib/api/helpers/packages/dependency_proxy_helpers.rb
@@ -10,7 +10,7 @@ module API
def redirect_registry_request(forward_to_registry, package_type, options)
if forward_to_registry && redirect_registry_request_available?
- track_event("#{package_type}_request_forward")
+ ::Gitlab::Tracking.event(self.options[:for].name, "#{package_type}_request_forward")
redirect(registry_url(package_type, options))
else
yield
diff --git a/lib/api/helpers/packages_helpers.rb b/lib/api/helpers/packages_helpers.rb
index e1898d28ef7..2221eec0f82 100644
--- a/lib/api/helpers/packages_helpers.rb
+++ b/lib/api/helpers/packages_helpers.rb
@@ -50,7 +50,8 @@ module API
def track_package_event(event_name, scope, **args)
::Packages::CreateEventService.new(nil, current_user, event_name: event_name, scope: scope).execute
- track_event(event_name, **args)
+ category = args.delete(:category) || self.options[:for].name
+ ::Gitlab::Tracking.event(category, event_name.to_s, **args)
end
end
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 39586483990..688cd2da994 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -38,10 +38,17 @@ module API
end
end
+ # HTTP status codes to terminate the job on GitLab Runner:
+ # - 403
def authenticate_job!(require_running: true)
job = current_job
- not_found! unless job
+ # 404 is not returned here because we want to terminate the job if it's
+ # running. A 404 can be returned from anywhere in the networking stack which is why
+ # we are explicit about a 403, we should improve this in
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/327703
+ forbidden! unless job
+
forbidden! unless job_token_valid?(job)
forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete?
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index ed3d694f006..2f2ad88c942 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -394,7 +394,7 @@ module API
required: true,
name: :external_wiki_url,
type: String,
- desc: 'The URL of the external Wiki'
+ desc: 'The URL of the external wiki'
}
],
'flowdock' => [
@@ -543,9 +543,15 @@ module API
},
{
required: false,
+ name: :jira_issue_transition_automatic,
+ type: Boolean,
+ desc: 'Enable automatic issue transitions'
+ },
+ {
+ required: false,
name: :jira_issue_transition_id,
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`'
+ desc: 'The ID of one or more transitions for custom issue transitions'
},
{
required: false,
diff --git a/lib/api/helpers/variables_helpers.rb b/lib/api/helpers/variables_helpers.rb
new file mode 100644
index 00000000000..e2b3372fc33
--- /dev/null
+++ b/lib/api/helpers/variables_helpers.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module VariablesHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_group_variable_params_ee do
+ end
+
+ def filter_variable_parameters(_, params)
+ params # Overridden in EE
+ end
+
+ def find_variable(owner, params)
+ variables = ::Ci::VariablesFinder.new(owner, params).execute.to_a
+
+ return variables.first unless variables.many? # rubocop: disable CodeReuse/ActiveRecord
+
+ conflict!("There are multiple variables with provided parameters. Please use 'filter[environment_scope]'")
+ end
+ end
+ end
+end
+
+API::Helpers::VariablesHelpers.prepend_if_ee('EE::API::Helpers::VariablesHelpers')