mirror of
https://github.com/goharbor/harbor.git
synced 2024-06-23 21:35:04 +02:00
Refactor project metadata API
Refactor project metadata API Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
0b9cad33b1
commit
311d6336a7
|
@ -19,151 +19,6 @@ securityDefinitions:
|
||||||
security:
|
security:
|
||||||
- basicAuth: []
|
- basicAuth: []
|
||||||
paths:
|
paths:
|
||||||
'/projects/{project_id}/metadatas':
|
|
||||||
get:
|
|
||||||
summary: Get project metadata.
|
|
||||||
description: |
|
|
||||||
This endpoint returns metadata of the project specified by project ID.
|
|
||||||
parameters:
|
|
||||||
- name: project_id
|
|
||||||
in: path
|
|
||||||
description: The ID of project.
|
|
||||||
required: true
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
tags:
|
|
||||||
- Products
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Get metadata successfully.
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/ProjectMetadata'
|
|
||||||
'401':
|
|
||||||
description: User need to login first.
|
|
||||||
'500':
|
|
||||||
description: Internal server errors.
|
|
||||||
post:
|
|
||||||
summary: Add metadata for the project.
|
|
||||||
description: |
|
|
||||||
This endpoint is aimed to add metadata of a project.
|
|
||||||
parameters:
|
|
||||||
- name: project_id
|
|
||||||
in: path
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
required: true
|
|
||||||
description: Selected project ID.
|
|
||||||
- name: metadata
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/ProjectMetadata'
|
|
||||||
description: The metadata of project.
|
|
||||||
tags:
|
|
||||||
- Products
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Add metadata successfully.
|
|
||||||
'400':
|
|
||||||
description: Invalid request.
|
|
||||||
'401':
|
|
||||||
description: User need to log in first.
|
|
||||||
'403':
|
|
||||||
description: User does not have permission to the project.
|
|
||||||
'404':
|
|
||||||
description: Project ID does not exist.
|
|
||||||
'415':
|
|
||||||
$ref: '#/responses/UnsupportedMediaType'
|
|
||||||
'500':
|
|
||||||
description: Internal server errors.
|
|
||||||
'/projects/{project_id}/metadatas/{meta_name}':
|
|
||||||
get:
|
|
||||||
summary: Get project metadata
|
|
||||||
description: |
|
|
||||||
This endpoint returns specified metadata of a project.
|
|
||||||
parameters:
|
|
||||||
- name: project_id
|
|
||||||
in: path
|
|
||||||
description: Project ID for filtering results.
|
|
||||||
required: true
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
- name: meta_name
|
|
||||||
in: path
|
|
||||||
description: The name of metadat.
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
tags:
|
|
||||||
- Products
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Get metadata successfully.
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/ProjectMetadata'
|
|
||||||
'401':
|
|
||||||
description: User need to log in first.
|
|
||||||
'500':
|
|
||||||
description: Internal server errors.
|
|
||||||
put:
|
|
||||||
summary: Update metadata of a project.
|
|
||||||
description: |
|
|
||||||
This endpoint is aimed to update the metadata of a project.
|
|
||||||
parameters:
|
|
||||||
- name: project_id
|
|
||||||
in: path
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
required: true
|
|
||||||
description: The ID of project.
|
|
||||||
- name: meta_name
|
|
||||||
in: path
|
|
||||||
description: The name of metadat.
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
tags:
|
|
||||||
- Products
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Updated metadata successfully.
|
|
||||||
'400':
|
|
||||||
description: Invalid request.
|
|
||||||
'401':
|
|
||||||
description: User need to log in first.
|
|
||||||
'403':
|
|
||||||
description: User does not have permission to the project.
|
|
||||||
'404':
|
|
||||||
description: Project or metadata does not exist.
|
|
||||||
'500':
|
|
||||||
description: Internal server errors.
|
|
||||||
delete:
|
|
||||||
summary: Delete metadata of a project
|
|
||||||
description: |
|
|
||||||
This endpoint is aimed to delete metadata of a project.
|
|
||||||
parameters:
|
|
||||||
- name: project_id
|
|
||||||
in: path
|
|
||||||
description: The ID of project.
|
|
||||||
required: true
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
- name: meta_name
|
|
||||||
in: path
|
|
||||||
description: The name of metadat.
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
tags:
|
|
||||||
- Products
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Metadata is deleted successfully.
|
|
||||||
'400':
|
|
||||||
description: Invalid requst.
|
|
||||||
'403':
|
|
||||||
description: User need to log in first.
|
|
||||||
'404':
|
|
||||||
description: Project or metadata does not exist.
|
|
||||||
'500':
|
|
||||||
description: Internal server errors.
|
|
||||||
/email/ping:
|
/email/ping:
|
||||||
post:
|
post:
|
||||||
summary: Test connection and authentication with email server.
|
summary: Test connection and authentication with email server.
|
||||||
|
@ -376,28 +231,6 @@ responses:
|
||||||
InternalServerError:
|
InternalServerError:
|
||||||
description: 'Internal Server Error'
|
description: 'Internal Server Error'
|
||||||
definitions:
|
definitions:
|
||||||
ProjectMetadata:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
public:
|
|
||||||
type: string
|
|
||||||
description: 'The public status of the project. The valid values are "true", "false".'
|
|
||||||
enable_content_trust:
|
|
||||||
type: string
|
|
||||||
description: 'Whether content trust is enabled or not. If it is enabled, user can''t pull unsigned images from this project. The valid values are "true", "false".'
|
|
||||||
prevent_vul:
|
|
||||||
type: string
|
|
||||||
description: 'Whether prevent the vulnerable images from running. The valid values are "true", "false".'
|
|
||||||
severity:
|
|
||||||
type: string
|
|
||||||
description: 'If the vulnerability is high than severity defined here, the images can''t be pulled. The valid values are "none", "low", "medium", "high", "critical".'
|
|
||||||
auto_scan:
|
|
||||||
type: string
|
|
||||||
description: 'Whether scan images automatically when pushing. The valid values are "true", "false".'
|
|
||||||
reuse_sys_cve_allowlist:
|
|
||||||
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.'
|
|
||||||
Role:
|
Role:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -641,6 +641,164 @@ paths:
|
||||||
$ref: '#/responses/403'
|
$ref: '#/responses/403'
|
||||||
'500':
|
'500':
|
||||||
$ref: '#/responses/500'
|
$ref: '#/responses/500'
|
||||||
|
'/projects/{project_name_or_id}/metadatas/':
|
||||||
|
get:
|
||||||
|
summary: Get the metadata of the specific project
|
||||||
|
description: Get the metadata of the specific project
|
||||||
|
operationId: listProjectMetadatas
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- $ref: '#/parameters/isResourceName'
|
||||||
|
- $ref: '#/parameters/projectNameOrId'
|
||||||
|
tags:
|
||||||
|
- projectMetadata
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
'400':
|
||||||
|
$ref: '#/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'404':
|
||||||
|
$ref: '#/responses/404'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
|
post:
|
||||||
|
summary: Add metadata for the specific project
|
||||||
|
operationId: addProjectMetadatas
|
||||||
|
description: Add metadata for the specific project
|
||||||
|
tags:
|
||||||
|
- projectMetadata
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- $ref: '#/parameters/isResourceName'
|
||||||
|
- $ref: '#/parameters/projectNameOrId'
|
||||||
|
- name: metadata
|
||||||
|
in: body
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/responses/200'
|
||||||
|
'400':
|
||||||
|
$ref: '#/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'404':
|
||||||
|
$ref: '#/responses/404'
|
||||||
|
'409':
|
||||||
|
$ref: '#/responses/409'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
|
'/projects/{project_name_or_id}/metadatas/{meta_name}':
|
||||||
|
get:
|
||||||
|
summary: Get the specific metadata of the specific project
|
||||||
|
description: Get the specific metadata of the specific project
|
||||||
|
operationId: getProjectMetadata
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- $ref: '#/parameters/isResourceName'
|
||||||
|
- $ref: '#/parameters/projectNameOrId'
|
||||||
|
- name: meta_name
|
||||||
|
in: path
|
||||||
|
description: The name of metadata.
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
tags:
|
||||||
|
- projectMetadata
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
'400':
|
||||||
|
$ref: '#/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'404':
|
||||||
|
$ref: '#/responses/404'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
|
put:
|
||||||
|
summary: Update the specific metadata for the specific project
|
||||||
|
description: Update the specific metadata for the specific project
|
||||||
|
operationId: updateProjectMetadata
|
||||||
|
tags:
|
||||||
|
- projectMetadata
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- $ref: '#/parameters/isResourceName'
|
||||||
|
- $ref: '#/parameters/projectNameOrId'
|
||||||
|
- name: meta_name
|
||||||
|
in: path
|
||||||
|
description: The name of metadata.
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- name: metadata
|
||||||
|
in: body
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/responses/200'
|
||||||
|
'400':
|
||||||
|
$ref: '#/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'404':
|
||||||
|
$ref: '#/responses/404'
|
||||||
|
'409':
|
||||||
|
$ref: '#/responses/409'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
|
delete:
|
||||||
|
summary: Delete the specific metadata for the specific project
|
||||||
|
description: Delete the specific metadata for the specific project
|
||||||
|
operationId: deleteProjectMetadata
|
||||||
|
tags:
|
||||||
|
- projectMetadata
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- $ref: '#/parameters/isResourceName'
|
||||||
|
- $ref: '#/parameters/projectNameOrId'
|
||||||
|
- name: meta_name
|
||||||
|
in: path
|
||||||
|
description: The name of metadata.
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/responses/200'
|
||||||
|
'400':
|
||||||
|
$ref: '#/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'404':
|
||||||
|
$ref: '#/responses/404'
|
||||||
|
'409':
|
||||||
|
$ref: '#/responses/409'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
/repositories:
|
/repositories:
|
||||||
get:
|
get:
|
||||||
summary: List all authorized repositories
|
summary: List all authorized repositories
|
||||||
|
|
65
src/controller/project/metadata/controller.go
Normal file
65
src/controller/project/metadata/controller.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// 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 metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/project/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ctl is the global controller instance
|
||||||
|
var Ctl = NewController()
|
||||||
|
|
||||||
|
// Controller defines the operations that a project metadata controller should implement
|
||||||
|
type Controller interface {
|
||||||
|
// Add metadatas for project specified by projectID
|
||||||
|
Add(ctx context.Context, projectID int64, meta map[string]string) error
|
||||||
|
|
||||||
|
// Delete metadatas whose keys are specified in parameter meta, if it is absent, delete all
|
||||||
|
Delete(ctx context.Context, projectID int64, meta ...string) error
|
||||||
|
|
||||||
|
// Update metadatas
|
||||||
|
Update(ctx context.Context, projectID int64, meta map[string]string) error
|
||||||
|
|
||||||
|
// Get metadatas whose keys are specified in parameter meta, if it is absent, get all
|
||||||
|
Get(ctx context.Context, projectID int64, meta ...string) (map[string]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewController creates an instance of the default controller
|
||||||
|
func NewController() Controller {
|
||||||
|
return &controller{
|
||||||
|
mgr: metadata.Mgr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type controller struct {
|
||||||
|
mgr metadata.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) Add(ctx context.Context, projectID int64, meta map[string]string) error {
|
||||||
|
return c.mgr.Add(ctx, projectID, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) Delete(ctx context.Context, projectID int64, meta ...string) error {
|
||||||
|
return c.mgr.Delete(ctx, projectID, meta...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) Update(ctx context.Context, projectID int64, meta map[string]string) error {
|
||||||
|
return c.mgr.Update(ctx, projectID, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) Get(ctx context.Context, projectID int64, meta ...string) (map[string]string, error) {
|
||||||
|
return c.mgr.Get(ctx, projectID, meta...)
|
||||||
|
}
|
|
@ -93,9 +93,6 @@ func init() {
|
||||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||||
beego.TestBeegoInit(apppath)
|
beego.TestBeegoInit(apppath)
|
||||||
|
|
||||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/?:name", &MetadataAPI{}, "get:Get")
|
|
||||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/", &MetadataAPI{}, "post:Post")
|
|
||||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/:name", &MetadataAPI{}, "put:Put;delete:Delete")
|
|
||||||
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
|
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
|
||||||
|
|
||||||
// Charts are controlled under projects
|
// Charts are controlled under projects
|
||||||
|
|
|
@ -1,233 +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"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
|
||||||
"github.com/goharbor/harbor/src/lib/errors"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/project/metadata"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MetadataAPI ...
|
|
||||||
type MetadataAPI struct {
|
|
||||||
BaseController
|
|
||||||
metaMgr metadata.Manager
|
|
||||||
project *models.Project
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare ...
|
|
||||||
func (m *MetadataAPI) Prepare() {
|
|
||||||
m.BaseController.Prepare()
|
|
||||||
|
|
||||||
m.metaMgr = metadata.Mgr
|
|
||||||
|
|
||||||
id, err := m.GetInt64FromPath(":id")
|
|
||||||
if err != nil || id <= 0 {
|
|
||||||
text := "invalid project ID: "
|
|
||||||
if err != nil {
|
|
||||||
text += err.Error()
|
|
||||||
} else {
|
|
||||||
text += fmt.Sprintf("%d", id)
|
|
||||||
}
|
|
||||||
m.SendBadRequestError(errors.New(text))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
project, err := m.ProjectCtl.Get(m.Context(), id)
|
|
||||||
if err != nil {
|
|
||||||
if errors.IsNotFoundErr(err) {
|
|
||||||
m.handleProjectNotFound(id)
|
|
||||||
} else {
|
|
||||||
m.ParseAndHandleError(fmt.Sprintf("failed to get project %d", id), err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.project = project
|
|
||||||
|
|
||||||
name := m.GetStringFromPath(":name")
|
|
||||||
if len(name) > 0 {
|
|
||||||
m.name = name
|
|
||||||
metas, err := m.metaMgr.Get(m.Context(), project.ProjectID, name)
|
|
||||||
if err != nil {
|
|
||||||
m.SendInternalServerError(fmt.Errorf("failed to get metadata of project %d: %v", project.ProjectID, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(metas) == 0 {
|
|
||||||
m.SendNotFoundError(fmt.Errorf("metadata %s of project %d not found", name, project.ProjectID))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetadataAPI) requireAccess(action rbac.Action) bool {
|
|
||||||
return m.RequireProjectAccess(m.project.ProjectID, action, rbac.ResourceMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get ...
|
|
||||||
func (m *MetadataAPI) Get() {
|
|
||||||
if !m.requireAccess(rbac.ActionRead) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var metas map[string]string
|
|
||||||
var err error
|
|
||||||
if len(m.name) > 0 {
|
|
||||||
metas, err = m.metaMgr.Get(m.Context(), m.project.ProjectID, m.name)
|
|
||||||
} else {
|
|
||||||
metas, err = m.metaMgr.Get(m.Context(), m.project.ProjectID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
m.SendInternalServerError(fmt.Errorf("failed to get metadata %s of project %d: %v", m.name, m.project.ProjectID, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.Data["json"] = metas
|
|
||||||
m.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post ...
|
|
||||||
func (m *MetadataAPI) Post() {
|
|
||||||
if !m.requireAccess(rbac.ActionCreate) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var metas map[string]string
|
|
||||||
if err := m.DecodeJSONReq(&metas); err != nil {
|
|
||||||
m.SendBadRequestError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ms, err := validateProjectMetadata(metas)
|
|
||||||
if err != nil {
|
|
||||||
m.SendBadRequestError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ms) != 1 {
|
|
||||||
m.SendBadRequestError(errors.New("invalid request: has no valid key/value pairs or has more than one valid key/value pairs"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := reflect.ValueOf(ms).MapKeys()
|
|
||||||
mts, err := m.metaMgr.Get(m.Context(), m.project.ProjectID, keys[0].String())
|
|
||||||
if err != nil {
|
|
||||||
m.SendInternalServerError(fmt.Errorf("failed to get metadata for project %d: %v", m.project.ProjectID, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mts) != 0 {
|
|
||||||
m.SendConflictError(errors.New("conflict metadata"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.metaMgr.Add(m.Context(), m.project.ProjectID, ms); err != nil {
|
|
||||||
m.SendInternalServerError(fmt.Errorf("failed to create metadata for project %d: %v", m.project.ProjectID, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Ctx.ResponseWriter.WriteHeader(http.StatusCreated)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put ...
|
|
||||||
func (m *MetadataAPI) Put() {
|
|
||||||
if !m.requireAccess(rbac.ActionUpdate) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var metas map[string]string
|
|
||||||
if err := m.DecodeJSONReq(&metas); err != nil {
|
|
||||||
m.SendBadRequestError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
meta, exist := metas[m.name]
|
|
||||||
if !exist {
|
|
||||||
m.SendBadRequestError(fmt.Errorf("must contains key %s", m.name))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ms, err := validateProjectMetadata(map[string]string{
|
|
||||||
m.name: meta,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
m.SendBadRequestError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.metaMgr.Update(m.Context(), m.project.ProjectID, map[string]string{
|
|
||||||
m.name: ms[m.name],
|
|
||||||
}); err != nil {
|
|
||||||
m.SendInternalServerError(fmt.Errorf("failed to update metadata %s of project %d: %v", m.name, m.project.ProjectID, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete ...
|
|
||||||
func (m *MetadataAPI) Delete() {
|
|
||||||
if !m.requireAccess(rbac.ActionDelete) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.metaMgr.Delete(m.Context(), m.project.ProjectID, m.name); err != nil {
|
|
||||||
m.SendInternalServerError(fmt.Errorf("failed to delete metadata %s of project %d: %v", m.name, m.project.ProjectID, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate metas and return a new map which contains the valid key/value pairs only
|
|
||||||
func validateProjectMetadata(metas map[string]string) (map[string]string, error) {
|
|
||||||
if len(metas) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
boolMetas := []string{
|
|
||||||
models.ProMetaPublic,
|
|
||||||
models.ProMetaEnableContentTrust,
|
|
||||||
models.ProMetaPreventVul,
|
|
||||||
models.ProMetaAutoScan}
|
|
||||||
|
|
||||||
for _, boolMeta := range boolMetas {
|
|
||||||
value, exist := metas[boolMeta]
|
|
||||||
if exist {
|
|
||||||
b, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse %s to bool: %v", value, err)
|
|
||||||
}
|
|
||||||
metas[boolMeta] = strconv.FormatBool(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value, exist := metas[models.ProMetaSeverity]
|
|
||||||
if exist {
|
|
||||||
severity := vuln.ParseSeverityVersion3(strings.ToLower(value))
|
|
||||||
if severity == vuln.Unknown {
|
|
||||||
return nil, fmt.Errorf("invalid severity %s", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
metas[models.ProMetaSeverity] = strings.ToLower(severity.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return metas, nil
|
|
||||||
}
|
|
|
@ -1,112 +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 (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestValidateProjectMetadata(t *testing.T) {
|
|
||||||
var metas map[string]string
|
|
||||||
|
|
||||||
// nil metas
|
|
||||||
ms, err := validateProjectMetadata(metas)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Nil(t, ms)
|
|
||||||
|
|
||||||
// valid key, invalid value(bool)
|
|
||||||
metas = map[string]string{
|
|
||||||
models.ProMetaPublic: "invalid_value",
|
|
||||||
}
|
|
||||||
ms, err = validateProjectMetadata(metas)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
|
|
||||||
// valid key/value(bool)
|
|
||||||
metas = map[string]string{
|
|
||||||
models.ProMetaPublic: "1",
|
|
||||||
}
|
|
||||||
ms, err = validateProjectMetadata(metas)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, "true", ms[models.ProMetaPublic])
|
|
||||||
|
|
||||||
// valid key, invalid value(string)
|
|
||||||
metas = map[string]string{
|
|
||||||
models.ProMetaSeverity: "invalid_value",
|
|
||||||
}
|
|
||||||
ms, err = validateProjectMetadata(metas)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
|
|
||||||
// valid key, valid value(string)
|
|
||||||
metas = map[string]string{
|
|
||||||
models.ProMetaSeverity: "High",
|
|
||||||
}
|
|
||||||
ms, err = validateProjectMetadata(metas)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, "high", ms[models.ProMetaSeverity])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMetaAPI(t *testing.T) {
|
|
||||||
client := newHarborAPI()
|
|
||||||
|
|
||||||
// non-exist project, it should return 401 if user is not logged in.
|
|
||||||
code, _, err := client.PostMeta(*unknownUsr, int64(1000), nil)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, http.StatusUnauthorized, code)
|
|
||||||
|
|
||||||
// non-login
|
|
||||||
code, _, err = client.PostMeta(*unknownUsr, int64(1), nil)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, http.StatusUnauthorized, code)
|
|
||||||
|
|
||||||
// test post
|
|
||||||
code, _, err = client.PostMeta(*admin, int64(1), map[string]string{
|
|
||||||
models.ProMetaAutoScan: "true",
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, http.StatusCreated, code)
|
|
||||||
|
|
||||||
// test get
|
|
||||||
code, metas, err := client.GetMeta(*admin, int64(1), models.ProMetaAutoScan)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, http.StatusOK, code)
|
|
||||||
assert.Equal(t, "true", metas[models.ProMetaAutoScan])
|
|
||||||
|
|
||||||
// test put
|
|
||||||
code, _, err = client.PutMeta(*admin, int64(1), models.ProMetaAutoScan,
|
|
||||||
map[string]string{
|
|
||||||
models.ProMetaAutoScan: "false",
|
|
||||||
})
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, http.StatusOK, code)
|
|
||||||
|
|
||||||
code, metas, err = client.GetMeta(*admin, int64(1), models.ProMetaAutoScan)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, http.StatusOK, code)
|
|
||||||
assert.Equal(t, "false", metas[models.ProMetaAutoScan])
|
|
||||||
|
|
||||||
// test delete
|
|
||||||
code, _, err = client.DeleteMeta(*admin, int64(1), models.ProMetaAutoScan)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, http.StatusOK, code)
|
|
||||||
|
|
||||||
code, metas, err = client.GetMeta(*admin, int64(1), models.ProMetaAutoScan)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, http.StatusNotFound, code)
|
|
||||||
}
|
|
|
@ -61,7 +61,14 @@ func (d *dao) Create(ctx context.Context, projectID int64, name, value string) (
|
||||||
UpdateTime: now,
|
UpdateTime: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
return o.Insert(md)
|
id, err := o.Insert(md)
|
||||||
|
if err != nil {
|
||||||
|
if e := orm.AsConflictError(err, "metadata %s already exists for project %d", name, projectID); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete delete metadata interfaces filtered the query
|
// Delete delete metadata interfaces filtered the query
|
||||||
|
|
|
@ -63,6 +63,7 @@ func New() http.Handler {
|
||||||
UserAPI: newUsersAPI(),
|
UserAPI: newUsersAPI(),
|
||||||
HealthAPI: newHealthAPI(),
|
HealthAPI: newHealthAPI(),
|
||||||
StatisticAPI: newStatisticAPI(),
|
StatisticAPI: newStatisticAPI(),
|
||||||
|
ProjectMetadataAPI: newProjectMetadaAPI(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
160
src/server/v2.0/handler/project_metadata.go
Normal file
160
src/server/v2.0/handler/project_metadata.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
// 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/models"
|
||||||
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
|
"github.com/goharbor/harbor/src/controller/project/metadata"
|
||||||
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||||
|
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/project_metadata"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newProjectMetadaAPI() *projectMetadataAPI {
|
||||||
|
return &projectMetadataAPI{
|
||||||
|
ctl: metadata.Ctl,
|
||||||
|
proCtl: project.Ctl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type projectMetadataAPI struct {
|
||||||
|
BaseAPI
|
||||||
|
ctl metadata.Controller
|
||||||
|
proCtl project.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *projectMetadataAPI) AddProjectMetadatas(ctx context.Context, params operation.AddProjectMetadatasParams) middleware.Responder {
|
||||||
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||||
|
if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionCreate, rbac.ResourceMetadata); err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
metadata := params.Metadata
|
||||||
|
metadata, err := p.validate(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
project, err := p.proCtl.Get(ctx, projectNameOrID)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
if err = p.ctl.Add(ctx, project.ProjectID, metadata); err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
return operation.NewAddProjectMetadatasOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *projectMetadataAPI) ListProjectMetadatas(ctx context.Context, params operation.ListProjectMetadatasParams) middleware.Responder {
|
||||||
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||||
|
if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionList, rbac.ResourceMetadata); err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
project, err := p.proCtl.Get(ctx, projectNameOrID)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
metadata, err := p.ctl.Get(ctx, project.ProjectID)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
return operation.NewListProjectMetadatasOK().WithPayload(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *projectMetadataAPI) DeleteProjectMetadata(ctx context.Context, params operation.DeleteProjectMetadataParams) middleware.Responder {
|
||||||
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||||
|
if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionDelete, rbac.ResourceMetadata); err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
project, err := p.proCtl.Get(ctx, projectNameOrID)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
if err = p.ctl.Delete(ctx, project.ProjectID, params.MetaName); err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
return operation.NewDeleteProjectMetadataOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *projectMetadataAPI) GetProjectMetadata(ctx context.Context, params operation.GetProjectMetadataParams) middleware.Responder {
|
||||||
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||||
|
if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead, rbac.ResourceMetadata); err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
project, err := p.proCtl.Get(ctx, projectNameOrID)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
metadata, err := p.ctl.Get(ctx, project.ProjectID, params.MetaName)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
return operation.NewGetProjectMetadataOK().WithPayload(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *projectMetadataAPI) UpdateProjectMetadata(ctx context.Context, params operation.UpdateProjectMetadataParams) middleware.Responder {
|
||||||
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||||
|
if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionUpdate, rbac.ResourceMetadata); err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
metadata := map[string]string{
|
||||||
|
params.MetaName: params.Metadata[params.MetaName],
|
||||||
|
}
|
||||||
|
metadata, err := p.validate(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
project, err := p.proCtl.Get(ctx, projectNameOrID)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
if err = p.ctl.Update(ctx, project.ProjectID, metadata); err != nil {
|
||||||
|
return p.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
return operation.NewUpdateProjectMetadataOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *projectMetadataAPI) validate(metas map[string]string) (map[string]string, error) {
|
||||||
|
if len(metas) != 1 {
|
||||||
|
return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("only allow one key/value pair")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, value := "", ""
|
||||||
|
for key, value = range metas {
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case models.ProMetaPublic, models.ProMetaEnableContentTrust,
|
||||||
|
models.ProMetaPreventVul, models.ProMetaAutoScan:
|
||||||
|
v, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid value: %s", value)
|
||||||
|
}
|
||||||
|
metas[key] = strconv.FormatBool(v)
|
||||||
|
case models.ProMetaSeverity:
|
||||||
|
severity := vuln.ParseSeverityVersion3(strings.ToLower(value))
|
||||||
|
if severity == vuln.Unknown {
|
||||||
|
return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid value: %s", value)
|
||||||
|
}
|
||||||
|
metas[models.ProMetaSeverity] = strings.ToLower(severity.String())
|
||||||
|
default:
|
||||||
|
return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid key: %s", key)
|
||||||
|
}
|
||||||
|
return metas, nil
|
||||||
|
}
|
|
@ -24,8 +24,6 @@ import (
|
||||||
func registerLegacyRoutes() {
|
func registerLegacyRoutes() {
|
||||||
version := APIVersion
|
version := APIVersion
|
||||||
beego.Router("/api/"+version+"/email/ping", &api.EmailAPI{}, "post:Ping")
|
beego.Router("/api/"+version+"/email/ping", &api.EmailAPI{}, "post:Ping")
|
||||||
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")
|
|
||||||
|
|
||||||
// APIs for chart repository
|
// APIs for chart repository
|
||||||
if config.WithChartMuseum() {
|
if config.WithChartMuseum() {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user