mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 10:15:35 +01:00
refactor: generate quota APIs by go-swagger
Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
a4a995327b
commit
4b033c266a
@ -1806,120 +1806,6 @@ paths:
|
||||
description: User does not have permission to call this API.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/quotas':
|
||||
get:
|
||||
summary: List quotas
|
||||
description: List quotas
|
||||
tags:
|
||||
- Products
|
||||
parameters:
|
||||
- name: reference
|
||||
in: query
|
||||
description: The reference type of quota.
|
||||
required: false
|
||||
type: string
|
||||
- name: reference_id
|
||||
in: query
|
||||
description: The reference id of quota.
|
||||
required: false
|
||||
type: string
|
||||
- name: sort
|
||||
in: query
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
Sort method, valid values include:
|
||||
'hard.resource_name', '-hard.resource_name', 'used.resource_name', '-used.resource_name'.
|
||||
Here '-' stands for descending order, resource_name should be the real resource name of the quota.
|
||||
- name: page
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: 'The page number, default is 1.'
|
||||
- name: page_size
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: 'The size of per page, default is 10, maximum is 100.'
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully retrieved the quotas.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Quota'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of access logs
|
||||
type: integer
|
||||
Link:
|
||||
description: Link refers to the previous page and next page
|
||||
type: string
|
||||
'401':
|
||||
description: User is not authenticated.
|
||||
'403':
|
||||
description: User does not have permission to call this API.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/quotas/{id}':
|
||||
get:
|
||||
summary: Get the specified quota
|
||||
description: Get the specified quota
|
||||
tags:
|
||||
- Products
|
||||
- Quota
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
description: Quota ID
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully retrieved the quota.
|
||||
schema:
|
||||
$ref: '#/definitions/Quota'
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User does not have permission to call this API
|
||||
'404':
|
||||
description: Quota does not exist.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
put:
|
||||
summary: Update the specified quota
|
||||
description: Update hard limits of the specified quota
|
||||
tags:
|
||||
- Products
|
||||
- Quota
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
description: Quota ID
|
||||
- name: hard
|
||||
in: body
|
||||
required: true
|
||||
description: The new hard limits for the quota
|
||||
schema:
|
||||
$ref: '#/definitions/QuotaUpdateReq'
|
||||
responses:
|
||||
'200':
|
||||
description: Updated quota hard limits successfully.
|
||||
'400':
|
||||
description: Illegal format of quota update request.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User does not have permission to the quota.
|
||||
'404':
|
||||
description: Quota ID does not exist.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/projects/{project_id}/webhook/policies':
|
||||
get:
|
||||
summary: List project webhook policies.
|
||||
@ -2736,30 +2622,6 @@ definitions:
|
||||
artifact_count:
|
||||
type: integer
|
||||
description: The count of artifacts in the repository
|
||||
ProjectReq:
|
||||
type: object
|
||||
properties:
|
||||
project_name:
|
||||
type: string
|
||||
description: The name of the project.
|
||||
metadata:
|
||||
description: The metadata of the project.
|
||||
$ref: '#/definitions/ProjectMetadata'
|
||||
cve_allowlist:
|
||||
description: The CVE allowlist of the project.
|
||||
$ref: '#/definitions/CVEAllowlist'
|
||||
count_limit:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The count quota of the project.
|
||||
storage_limit:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The storage quota of the project.
|
||||
registry_id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The ID of referenced registry when creating the proxy cache project
|
||||
Project:
|
||||
type: object
|
||||
properties:
|
||||
@ -2836,38 +2698,6 @@ definitions:
|
||||
type: string
|
||||
description: 'Whether this project reuse the system level CVE allowlist as the allowlist of its own. The valid values are "true", "false".
|
||||
If it is set to "true" the actual allowlist associate with this project, if any, will be ignored.'
|
||||
ProjectSummary:
|
||||
type: object
|
||||
properties:
|
||||
repo_count:
|
||||
type: integer
|
||||
description: The number of the repositories under this project.
|
||||
chart_count:
|
||||
type: integer
|
||||
description: The total number of charts under this project.
|
||||
project_admin_count:
|
||||
type: integer
|
||||
description: The total number of project admin members.
|
||||
maintainer_count:
|
||||
type: integer
|
||||
description: The total number of maintainer members.
|
||||
developer_count:
|
||||
type: integer
|
||||
description: The total number of developer members.
|
||||
guest_count:
|
||||
type: integer
|
||||
description: The total number of guest members.
|
||||
quota:
|
||||
type: object
|
||||
properties:
|
||||
hard:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The hard limits of the quota
|
||||
used:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The used status of the quota
|
||||
registry:
|
||||
$ref: "#/definitions/Registry"
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
@ -3833,42 +3663,6 @@ definitions:
|
||||
cve_id:
|
||||
type: string
|
||||
description: The ID of the CVE, such as "CVE-2019-10164"
|
||||
ResourceList:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
format: int64
|
||||
QuotaUpdateReq:
|
||||
type: object
|
||||
properties:
|
||||
hard:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The new hard limits for the quota
|
||||
QuotaRefObject:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
Quota:
|
||||
type: object
|
||||
description: The quota object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: ID of the quota
|
||||
ref:
|
||||
$ref: "#/definitions/QuotaRefObject"
|
||||
description: The reference object of the quota
|
||||
hard:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The hard limits of the quota
|
||||
used:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The used status of the quota
|
||||
creation_time:
|
||||
type: string
|
||||
description: the creation time of the quota
|
||||
update_time:
|
||||
type: string
|
||||
description: the update time of the quota
|
||||
WebhookTargetObject:
|
||||
type: object
|
||||
description: The webhook policy target object.
|
||||
|
@ -1700,6 +1700,114 @@ paths:
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/quotas':
|
||||
get:
|
||||
summary: List quotas
|
||||
description: List quotas
|
||||
tags:
|
||||
- quota
|
||||
operationId: listQuotas
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/page'
|
||||
- $ref: '#/parameters/pageSize'
|
||||
- name: reference
|
||||
in: query
|
||||
description: The reference type of quota.
|
||||
required: false
|
||||
type: string
|
||||
- name: reference_id
|
||||
in: query
|
||||
description: The reference id of quota.
|
||||
required: false
|
||||
type: string
|
||||
- name: sort
|
||||
in: query
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
Sort method, valid values include:
|
||||
'hard.resource_name', '-hard.resource_name', 'used.resource_name', '-used.resource_name'.
|
||||
Here '-' stands for descending order, resource_name should be the real resource name of the quota.
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully retrieved the quotas.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Quota'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of access logs
|
||||
type: integer
|
||||
Link:
|
||||
description: Link refers to the previous page and next page
|
||||
type: string
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/quotas/{id}':
|
||||
get:
|
||||
summary: Get the specified quota
|
||||
description: Get the specified quota
|
||||
tags:
|
||||
- quota
|
||||
operationId: getQuota
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
description: Quota ID
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully retrieved the quota.
|
||||
schema:
|
||||
$ref: '#/definitions/Quota'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
put:
|
||||
summary: Update the specified quota
|
||||
description: Update hard limits of the specified quota
|
||||
tags:
|
||||
- quota
|
||||
operationId: updateQuota
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
description: Quota ID
|
||||
- name: hard
|
||||
in: body
|
||||
required: true
|
||||
description: The new hard limits for the quota
|
||||
schema:
|
||||
$ref: '#/definitions/QuotaUpdateReq'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/responses/200'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/robots/{robot_id}:
|
||||
get:
|
||||
summary: Get a robot account
|
||||
@ -3585,16 +3693,18 @@ definitions:
|
||||
type: integer
|
||||
description: The total number of limited guest members.
|
||||
quota:
|
||||
type: object
|
||||
properties:
|
||||
hard:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The hard limits of the quota
|
||||
used:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The used status of the quota
|
||||
$ref: "#/definitions/ProjectSummaryQuota"
|
||||
registry:
|
||||
$ref: "#/definitions/Registry"
|
||||
ProjectSummaryQuota:
|
||||
type: object
|
||||
properties:
|
||||
hard:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The hard limits of the quota
|
||||
used:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The used status of the quota
|
||||
CVEAllowlist:
|
||||
type: object
|
||||
description: The CVE Allowlist for system or project
|
||||
@ -3678,6 +3788,10 @@ definitions:
|
||||
additionalProperties:
|
||||
type: integer
|
||||
format: int64
|
||||
x-go-type:
|
||||
type: ResourceList
|
||||
import:
|
||||
package: "github.com/goharbor/harbor/src/pkg/quota/types"
|
||||
ReplicationExecution:
|
||||
type: object
|
||||
description: The replication execution
|
||||
@ -4328,3 +4442,38 @@ definitions:
|
||||
retained:
|
||||
type: integer
|
||||
x-omitempty: false
|
||||
QuotaUpdateReq:
|
||||
type: object
|
||||
properties:
|
||||
hard:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The new hard limits for the quota
|
||||
QuotaRefObject:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
Quota:
|
||||
type: object
|
||||
description: The quota object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: ID of the quota
|
||||
ref:
|
||||
$ref: "#/definitions/QuotaRefObject"
|
||||
description: The reference object of the quota
|
||||
hard:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The hard limits of the quota
|
||||
x-omitempty: false
|
||||
used:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The used status of the quota
|
||||
x-omitempty: false
|
||||
creation_time:
|
||||
type: string
|
||||
format: date-time
|
||||
description: the creation time of the quota
|
||||
update_time:
|
||||
type: string
|
||||
format: date-time
|
||||
description: the update time of the quota
|
||||
|
@ -158,7 +158,6 @@ var (
|
||||
{Name: common.MetricPath, Scope: SystemScope, Group: BasicGroup, EnvKey: "METRIC_PATH", DefaultValue: "/metrics", ItemType: &StringType{}, Editable: true},
|
||||
|
||||
{Name: common.QuotaPerProjectEnable, Scope: UserScope, Group: QuotaGroup, EnvKey: "QUOTA_PER_PROJECT_ENABLE", DefaultValue: "true", ItemType: &BoolType{}, Editable: true},
|
||||
{Name: common.CountPerProject, Scope: UserScope, Group: QuotaGroup, EnvKey: "COUNT_PER_PROJECT", DefaultValue: "-1", ItemType: &QuotaType{}, Editable: true},
|
||||
{Name: common.StoragePerProject, Scope: UserScope, Group: QuotaGroup, EnvKey: "STORAGE_PER_PROJECT", DefaultValue: "-1", ItemType: &QuotaType{}, Editable: true},
|
||||
}
|
||||
)
|
||||
|
@ -156,7 +156,6 @@ const (
|
||||
|
||||
// Quota setting items for project
|
||||
QuotaPerProjectEnable = "quota_per_project_enable"
|
||||
CountPerProject = "count_per_project"
|
||||
StoragePerProject = "storage_per_project"
|
||||
|
||||
// DefaultGCTimeWindowHours is the reserve blob time window used by GC, default is 2 hours
|
||||
|
@ -22,8 +22,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/quota/types"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
@ -263,24 +261,3 @@ type ProjectQueryResult struct {
|
||||
func (p *Project) TableName() string {
|
||||
return ProjectTable
|
||||
}
|
||||
|
||||
// QuotaSummary ...
|
||||
type QuotaSummary struct {
|
||||
Hard types.ResourceList `json:"hard"`
|
||||
Used types.ResourceList `json:"used"`
|
||||
}
|
||||
|
||||
// ProjectSummary ...
|
||||
type ProjectSummary struct {
|
||||
RepoCount int64 `json:"repo_count"`
|
||||
ChartCount uint64 `json:"chart_count"`
|
||||
|
||||
ProjectAdminCount int64 `json:"project_admin_count"`
|
||||
MaintainerCount int64 `json:"maintainer_count"`
|
||||
DeveloperCount int64 `json:"developer_count"`
|
||||
GuestCount int64 `json:"guest_count"`
|
||||
LimitedGuestCount int64 `json:"limited_guest_count"`
|
||||
|
||||
Quota *QuotaSummary `json:"quota,omitempty"`
|
||||
Registry *model.Registry `json:"registry"`
|
||||
}
|
||||
|
@ -157,10 +157,6 @@ func init() {
|
||||
beego.Router("/api/"+api.APIVersion+"/chartrepo/:repo/charts/:name/:version/labels", chartLabelAPIType, "get:GetLabels;post:MarkLabel")
|
||||
beego.Router("/api/"+api.APIVersion+"/chartrepo/:repo/charts/:name/:version/labels/:id([0-9]+)", chartLabelAPIType, "delete:RemoveLabel")
|
||||
|
||||
quotaAPIType := &QuotaAPI{}
|
||||
beego.Router("/api/quotas", quotaAPIType, "get:List")
|
||||
beego.Router("/api/quotas/:id([0-9]+)", quotaAPIType, "get:Get;put:Put")
|
||||
|
||||
beego.Router("/api/internal/switchquota", &InternalAPI{}, "put:SwitchQuota")
|
||||
beego.Router("/api/internal/syncquota", &InternalAPI{}, "post:SyncQuota")
|
||||
|
||||
@ -918,55 +914,3 @@ func (a testapi) RegistryUpdate(authInfo usrInfo, registryID int64, req *apimode
|
||||
|
||||
return code, nil
|
||||
}
|
||||
|
||||
// QuotasGet returns quotas
|
||||
func (a testapi) QuotasGet(query *apilib.QuotaQuery, authInfo ...usrInfo) (int, []apilib.Quota, error) {
|
||||
_sling := sling.New().Get(a.basePath).
|
||||
Path("api/quotas").
|
||||
QueryStruct(query)
|
||||
|
||||
var successPayload []apilib.Quota
|
||||
|
||||
var httpStatusCode int
|
||||
var err error
|
||||
var body []byte
|
||||
if len(authInfo) > 0 {
|
||||
httpStatusCode, body, err = request(_sling, jsonAcceptHeader, authInfo[0])
|
||||
} else {
|
||||
httpStatusCode, body, err = request(_sling, jsonAcceptHeader)
|
||||
}
|
||||
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
} else {
|
||||
log.Println(string(body))
|
||||
}
|
||||
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
// Return specific quota
|
||||
func (a testapi) QuotasGetByID(authInfo usrInfo, quotaID string) (int, apilib.Quota, error) {
|
||||
_sling := sling.New().Get(a.basePath)
|
||||
|
||||
// create api path
|
||||
path := "api/quotas/" + quotaID
|
||||
_sling = _sling.Path(path)
|
||||
|
||||
var successPayload apilib.Quota
|
||||
|
||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
if err == nil && httpStatusCode == 200 {
|
||||
err = json.Unmarshal(body, &successPayload)
|
||||
}
|
||||
return httpStatusCode, successPayload, err
|
||||
}
|
||||
|
||||
// Update spec for the quota
|
||||
func (a testapi) QuotasPut(authInfo usrInfo, quotaID string, req QuotaUpdateRequest) (int, error) {
|
||||
path := "/api/quotas/" + quotaID
|
||||
_sling := sling.New().Put(a.basePath).Path(path).BodyJSON(req)
|
||||
|
||||
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||
return httpStatusCode, err
|
||||
}
|
||||
|
@ -1,149 +0,0 @@
|
||||
// Copyright 2018 Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/quota"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/quota/types"
|
||||
)
|
||||
|
||||
// QuotaUpdateRequest struct for the body of put quota API
|
||||
type QuotaUpdateRequest struct {
|
||||
Hard types.ResourceList `json:"hard"`
|
||||
}
|
||||
|
||||
// QuotaAPI handles request to /api/quotas/
|
||||
type QuotaAPI struct {
|
||||
BaseController
|
||||
id int64
|
||||
}
|
||||
|
||||
// Prepare validates the URL and the user
|
||||
func (qa *QuotaAPI) Prepare() {
|
||||
qa.BaseController.Prepare()
|
||||
|
||||
if !qa.SecurityCtx.IsAuthenticated() {
|
||||
qa.SendUnAuthorizedError(errors.New("Unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(qa.GetStringFromPath(":id")) != 0 {
|
||||
id, err := qa.GetInt64FromPath(":id")
|
||||
if err != nil || id <= 0 {
|
||||
text := "invalid quota ID: "
|
||||
if err != nil {
|
||||
text += err.Error()
|
||||
} else {
|
||||
text += fmt.Sprintf("%d", id)
|
||||
}
|
||||
qa.SendBadRequestError(errors.New(text))
|
||||
return
|
||||
}
|
||||
qa.id = id
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns quota by id
|
||||
func (qa *QuotaAPI) Get() {
|
||||
if !qa.SecurityCtx.Can(orm.Context(), rbac.ActionRead, rbac.ResourceQuota) {
|
||||
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
quota, err := quota.Ctl.Get(qa.Ctx.Request.Context(), qa.id)
|
||||
if err != nil {
|
||||
qa.SendError(err)
|
||||
return
|
||||
}
|
||||
qa.Data["json"] = quota
|
||||
qa.ServeJSON()
|
||||
}
|
||||
|
||||
// Put update the quota
|
||||
func (qa *QuotaAPI) Put() {
|
||||
if !qa.SecurityCtx.Can(orm.Context(), rbac.ActionUpdate, rbac.ResourceQuota) {
|
||||
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
|
||||
var req *QuotaUpdateRequest
|
||||
if err := qa.DecodeJSONReq(&req); err != nil {
|
||||
qa.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := qa.Ctx.Request.Context()
|
||||
q, err := quota.Ctl.Get(ctx, qa.id)
|
||||
if err != nil {
|
||||
qa.SendError(err)
|
||||
return
|
||||
}
|
||||
if err := quota.Validate(ctx, q.Reference, req.Hard); err != nil {
|
||||
qa.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
q.SetHard(req.Hard)
|
||||
|
||||
if err := quota.Ctl.Update(ctx, q); err != nil {
|
||||
qa.SendInternalServerError(fmt.Errorf("failed to update hard limits of the quota, error: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// List returns quotas by query
|
||||
func (qa *QuotaAPI) List() {
|
||||
if !qa.SecurityCtx.Can(orm.Context(), rbac.ActionList, rbac.ResourceQuota) {
|
||||
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
page, size, err := qa.GetPaginationParams()
|
||||
if err != nil {
|
||||
qa.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
query := &q.Query{
|
||||
Keywords: q.KeyWords{
|
||||
"reference": qa.GetString("reference"),
|
||||
"reference_id": qa.GetString("reference_id"),
|
||||
},
|
||||
PageNumber: page,
|
||||
PageSize: size,
|
||||
Sorting: qa.GetString("sort"),
|
||||
}
|
||||
|
||||
ctx := qa.Ctx.Request.Context()
|
||||
|
||||
total, err := quota.Ctl.Count(ctx, query)
|
||||
if err != nil {
|
||||
qa.SendInternalServerError(fmt.Errorf("failed to query database for total of quotas, error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
quotas, err := quota.Ctl.List(ctx, query, quota.WithReferenceObject())
|
||||
if err != nil {
|
||||
qa.SendInternalServerError(fmt.Errorf("failed to query database for quotas, error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
qa.SetPaginationHeader(total, page, size)
|
||||
qa.Data["json"] = quotas
|
||||
qa.ServeJSON()
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
// Copyright 2018 Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
o "github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/controller/quota"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/quota/driver"
|
||||
"github.com/goharbor/harbor/src/pkg/quota/types"
|
||||
"github.com/goharbor/harbor/src/testing/apitests/apilib"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
drivertesting "github.com/goharbor/harbor/src/testing/pkg/quota/driver"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
reference = uuid.New().String()
|
||||
hardLimits = types.ResourceList{types.ResourceStorage: -1}
|
||||
)
|
||||
|
||||
func init() {
|
||||
mockDriver := &drivertesting.Driver{}
|
||||
|
||||
mockHardLimitsFn := func() types.ResourceList {
|
||||
return hardLimits
|
||||
}
|
||||
|
||||
mockLoadFn := func(ctx context.Context, key string) driver.RefObject {
|
||||
return driver.RefObject{"id": key}
|
||||
}
|
||||
|
||||
mockValidateFn := func(hardLimits types.ResourceList) error {
|
||||
if len(hardLimits) == 0 {
|
||||
return fmt.Errorf("no resources found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
mockDriver.On("HardLimits").Return(mockHardLimitsFn)
|
||||
mock.OnAnything(mockDriver, "Load").Return(mockLoadFn, nil)
|
||||
mock.OnAnything(mockDriver, "Validate").Return(mockValidateFn)
|
||||
|
||||
driver.Register(reference, mockDriver)
|
||||
}
|
||||
|
||||
func TestQuotaAPIList(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
ctx := orm.NewContext(context.TODO(), o.NewOrm())
|
||||
var quotaIDs []int64
|
||||
defer func() {
|
||||
for _, quotaID := range quotaIDs {
|
||||
quota.Ctl.Delete(ctx, quotaID)
|
||||
}
|
||||
}()
|
||||
|
||||
count := 10
|
||||
for i := 0; i < count; i++ {
|
||||
quotaID, err := quota.Ctl.Create(ctx, reference, uuid.New().String(), hardLimits)
|
||||
assert.Nil(err)
|
||||
quotaIDs = append(quotaIDs, quotaID)
|
||||
}
|
||||
|
||||
code, quotas, err := apiTest.QuotasGet(&apilib.QuotaQuery{Reference: reference}, *admin)
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
assert.Len(quotas, count, fmt.Sprintf("quotas len should be %d", count))
|
||||
|
||||
code, quotas, err = apiTest.QuotasGet(&apilib.QuotaQuery{Reference: reference, PageSize: 1}, *admin)
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
assert.Len(quotas, 1)
|
||||
}
|
||||
|
||||
func TestQuotaAPIGet(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
ctx := orm.NewContext(context.TODO(), o.NewOrm())
|
||||
quotaID, err := quota.Ctl.Create(ctx, reference, uuid.New().String(), hardLimits)
|
||||
assert.Nil(err)
|
||||
defer quota.Ctl.Delete(ctx, quotaID)
|
||||
|
||||
code, quota, err := apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
assert.Equal(map[string]int64{"storage": -1}, quota.Hard)
|
||||
|
||||
code, _, err = apiTest.QuotasGetByID(*admin, "100")
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(404), code)
|
||||
}
|
||||
|
||||
func TestQuotaPut(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
ctx := orm.NewContext(context.TODO(), o.NewOrm())
|
||||
quotaID, err := quota.Ctl.Create(ctx, reference, uuid.New().String(), hardLimits)
|
||||
assert.Nil(err)
|
||||
defer quota.Ctl.Delete(ctx, quotaID)
|
||||
|
||||
code, quota, err := apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
assert.Equal(map[string]int64{"storage": -1}, quota.Hard)
|
||||
|
||||
code, err = apiTest.QuotasPut(*admin, fmt.Sprintf("%d", quotaID), QuotaUpdateRequest{})
|
||||
assert.Nil(err, err)
|
||||
assert.Equal(int(400), code)
|
||||
|
||||
code, err = apiTest.QuotasPut(*admin, fmt.Sprintf("%d", quotaID), QuotaUpdateRequest{Hard: types.ResourceList{types.ResourceStorage: 100}})
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
|
||||
code, quota, err = apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
assert.Equal(map[string]int64{"storage": 100}, quota.Hard)
|
||||
}
|
@ -17,6 +17,8 @@ package types
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -43,6 +45,11 @@ func (resource ResourceName) FormatValue(value int64) string {
|
||||
// ResourceList is a set of (resource name, value) pairs.
|
||||
type ResourceList map[ResourceName]int64
|
||||
|
||||
// Validate validates this resource list
|
||||
func (resources ResourceList) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (resources ResourceList) String() string {
|
||||
bytes, _ := json.Marshal(resources)
|
||||
return string(bytes)
|
||||
|
@ -45,6 +45,7 @@ func New() http.Handler {
|
||||
SysteminfoAPI: newSystemInfoAPI(),
|
||||
PingAPI: newPingAPI(),
|
||||
GCAPI: newGCAPI(),
|
||||
QuotaAPI: newQuotaAPI(),
|
||||
RetentionAPI: newRetentionAPI(),
|
||||
})
|
||||
if err != nil {
|
||||
|
67
src/server/v2.0/handler/model/quota.go
Normal file
67
src/server/v2.0/handler/model/quota.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/quota"
|
||||
"github.com/goharbor/harbor/src/pkg/quota/types"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
)
|
||||
|
||||
// Quota model
|
||||
type Quota struct {
|
||||
*quota.Quota
|
||||
}
|
||||
|
||||
// ToSwagger converts the quota to the swagger model
|
||||
func (q *Quota) ToSwagger(ctx context.Context) *models.Quota {
|
||||
if q.Quota == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
hard, err := q.GetHard()
|
||||
if err != nil {
|
||||
fields := log.Fields{"quota_id": q.ID, "error": err}
|
||||
log.G(ctx).WithFields(fields).Warningf("failed to get hard from quota")
|
||||
|
||||
hard = types.ResourceList{}
|
||||
}
|
||||
|
||||
used, err := q.GetUsed()
|
||||
if err != nil {
|
||||
fields := log.Fields{"quota_id": q.ID, "error": err}
|
||||
log.G(ctx).WithFields(fields).Warningf("failed to get used from quota")
|
||||
|
||||
used = types.ResourceList{}
|
||||
}
|
||||
|
||||
return &models.Quota{
|
||||
ID: q.ID,
|
||||
Ref: q.Ref,
|
||||
Hard: hard,
|
||||
Used: used,
|
||||
CreationTime: strfmt.DateTime(q.CreationTime),
|
||||
UpdateTime: strfmt.DateTime(q.UpdateTime),
|
||||
}
|
||||
}
|
||||
|
||||
// NewQuota new quota instance
|
||||
func NewQuota(quota *quota.Quota) *Quota {
|
||||
return &Quota{Quota: quota}
|
||||
}
|
@ -1,9 +1,22 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/controller/retention"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -19,6 +32,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/project"
|
||||
"github.com/goharbor/harbor/src/controller/quota"
|
||||
"github.com/goharbor/harbor/src/controller/repository"
|
||||
"github.com/goharbor/harbor/src/controller/retention"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
@ -606,10 +620,10 @@ func getProjectQuotaSummary(ctx context.Context, p *project.Project, summary *mo
|
||||
|
||||
summary.Quota = &models.ProjectSummaryQuota{}
|
||||
if hard, err := q.GetHard(); err == nil {
|
||||
lib.JSONCopy(&summary.Quota.Hard, hard)
|
||||
summary.Quota.Hard = hard
|
||||
}
|
||||
if used, err := q.GetUsed(); err == nil {
|
||||
lib.JSONCopy(&summary.Quota.Used, used)
|
||||
summary.Quota.Used = used
|
||||
}
|
||||
}
|
||||
|
||||
|
116
src/server/v2.0/handler/quota.go
Normal file
116
src/server/v2.0/handler/quota.go
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/quota"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/quota"
|
||||
)
|
||||
|
||||
func newQuotaAPI() *quotaAPI {
|
||||
return "aAPI{
|
||||
quotaCtl: quota.Ctl,
|
||||
}
|
||||
}
|
||||
|
||||
type quotaAPI struct {
|
||||
BaseAPI
|
||||
quotaCtl quota.Controller
|
||||
}
|
||||
|
||||
func (qa *quotaAPI) GetQuota(ctx context.Context, params operation.GetQuotaParams) middleware.Responder {
|
||||
if err := qa.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceQuota); err != nil {
|
||||
return qa.SendError(ctx, err)
|
||||
}
|
||||
|
||||
quota, err := qa.quotaCtl.Get(ctx, params.ID, quota.WithReferenceObject())
|
||||
if err != nil {
|
||||
return qa.SendError(ctx, err)
|
||||
|
||||
}
|
||||
return operation.NewGetQuotaOK().WithPayload(model.NewQuota(quota).ToSwagger(ctx))
|
||||
}
|
||||
|
||||
func (qa *quotaAPI) ListQuotas(ctx context.Context, params operation.ListQuotasParams) middleware.Responder {
|
||||
if err := qa.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceQuota); err != nil {
|
||||
return qa.SendError(ctx, err)
|
||||
}
|
||||
|
||||
query := &q.Query{
|
||||
Keywords: q.KeyWords{
|
||||
"reference": lib.StringValue(params.Reference),
|
||||
"reference_id": lib.StringValue(params.ReferenceID),
|
||||
},
|
||||
PageNumber: *params.Page,
|
||||
PageSize: *params.PageSize,
|
||||
Sorting: lib.StringValue(params.Sort),
|
||||
}
|
||||
|
||||
total, err := qa.quotaCtl.Count(ctx, query)
|
||||
if err != nil {
|
||||
return qa.SendError(ctx, err)
|
||||
}
|
||||
|
||||
quotas, err := qa.quotaCtl.List(ctx, query, quota.WithReferenceObject())
|
||||
if err != nil {
|
||||
return qa.SendError(ctx, err)
|
||||
}
|
||||
|
||||
payload := make([]*models.Quota, len(quotas))
|
||||
for i, quota := range quotas {
|
||||
payload[i] = model.NewQuota(quota).ToSwagger(ctx)
|
||||
}
|
||||
|
||||
return operation.NewListQuotasOK().
|
||||
WithXTotalCount(total).
|
||||
WithLink(qa.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
||||
WithPayload(payload)
|
||||
}
|
||||
|
||||
func (qa *quotaAPI) UpdateQuota(ctx context.Context, params operation.UpdateQuotaParams) middleware.Responder {
|
||||
if err := qa.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceQuota); err != nil {
|
||||
return qa.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if params.Hard == nil || len(params.Hard.Hard) == 0 {
|
||||
return qa.SendError(ctx, errors.BadRequestError(nil).WithMessage("hard required in body"))
|
||||
}
|
||||
|
||||
q, err := qa.quotaCtl.Get(ctx, params.ID)
|
||||
if err != nil {
|
||||
return qa.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := quota.Validate(ctx, q.Reference, params.Hard.Hard); err != nil {
|
||||
return qa.SendError(ctx, errors.BadRequestError(nil).WithMessage(err.Error()))
|
||||
}
|
||||
|
||||
q.SetHard(params.Hard.Hard)
|
||||
|
||||
if err := qa.quotaCtl.Update(ctx, q); err != nil {
|
||||
return qa.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewUpdateQuotaOK()
|
||||
}
|
283
src/server/v2.0/handler/quota_test.go
Normal file
283
src/server/v2.0/handler/quota_test.go
Normal file
@ -0,0 +1,283 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/quota"
|
||||
"github.com/goharbor/harbor/src/pkg/quota/types"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/restapi"
|
||||
quotatesting "github.com/goharbor/harbor/src/testing/controller/quota"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
htesting "github.com/goharbor/harbor/src/testing/server/v2.0/handler"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type QuotaTestSuite struct {
|
||||
htesting.Suite
|
||||
|
||||
quotaCtl *quotatesting.Controller
|
||||
quota *quota.Quota
|
||||
}
|
||||
|
||||
func (suite *QuotaTestSuite) SetupSuite() {
|
||||
suite.quota = "a.Quota{
|
||||
ID: 1,
|
||||
Reference: "project",
|
||||
ReferenceID: "1",
|
||||
Hard: `{"storage": 100}`,
|
||||
Used: `{"storage": 1000}`,
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
}
|
||||
|
||||
suite.quotaCtl = "atesting.Controller{}
|
||||
|
||||
suite.Config = &restapi.Config{
|
||||
QuotaAPI: "aAPI{
|
||||
quotaCtl: suite.quotaCtl,
|
||||
},
|
||||
}
|
||||
|
||||
suite.Suite.SetupSuite()
|
||||
}
|
||||
|
||||
func (suite *QuotaTestSuite) TestAuthorization() {
|
||||
newBody := func(body interface{}) io.Reader {
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(body)
|
||||
suite.Require().NoError(err)
|
||||
return bytes.NewBuffer(buf)
|
||||
}
|
||||
|
||||
quota := models.QuotaUpdateReq{
|
||||
Hard: types.ResourceList{"storage": 1000},
|
||||
}
|
||||
|
||||
reqs := []struct {
|
||||
method string
|
||||
url string
|
||||
body interface{}
|
||||
}{
|
||||
{http.MethodGet, "/quotas/1", nil},
|
||||
{http.MethodGet, "/quotas", nil},
|
||||
{http.MethodPut, "/quotas/1", quota},
|
||||
}
|
||||
|
||||
for _, req := range reqs {
|
||||
{
|
||||
// authorized required
|
||||
suite.Security.On("IsAuthenticated").Return(false).Once()
|
||||
|
||||
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
|
||||
suite.NoError(err)
|
||||
suite.Equal(401, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// permission required
|
||||
suite.Security.On("IsAuthenticated").Return(true).Once()
|
||||
suite.Security.On("GetUsername").Return("username").Once()
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once()
|
||||
|
||||
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
|
||||
suite.NoError(err)
|
||||
suite.Equal(403, res.StatusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *QuotaTestSuite) TestGetQuota() {
|
||||
times := 3
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// get quota failed
|
||||
mock.OnAnything(suite.quotaCtl, "Get").Return(nil, fmt.Errorf("failed to get quota")).Once()
|
||||
|
||||
res, err := suite.Get("/quotas/1")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// quota not found
|
||||
mock.OnAnything(suite.quotaCtl, "Get").Return(nil, errors.NotFoundError(nil)).Once()
|
||||
|
||||
var quota map[string]interface{}
|
||||
res, err := suite.GetJSON("/quotas/1", "a)
|
||||
suite.NoError(err)
|
||||
suite.Equal(404, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// quota found
|
||||
mock.OnAnything(suite.quotaCtl, "Get").Return(suite.quota, nil).Once()
|
||||
|
||||
var quota map[string]interface{}
|
||||
res, err := suite.GetJSON("/quotas/1", "a)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Equal(float64(1), quota["id"])
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *QuotaTestSuite) TestListQuotas() {
|
||||
times := 5
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// list quotas failed
|
||||
mock.OnAnything(suite.quotaCtl, "Count").Return(int64(0), fmt.Errorf("failed to count quotas")).Once()
|
||||
|
||||
res, err := suite.Get("/quotas")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// list quotas failed
|
||||
mock.OnAnything(suite.quotaCtl, "Count").Return(int64(1), nil).Once()
|
||||
mock.OnAnything(suite.quotaCtl, "List").Return(nil, fmt.Errorf("failed to list quotas")).Once()
|
||||
|
||||
res, err := suite.Get("/quotas")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// quotas not found
|
||||
mock.OnAnything(suite.quotaCtl, "Count").Return(int64(0), nil).Once()
|
||||
mock.OnAnything(suite.quotaCtl, "List").Return(nil, nil).Once()
|
||||
|
||||
var quotas []interface{}
|
||||
res, err := suite.GetJSON("/quotas", "as)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Len(quotas, 0)
|
||||
}
|
||||
|
||||
{
|
||||
// quotas found
|
||||
mock.OnAnything(suite.quotaCtl, "Count").Return(int64(3), nil).Once()
|
||||
mock.OnAnything(suite.quotaCtl, "List").Return([]*quota.Quota{suite.quota}, nil).Once()
|
||||
|
||||
var quotas []interface{}
|
||||
res, err := suite.GetJSON("/quotas?page_size=1&page=2", "as)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Len(quotas, 1)
|
||||
suite.Equal("3", res.Header.Get("X-Total-Count"))
|
||||
suite.Contains(res.Header, "Link")
|
||||
suite.Equal(`</api/v2.0/quotas?page=1&page_size=1>; rel="prev" , </api/v2.0/quotas?page=3&page_size=1>; rel="next"`, res.Header.Get("Link"))
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *QuotaTestSuite) TestUpdateQuota() {
|
||||
times := 6
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// update quota no body
|
||||
res, err := suite.Put("/quotas/1", nil)
|
||||
suite.NoError(err)
|
||||
suite.Equal(422, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// update quota with empty hard
|
||||
quota := models.QuotaUpdateReq{
|
||||
Hard: types.ResourceList{},
|
||||
}
|
||||
|
||||
res, err := suite.PutJSON("/quotas/1", quota)
|
||||
suite.NoError(err)
|
||||
suite.Equal(400, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// quota not found
|
||||
mock.OnAnything(suite.quotaCtl, "Get").Return(nil, errors.NotFoundError(nil)).Once()
|
||||
|
||||
quota := models.QuotaUpdateReq{
|
||||
Hard: types.ResourceList{"storage": 1000},
|
||||
}
|
||||
|
||||
res, err := suite.PutJSON("/quotas/1", quota)
|
||||
suite.NoError(err)
|
||||
suite.Equal(404, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// update quota
|
||||
mock.OnAnything(suite.quotaCtl, "Get").Return(suite.quota, nil).Once()
|
||||
mock.OnAnything(suite.quotaCtl, "Update").Return(nil).Once()
|
||||
|
||||
quota := models.QuotaUpdateReq{
|
||||
Hard: types.ResourceList{"storage": 1000},
|
||||
}
|
||||
|
||||
res, err := suite.PutJSON("/quotas/1", quota)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// update quota failed
|
||||
mock.OnAnything(suite.quotaCtl, "Get").Return(suite.quota, nil).Once()
|
||||
mock.OnAnything(suite.quotaCtl, "Update").Return(fmt.Errorf("failed to update the quota")).Once()
|
||||
|
||||
quota := models.QuotaUpdateReq{
|
||||
Hard: types.ResourceList{"storage": 1000},
|
||||
}
|
||||
|
||||
res, err := suite.PutJSON("/quotas/1", quota)
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// resource not support
|
||||
mock.OnAnything(suite.quotaCtl, "Get").Return(suite.quota, nil).Once()
|
||||
mock.OnAnything(suite.quotaCtl, "Update").Return(nil).Once()
|
||||
|
||||
quota := models.QuotaUpdateReq{
|
||||
Hard: types.ResourceList{"size": 1000},
|
||||
}
|
||||
|
||||
res, err := suite.PutJSON("/quotas/1", quota)
|
||||
suite.NoError(err)
|
||||
suite.Equal(400, res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuotaTestSuite(t *testing.T) {
|
||||
suite.Run(t, &QuotaTestSuite{})
|
||||
}
|
@ -42,9 +42,6 @@ func registerLegacyRoutes() {
|
||||
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/?:name", &api.MetadataAPI{}, "get:Get")
|
||||
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post")
|
||||
|
||||
beego.Router("/api/"+version+"/quotas", &api.QuotaAPI{}, "get:List")
|
||||
beego.Router("/api/"+version+"/quotas/:id([0-9]+)", &api.QuotaAPI{}, "get:Get;put:Put")
|
||||
|
||||
beego.Router("/api/"+version+"/system/CVEAllowlist", &api.SysCVEAllowlistAPI{}, "get:Get;put:Put")
|
||||
beego.Router("/api/"+version+"/system/oidc/ping", &api.OIDCAPI{}, "post:Ping")
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Harbor API
|
||||
*
|
||||
* These APIs provide services for manipulating Harbor project.
|
||||
*
|
||||
* OpenAPI spec version: 0.3.0
|
||||
*
|
||||
* Generated by: https://github.com/swagger-api/swagger-codegen.git
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package apilib
|
||||
|
||||
// QuotaQuery query for quota
|
||||
type QuotaQuery struct {
|
||||
Reference string `url:"reference,omitempty"`
|
||||
ReferenceID string `url:"reference_id,omitempty"`
|
||||
Page int64 `url:"page,omitempty"`
|
||||
PageSize int64 `url:"page_size,omitempty"`
|
||||
}
|
||||
|
||||
// Quota ...
|
||||
type Quota struct {
|
||||
ID int `json:"id"`
|
||||
Ref map[string]interface{} `json:"ref"`
|
||||
Hard map[string]int64 `json:"hard"`
|
||||
Used map[string]int64 `json:"used"`
|
||||
}
|
@ -28,7 +28,7 @@ def get_endpoint():
|
||||
|
||||
def _create_client(server, credential, debug, api_type="products"):
|
||||
cfg = None
|
||||
if api_type in ('projectv2', 'artifact', 'repository', 'scan', 'scanall', 'preheat', 'replication', 'robot', 'gc', 'retention'):
|
||||
if api_type in ('projectv2', 'artifact', 'repository', 'scan', 'scanall', 'preheat', 'quota', 'replication', 'robot', 'gc', 'retention'):
|
||||
cfg = v2_swagger_client.Configuration()
|
||||
else:
|
||||
cfg = swagger_client.Configuration()
|
||||
@ -56,6 +56,7 @@ def _create_client(server, credential, debug, api_type="products"):
|
||||
"projectv2": v2_swagger_client.ProjectApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"artifact": v2_swagger_client.ArtifactApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"preheat": v2_swagger_client.PreheatApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"quota": v2_swagger_client.QuotaApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"repository": v2_swagger_client.RepositoryApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"scan": v2_swagger_client.ScanApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"scanall": v2_swagger_client.ScanAllApi(v2_swagger_client.ApiClient(cfg)),
|
||||
|
@ -137,7 +137,7 @@ class System(base.Base):
|
||||
params['reference'] = reference
|
||||
params['reference_id'] = reference_id
|
||||
|
||||
client = self._get_client(**kwargs)
|
||||
data, status_code, _ = client.quotas_get_with_http_info(**params)
|
||||
client = self._get_client(api_type='quota', **kwargs)
|
||||
data, status_code, _ = client.list_quotas_with_http_info(**params)
|
||||
base._assert_status_code(200, status_code)
|
||||
return data
|
||||
|
Loading…
Reference in New Issue
Block a user