diff options
Diffstat (limited to 'lib/api')
54 files changed, 786 insertions, 75 deletions
diff --git a/lib/api/admin/instance_clusters.rb b/lib/api/admin/instance_clusters.rb index 8208d10c089..0db2321199a 100644 --- a/lib/api/admin/instance_clusters.rb +++ b/lib/api/admin/instance_clusters.rb @@ -37,6 +37,7 @@ module API requires :name, type: String, desc: 'Cluster name' optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true' optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :domain, type: String, desc: 'Cluster base domain' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true' @@ -70,6 +71,7 @@ module API optional :name, type: String, desc: 'Cluster name' optional :enabled, type: Boolean, desc: 'Enable or disable Gitlab\'s connection to your Kubernetes cluster' optional :environment_scope, type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :domain, type: String, desc: 'Cluster base domain' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do diff --git a/lib/api/api.rb b/lib/api/api.rb index b37751e1b47..546d726243e 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -196,6 +196,8 @@ module API mount ::API::ComposerPackages mount ::API::ConanProjectPackages mount ::API::ConanInstancePackages + mount ::API::DebianGroupPackages + mount ::API::DebianProjectPackages mount ::API::MavenPackages mount ::API::NpmPackages mount ::API::GenericPackages @@ -216,6 +218,7 @@ module API mount ::API::ProjectStatistics mount ::API::ProjectTemplates mount ::API::Terraform::State + mount ::API::Terraform::StateVersion mount ::API::ProtectedBranches mount ::API::ProtectedTags mount ::API::Releases @@ -236,6 +239,7 @@ module API mount ::API::Templates mount ::API::Todos mount ::API::Triggers + mount ::API::Unleash mount ::API::UsageData mount ::API::UserCounts mount ::API::Users @@ -245,6 +249,7 @@ module API end mount ::API::Internal::Base + mount ::API::Internal::Lfs mount ::API::Internal::Pages mount ::API::Internal::Kubernetes diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb index 68497a08fb8..6a86c02bf4a 100644 --- a/lib/api/boards_responses.rb +++ b/lib/api/boards_responses.rb @@ -45,7 +45,7 @@ module API def destroy_list(list) destroy_conditionally!(list) do |list| service = ::Boards::Lists::DestroyService.new(board_parent, current_user) - unless service.execute(list) + if service.execute(list).error? render_api_error!({ error: 'List could not be deleted!' }, 400) end end diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 08903dce3dc..e293c299d75 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -181,6 +181,7 @@ module API .new(job, declared_params(include_missing: false)) service.execute.then do |result| + header 'X-GitLab-Trace-Update-Interval', result.backoff status result.status end end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 20877fb5c5f..3097bcc0ef1 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -62,19 +62,29 @@ module API first_parent: first_parent, order: order) - commit_count = - if all || path || before || after || first_parent - user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all, first_parent: first_parent) - else - # Cacheable commit count. - user_project.repository.commit_count_for_ref(ref) - end + serializer = with_stats ? Entities::CommitWithStats : Entities::Commit - paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count) + if Feature.enabled?(:api_commits_without_count, user_project) + # This tells kaminari that there is 1 more commit after the one we've + # loaded, meaning there will be a next page, if the currently loaded set + # of commits is equal to the requested page size. + commit_count = offset + commits.size + 1 + paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count) - serializer = with_stats ? Entities::CommitWithStats : Entities::Commit + present paginate(paginated_commits, exclude_total_headers: true), with: serializer + else + commit_count = + if all || path || before || after || first_parent + user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all, first_parent: first_parent) + else + # Cacheable commit count. + user_project.repository.commit_count_for_ref(ref) + end - present paginate(paginated_commits), with: serializer + paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count) + + present paginate(paginated_commits), with: serializer + end end desc 'Commit multiple file changes as one commit' do diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index 31d097c4bea..69e44ffcaf9 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -123,7 +123,7 @@ module API bad_request! end - package_event('push_package') + track_package_event('push_package', :composer) ::Packages::Composer::CreatePackageService .new(authorized_user_project, current_user, declared_params) diff --git a/lib/api/conan_package_endpoints.rb b/lib/api/conan_package_endpoints.rb index 445447cfcd2..9b6867a328b 100644 --- a/lib/api/conan_package_endpoints.rb +++ b/lib/api/conan_package_endpoints.rb @@ -246,7 +246,7 @@ module API delete do authorize!(:destroy_package, project) - package_event('delete_package', category: 'API::ConanPackages') + track_package_event('delete_package', :conan, category: 'API::ConanPackages') package.destroy end diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb new file mode 100644 index 00000000000..c56d84ed313 --- /dev/null +++ b/lib/api/debian_group_packages.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module API + class DebianGroupPackages < Grape::API::Instance + params do + requires :id, type: String, desc: 'The ID of a group' + end + + resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before do + not_found! unless ::Feature.enabled?(:debian_packages, user_group) + + authorize_read_package!(user_group) + end + + namespace ':id/-/packages/debian' do + include DebianPackageEndpoints + end + end + end +end diff --git a/lib/api/debian_package_endpoints.rb b/lib/api/debian_package_endpoints.rb new file mode 100644 index 00000000000..168b3ca7a4f --- /dev/null +++ b/lib/api/debian_package_endpoints.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +module API + module DebianPackageEndpoints + extend ActiveSupport::Concern + + DISTRIBUTION_REGEX = %r{[a-zA-Z0-9][a-zA-Z0-9.-]*}.freeze + COMPONENT_REGEX = %r{[a-z-]+}.freeze + ARCHITECTURE_REGEX = %r{[a-z][a-z0-9]*}.freeze + LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze + PACKAGE_REGEX = API::NO_SLASH_URL_PART_REGEX + DISTRIBUTION_REQUIREMENTS = { + distribution: DISTRIBUTION_REGEX + }.freeze + COMPONENT_ARCHITECTURE_REQUIREMENTS = { + component: COMPONENT_REGEX, + architecture: ARCHITECTURE_REGEX + }.freeze + COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS = { + component: COMPONENT_REGEX, + letter: LETTER_REGEX, + source_package: PACKAGE_REGEX + }.freeze + FILE_NAME_REQUIREMENTS = { + file_name: API::NO_SLASH_URL_PART_REGEX + }.freeze + + included do + helpers ::API::Helpers::PackagesHelpers + helpers ::API::Helpers::Packages::BasicAuthHelpers + + format :txt + + rescue_from ArgumentError do |e| + render_api_error!(e.message, 400) + end + + rescue_from ActiveRecord::RecordInvalid do |e| + render_api_error!(e.message, 400) + end + + before do + require_packages_enabled! + end + + params do + requires :distribution, type: String, desc: 'The Debian Codename', regexp: Gitlab::Regex.debian_distribution_regex + end + + namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do + # GET {projects|groups}/:id/-/packages/debian/dists/*distribution/Release.gpg + desc 'The Release file signature' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + get 'Release.gpg' do + not_found! + end + + # GET {projects|groups}/:id/-/packages/debian/dists/*distribution/Release + desc 'The unsigned Release file' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + get 'Release' do + # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 + 'TODO Release' + end + + # GET {projects|groups}/:id/-/packages/debian/dists/*distribution/InRelease + desc 'The signed Release file' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + get 'InRelease' do + not_found! + end + + params do + requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex + requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex + end + + namespace ':component/binary-:architecture', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do + # GET {projects|groups}/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/Packages + desc 'The binary files index' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + get 'Packages' do + # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 + 'TODO Packages' + end + end + end + + params do + requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex + requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)' + requires :source_package, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex + end + + namespace 'pool/:component/:letter/:source_package', requirements: COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS do + # GET {projects|groups}/:id/-/packages/debian/pool/:component/:letter/:source_package/:file_name + params do + requires :file_name, type: String, desc: 'The Debian File Name' + end + desc 'The package' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + get ':file_name', requirements: FILE_NAME_REQUIREMENTS do + # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 + 'TODO File' + end + end + end + end +end diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb new file mode 100644 index 00000000000..7cd796aac2b --- /dev/null +++ b/lib/api/debian_project_packages.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module API + class DebianProjectPackages < Grape::API::Instance + params do + requires :id, type: String, desc: 'The ID of a project' + end + + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before do + not_found! unless ::Feature.enabled?(:debian_packages, user_project) + + authorize_read_package! + end + + namespace ':id/-/packages/debian' do + include DebianPackageEndpoints + + params do + requires :file_name, type: String, desc: 'The file name' + end + + namespace 'incoming/:file_name', requirements: FILE_NAME_REQUIREMENTS do + # PUT {projects|groups}/:id/-/packages/debian/incoming/:file_name + params do + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + put do + authorize_upload!(authorized_user_project) + bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:debian_max_file_size, params[:file].size) + + track_package_event('push_package', :debian) + + created! + rescue ObjectStorage::RemoteStoreError => e + Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id }) + + forbidden! + end + + # PUT {projects|groups}/:id/-/packages/debian/incoming/:file_name/authorize + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + post 'authorize' do + authorize_workhorse!( + subject: authorized_user_project, + has_length: false, + maximum_size: authorized_user_project.actual_limits.debian_max_file_size + ) + end + end + end + end + end +end diff --git a/lib/api/entities/cluster.rb b/lib/api/entities/cluster.rb index 4cb54e988ce..67459092a33 100644 --- a/lib/api/entities/cluster.rb +++ b/lib/api/entities/cluster.rb @@ -4,7 +4,7 @@ module API module Entities class Cluster < Grape::Entity expose :id, :name, :created_at, :domain - expose :provider_type, :platform_type, :environment_scope, :cluster_type + expose :provider_type, :platform_type, :environment_scope, :cluster_type, :namespace_per_environment expose :user, using: Entities::UserBasic expose :platform_kubernetes, using: Entities::Platform::Kubernetes expose :provider_gcp, using: Entities::Provider::Gcp diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb index cff627ab50a..c430b73580b 100644 --- a/lib/api/entities/container_registry.rb +++ b/lib/api/entities/container_registry.rb @@ -16,6 +16,7 @@ module API expose :project_id expose :location expose :created_at + expose :expiration_policy_started_at, as: :cleanup_policy_started_at expose :tags_count, if: -> (_, options) { options[:tags_count] } expose :tags, using: Tag, if: -> (_, options) { options[:tags] } end diff --git a/lib/api/entities/feature_flag.rb b/lib/api/entities/feature_flag.rb new file mode 100644 index 00000000000..82fdb20af00 --- /dev/null +++ b/lib/api/entities/feature_flag.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + expose :name + expose :description + expose :active + expose :version, if: :feature_flags_new_version_enabled + expose :created_at + expose :updated_at + expose :scopes, using: FeatureFlag::LegacyScope + expose :strategies, using: FeatureFlag::Strategy, if: :feature_flags_new_version_enabled + end + end +end diff --git a/lib/api/entities/feature_flag/detailed_legacy_scope.rb b/lib/api/entities/feature_flag/detailed_legacy_scope.rb new file mode 100644 index 00000000000..47078c1dfde --- /dev/null +++ b/lib/api/entities/feature_flag/detailed_legacy_scope.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class DetailedLegacyScope < LegacyScope + expose :name + end + end + end +end diff --git a/lib/api/entities/feature_flag/legacy_scope.rb b/lib/api/entities/feature_flag/legacy_scope.rb new file mode 100644 index 00000000000..7329f71c599 --- /dev/null +++ b/lib/api/entities/feature_flag/legacy_scope.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class LegacyScope < Grape::Entity + expose :id + expose :active + expose :environment_scope + expose :strategies + expose :created_at + expose :updated_at + end + end + end +end diff --git a/lib/api/entities/feature_flag/scope.rb b/lib/api/entities/feature_flag/scope.rb new file mode 100644 index 00000000000..906fe718257 --- /dev/null +++ b/lib/api/entities/feature_flag/scope.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class Scope < Grape::Entity + expose :id + expose :environment_scope + end + end + end +end diff --git a/lib/api/entities/feature_flag/strategy.rb b/lib/api/entities/feature_flag/strategy.rb new file mode 100644 index 00000000000..32699be0ee3 --- /dev/null +++ b/lib/api/entities/feature_flag/strategy.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class Strategy < Grape::Entity + expose :id + expose :name + expose :parameters + expose :scopes, using: FeatureFlag::Scope + end + end + end +end diff --git a/lib/api/entities/feature_flag/user_list.rb b/lib/api/entities/feature_flag/user_list.rb new file mode 100644 index 00000000000..bc8b12ea22e --- /dev/null +++ b/lib/api/entities/feature_flag/user_list.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class UserList < Grape::Entity + include RequestAwareEntity + + expose :id + expose :iid + expose :project_id + expose :created_at + expose :updated_at + expose :name + expose :user_xids + + expose :path do |list| + project_feature_flags_user_list_path(list.project, list) + end + + expose :edit_path do |list| + edit_project_feature_flags_user_list_path(list.project, list) + end + end + end + end +end diff --git a/lib/api/entities/package.rb b/lib/api/entities/package.rb index d903f50befa..b54f0e04a9d 100644 --- a/lib/api/entities/package.rb +++ b/lib/api/entities/package.rb @@ -7,7 +7,19 @@ module API extend ::API::Entities::EntityHelpers expose :id - expose :name + + expose :name do |package| + if package.conan? + package.conan_recipe + else + package.name + end + end + + expose :conan_package_name, if: ->(package) { package.conan? } do |package| + package.name + end + expose :version expose :package_type diff --git a/lib/api/entities/unleash_feature.rb b/lib/api/entities/unleash_feature.rb new file mode 100644 index 00000000000..8ee87d1fc11 --- /dev/null +++ b/lib/api/entities/unleash_feature.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module API + module Entities + class UnleashFeature < Grape::Entity + expose :name + expose :description, unless: ->(feature) { feature.description.nil? } + expose :active, as: :enabled + expose :strategies do |flag| + flag.strategies.map do |strategy| + if legacy_strategy?(strategy) + UnleashLegacyStrategy.represent(strategy) + elsif gitlab_user_list_strategy?(strategy) + UnleashGitlabUserListStrategy.represent(strategy) + else + UnleashStrategy.represent(strategy) + end + end + end + + private + + def legacy_strategy?(strategy) + !strategy.respond_to?(:name) + end + + def gitlab_user_list_strategy?(strategy) + strategy.name == ::Operations::FeatureFlags::Strategy::STRATEGY_GITLABUSERLIST + end + end + end +end diff --git a/lib/api/entities/unleash_gitlab_user_list_strategy.rb b/lib/api/entities/unleash_gitlab_user_list_strategy.rb new file mode 100644 index 00000000000..5617f8002d9 --- /dev/null +++ b/lib/api/entities/unleash_gitlab_user_list_strategy.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class UnleashGitlabUserListStrategy < Grape::Entity + expose :name do |_strategy| + ::Operations::FeatureFlags::Strategy::STRATEGY_USERWITHID + end + expose :parameters do |strategy| + { userIds: strategy.user_list.user_xids } + end + end + end +end diff --git a/lib/api/entities/unleash_legacy_strategy.rb b/lib/api/entities/unleash_legacy_strategy.rb new file mode 100644 index 00000000000..5d5954f8da0 --- /dev/null +++ b/lib/api/entities/unleash_legacy_strategy.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class UnleashLegacyStrategy < Grape::Entity + expose :name do |strategy| + strategy['name'] + end + expose :parameters do |strategy| + strategy['parameters'] + end + end + end +end diff --git a/lib/api/entities/unleash_strategy.rb b/lib/api/entities/unleash_strategy.rb new file mode 100644 index 00000000000..7627ce3873c --- /dev/null +++ b/lib/api/entities/unleash_strategy.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module API + module Entities + class UnleashStrategy < Grape::Entity + expose :name + expose :parameters + end + end +end diff --git a/lib/api/entities/user_with_admin.rb b/lib/api/entities/user_with_admin.rb index c225ade6eb6..ab7bc738ff8 100644 --- a/lib/api/entities/user_with_admin.rb +++ b/lib/api/entities/user_with_admin.rb @@ -8,3 +8,5 @@ module API end end end + +API::Entities::UserWithAdmin.prepend_if_ee('EE::API::Entities::UserWithAdmin') diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index 98b8a40c7c9..a0c29ada950 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -2,6 +2,11 @@ module API class GenericPackages < Grape::API::Instance + GENERIC_PACKAGES_REQUIREMENTS = { + package_name: API::NO_SLASH_URL_PART_REGEX, + file_name: API::NO_SLASH_URL_PART_REGEX + }.freeze + before do require_packages_enabled! authenticate! @@ -17,17 +22,94 @@ module API route_setting :authentication, job_token_allowed: true namespace ':id/packages/generic' do - get 'ping' do - :pong + namespace ':package_name/*package_version/:file_name', requirements: GENERIC_PACKAGES_REQUIREMENTS do + desc 'Workhorse authorize generic package file' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, job_token_allowed: true + + params do + requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true + requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex + requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true + end + + put 'authorize' do + authorize_workhorse!(subject: project, maximum_size: project.actual_limits.generic_packages_max_file_size) + end + + desc 'Upload package file' do + detail 'This feature was introduced in GitLab 13.5' + end + + params do + requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true + requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex + requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + end + + route_setting :authentication, job_token_allowed: true + + put do + authorize_upload!(project) + bad_request!('File is too large') if max_file_size_exceeded? + + track_event('push_package') + + create_package_file_params = declared_params.merge(build: current_authenticated_job) + ::Packages::Generic::CreatePackageFileService + .new(project, current_user, create_package_file_params) + .execute + + created! + rescue ObjectStorage::RemoteStoreError => e + Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project.id }) + + forbidden! + end + + desc 'Download package file' do + detail 'This feature was introduced in GitLab 13.5' + end + + params do + requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true + requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex + requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true + end + + route_setting :authentication, job_token_allowed: true + + get do + authorize_read_package!(project) + + package = ::Packages::Generic::PackageFinder.new(project).execute!(params[:package_name], params[:package_version]) + package_file = ::Packages::PackageFileFinder.new(package, params[:file_name]).execute! + + track_event('pull_package') + + present_carrierwave_file!(package_file.file) + end end end end helpers do include ::API::Helpers::PackagesHelpers + include ::API::Helpers::Packages::BasicAuthHelpers def require_generic_packages_available! - not_found! unless Feature.enabled?(:generic_packages, user_project) + not_found! unless Feature.enabled?(:generic_packages, project) + end + + def project + authorized_user_project + end + + def max_file_size_exceeded? + project.actual_limits.exceeded?(:generic_packages_max_file_size, params[:file].size) end end end diff --git a/lib/api/github/entities.rb b/lib/api/github/entities.rb index c28a0b8eb7e..fe228c9a2d2 100644 --- a/lib/api/github/entities.rb +++ b/lib/api/github/entities.rb @@ -119,7 +119,9 @@ module API expose :username, as: :login expose :user_url, as: :url expose :user_url, as: :html_url - expose :avatar_url + expose :avatar_url do |user| + user.avatar_url(only_path: false) + end private diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb index ae41d9f13b8..77095ee62e0 100644 --- a/lib/api/group_clusters.rb +++ b/lib/api/group_clusters.rb @@ -41,6 +41,7 @@ module API requires :name, type: String, desc: 'Cluster name' optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true' optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :domain, type: String, desc: 'Cluster base domain' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true' @@ -74,6 +75,7 @@ module API optional :name, type: String, desc: 'Cluster name' optional :domain, type: String, desc: 'Cluster base domain' optional :environment_scope, type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb index 25b3059f63b..5b6a3bd36cf 100644 --- a/lib/api/group_container_repositories.rb +++ b/lib/api/group_container_repositories.rb @@ -4,6 +4,8 @@ module API class GroupContainerRepositories < Grape::API::Instance include PaginationParams + helpers ::API::Helpers::PackagesHelpers + before { authorize_read_group_container_images! } REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( @@ -27,7 +29,7 @@ module API user: current_user, subject: user_group ).execute - track_event('list_repositories') + track_package_event('list_repositories', :container) present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags], tags_count: params[:tags_count] end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 813e41b4d39..efd4b22a591 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -29,7 +29,12 @@ module API # rubocop: disable CodeReuse/ActiveRecord def find_groups(params, parent_id = nil) - find_params = params.slice(:all_available, :custom_attributes, :owned, :min_access_level) + find_params = params.slice( + :all_available, + :custom_attributes, + :owned, :min_access_level, + :include_parent_descendants + ) find_params[:parent] = if params[:top_level_only] [nil] @@ -309,6 +314,19 @@ module API present_groups params, groups end + desc 'Get a list of descendant groups of this group.' do + success Entities::Group + end + params do + use :group_list_params + use :with_custom_attributes + end + get ":id/descendant_groups" do + finder_params = declared_params(include_missing: false).merge(include_parent_descendants: true) + groups = find_groups(finder_params, params[:id]) + present_groups params, groups + end + desc 'Transfer a project to the group namespace. Available only for admin.' do success Entities::GroupDetail end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 1912a06682e..690160cd5ac 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -544,7 +544,6 @@ module API feature_name = "usage_data_#{event_name}" return unless Feature.enabled?(feature_name) - return unless Gitlab::CurrentSettings.usage_ping_enabled? Gitlab::UsageDataCounters::HLLRedisCounter.track_event(values, event_name) rescue => error diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb index dcbf933a4e1..934e18bdd0a 100644 --- a/lib/api/helpers/packages/conan/api_helpers.rb +++ b/lib/api/helpers/packages/conan/api_helpers.rb @@ -158,7 +158,7 @@ module API conan_package_reference: params[:conan_package_reference] ).execute! - package_event('pull_package', category: 'API::ConanPackages') if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY + track_package_event('pull_package', :conan, category: 'API::ConanPackages') if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY present_carrierwave_file!(package_file.file) end @@ -169,7 +169,7 @@ module API def track_push_package_event if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params[:file].size > 0 # rubocop: disable Style/ZeroLengthPredicate - package_event('push_package', category: 'API::ConanPackages') + track_package_event('push_package', :conan, category: 'API::ConanPackages') end end diff --git a/lib/api/helpers/packages/dependency_proxy_helpers.rb b/lib/api/helpers/packages/dependency_proxy_helpers.rb index 254af7690a2..577ba97d68a 100644 --- a/lib/api/helpers/packages/dependency_proxy_helpers.rb +++ b/lib/api/helpers/packages/dependency_proxy_helpers.rb @@ -10,6 +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") 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 403f5ea3851..e1898d28ef7 100644 --- a/lib/api/helpers/packages_helpers.rb +++ b/lib/api/helpers/packages_helpers.rb @@ -40,7 +40,7 @@ module API params = { has_length: has_length } params[:maximum_size] = maximum_size unless has_length - ::Packages::PackageFileUploader.workhorse_authorize(params) + ::Packages::PackageFileUploader.workhorse_authorize(**params) end def authorize_upload!(subject = user_project) @@ -48,7 +48,8 @@ module API require_gitlab_workhorse! end - def package_event(event_name, **args) + 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) end end diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb index a6ae9a87f98..227aec224e5 100644 --- a/lib/api/helpers/pagination.rb +++ b/lib/api/helpers/pagination.rb @@ -3,8 +3,8 @@ module API module Helpers module Pagination - def paginate(relation) - Gitlab::Pagination::OffsetPagination.new(self).paginate(relation) + def paginate(*args) + Gitlab::Pagination::OffsetPagination.new(self).paginate(*args) end end end diff --git a/lib/api/helpers/presentable.rb b/lib/api/helpers/presentable.rb index a5186cc56ea..f05467ba40b 100644 --- a/lib/api/helpers/presentable.rb +++ b/lib/api/helpers/presentable.rb @@ -23,7 +23,7 @@ module API def initialize(object, options = {}) options = options.opts_hash if options.is_a?(Grape::Entity::Options) - super(object.present(options), options) + super(object.present(options), **options) end end end diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 34a2fb09875..1c85669a626 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -51,9 +51,7 @@ module API job_forbidden!(job, 'Job is not running') unless job.running? end - if Gitlab::Ci::Features.job_heartbeats_runner?(job.project) - job.runner&.heartbeat(get_runner_ip) - end + job.runner&.heartbeat(get_runner_ip) job end diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 4bceda51900..4adb27a7414 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -381,6 +381,12 @@ module API type: String, desc: 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…' }, + { + required: false, + name: :branches_to_be_notified, + type: String, + desc: 'Branches for which notifications are to be sent' + }, chat_notification_events ].flatten, 'hipchat' => [ diff --git a/lib/api/helpers/settings_helpers.rb b/lib/api/helpers/settings_helpers.rb index 65aec6ae2e7..451e578fdd6 100644 --- a/lib/api/helpers/settings_helpers.rb +++ b/lib/api/helpers/settings_helpers.rb @@ -12,6 +12,7 @@ module API def self.optional_attributes [*::ApplicationSettingsHelper.visible_attributes, *::ApplicationSettingsHelper.external_authorization_service_attributes, + *::ApplicationSettingsHelper.deprecated_attributes, :performance_bar_allowed_group_id].freeze end end diff --git a/lib/api/internal/lfs.rb b/lib/api/internal/lfs.rb new file mode 100644 index 00000000000..adedc38b847 --- /dev/null +++ b/lib/api/internal/lfs.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module API + module Internal + class Lfs < Grape::API::Instance + use Rack::Sendfile + + before { authenticate_by_gitlab_shell_token! } + + helpers do + def find_lfs_object(lfs_oid) + LfsObject.find_by_oid(lfs_oid) + end + end + + namespace 'internal' do + namespace 'lfs' do + desc 'Get LFS URL for object ID' do + detail 'This feature was introduced in GitLab 13.5.' + end + params do + requires :oid, type: String, desc: 'The object ID to query' + requires :gl_repository, type: String, desc: "Project identifier (e.g. project-1)" + end + get "/" do + lfs_object = find_lfs_object(params[:oid]) + + not_found! unless lfs_object + + _, project, repo_type = Gitlab::GlRepository.parse(params[:gl_repository]) + + not_found! unless repo_type.project? && project + not_found! unless lfs_object.project_allowed_access?(project) + + file = lfs_object.file + + not_found! unless file&.exists? + + content_type 'application/octet-stream' + + if file.file_storage? + sendfile file.path + else + workhorse_headers = Gitlab::Workhorse.send_url(file.url) + header workhorse_headers[0], workhorse_headers[1] + env['api.format'] = :binary + body nil + end + end + end + end + end + end +end diff --git a/lib/api/lint.rb b/lib/api/lint.rb index f7796b1e969..51a87f9433c 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -6,17 +6,22 @@ module API desc 'Validation of .gitlab-ci.yml content' params do requires :content, type: String, desc: 'Content of .gitlab-ci.yml' + optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response' end post '/lint' do - error = Gitlab::Ci::YamlProcessor.validation_message(params[:content], - user: current_user) + result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute + error = result.errors.first status 200 - if error.blank? - { status: 'valid', errors: [] } - else - { status: 'invalid', errors: [error] } + response = if error.blank? + { status: 'valid', errors: [] } + else + { status: 'invalid', errors: [error] } + end + + response.tap do |response| + response[:merged_yaml] = result.merged_yaml if params[:include_merged_yaml] end end end diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb index e6d9a9a7c20..d1dd3babb8b 100644 --- a/lib/api/maven_packages.rb +++ b/lib/api/maven_packages.rb @@ -107,7 +107,7 @@ module API when 'sha1' package_file.file_sha1 else - package_event('pull_package') if jar_file?(format) + track_package_event('pull_package', :maven) if jar_file?(format) present_carrierwave_file_with_head_support!(package_file.file) end end @@ -145,7 +145,7 @@ module API when 'sha1' package_file.file_sha1 else - package_event('pull_package') if jar_file?(format) + track_package_event('pull_package', :maven) if jar_file?(format) present_carrierwave_file_with_head_support!(package_file.file) end @@ -181,7 +181,7 @@ module API when 'sha1' package_file.file_sha1 else - package_event('pull_package') if jar_file?(format) + track_package_event('pull_package', :maven) if jar_file?(format) present_carrierwave_file_with_head_support!(package_file.file) end @@ -233,7 +233,7 @@ module API when 'md5' nil else - package_event('push_package') if jar_file?(format) + track_package_event('push_package', :maven) if jar_file?(format) file_params = { file: params[:file], diff --git a/lib/api/npm_packages.rb b/lib/api/npm_packages.rb index fca405b76b7..41238221aad 100644 --- a/lib/api/npm_packages.rb +++ b/lib/api/npm_packages.rb @@ -141,7 +141,7 @@ module API package_file = ::Packages::PackageFileFinder .new(package, params[:file_name]).execute! - package_event('pull_package') + track_package_event('pull_package', package) present_carrierwave_file!(package_file.file) end @@ -157,7 +157,7 @@ module API put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do authorize_create_package!(user_project) - package_event('push_package') + track_package_event('push_package', :npm) created_package = ::Packages::Npm::CreatePackageService .new(user_project, current_user, params.merge(build: current_authenticated_job)).execute diff --git a/lib/api/nuget_packages.rb b/lib/api/nuget_packages.rb index f84a3acbe6d..c0c6efb66b5 100644 --- a/lib/api/nuget_packages.rb +++ b/lib/api/nuget_packages.rb @@ -42,7 +42,7 @@ module API def package_finder(finder_params = {}) ::Packages::Nuget::PackageFinder.new( authorized_user_project, - finder_params.merge(package_name: params[:package_name]) + **finder_params.merge(package_name: params[:package_name]) ) end end @@ -73,7 +73,7 @@ module API get 'index', format: :json do authorize_read_package!(authorized_user_project) - track_event('nuget_service_index') + track_package_event('cli_metadata', :nuget) present ::Packages::Nuget::ServiceIndexPresenter.new(authorized_user_project), with: ::API::Entities::Nuget::ServiceIndex @@ -105,7 +105,7 @@ module API package_file = ::Packages::CreatePackageFileService.new(package, file_params) .execute - package_event('push_package') + track_package_event('push_package', :nuget) ::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker @@ -198,7 +198,7 @@ module API not_found!('Package') unless package_file - package_event('pull_package') + track_package_event('pull_package', :nuget) # nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false present_carrierwave_file!(package_file.file, supports_direct_download: false) @@ -233,7 +233,7 @@ module API .new(authorized_user_project, params[:q], search_options) .execute - package_event('search_package') + track_package_event('search_package', :nuget) present ::Packages::Nuget::SearchResultsPresenter.new(search), with: ::API::Entities::Nuget::SearchResults diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb index 0e5605984e6..6f189110d76 100644 --- a/lib/api/project_clusters.rb +++ b/lib/api/project_clusters.rb @@ -45,6 +45,7 @@ module API optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true' optional :domain, type: String, desc: 'Cluster base domain' optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true' requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do @@ -78,6 +79,7 @@ module API optional :name, type: String, desc: 'Cluster name' optional :domain, type: String, desc: 'Cluster base domain' optional :environment_scope, type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb index 8f2a62bc5a4..173e7799325 100644 --- a/lib/api/project_container_repositories.rb +++ b/lib/api/project_container_repositories.rb @@ -3,6 +3,7 @@ module API class ProjectContainerRepositories < Grape::API::Instance include PaginationParams + helpers ::API::Helpers::PackagesHelpers REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( tag_name: API::NO_SLASH_URL_PART_REGEX) @@ -28,7 +29,7 @@ module API user: current_user, subject: user_project ).execute - track_event( 'list_repositories') + track_package_event('list_repositories', :container) present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags], tags_count: params[:tags_count] end @@ -43,7 +44,7 @@ module API authorize_admin_container_image! DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id) # rubocop:disable CodeReuse/Worker - track_event('delete_repository') + track_package_event('delete_repository', :container) status :accepted end @@ -60,7 +61,7 @@ module API authorize_read_container_image! tags = Kaminari.paginate_array(repository.tags) - track_event('list_tags') + track_package_event('list_tags', :container) present paginate(tags), with: Entities::ContainerRegistry::Tag end @@ -89,7 +90,7 @@ module API declared_params.except(:repository_id).merge(container_expiration_policy: false)) # rubocop:enable CodeReuse/Worker - track_event('delete_tag_bulk') + track_package_event('delete_tag_bulk', :container) status :accepted end @@ -125,7 +126,7 @@ module API .execute(repository) if result[:status] == :success - track_event('delete_tag') + track_package_event('delete_tag', :container) status :ok else diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 377d61689b3..6e4097fd76c 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -55,7 +55,7 @@ module API export_strategy = if after_export_params[:url].present? params = after_export_params.slice(:url, :http_method).symbolize_keys - Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy.new(params) + Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy.new(**params) end if export_strategy&.invalid? diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index 9f43c3c7993..6ac7f02f305 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -4,8 +4,6 @@ module API class ProjectImport < Grape::API::Instance include PaginationParams - MAXIMUM_FILE_SIZE = 50.megabytes - helpers Helpers::ProjectsHelpers helpers Helpers::FileUploadHelpers helpers Helpers::RateLimiter diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index c07db68f8a8..55cea075243 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -72,7 +72,7 @@ module API package = packages_finder(project).by_file_name_and_sha256(filename, params[:sha256]) package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute - package_event('pull_package') + track_package_event('pull_package', :pypi) present_carrierwave_file!(package_file.file, supports_direct_download: true) end @@ -91,7 +91,7 @@ module API get 'simple/*package_name', format: :txt do authorize_read_package!(authorized_user_project) - package_event('list_package') + track_package_event('list_package', :pypi) packages = find_package_versions presenter = ::Packages::Pypi::PackagePresenter.new(packages, authorized_user_project) @@ -122,7 +122,7 @@ module API authorize_upload!(authorized_user_project) bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:pypi_max_file_size, params[:content].size) - package_event('push_package') + track_package_event('push_package', :pypi) ::Packages::Pypi::CreatePackageService .new(authorized_user_project, current_user, declared_params) diff --git a/lib/api/search.rb b/lib/api/search.rb index b9c6a823f4f..8b6569dd57d 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -62,12 +62,6 @@ module API # Defining this method here as a noop allows us to easily extend it in # EE, without having to modify this file directly. end - - def check_users_search_allowed! - if params[:scope].to_sym == :users && Feature.disabled?(:users_search, default_enabled: true) - render_api_error!({ error: _("Scope not supported with disabled 'users_search' feature!") }, 400) - end - end end resource :search do @@ -85,7 +79,6 @@ module API end get do verify_search_scope!(resource: nil) - check_users_search_allowed! present search, with: entity end @@ -107,7 +100,6 @@ module API end get ':id/(-/)search' do verify_search_scope!(resource: user_group) - check_users_search_allowed! present search(group_id: user_group.id), with: entity end @@ -129,8 +121,6 @@ module API use :pagination end get ':id/(-/)search' do - check_users_search_allowed! - present search({ project_id: user_project.id, repository_ref: params[:ref] }), with: entity end end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 6e5534d0c9a..4056d8602f3 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -29,7 +29,8 @@ module API success Entities::ApplicationSetting end params do - optional :admin_notification_email, type: String, desc: 'Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.' + optional :admin_notification_email, type: String, desc: 'Deprecated: Use :abuse_notification_email instead. Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.' + optional :abuse_notification_email, type: String, desc: 'Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.' optional :after_sign_up_text, type: String, desc: 'Text shown after sign up' optional :after_sign_out_path, type: String, desc: 'We will redirect users to this page after they sign out' optional :akismet_enabled, type: Boolean, desc: 'Helps prevent bots from creating issues' @@ -194,6 +195,11 @@ module API attrs[:allow_local_requests_from_web_hooks_and_services] = attrs.delete(:allow_local_requests_from_hooks_and_services) end + # support legacy names, can be removed in v5 + if attrs.has_key?(:admin_notification_email) + attrs[:abuse_notification_email] = attrs.delete(:admin_notification_email) + end + # since 13.0 it's not possible to disable hashed storage - support can be removed in 14.0 attrs.delete(:hashed_storage_enabled) if attrs.has_key?(:hashed_storage_enabled) diff --git a/lib/api/terraform/state_version.rb b/lib/api/terraform/state_version.rb new file mode 100644 index 00000000000..5a4bc620cf6 --- /dev/null +++ b/lib/api/terraform/state_version.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module API + module Terraform + class StateVersion < Grape::API::Instance + default_format :json + + before do + authenticate! + authorize! :read_terraform_state, user_project + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + namespace ':id/terraform/state/:name/versions/:serial' do + params do + requires :name, type: String, desc: 'The name of a Terraform state' + requires :serial, type: Integer, desc: 'The version number of the state' + end + + helpers do + def remote_state_handler + ::Terraform::RemoteStateHandler.new(user_project, current_user, name: params[:name]) + end + + def find_version(serial) + remote_state_handler.find_with_lock do |state| + version = state.versions.find_by_version(serial) + + if version.present? + yield version + else + not_found! + end + end + end + end + + desc 'Get a terraform state version' + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + get do + find_version(params[:serial]) do |version| + env['api.format'] = :binary # Bypass json serialization + body version.file.read + status :ok + end + end + + desc 'Delete a terraform state version' + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + delete do + authorize! :admin_terraform_state, user_project + + find_version(params[:serial]) do |version| + version.destroy! + + body false + status :no_content + end + end + end + end + end + end +end diff --git a/lib/api/unleash.rb b/lib/api/unleash.rb new file mode 100644 index 00000000000..8db23c3aaec --- /dev/null +++ b/lib/api/unleash.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module API + class Unleash < Grape::API::Instance + include PaginationParams + + namespace :feature_flags do + resource :unleash, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + params do + requires :project_id, type: String, desc: 'The ID of a project' + optional :instance_id, type: String, desc: 'The Instance ID of Unleash Client' + optional :app_name, type: String, desc: 'The Application Name of Unleash Client' + end + route_param :project_id do + before do + authorize_by_unleash_instance_id! + end + + get do + # not supported yet + status :ok + end + + desc 'Get a list of features (deprecated, v2 client support)' + get 'features' do + present :version, 1 + present :features, feature_flags, with: ::API::Entities::UnleashFeature + end + + desc 'Get a list of features' + get 'client/features' do + present :version, 1 + present :features, feature_flags, with: ::API::Entities::UnleashFeature + end + + post 'client/register' do + # not supported yet + status :ok + end + + post 'client/metrics' do + # not supported yet + status :ok + end + end + end + end + + helpers do + def project + @project ||= find_project(params[:project_id]) + end + + def unleash_instance_id + env['HTTP_UNLEASH_INSTANCEID'] || params[:instance_id] + end + + def unleash_app_name + env['HTTP_UNLEASH_APPNAME'] || params[:app_name] + end + + def authorize_by_unleash_instance_id! + unauthorized! unless Operations::FeatureFlagsClient + .find_for_project_and_token(project, unleash_instance_id) + end + + def feature_flags + return [] unless unleash_app_name.present? + + legacy_flags = Operations::FeatureFlagScope.for_unleash_client(project, unleash_app_name) + new_version_flags = Operations::FeatureFlag.for_unleash_client(project, unleash_app_name) + + legacy_flags + new_version_flags + end + end + end +end diff --git a/lib/api/users.rb b/lib/api/users.rb index 73bb43b88fc..b20ee590124 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -348,7 +348,7 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Get the GPG keys of a specified user. Available only for admins.' do + desc 'Get the GPG keys of a specified user.' do detail 'This feature was added in GitLab 10.0' success Entities::GpgKey end @@ -358,8 +358,6 @@ module API end # rubocop: disable CodeReuse/ActiveRecord get ':id/gpg_keys' do - authenticated_as_admin! - user = User.find_by(id: params[:id]) not_found!('User') unless user diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb index 593f90460ac..08bf395fa98 100644 --- a/lib/api/v3/github.rb +++ b/lib/api/v3/github.rb @@ -51,7 +51,7 @@ module API def find_project_with_access(params) project = find_project!( - ::Gitlab::Jira::Dvcs.restore_full_path(params.slice(:namespace, :project).symbolize_keys) + ::Gitlab::Jira::Dvcs.restore_full_path(**params.slice(:namespace, :project).symbolize_keys) ) not_found! unless can?(current_user, :download_code, project) project |