Commit 636afb58 authored by Jonas Wälter's avatar Jonas Wälter Committed by Michael Kozono

Add API endpoint /application/plan_limits for package file size limits

parent a9cda936
# frozen_string_literal: true
module Admin
class PlansFinder
attr_reader :params
def initialize(params = {})
@params = params
end
def execute
plans = Plan.all
by_name(plans)
end
private
def by_name(plans)
return plans unless params[:name]
Plan.find_by(name: params[:name]) # rubocop: disable CodeReuse/ActiveRecord
end
end
end
---
title: Add API endpoint /application/plan_limits for package file size limits
merge_request: 54232
author: Jonas Wälter @wwwjon
type: added
......@@ -155,6 +155,7 @@ The following API resources are available outside of project and group contexts
| [Namespaces](namespaces.md) | `/namespaces` |
| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
| [Plan limits](plan_limits.md) | `/application/plan_limits` |
| [Personal access tokens](personal_access_tokens.md) | `/personal_access_tokens` |
| [Projects](projects.md) | `/users/:id/projects` (also available for projects) |
| [Project repository storage moves](project_repository_storage_moves.md) **(FREE SELF)** | `/project_repository_storage_moves` |
......
---
stage: Manage
group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Plan limits API **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54232) in GitLab 13.10.
The plan limits API allows you to maintain the application limits for the existing subscription plans.
The existing plans depend on the GitLab edition. In the Community Edition, only the plan `default`
is available. In the Enterprise Edition, additional plans are available as well.
NOTE:
Administrator access is required to use this API.
## Get current plan limits
List the current limits of a plan on the GitLab instance.
```plaintext
GET /application/plan_limits
```
| Attribute | Type | Required | Description |
| --------------------------------- | ------- | -------- | ----------- |
| `plan_name` | string | no | Name of the plan to get the limits from. Default: `default`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/application/plan_limits"
```
Example response:
```json
{
"conan_max_file_size": 3221225472,
"generic_packages_max_file_size": 5368709120,
"maven_max_file_size": 3221225472,
"npm_max_file_size": 524288000,
"nuget_max_file_size": 524288000,
"pypi_max_file_size": 3221225472
}
```
## Change plan limits
Modify the limits of a plan on the GitLab instance.
```plaintext
PUT /application/plan_limits
```
| Attribute | Type | Required | Description |
| --------------------------------- | ------- | -------- | ----------- |
| `plan_name` | string | yes | Name of the plan to update. |
| `conan_max_file_size` | integer | no | Maximum Conan package file size in bytes. |
| `generic_packages_max_file_size` | integer | no | Maximum generic package file size in bytes. |
| `maven_max_file_size` | integer | no | Maximum Maven package file size in bytes. |
| `npm_max_file_size` | integer | no | Maximum NPM package file size in bytes. |
| `nuget_max_file_size` | integer | no | Maximum NuGet package file size in bytes. |
| `pypi_max_file_size` | integer | no | Maximum PyPI package file size in bytes. |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/application/plan_limits?plan_name=default&conan_max_file_size=3221225472"
```
Example response:
```json
{
"conan_max_file_size": 3221225472,
"generic_packages_max_file_size": 5368709120,
"maven_max_file_size": 3221225472,
"npm_max_file_size": 524288000,
"nuget_max_file_size": 524288000,
"pypi_max_file_size": 3221225472
}
```
......@@ -400,3 +400,8 @@ listed in the descriptions of the relevant settings.
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
| `web_ide_clientside_preview_enabled` | boolean | no | Live Preview (allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview). |
| `wiki_page_max_content_bytes` | integer | no | Maximum wiki page content size in **bytes**. Default: 52428800 Bytes (50 MB). The minimum value is 1024 bytes. |
### Package Registry: Package file size limits
The package file size limits are not part of the Application settings API.
Instead, these settings can be accessed using the [Plan limits API](plan_limits.md).
# frozen_string_literal: true
module API
module Admin
class PlanLimits < ::API::Base
before { authenticated_as_admin! }
feature_category :not_owned
helpers do
def current_plan(name)
plan = ::Admin::PlansFinder.new({ name: name }).execute
not_found!('Plan') unless plan
plan
end
end
desc 'Get current plan limits' do
success Entities::PlanLimit
end
params do
optional :plan_name, type: String, values: Plan.all_plans, default: Plan::DEFAULT, desc: 'Name of the plan'
end
get "application/plan_limits" do
params = declared_params(include_missing: false)
plan = current_plan(params.delete(:plan_name))
present plan.actual_limits, with: Entities::PlanLimit
end
desc 'Modify plan limits' do
success Entities::PlanLimit
end
params do
requires :plan_name, type: String, values: Plan.all_plans, desc: 'Name of the plan'
optional :conan_max_file_size, type: Integer, desc: 'Maximum Conan package file size in bytes'
optional :generic_packages_max_file_size, type: Integer, desc: 'Maximum generic package file size in bytes'
optional :maven_max_file_size, type: Integer, desc: 'Maximum Maven package file size in bytes'
optional :npm_max_file_size, type: Integer, desc: 'Maximum NPM package file size in bytes'
optional :nuget_max_file_size, type: Integer, desc: 'Maximum NuGet package file size in bytes'
optional :pypi_max_file_size, type: Integer, desc: 'Maximum PyPI package file size in bytes'
end
put "application/plan_limits" do
params = declared_params(include_missing: false)
plan = current_plan(params.delete(:plan_name))
if plan.actual_limits.update(params)
present plan.actual_limits, with: Entities::PlanLimit
else
render_validation_error!(plan.actual_limits)
end
end
end
end
end
......@@ -169,6 +169,7 @@ module API
mount ::API::AccessRequests
mount ::API::Admin::Ci::Variables
mount ::API::Admin::InstanceClusters
mount ::API::Admin::PlanLimits
mount ::API::Admin::Sidekiq
mount ::API::Appearance
mount ::API::Applications
......
# frozen_string_literal: true
module API
module Entities
class PlanLimit < Grape::Entity
expose :conan_max_file_size
expose :generic_packages_max_file_size
expose :maven_max_file_size
expose :npm_max_file_size
expose :nuget_max_file_size
expose :pypi_max_file_size
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Admin::PlansFinder do
let_it_be(:plan1) { create(:plan, name: 'plan1') }
let_it_be(:plan2) { create(:plan, name: 'plan2') }
describe '#execute' do
context 'with no params' do
it 'returns all plans' do
found = described_class.new.execute
expect(found).to match_array([plan1, plan2])
end
end
context 'with missing name in params' do
before do
@params = { title: 'plan2' }
end
it 'returns all plans' do
found = described_class.new(@params).execute
expect(found).to match_array([plan1, plan2])
end
end
context 'with existing name in params' do
before do
@params = { name: 'plan2' }
end
it 'returns the plan' do
found = described_class.new(@params).execute
expect(found).to match(plan2)
end
end
context 'with non-existing name in params' do
before do
@params = { name: 'non-existing-plan' }
end
it 'returns nil' do
found = described_class.new(@params).execute
expect(found).to be_nil
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Entities::PlanLimit do
let(:plan_limits) { create(:plan_limits) }
subject { described_class.new(plan_limits).as_json }
it 'exposes correct attributes' do
expect(subject).to include(
:conan_max_file_size,
:generic_packages_max_file_size,
:maven_max_file_size,
:npm_max_file_size,
:nuget_max_file_size,
:pypi_max_file_size
)
end
it 'does not expose id and plan_id' do
expect(subject).not_to include(:id, :plan_id)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
let_it_be(:plan) { create(:plan, name: 'default') }
describe 'GET /application/plan_limits' do
context 'as a non-admin user' do
it 'returns 403' do
get api('/application/plan_limits', user)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'as an admin user' do
context 'no params' do
it 'returns plan limits' do
get api('/application/plan_limits', admin)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Hash
expect(json_response['conan_max_file_size']).to eq(Plan.default.actual_limits.conan_max_file_size)
expect(json_response['generic_packages_max_file_size']).to eq(Plan.default.actual_limits.generic_packages_max_file_size)
expect(json_response['maven_max_file_size']).to eq(Plan.default.actual_limits.maven_max_file_size)
expect(json_response['npm_max_file_size']).to eq(Plan.default.actual_limits.npm_max_file_size)
expect(json_response['nuget_max_file_size']).to eq(Plan.default.actual_limits.nuget_max_file_size)
expect(json_response['pypi_max_file_size']).to eq(Plan.default.actual_limits.pypi_max_file_size)
end
end
context 'correct plan name in params' do
before do
@params = { plan_name: 'default' }
end
it 'returns plan limits' do
get api('/application/plan_limits', admin), params: @params
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Hash
expect(json_response['conan_max_file_size']).to eq(Plan.default.actual_limits.conan_max_file_size)
expect(json_response['generic_packages_max_file_size']).to eq(Plan.default.actual_limits.generic_packages_max_file_size)
expect(json_response['maven_max_file_size']).to eq(Plan.default.actual_limits.maven_max_file_size)
expect(json_response['npm_max_file_size']).to eq(Plan.default.actual_limits.npm_max_file_size)
expect(json_response['nuget_max_file_size']).to eq(Plan.default.actual_limits.nuget_max_file_size)
expect(json_response['pypi_max_file_size']).to eq(Plan.default.actual_limits.pypi_max_file_size)
end
end
context 'invalid plan name in params' do
before do
@params = { plan_name: 'my-plan' }
end
it 'returns validation error' do
get api('/application/plan_limits', admin), params: @params
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('plan_name does not have a valid value')
end
end
end
end
describe 'PUT /application/plan_limits' do
context 'as a non-admin user' do
it 'returns 403' do
put api('/application/plan_limits', user), params: { plan_name: 'default' }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'as an admin user' do
context 'correct params' do
it 'updates multiple plan limits' do
put api('/application/plan_limits', admin), params: {
'plan_name': 'default',
'conan_max_file_size': 10,
'generic_packages_max_file_size': 20,
'maven_max_file_size': 30,
'npm_max_file_size': 40,
'nuget_max_file_size': 50,
'pypi_max_file_size': 60
}
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Hash
expect(json_response['conan_max_file_size']).to eq(10)
expect(json_response['generic_packages_max_file_size']).to eq(20)
expect(json_response['maven_max_file_size']).to eq(30)
expect(json_response['npm_max_file_size']).to eq(40)
expect(json_response['nuget_max_file_size']).to eq(50)
expect(json_response['pypi_max_file_size']).to eq(60)
end
it 'updates single plan limits' do
put api('/application/plan_limits', admin), params: {
'plan_name': 'default',
'maven_max_file_size': 100
}
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Hash
expect(json_response['maven_max_file_size']).to eq(100)
end
end
context 'empty params' do
it 'fails to update plan limits' do
put api('/application/plan_limits', admin), params: {}
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to match('plan_name is missing')
end
end
context 'params with wrong type' do
it 'fails to update plan limits' do
put api('/application/plan_limits', admin), params: {
'plan_name': 'default',
'conan_max_file_size': 'a',
'generic_packages_max_file_size': 'b',
'maven_max_file_size': 'c',
'npm_max_file_size': 'd',
'nuget_max_file_size': 'e',
'pypi_max_file_size': 'f'
}
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to include(
'conan_max_file_size is invalid',
'generic_packages_max_file_size is invalid',
'maven_max_file_size is invalid',
'generic_packages_max_file_size is invalid',
'npm_max_file_size is invalid',
'nuget_max_file_size is invalid',
'pypi_max_file_size is invalid'
)
end
end
context 'missing plan_name in params' do
it 'fails to update plan limits' do
put api('/application/plan_limits', admin), params: { 'conan_max_file_size': 0 }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to match('plan_name is missing')
end
end
context 'additional undeclared params' do
before do
Plan.default.actual_limits.update!({ 'golang_max_file_size': 1000 })
end
it 'updates only declared plan limits' do
put api('/application/plan_limits', admin), params: {
'plan_name': 'default',
'pypi_max_file_size': 200,
'golang_max_file_size': 999
}
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Hash
expect(json_response['pypi_max_file_size']).to eq(200)
expect(json_response['golang_max_file_size']).to be_nil
expect(Plan.default.actual_limits.golang_max_file_size).to eq(1000)
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment