diff options
Diffstat (limited to 'lib/api/helpers')
-rw-r--r-- | lib/api/helpers/authentication.rb | 5 | ||||
-rw-r--r-- | lib/api/helpers/caching.rb | 137 | ||||
-rw-r--r-- | lib/api/helpers/common_helpers.rb | 4 | ||||
-rw-r--r-- | lib/api/helpers/graphql_helpers.rb | 4 | ||||
-rw-r--r-- | lib/api/helpers/notes_helpers.rb | 6 | ||||
-rw-r--r-- | lib/api/helpers/packages/conan/api_helpers.rb | 23 | ||||
-rw-r--r-- | lib/api/helpers/packages/dependency_proxy_helpers.rb | 2 | ||||
-rw-r--r-- | lib/api/helpers/packages_helpers.rb | 3 | ||||
-rw-r--r-- | lib/api/helpers/runner.rb | 9 | ||||
-rw-r--r-- | lib/api/helpers/services_helpers.rb | 10 | ||||
-rw-r--r-- | lib/api/helpers/variables_helpers.rb | 27 |
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') |