mirror of
https://github.com/goharbor/harbor.git
synced 2024-06-23 05:15:06 +02:00
support acceleration service (#16682)
1, support acceleration service endpoints manage. 2, support auto convert. 3, support nydus as a new kinds of accessory. Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
955a857da0
commit
05d070865c
|
@ -3875,6 +3875,187 @@ paths:
|
||||||
$ref: '#/responses/404'
|
$ref: '#/responses/404'
|
||||||
'500':
|
'500':
|
||||||
$ref: '#/responses/500'
|
$ref: '#/responses/500'
|
||||||
|
/accelerations:
|
||||||
|
post:
|
||||||
|
summary: Create a acceleration service
|
||||||
|
description: Create a acceleration service
|
||||||
|
tags:
|
||||||
|
- acceleration
|
||||||
|
operationId: createAccelerationService
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- name: acceleration
|
||||||
|
in: body
|
||||||
|
description: The acceleration
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Acceleration'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
$ref: '#/responses/201'
|
||||||
|
'400':
|
||||||
|
$ref: '#/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'409':
|
||||||
|
$ref: '#/responses/409'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
|
get:
|
||||||
|
summary: List the acceleration services
|
||||||
|
description: List the acceleration services
|
||||||
|
tags:
|
||||||
|
- acceleration
|
||||||
|
operationId: listAccelerationServices
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- $ref: '#/parameters/query'
|
||||||
|
- $ref: '#/parameters/sort'
|
||||||
|
- $ref: '#/parameters/page'
|
||||||
|
- $ref: '#/parameters/pageSize'
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
description: Deprecated, use `q` instead.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
headers:
|
||||||
|
X-Total-Count:
|
||||||
|
description: The total count of the resources
|
||||||
|
type: integer
|
||||||
|
Link:
|
||||||
|
description: Link refers to the previous page and next page
|
||||||
|
type: string
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Acceleration'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
|
/accelerations/ping:
|
||||||
|
post:
|
||||||
|
summary: Check status of a acceleration service
|
||||||
|
description: Check status of a acceleration service
|
||||||
|
tags:
|
||||||
|
- acceleration
|
||||||
|
operationId: pingAccelerationService
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- name: acceleration
|
||||||
|
in: body
|
||||||
|
description: The acceleration service
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/AccelerationPing'
|
||||||
|
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'
|
||||||
|
/accelerations/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get the specific acceleration service
|
||||||
|
description: Get the specific acceleration service
|
||||||
|
tags:
|
||||||
|
- acceleration
|
||||||
|
operationId: getAccelerationService
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: acceleration ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Acceleration'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'404':
|
||||||
|
$ref: '#/responses/404'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
|
delete:
|
||||||
|
summary: Delete the specific acceleration service
|
||||||
|
description: Delete the specific acceleration service
|
||||||
|
tags:
|
||||||
|
- acceleration
|
||||||
|
operationId: deleteAccelerationService
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Acceleration ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/responses/200'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'404':
|
||||||
|
$ref: '#/responses/404'
|
||||||
|
'412':
|
||||||
|
$ref: '#/responses/412'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
|
put:
|
||||||
|
summary: Update the acceleration service
|
||||||
|
description: Update the acceleration service
|
||||||
|
tags:
|
||||||
|
- acceleration
|
||||||
|
operationId: updateAccelerationService
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/requestId'
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: The acceleration service ID
|
||||||
|
- name: acceleration
|
||||||
|
in: body
|
||||||
|
description: The acceleration service
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/AccelerationUpdate'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/responses/200'
|
||||||
|
'401':
|
||||||
|
$ref: '#/responses/401'
|
||||||
|
'403':
|
||||||
|
$ref: '#/responses/403'
|
||||||
|
'404':
|
||||||
|
$ref: '#/responses/404'
|
||||||
|
'409':
|
||||||
|
$ref: '#/responses/409'
|
||||||
|
'500':
|
||||||
|
$ref: '#/responses/500'
|
||||||
/scans/all/metrics:
|
/scans/all/metrics:
|
||||||
get:
|
get:
|
||||||
summary: Get the metrics of the latest scan all process
|
summary: Get the metrics of the latest scan all process
|
||||||
|
@ -6471,6 +6652,10 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
description: 'Whether scan images automatically when pushing. The valid values are "true", "false".'
|
description: 'Whether scan images automatically when pushing. The valid values are "true", "false".'
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
auto_accelerate:
|
||||||
|
type: string
|
||||||
|
description: 'Whether acclerate images automatically when pushing. The valid values are "true", "false".'
|
||||||
|
x-nullable: true
|
||||||
reuse_sys_cve_allowlist:
|
reuse_sys_cve_allowlist:
|
||||||
type: string
|
type: string
|
||||||
description: 'Whether this project reuse the system level CVE allowlist as the allowlist of its own. The valid values are "true", "false".
|
description: 'Whether this project reuse the system level CVE allowlist as the allowlist of its own. The valid values are "true", "false".
|
||||||
|
@ -6826,6 +7011,117 @@ definitions:
|
||||||
value:
|
value:
|
||||||
type: string
|
type: string
|
||||||
description: The endpoint value
|
description: The endpoint value
|
||||||
|
AccelerationCredential:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
description: Credential type, such as 'basic', 'oauth'.
|
||||||
|
access_key:
|
||||||
|
type: string
|
||||||
|
description: Access key, e.g. user name when credential type is 'basic'.
|
||||||
|
access_secret:
|
||||||
|
type: string
|
||||||
|
description: Access secret, e.g. password when credential type is 'basic'.
|
||||||
|
Acceleration:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The acceleration ID.
|
||||||
|
x-omitempty: false
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
description: The acceleration URL string.
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: The acceleration name.
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
description: Type of the acceleration, e.g. 'nydus'.
|
||||||
|
credential:
|
||||||
|
$ref: '#/definitions/AccelerationCredential'
|
||||||
|
insecure:
|
||||||
|
type: boolean
|
||||||
|
description: Whether or not the certificate will be verified when Harbor tries to access the server.
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: Description of the acceleration.
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
description: Health status of the acceleration.
|
||||||
|
creation_time:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: The create time of the policy.
|
||||||
|
update_time:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: The update time of the policy.
|
||||||
|
AccelerationUpdate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: The acceleration name.
|
||||||
|
x-nullable: true
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: Description of the acceleration.
|
||||||
|
x-nullable: true
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
description: The acceleration URL.
|
||||||
|
x-nullable: true
|
||||||
|
credential_type:
|
||||||
|
type: string
|
||||||
|
description: Credential type of the registry, e.g. 'basic'.
|
||||||
|
x-nullable: true
|
||||||
|
access_key:
|
||||||
|
type: string
|
||||||
|
description: The registry access key.
|
||||||
|
x-nullable: true
|
||||||
|
access_secret:
|
||||||
|
type: string
|
||||||
|
description: The registry access secret.
|
||||||
|
x-nullable: true
|
||||||
|
insecure:
|
||||||
|
type: boolean
|
||||||
|
description: Whether or not the certificate will be verified when Harbor tries to access the server.
|
||||||
|
x-nullable: true
|
||||||
|
AccelerationPing:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The acceleration ID.
|
||||||
|
x-nullable: true
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
description: Type of the acceleration, e.g. 'nudys'.
|
||||||
|
x-nullable: true
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
description: The acceleration URL.
|
||||||
|
x-nullable: true
|
||||||
|
credential_type:
|
||||||
|
type: string
|
||||||
|
description: Credential type of the registry, e.g. 'basic'.
|
||||||
|
x-nullable: true
|
||||||
|
access_key:
|
||||||
|
type: string
|
||||||
|
description: The acceleration access key.
|
||||||
|
x-nullable: true
|
||||||
|
access_secret:
|
||||||
|
type: string
|
||||||
|
description: The acceleration access secret.
|
||||||
|
x-nullable: true
|
||||||
|
insecure:
|
||||||
|
type: boolean
|
||||||
|
description: Whether or not the certificate will be verified when Harbor tries to access the server.
|
||||||
|
x-nullable: true
|
||||||
FilterStyle:
|
FilterStyle:
|
||||||
type: object
|
type: object
|
||||||
description: The style of the resource filter
|
description: The style of the resource filter
|
||||||
|
|
|
@ -1,2 +1,19 @@
|
||||||
/* Correct project_metadata.public value, should only be true or false, other invaild value will be rewrite to false */
|
/* Correct project_metadata.public value, should only be true or false, other invaild value will be rewrite to false */
|
||||||
UPDATE project_metadata SET value='false' WHERE name='public' AND value NOT IN('true', 'false');
|
UPDATE project_metadata SET value='false' WHERE name='public' AND value NOT IN('true', 'false');
|
||||||
|
|
||||||
|
CREATE TABLE acceleration_registration
|
||||||
|
(
|
||||||
|
id SERIAL PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(128) UNIQUE NOT NULL,
|
||||||
|
url VARCHAR(256) NOT NULL,
|
||||||
|
access_key VARCHAR(255) NOT NULL,
|
||||||
|
access_secret VARCHAR(4096) NOT NULL,
|
||||||
|
insecure BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
credential_type VARCHAR(16) NOT NULL,
|
||||||
|
type VARCHAR(128) NOT NULL,
|
||||||
|
description VARCHAR(1024) NULL,
|
||||||
|
health VARCHAR(16) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ const (
|
||||||
ResourceUser = Resource("user")
|
ResourceUser = Resource("user")
|
||||||
ResourceUserGroup = Resource("user-group")
|
ResourceUserGroup = Resource("user-group")
|
||||||
ResourceRegistry = Resource("registry")
|
ResourceRegistry = Resource("registry")
|
||||||
|
ResourceAcceleration = Resource("acceleration")
|
||||||
ResourceReplication = Resource("replication")
|
ResourceReplication = Resource("replication")
|
||||||
ResourceDistribution = Resource("distribution")
|
ResourceDistribution = Resource("distribution")
|
||||||
ResourceGarbageCollection = Resource("garbage-collection")
|
ResourceGarbageCollection = Resource("garbage-collection")
|
||||||
|
|
|
@ -173,6 +173,7 @@ func (c *controller) Ensure(ctx context.Context, repository, digest string, opti
|
||||||
e.Tag = option.Tags[0]
|
e.Tag = option.Tags[0]
|
||||||
}
|
}
|
||||||
notification.AddEvent(ctx, e)
|
notification.AddEvent(ctx, e)
|
||||||
|
|
||||||
return created, artifact.ID, nil
|
return created, artifact.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -238,5 +238,11 @@ func (a *Handler) onPush(ctx context.Context, event *event.ArtifactEvent) error
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := autoAcc(ctx, &artifact.Artifact{Artifact: *event.Artifact}, event.Tags...); err != nil {
|
||||||
|
log.Errorf("acc artifact %s@%s failed, error: %v", event.Artifact.RepositoryName, event.Artifact.Digest, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,15 @@ package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
|
accel "github.com/goharbor/harbor/src/pkg/acceleration"
|
||||||
|
accelModel "github.com/goharbor/harbor/src/pkg/acceleration/model"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/controller/artifact"
|
"github.com/goharbor/harbor/src/controller/artifact"
|
||||||
"github.com/goharbor/harbor/src/controller/project"
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/controller/scan"
|
"github.com/goharbor/harbor/src/controller/scan"
|
||||||
"github.com/goharbor/harbor/src/lib/orm"
|
"github.com/goharbor/harbor/src/lib/orm"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/acceleration/adapter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// autoScan scan artifact when the project of the artifact enable auto scan
|
// autoScan scan artifact when the project of the artifact enable auto scan
|
||||||
|
@ -43,3 +47,37 @@ func autoScan(ctx context.Context, a *artifact.Artifact, tags ...string) error {
|
||||||
return scan.DefaultController.Scan(ctx, a, options...)
|
return scan.DefaultController.Scan(ctx, a, options...)
|
||||||
})(orm.SetTransactionOpNameToContext(ctx, "tx-auto-scan"))
|
})(orm.SetTransactionOpNameToContext(ctx, "tx-auto-scan"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// autoAcc accelerate artifact
|
||||||
|
func autoAcc(ctx context.Context, a *artifact.Artifact, tags ...string) error {
|
||||||
|
proj, err := project.Ctl.Get(ctx, a.ProjectID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !proj.AutoAcc() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert
|
||||||
|
acceleration, err := adapter.GetFactory(accelModel.AccelerationTypeNydus)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
accs, err := accel.Mgr.List(ctx, q.New(q.KeyWords{"type": accelModel.AccelerationTypeNydus}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
adapter, err := acceleration.Create(accs[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var tagName string
|
||||||
|
if len(tags) > 0 {
|
||||||
|
tagName = tags[0]
|
||||||
|
}
|
||||||
|
err = adapter.Convert(&a.Artifact, tagName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
48
src/pkg/acceleration/adapter/adpater.go
Normal file
48
src/pkg/acceleration/adapter/adpater.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/acceleration/model"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||||
|
)
|
||||||
|
|
||||||
|
var registry = map[string]Factory{}
|
||||||
|
|
||||||
|
// Factory creates a specific Adapter according to the params
|
||||||
|
type Factory interface {
|
||||||
|
Create(service *model.AccelerationService) (Adapter, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapter interface defines the capabilities of AccelerationService
|
||||||
|
type Adapter interface {
|
||||||
|
// Convert ...
|
||||||
|
Convert(art *artifact.Artifact, tag string) error
|
||||||
|
// HealthCheck checks health status of registry
|
||||||
|
HealthCheck() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFactory registers one adapter factory to the registry
|
||||||
|
func RegisterFactory(t string, factory Factory) error {
|
||||||
|
if len(t) == 0 {
|
||||||
|
return errors.New("invalid registry type")
|
||||||
|
}
|
||||||
|
if factory == nil {
|
||||||
|
return errors.New("empty adapter factory")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exist := registry[t]; exist {
|
||||||
|
return fmt.Errorf("adapter factory for %s already exists", t)
|
||||||
|
}
|
||||||
|
registry[t] = factory
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFactory gets the adapter factory by the specified name
|
||||||
|
func GetFactory(t string) (Factory, error) {
|
||||||
|
factory, exist := registry[t]
|
||||||
|
if !exist {
|
||||||
|
return nil, fmt.Errorf("adapter factory for %s not found", t)
|
||||||
|
}
|
||||||
|
return factory, nil
|
||||||
|
}
|
109
src/pkg/acceleration/adapter/nydus/nydus.go
Normal file
109
src/pkg/acceleration/adapter/nydus/nydus.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package nydus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
event2 "github.com/goharbor/harbor/src/controller/event"
|
||||||
|
"github.com/goharbor/harbor/src/lib/config"
|
||||||
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
|
adp "github.com/goharbor/harbor/src/pkg/acceleration/adapter"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/acceleration/model"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/distribution"
|
||||||
|
notifyModel "github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := adp.RegisterFactory(model.AccelerationTypeNydus, new(factory)); err != nil {
|
||||||
|
log.Errorf("failed to register factory for %s: %v", model.AccelerationTypeNydus, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infof("the factory for adapter %s registered", model.AccelerationTypeNydus)
|
||||||
|
}
|
||||||
|
|
||||||
|
type factory struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create ...
|
||||||
|
func (f *factory) Create(r *model.AccelerationService) (adp.Adapter, error) {
|
||||||
|
return &adapter{
|
||||||
|
url: r.URL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ adp.Adapter = (*adapter)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type adapter struct {
|
||||||
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adapter) Convert(art *artifact.Artifact, tag string) error {
|
||||||
|
hc := &http.Client{}
|
||||||
|
addr := fmt.Sprintf("%s/api/v1/conversions", a.url)
|
||||||
|
payload, err := a.getPayload(art, tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, addr, bytes.NewReader(payload))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return fmt.Errorf("nydus job(target: %s) response code is %d", addr, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adapter) HealthCheck() (string, error) {
|
||||||
|
return model.Healthy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adapter) getPayload(art *artifact.Artifact, tag string) ([]byte, error) {
|
||||||
|
url, err := BuildImageResourceURL(art.RepositoryName, tag)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
payload := ¬ifyModel.Payload{
|
||||||
|
Type: event2.TopicPushArtifact,
|
||||||
|
Operator: "admin",
|
||||||
|
OccurAt: time.Now().Unix(),
|
||||||
|
EventData: ¬ifyModel.EventData{
|
||||||
|
Resources: []*notifyModel.Resource{
|
||||||
|
{
|
||||||
|
Digest: art.Digest,
|
||||||
|
Tag: tag,
|
||||||
|
ResourceURL: url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildImageResourceURL ...
|
||||||
|
func BuildImageResourceURL(repoName, reference string) (string, error) {
|
||||||
|
extURL, err := config.ExtURL()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("get external endpoint failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if distribution.IsDigest(reference) {
|
||||||
|
return fmt.Sprintf("%s/%s@%s", extURL, repoName, reference), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s:%s", extURL, repoName, reference), nil
|
||||||
|
}
|
129
src/pkg/acceleration/dao/dao.go
Normal file
129
src/pkg/acceleration/dao/dao.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
// 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 dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
|
"github.com/goharbor/harbor/src/lib/orm"
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DAO defines the DAO operations of registry
|
||||||
|
type DAO interface {
|
||||||
|
// Create the registry
|
||||||
|
Create(ctx context.Context, as *AccelerationService) (id int64, err error)
|
||||||
|
// Count returns the count of registries according to the query
|
||||||
|
Count(ctx context.Context, query *q.Query) (count int64, err error)
|
||||||
|
// List the registries according to the query
|
||||||
|
List(ctx context.Context, query *q.Query) (ases []*AccelerationService, err error)
|
||||||
|
// Get the registry specified by ID
|
||||||
|
Get(ctx context.Context, id int64) (as *AccelerationService, err error)
|
||||||
|
// Update the specified registry
|
||||||
|
Update(ctx context.Context, as *AccelerationService, props ...string) (err error)
|
||||||
|
// Delete the registry specified by ID
|
||||||
|
Delete(ctx context.Context, id int64) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDAO creates an instance of DAO
|
||||||
|
func NewDAO() DAO {
|
||||||
|
return &dao{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dao struct{}
|
||||||
|
|
||||||
|
func (d *dao) Create(ctx context.Context, as *AccelerationService) (int64, error) {
|
||||||
|
ormer, err := orm.FromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
id, err := ormer.Insert(as)
|
||||||
|
if e := orm.AsConflictError(err, "AccelerationService %s already exists", as.Name); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dao) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||||
|
qs, err := orm.QuerySetterForCount(ctx, &AccelerationService{}, query)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return qs.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dao) List(ctx context.Context, query *q.Query) ([]*AccelerationService, error) {
|
||||||
|
registries := []*AccelerationService{}
|
||||||
|
qs, err := orm.QuerySetter(ctx, &AccelerationService{}, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err = qs.All(®istries); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return registries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dao) Get(ctx context.Context, id int64) (*AccelerationService, error) {
|
||||||
|
registry := &AccelerationService{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
ormer, err := orm.FromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := ormer.Read(registry); err != nil {
|
||||||
|
if e := orm.AsNotFoundError(err, "AccelerationService %d not found", id); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return registry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dao) Update(ctx context.Context, registry *AccelerationService, props ...string) error {
|
||||||
|
ormer, err := orm.FromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
registry.UpdateTime = time.Now()
|
||||||
|
n, err := ormer.Update(registry, props...)
|
||||||
|
if err != nil {
|
||||||
|
if e := orm.AsConflictError(err, "AccelerationService %s already exists", registry.Name); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return errors.NotFoundError(nil).WithMessage("registry %d not found", registry.ID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dao) Delete(ctx context.Context, id int64) error {
|
||||||
|
ormer, err := orm.FromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err := ormer.Delete(&AccelerationService{
|
||||||
|
ID: id,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return errors.NotFoundError(nil).WithMessage("AccelerationService %d not found", id)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
45
src/pkg/acceleration/dao/model.go
Normal file
45
src/pkg/acceleration/dao/model.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// 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 dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/orm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
orm.RegisterModel(&AccelerationService{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccelerationService model in database
|
||||||
|
type AccelerationService struct {
|
||||||
|
ID int64 `orm:"pk;auto;column(id)"`
|
||||||
|
URL string `orm:"column(url)"`
|
||||||
|
Name string `orm:"column(name)"`
|
||||||
|
CredentialType string `orm:"column(credential_type);default(basic)"`
|
||||||
|
AccessKey string `orm:"column(access_key)"`
|
||||||
|
AccessSecret string `orm:"column(access_secret)"`
|
||||||
|
Type string `orm:"column(type)"`
|
||||||
|
Insecure bool `orm:"column(insecure)"`
|
||||||
|
Description string `orm:"column(description)"`
|
||||||
|
Status string `orm:"column(health)"`
|
||||||
|
CreationTime time.Time `orm:"column(creation_time);auto_now_add"`
|
||||||
|
UpdateTime time.Time `orm:"column(update_time);auto_now"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName for artifact reference
|
||||||
|
func (a *AccelerationService) TableName() string {
|
||||||
|
return "acceleration_registration"
|
||||||
|
}
|
219
src/pkg/acceleration/manager.go
Normal file
219
src/pkg/acceleration/manager.go
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
// 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 acceleration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
|
"github.com/goharbor/harbor/src/lib/config"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/acceleration/adapter"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/acceleration/dao"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/acceleration/model"
|
||||||
|
|
||||||
|
_ "github.com/goharbor/harbor/src/pkg/acceleration/adapter/nydus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Mgr is the global registry manager instance
|
||||||
|
Mgr = NewManager()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager defines the registry related operations
|
||||||
|
type Manager interface {
|
||||||
|
// Create the registry
|
||||||
|
Create(ctx context.Context, registry *model.AccelerationService) (id int64, err error)
|
||||||
|
// Count returns the count of registries according to the query
|
||||||
|
Count(ctx context.Context, query *q.Query) (count int64, err error)
|
||||||
|
// List registries according to the query
|
||||||
|
List(ctx context.Context, query *q.Query) (registries []*model.AccelerationService, err error)
|
||||||
|
// Get the registry specified by ID
|
||||||
|
Get(ctx context.Context, id int64) (registry *model.AccelerationService, err error)
|
||||||
|
// Update the specified registry
|
||||||
|
Update(ctx context.Context, registry *model.AccelerationService, props ...string) (err error)
|
||||||
|
// Delete the registry specified by ID
|
||||||
|
Delete(ctx context.Context, id int64) (err error)
|
||||||
|
// CreateAdapter for the provided registry
|
||||||
|
CreateAdapter(ctx context.Context, registry *model.AccelerationService) (adapter adapter.Adapter, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager creates an instance of registry manager
|
||||||
|
func NewManager() Manager {
|
||||||
|
return &manager{
|
||||||
|
dao: dao.NewDAO(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type manager struct {
|
||||||
|
dao dao.DAO
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) Create(ctx context.Context, registry *model.AccelerationService) (int64, error) {
|
||||||
|
reg, err := toDaoModel(registry)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return m.dao.Create(ctx, reg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||||
|
return m.dao.Count(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) List(ctx context.Context, query *q.Query) ([]*model.AccelerationService, error) {
|
||||||
|
registries, err := m.dao.List(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var regs []*model.AccelerationService
|
||||||
|
for _, registry := range registries {
|
||||||
|
r, err := fromDaoModel(registry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
regs = append(regs, r)
|
||||||
|
}
|
||||||
|
return regs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) Get(ctx context.Context, id int64) (*model.AccelerationService, error) {
|
||||||
|
registry, err := m.dao.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fromDaoModel(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) Update(ctx context.Context, registry *model.AccelerationService, props ...string) error {
|
||||||
|
reg, err := toDaoModel(registry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.dao.Update(ctx, reg, props...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) Delete(ctx context.Context, id int64) error {
|
||||||
|
return m.dao.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) CreateAdapter(ctx context.Context, registry *model.AccelerationService) (adapter.Adapter, error) {
|
||||||
|
factory, err := adapter.GetFactory(registry.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return factory.Create(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt checks whether access secret is set in the registry, if so, decrypt it.
|
||||||
|
func decrypt(secret string) (string, error) {
|
||||||
|
if len(secret) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
secretKey, err := config.SecretKey()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
decrypted, err := utils.ReversibleDecrypt(secret, secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decrypted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt checks whether access secret is set in the registry, if so, encrypt it.
|
||||||
|
func encrypt(secret string) (string, error) {
|
||||||
|
if len(secret) == 0 {
|
||||||
|
return secret, nil
|
||||||
|
}
|
||||||
|
secretKey, err := config.SecretKey()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
encrypted, err := utils.ReversibleEncrypt(secret, secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return encrypted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDaoModel converts DAO layer registry model to replication model.
|
||||||
|
// Also, if access secret is provided, decrypt it.
|
||||||
|
func fromDaoModel(registry *dao.AccelerationService) (*model.AccelerationService, error) {
|
||||||
|
r := &model.AccelerationService{
|
||||||
|
ID: registry.ID,
|
||||||
|
Name: registry.Name,
|
||||||
|
Description: registry.Description,
|
||||||
|
Type: registry.Type,
|
||||||
|
Credential: &model.Credential{},
|
||||||
|
URL: registry.URL,
|
||||||
|
Insecure: registry.Insecure,
|
||||||
|
Status: registry.Status,
|
||||||
|
CreationTime: registry.CreationTime,
|
||||||
|
UpdateTime: registry.UpdateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(registry.AccessKey) != 0 {
|
||||||
|
credentialType := registry.CredentialType
|
||||||
|
if len(credentialType) == 0 {
|
||||||
|
credentialType = model.CredentialTypeBasic
|
||||||
|
}
|
||||||
|
decrypted, err := decrypt(registry.AccessSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.Credential = &model.Credential{
|
||||||
|
Type: credentialType,
|
||||||
|
AccessKey: registry.AccessKey,
|
||||||
|
AccessSecret: decrypted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toDaoModel ...
|
||||||
|
func toDaoModel(registry *model.AccelerationService) (*dao.AccelerationService, error) {
|
||||||
|
m := &dao.AccelerationService{
|
||||||
|
ID: registry.ID,
|
||||||
|
URL: registry.URL,
|
||||||
|
Name: registry.Name,
|
||||||
|
Type: string(registry.Type),
|
||||||
|
Insecure: registry.Insecure,
|
||||||
|
Description: registry.Description,
|
||||||
|
Status: registry.Status,
|
||||||
|
CreationTime: registry.CreationTime,
|
||||||
|
UpdateTime: registry.UpdateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
if registry.Credential != nil && len(registry.Credential.AccessKey) != 0 {
|
||||||
|
credentialType := registry.Credential.Type
|
||||||
|
if len(credentialType) == 0 {
|
||||||
|
credentialType = model.CredentialTypeBasic
|
||||||
|
}
|
||||||
|
encrypted, err := encrypt(registry.Credential.AccessSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.CredentialType = string(credentialType)
|
||||||
|
m.AccessKey = registry.Credential.AccessKey
|
||||||
|
m.AccessSecret = encrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
60
src/pkg/acceleration/model/model.go
Normal file
60
src/pkg/acceleration/model/model.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// 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 (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AccelerationTypeNydu ...
|
||||||
|
AccelerationTypeNydus = "nydus"
|
||||||
|
|
||||||
|
// Healthy indicates registry is healthy
|
||||||
|
Healthy = "healthy"
|
||||||
|
// Unhealthy indicates registry is unhealthy
|
||||||
|
Unhealthy = "unhealthy"
|
||||||
|
|
||||||
|
// CredentialTypeBasic indicates credential by user name, password
|
||||||
|
CredentialTypeBasic = "basic"
|
||||||
|
// CredentialTypeOAuth indicates credential by OAuth token
|
||||||
|
CredentialTypeOAuth = "oauth"
|
||||||
|
// CredentialTypeSecret is only used by the communication of Harbor internal components
|
||||||
|
CredentialTypeSecret = "secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Credential keeps the access key and/or secret for the related registry
|
||||||
|
type Credential struct {
|
||||||
|
// Type of the credential
|
||||||
|
Type string `json:"type"`
|
||||||
|
// The key of the access account, for OAuth token, it can be empty
|
||||||
|
AccessKey string `json:"access_key"`
|
||||||
|
// The secret or password for the key
|
||||||
|
AccessSecret string `json:"access_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccelerationService ...
|
||||||
|
type AccelerationService struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Credential *Credential `json:"credential"`
|
||||||
|
Insecure bool `json:"insecure"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreationTime time.Time `json:"creation_time"`
|
||||||
|
UpdateTime time.Time `json:"update_time"`
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import (
|
||||||
|
|
||||||
_ "github.com/goharbor/harbor/src/pkg/accessory/model/base"
|
_ "github.com/goharbor/harbor/src/pkg/accessory/model/base"
|
||||||
_ "github.com/goharbor/harbor/src/pkg/accessory/model/cosign"
|
_ "github.com/goharbor/harbor/src/pkg/accessory/model/cosign"
|
||||||
|
_ "github.com/goharbor/harbor/src/pkg/accessory/model/nydus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -63,6 +63,8 @@ const (
|
||||||
TypeNone = "base"
|
TypeNone = "base"
|
||||||
// TypeCosignSignature ...
|
// TypeCosignSignature ...
|
||||||
TypeCosignSignature = "signature.cosign"
|
TypeCosignSignature = "signature.cosign"
|
||||||
|
// TypeAccelNydus ...
|
||||||
|
TypeAccelNydus = "acceleration.nydus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccessoryData ...
|
// AccessoryData ...
|
||||||
|
|
46
src/pkg/accessory/model/nydus/nydus.go
Normal file
46
src/pkg/accessory/model/nydus/nydus.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// 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 nydus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/pkg/accessory/model"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/accessory/model/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nydus signature model
|
||||||
|
type Nydus struct {
|
||||||
|
base.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind gives the reference type of nydus.
|
||||||
|
func (n *Nydus) Kind() string {
|
||||||
|
return model.RefHard
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHard ...
|
||||||
|
func (n *Nydus) IsHard() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns nydus
|
||||||
|
func New(data model.AccessoryData) model.Accessory {
|
||||||
|
return &Nydus{base.Default{
|
||||||
|
Data: data,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
model.Register(model.TypeAccelNydus, New)
|
||||||
|
}
|
|
@ -22,5 +22,6 @@ const (
|
||||||
ProMetaPreventVul = "prevent_vul" // prevent vulnerable images from being pulled
|
ProMetaPreventVul = "prevent_vul" // prevent vulnerable images from being pulled
|
||||||
ProMetaSeverity = "severity"
|
ProMetaSeverity = "severity"
|
||||||
ProMetaAutoScan = "auto_scan"
|
ProMetaAutoScan = "auto_scan"
|
||||||
|
ProMetaAutoAcc = "auto_accelerate"
|
||||||
ProMetaReuseSysCVEAllowlist = "reuse_sys_cve_allowlist"
|
ProMetaReuseSysCVEAllowlist = "reuse_sys_cve_allowlist"
|
||||||
)
|
)
|
||||||
|
|
|
@ -148,6 +148,15 @@ func (p *Project) AutoScan() bool {
|
||||||
return isTrue(auto)
|
return isTrue(auto)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AutoAcc ...
|
||||||
|
func (p *Project) AutoAcc() bool {
|
||||||
|
auto, exist := p.GetMetadata(ProMetaAutoAcc)
|
||||||
|
if !exist {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isTrue(auto)
|
||||||
|
}
|
||||||
|
|
||||||
// FilterByPublic returns orm.QuerySeter with public filter
|
// FilterByPublic returns orm.QuerySeter with public filter
|
||||||
func (p *Project) FilterByPublic(ctx context.Context, qs orm.QuerySeter, key string, value interface{}) orm.QuerySeter {
|
func (p *Project) FilterByPublic(ctx context.Context, qs orm.QuerySeter, key string, value interface{}) orm.QuerySeter {
|
||||||
subQuery := `SELECT project_id FROM project_metadata WHERE name = 'public' AND value = '%s'`
|
subQuery := `SELECT project_id FROM project_metadata WHERE name = 'public' AND value = '%s'`
|
||||||
|
|
106
src/server/middleware/nydus/nydus.go
Normal file
106
src/server/middleware/nydus/nydus.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package nydus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/goharbor/harbor/src/controller/artifact"
|
||||||
|
"github.com/goharbor/harbor/src/lib"
|
||||||
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
|
"github.com/goharbor/harbor/src/lib/orm"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/accessory"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/accessory/model"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/distribution"
|
||||||
|
"github.com/goharbor/harbor/src/server/middleware"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// the media type of consign signature layer
|
||||||
|
mediaTypeNydusLayer = "application/vnd.oci.image.layer.nydus.blob.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NydusMiddleware middleware to record the linkage of artifact and its accessory
|
||||||
|
func NydusMiddleware() func(http.Handler) http.Handler {
|
||||||
|
return middleware.AfterResponse(func(w http.ResponseWriter, r *http.Request, statusCode int) error {
|
||||||
|
if statusCode != http.StatusCreated {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
logger := log.G(ctx).WithFields(log.Fields{"middleware": "nydus"})
|
||||||
|
|
||||||
|
none := lib.ArtifactInfo{}
|
||||||
|
info := lib.GetArtifactInfo(ctx)
|
||||||
|
if info == none {
|
||||||
|
return errors.New("artifactinfo middleware required before this middleware").WithCode(errors.NotFoundCode)
|
||||||
|
}
|
||||||
|
if info.Tag == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
manifest, desc, err := distribution.UnmarshalManifest(contentType, body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("unmarshal manifest failed, error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var isNydus bool
|
||||||
|
for _, descriptor := range manifest.References() {
|
||||||
|
if descriptor.MediaType == mediaTypeNydusLayer {
|
||||||
|
isNydus = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, content, err := manifest.Payload()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get manifest
|
||||||
|
mani := &v1.Manifest{}
|
||||||
|
if err := json.Unmarshal(content, mani); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNydus {
|
||||||
|
subjectArt, err := artifact.Ctl.GetByReference(ctx, info.Repository, mani.Annotations["io.goharbor.artifact.v1alpha1.acceleration.source.digest"], nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to get subject artifact: %s, error: %v", info.Tag, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
art, err := artifact.Ctl.GetByReference(ctx, info.Repository, desc.Digest.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to get cosign signature artifact: %s, error: %v", desc.Digest.String(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := orm.WithTransaction(func(ctx context.Context) error {
|
||||||
|
_, err := accessory.Mgr.Create(ctx, model.AccessoryData{
|
||||||
|
ArtifactID: art.ID,
|
||||||
|
SubArtifactID: subjectArt.ID,
|
||||||
|
Size: desc.Size,
|
||||||
|
Digest: desc.Digest.String(),
|
||||||
|
Type: model.TypeAccelNydus,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})(orm.SetTransactionOpNameToContext(ctx, "tx-create-nydus-accessory")); err != nil {
|
||||||
|
if !errors.IsConflictErr(err) {
|
||||||
|
logger.Errorf("failed to create cosign signature artifact: %s, error: %v", desc.Digest.String(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
|
@ -15,12 +15,11 @@
|
||||||
package requestid
|
package requestid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
tracelib "github.com/goharbor/harbor/src/lib/trace"
|
tracelib "github.com/goharbor/harbor/src/lib/trace"
|
||||||
"github.com/goharbor/harbor/src/server/middleware"
|
"github.com/goharbor/harbor/src/server/middleware"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
oteltrace "go.opentelemetry.io/otel/trace"
|
oteltrace "go.opentelemetry.io/otel/trace"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/server/middleware/nydus"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/server/middleware/blob"
|
"github.com/goharbor/harbor/src/server/middleware/blob"
|
||||||
|
@ -79,6 +80,7 @@ func RegisterRoutes() {
|
||||||
Middleware(repoproxy.DisableBlobAndManifestUploadMiddleware()).
|
Middleware(repoproxy.DisableBlobAndManifestUploadMiddleware()).
|
||||||
Middleware(immutable.Middleware()).
|
Middleware(immutable.Middleware()).
|
||||||
Middleware(quota.PutManifestMiddleware()).
|
Middleware(quota.PutManifestMiddleware()).
|
||||||
|
Middleware(nydus.NydusMiddleware()).
|
||||||
Middleware(cosign.CosignSignatureMiddleware()).
|
Middleware(cosign.CosignSignatureMiddleware()).
|
||||||
Middleware(blob.PutManifestMiddleware()).
|
Middleware(blob.PutManifestMiddleware()).
|
||||||
HandlerFunc(putManifest)
|
HandlerFunc(putManifest)
|
||||||
|
|
194
src/server/v2.0/handler/acceleration.go
Normal file
194
src/server/v2.0/handler/acceleration.go
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
// 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/go-openapi/strfmt"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/acceleration"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-openapi/runtime/middleware"
|
||||||
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/acceleration/model"
|
||||||
|
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||||
|
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/acceleration"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newAccelerationAPI() *accelerationAPI {
|
||||||
|
return &accelerationAPI{
|
||||||
|
mgr: acceleration.Mgr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type accelerationAPI struct {
|
||||||
|
BaseAPI
|
||||||
|
mgr acceleration.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *accelerationAPI) CreateAccelerationService(ctx context.Context, params operation.CreateAccelerationServiceParams) middleware.Responder {
|
||||||
|
if err := r.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourceAcceleration); err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
accel := &model.AccelerationService{
|
||||||
|
Name: params.Acceleration.Name,
|
||||||
|
Description: params.Acceleration.Description,
|
||||||
|
Type: params.Acceleration.Type,
|
||||||
|
URL: params.Acceleration.URL,
|
||||||
|
Insecure: params.Acceleration.Insecure,
|
||||||
|
}
|
||||||
|
if params.Acceleration.Credential != nil {
|
||||||
|
accel.Credential = &model.Credential{
|
||||||
|
Type: params.Acceleration.Credential.Type,
|
||||||
|
AccessKey: params.Acceleration.Credential.AccessKey,
|
||||||
|
AccessSecret: params.Acceleration.Credential.AccessSecret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := r.mgr.Create(ctx, accel)
|
||||||
|
if err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
location := fmt.Sprintf("%s/%d", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), id)
|
||||||
|
return operation.NewCreateAccelerationServiceCreated().WithLocation(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *accelerationAPI) GetAccelerationService(ctx context.Context, params operation.GetAccelerationServiceParams) middleware.Responder {
|
||||||
|
if err := r.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceAcceleration); err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accel, err := r.mgr.Get(ctx, params.ID)
|
||||||
|
if err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
return operation.NewGetAccelerationServiceOK().WithPayload(convertAcceleration(accel))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *accelerationAPI) ListAccelerationServices(ctx context.Context, params operation.ListAccelerationServicesParams) middleware.Responder {
|
||||||
|
if err := r.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceAcceleration); err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query, err := r.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
||||||
|
if err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
// keep backward compatibility for the "name" query
|
||||||
|
if params.Name != nil {
|
||||||
|
query.Keywords["Name"] = q.NewFuzzyMatchValue(*params.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := r.mgr.Count(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
accs, err := r.mgr.List(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
var accels []*models.Acceleration
|
||||||
|
for _, acc := range accs {
|
||||||
|
accels = append(accels, convertAcceleration(acc))
|
||||||
|
}
|
||||||
|
return operation.NewListAccelerationServicesOK().WithXTotalCount(total).
|
||||||
|
WithLink(r.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
||||||
|
WithPayload(accels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *accelerationAPI) DeleteAccelerationService(ctx context.Context, params operation.DeleteAccelerationServiceParams) middleware.Responder {
|
||||||
|
if err := r.RequireSystemAccess(ctx, rbac.ActionDelete, rbac.ResourceAcceleration); err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
if err := r.mgr.Delete(ctx, params.ID); err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
return operation.NewDeleteAccelerationServiceOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *accelerationAPI) UpdateAccelerationService(ctx context.Context, params operation.UpdateAccelerationServiceParams) middleware.Responder {
|
||||||
|
if err := r.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceAcceleration); err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
accel, err := r.mgr.Get(ctx, params.ID)
|
||||||
|
if err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
if params.Acceleration != nil {
|
||||||
|
if params.Acceleration.Name != nil {
|
||||||
|
accel.Name = *params.Acceleration.Name
|
||||||
|
}
|
||||||
|
if params.Acceleration.Description != nil {
|
||||||
|
accel.Description = *params.Acceleration.Description
|
||||||
|
}
|
||||||
|
if params.Acceleration.URL != nil {
|
||||||
|
accel.URL = *params.Acceleration.URL
|
||||||
|
}
|
||||||
|
if params.Acceleration.Insecure != nil {
|
||||||
|
accel.Insecure = *params.Acceleration.Insecure
|
||||||
|
}
|
||||||
|
if accel.Credential == nil {
|
||||||
|
accel.Credential = &model.Credential{}
|
||||||
|
}
|
||||||
|
if params.Acceleration.CredentialType != nil {
|
||||||
|
accel.Credential.Type = *params.Acceleration.CredentialType
|
||||||
|
}
|
||||||
|
if params.Acceleration.AccessKey != nil {
|
||||||
|
accel.Credential.AccessKey = *params.Acceleration.AccessKey
|
||||||
|
}
|
||||||
|
if params.Acceleration.AccessSecret != nil {
|
||||||
|
accel.Credential.AccessSecret = *params.Acceleration.AccessSecret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := r.mgr.Update(ctx, accel); err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
return operation.NewUpdateAccelerationServiceOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *accelerationAPI) PingAccelerationService(ctx context.Context, params operation.PingAccelerationServiceParams) middleware.Responder {
|
||||||
|
if err := r.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceAcceleration); err != nil {
|
||||||
|
return r.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation.NewPingAccelerationServiceOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertAcceleration(registry *model.AccelerationService) *models.Acceleration {
|
||||||
|
r := &models.Acceleration{
|
||||||
|
CreationTime: strfmt.DateTime(registry.CreationTime),
|
||||||
|
Description: registry.Description,
|
||||||
|
ID: registry.ID,
|
||||||
|
Insecure: registry.Insecure,
|
||||||
|
Name: registry.Name,
|
||||||
|
Status: registry.Status,
|
||||||
|
Type: string(registry.Type),
|
||||||
|
UpdateTime: strfmt.DateTime(registry.UpdateTime),
|
||||||
|
URL: registry.URL,
|
||||||
|
}
|
||||||
|
if registry.Credential != nil {
|
||||||
|
credential := &models.AccelerationCredential{
|
||||||
|
AccessKey: registry.Credential.AccessKey,
|
||||||
|
Type: string(registry.Credential.Type),
|
||||||
|
}
|
||||||
|
if len(registry.Credential.AccessSecret) > 0 {
|
||||||
|
credential.AccessSecret = "*****"
|
||||||
|
}
|
||||||
|
r.Credential = credential
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ func New() http.Handler {
|
||||||
Robotv1API: newRobotV1API(),
|
Robotv1API: newRobotV1API(),
|
||||||
ReplicationAPI: newReplicationAPI(),
|
ReplicationAPI: newReplicationAPI(),
|
||||||
RegistryAPI: newRegistryAPI(),
|
RegistryAPI: newRegistryAPI(),
|
||||||
|
AccelerationAPI: newAccelerationAPI(),
|
||||||
SysteminfoAPI: newSystemInfoAPI(),
|
SysteminfoAPI: newSystemInfoAPI(),
|
||||||
PingAPI: newPingAPI(),
|
PingAPI: newPingAPI(),
|
||||||
LdapAPI: newLdapAPI(),
|
LdapAPI: newLdapAPI(),
|
||||||
|
|
|
@ -141,7 +141,7 @@ func (p *projectMetadataAPI) validate(metas map[string]string) (map[string]strin
|
||||||
|
|
||||||
switch key {
|
switch key {
|
||||||
case proModels.ProMetaPublic, proModels.ProMetaEnableContentTrust, proModels.ProMetaEnableContentTrustCosign,
|
case proModels.ProMetaPublic, proModels.ProMetaEnableContentTrust, proModels.ProMetaEnableContentTrustCosign,
|
||||||
proModels.ProMetaPreventVul, proModels.ProMetaAutoScan:
|
proModels.ProMetaPreventVul, proModels.ProMetaAutoScan, proModels.ProMetaAutoAcc:
|
||||||
v, err := strconv.ParseBool(value)
|
v, err := strconv.ParseBool(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid value: %s", value)
|
return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid value: %s", value)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user