Merge pull request #14702 from ywk253100/210419_metadata

Refactor project metadata API
This commit is contained in:
Wenkai Yin(尹文开) 2021-04-27 18:02:09 +08:00 committed by GitHub
commit c54e690f69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 392 additions and 518 deletions

View File

@ -19,151 +19,6 @@ securityDefinitions:
security:
- basicAuth: []
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:
post:
summary: Test connection and authentication with email server.
@ -376,28 +231,6 @@ responses:
InternalServerError:
description: 'Internal Server Error'
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:
type: object
properties:

View File

@ -644,6 +644,164 @@ paths:
$ref: '#/responses/403'
'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:
get:
summary: List all authorized repositories

View 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...)
}

View File

@ -93,9 +93,6 @@ func init() {
beego.BConfig.WebConfig.Session.SessionOn = true
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")
// Charts are controlled under projects

View File

@ -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
}

View File

@ -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)
}

View File

@ -61,7 +61,14 @@ func (d *dao) Create(ctx context.Context, projectID int64, name, value string) (
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

View File

@ -63,6 +63,7 @@ func New() http.Handler {
UserAPI: newUsersAPI(),
HealthAPI: newHealthAPI(),
StatisticAPI: newStatisticAPI(),
ProjectMetadataAPI: newProjectMetadaAPI(),
})
if err != nil {
log.Fatal(err)

View 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
}

View File

@ -24,8 +24,6 @@ import (
func registerLegacyRoutes() {
version := APIVersion
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
if config.WithChartMuseum() {