Refactor registry API (#14528)

* Refactor registry API

Refactor registry API

Signed-off-by: Wenkai Yin <yinw@vmware.com>

* Fix bugs of replications

1. Fix the scheduled replication doesn't work issue
2. Fix the destination name lost issue when updating replication policy

Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
Wenkai Yin(尹文开) 2021-03-31 15:49:23 +08:00 committed by GitHub
parent 70165be3f0
commit 28596c3ffb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
188 changed files with 2255 additions and 3234 deletions

View File

@ -883,245 +883,6 @@ paths:
description: The resource does not exist.
'500':
description: Unexpected internal errors.
/replication/adapters:
get:
summary: List supported adapters.
description: |
This endpoint let user list supported adapters.
tags:
- Products
responses:
'200':
description: Success.
schema:
type: array
items:
type: string
'401':
description: Unauthorized.
'403':
description: Forbidden.
'500':
description: Unexpected internal errors.
/registries:
get:
summary: List registries.
description: |
List registries according to the query.
parameters:
- name: name
in: query
type: string
required: false
description: Deprecated, use `q` instead.
- $ref: '#/parameters/query'
tags:
- Products
responses:
'200':
description: List registries successfully.
schema:
type: array
items:
$ref: '#/definitions/Registry'
'401':
description: User need to log in first.
'500':
description: Unexpected internal errors.
post:
summary: Create a new registry.
description: |
This endpoint is for user to create a new registry.
parameters:
- name: registry
in: body
description: New created registry.
required: true
schema:
$ref: '#/definitions/Registry'
tags:
- Products
responses:
'201':
description: Registry created successfully.
headers:
Location:
type: string
description: The URL of the created resource
'400':
description: Unsatisfied with constraints of the registry creation.
'401':
description: User need to log in first.
'409':
description: Registry name already exists.
'415':
$ref: '#/responses/UnsupportedMediaType'
'500':
description: Unexpected internal errors.
/registries/ping:
post:
summary: Ping status of a registry.
description: |
This endpoint checks status of a registry, the registry can be given by ID or URL (together with credential)
parameters:
- name: registry
in: body
description: Registry to ping.
required: true
schema:
$ref: '#/definitions/Registry'
tags:
- Products
responses:
'200':
description: Registry is healthy.
'400':
description: No proper registry information provided.
'401':
description: User need to log in first.
'404':
description: Registry not found (when registry is provided by ID).
'415':
$ref: '#/responses/UnsupportedMediaType'
'500':
description: Unexpected internal errors.
'/registries/{id}':
put:
summary: Update a given registry.
description: |
This endpoint is for update a given registry.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: The registry's ID.
- name: repo_target
in: body
required: true
schema:
$ref: '#/definitions/PutRegistry'
description: Updates registry.
tags:
- Products
responses:
'200':
description: Updated registry successfully.
'400':
description: The registry is associated with policy which is enabled.
'401':
description: User need to log in first.
'404':
description: Registry does not exist.
'409':
description: Registry name is already used.
'500':
description: Unexpected internal errors.
get:
summary: Get registry.
description: This endpoint is for get specific registry.
tags:
- Products
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: The registry ID.
responses:
'200':
description: Get registry successfully.
schema:
$ref: '#/definitions/Registry'
'401':
description: User need to log in first.
'404':
description: Registry not found
'500':
description: Unexpected internal errors.
delete:
summary: Delete specific registry.
description: |
This endpoint is for to delete specific registry.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: The registry's ID.
tags:
- Products
responses:
'200':
description: Registry deleted successfully.
'400':
description: Registry's ID is invalid or the registry is used by policies.
'401':
description: Only admin has this authority.
'404':
description: Registry does not exist.
'500':
description: Unexpected internal errors.
'/registries/{id}/info':
get:
summary: Get registry info.
description: Get the info of one specific registry.
tags:
- Products
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: The registry ID.
responses:
'200':
description: Get registry successfully.
schema:
$ref: '#/definitions/RegistryInfo'
'401':
description: User need to log in first.
'404':
description: Registry not found
'500':
description: Unexpected internal errors.
/registries/{id}/namespace:
get:
summary: List namespaces of registry
description: |
This endpoint let user list namespaces of registry according to query.
parameters:
- name: id
in: path
type: integer
required: true
description: The registry ID.
- name: name
in: query
type: string
required: false
description: The name of namespace.
tags:
- Products
responses:
'200':
description: Success
schema:
type: array
items:
$ref: '#/definitions/Namespace'
'401':
description: User need to login first.
'403':
description: User has no privilege for the operation.
'404':
description: No registry found.
'500':
description: Unexpected internal errors.
/usergroups:
get:
summary: Get all user groups information
@ -1630,99 +1391,6 @@ definitions:
type: integer
format: int32
description: 'The count of the total repositories, only be seen when the user is admin.'
RegistryCredential:
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'.
Registry:
type: object
properties:
id:
type: integer
format: int64
description: The registry ID.
url:
type: string
description: The registry URL string.
name:
type: string
description: The registry name.
credential:
$ref: '#/definitions/RegistryCredential'
type:
type: string
description: Type of the registry, e.g. 'harbor'.
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 registry.
status:
type: string
description: Health status of the registry.
creation_time:
type: string
description: The create time of the policy.
update_time:
type: string
description: The update time of the policy.
PingRegistry:
type: object
properties:
id:
type: integer
description: The ID of the registry
type:
type: string
description: Type of the registry, e.g. 'harbor'.
url:
type: string
description: The registry address URL string.
credential_type:
type: string
description: Credential type of the registry, e.g. 'basic'.
access_key:
type: string
description: The registry access key.
access_secret:
type: string
description: The registry access secret.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
PutRegistry:
type: object
properties:
name:
type: string
description: The registry name.
description:
type: string
description: Description of the registry.
url:
type: string
description: The registry address URL string.
credential_type:
type: string
description: Credential type of the registry, e.g. 'basic'.
access_key:
type: string
description: The registry access key.
access_secret:
type: string
description: The registry access secret.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
SysAdminFlag:
type: object
properties:
@ -2338,41 +2006,6 @@ definitions:
action:
type: string
description: The permission action
RegistryInfo:
type: object
description: The registry info contains the base info and capability declarations of the registry
properties:
type:
type: string
description: The registry type
description:
type: string
description: The description
supported_resource_filters:
type: array
description: The filters that the registry supports
items:
$ref: '#/definitions/FilterStyle'
supported_triggers:
type: array
description: The triggers that the registry supports
items:
type: string
FilterStyle:
type: object
description: The style of the resource filter
properties:
type:
type: string
description: The filter type
style:
type: string
description: The filter style
values:
type: array
description: The filter values
items:
type: string
Namespace:
type: object
description: The namespace of registry

View File

@ -2886,6 +2886,259 @@ paths:
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/replication/adapters:
get:
summary: List registry adapters
description: List registry adapters
tags:
- registry
operationId: listRegistryProviderTypes
parameters:
- $ref: '#/parameters/requestId'
responses:
'200':
description: Success.
schema:
type: array
items:
type: string
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'500':
$ref: '#/responses/500'
/replication/adapterinfos:
get:
summary: List all registered registry provider information
description: List all registered registry provider information
tags:
- registry
operationId: listRegistryProviderInfos
parameters:
- $ref: '#/parameters/requestId'
responses:
'200':
description: Success.
schema:
type: object
additionalProperties:
$ref: '#/definitions/RegistryProviderInfo'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'500':
$ref: '#/responses/500'
/registries:
post:
summary: Create a registry
description: Create a registry
tags:
- registry
operationId: createRegistry
parameters:
- $ref: '#/parameters/requestId'
- name: registry
in: body
description: The registry
required: true
schema:
$ref: '#/definitions/Registry'
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 registries
description: List the registries
tags:
- registry
operationId: listRegistries
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/Registry'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'500':
$ref: '#/responses/500'
/registries/ping:
post:
summary: Check status of a registry
description: Check status of a registry
tags:
- registry
operationId: pingRegistry
parameters:
- $ref: '#/parameters/requestId'
- name: registry
in: body
description: The registry
required: true
schema:
$ref: '#/definitions/RegistryPing'
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'
/registries/{id}:
get:
summary: Get the specific registry
description: Get the specific registry
tags:
- registry
operationId: getRegistry
parameters:
- $ref: '#/parameters/requestId'
- name: id
in: path
type: integer
format: int64
required: true
description: Registry ID
responses:
'200':
description: Success
schema:
$ref: '#/definitions/Registry'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
delete:
summary: Delete the specific registry
description: Delete the specific registry
tags:
- registry
operationId: deleteRegistry
parameters:
- $ref: '#/parameters/requestId'
- name: id
in: path
type: integer
format: int64
required: true
description: Registry 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 registry
description: Update the registry
tags:
- registry
operationId: updateRegistry
parameters:
- $ref: '#/parameters/requestId'
- name: id
in: path
type: integer
format: int64
required: true
description: The registry ID
- name: registry
in: body
description: The registry
required: true
schema:
$ref: '#/definitions/RegistryUpdate'
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'
/registries/{id}/info:
get:
summary: Get the registry info
description: Get the registry info
tags:
- registry
operationId: getRegistryInfo
parameters:
- $ref: '#/parameters/requestId'
- name: id
in: path
type: integer
format: int64
required: true
description: Registry ID
responses:
'200':
description: Success
schema:
$ref: '#/definitions/RegistryInfo'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/scans/all/metrics:
get:
summary: Get the metrics of the latest scan all process
@ -5086,6 +5339,152 @@ definitions:
type: string
format: date-time
description: The update time of the policy.
RegistryUpdate:
type: object
properties:
name:
type: string
description: The registry name.
x-nullable: true
description:
type: string
description: Description of the registry.
x-nullable: true
url:
type: string
description: The registry 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
RegistryPing:
type: object
properties:
id:
type: integer
format: int64
description: The registry ID.
x-nullable: true
type:
type: string
description: Type of the registry, e.g. 'harbor'.
x-nullable: true
url:
type: string
description: The registry 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
RegistryInfo:
type: object
description: The registry info contains the base info and capability declarations of the registry
properties:
type:
type: string
description: The registry type
description:
type: string
description: The description
supported_resource_filters:
type: array
description: The filters that the registry supports
items:
$ref: '#/definitions/FilterStyle'
supported_triggers:
type: array
description: The triggers that the registry supports
items:
type: string
RegistryProviderInfo:
type: object
description: The registry provider info contains the base info and capability declarations of the registry provider
properties:
endpoint_pattern:
description: The endpoint pattern
$ref: '#/definitions/RegistryProviderEndpointPattern'
credential_pattern:
description: The credential pattern
$ref: '#/definitions/RegistryProviderCredentialPattern'
RegistryProviderEndpointPattern:
type: object
description: The registry endpoint pattern
properties:
endpoint_type:
type: string
description: The endpoint type
endpoints:
type: array
description: The endpoint list
items:
$ref: '#/definitions/RegistryEndpoint'
RegistryProviderCredentialPattern:
type: object
description: The registry credential pattern
properties:
access_key_type:
type: string
description: The access key type
access_key_data:
type: string
description: The access key data
access_secret_type:
type: string
description: The access secret type
access_secret_data:
type: string
description: The access secret data
RegistryEndpoint:
type: object
description: The style of the resource filter
properties:
key:
type: string
description: The endpoint key
value:
type: string
description: The endpoint value
FilterStyle:
type: object
description: The style of the resource filter
properties:
type:
type: string
description: The filter type
style:
type: string
description: The filter style
values:
type: array
description: The filter values
items:
type: string
ResourceList:
type: object
additionalProperties:

View File

@ -10,11 +10,11 @@ import (
"github.com/ghodss/yaml"
commonhttp "github.com/goharbor/harbor/src/common/http"
rep_event "github.com/goharbor/harbor/src/controller/event/handler/replication/event"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/replication"
rep_event "github.com/goharbor/harbor/src/replication/event"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/reg/model"
helm_repo "helm.sh/helm/v3/pkg/repo"
)
@ -107,7 +107,7 @@ func (c *Controller) DeleteChartVersion(namespace, chartName, version string) er
},
},
}
if err := replication.EventHandler.Handle(e); err != nil {
if err := rep_event.Handle(orm.Context(), e); err != nil {
log.Errorf("failed to handle event: %v", err)
}
}()

View File

@ -16,11 +16,11 @@ import (
"github.com/goharbor/harbor/src/common"
commonhttp "github.com/goharbor/harbor/src/common/http"
rep_event "github.com/goharbor/harbor/src/controller/event/handler/replication/event"
"github.com/goharbor/harbor/src/controller/event/metadata"
hlog "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/orm"
n_event "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/replication"
rep_event "github.com/goharbor/harbor/src/replication/event"
)
const (
@ -112,7 +112,7 @@ func modifyResponse(res *http.Response) error {
} else {
// Todo: it used as the replacement of webhook, will be removed when webhook to be introduced.
go func() {
if err := replication.EventHandler.Handle(e); err != nil {
if err := rep_event.Handle(orm.Context(), e); err != nil {
hlog.Errorf("failed to handle event: %v", err)
}
}()

View File

@ -14,7 +14,7 @@
package event
import "github.com/goharbor/harbor/src/replication/model"
import "github.com/goharbor/harbor/src/pkg/reg/model"
// const definitions
const (

View File

@ -15,49 +15,31 @@
package event
import (
"context"
"errors"
"fmt"
"github.com/goharbor/harbor/src/controller/replication"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/orm"
rep "github.com/goharbor/harbor/src/pkg/replication"
"github.com/goharbor/harbor/src/pkg/reg/filter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/task"
"github.com/goharbor/harbor/src/replication/filter"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/registry"
)
// Handler is the handler to handle event
type Handler interface {
Handle(event *Event) error
}
// NewHandler ...
func NewHandler(registryMgr registry.Manager) Handler {
return &handler{
registryMgr: registryMgr,
ctl: replication.Ctl,
}
}
type handler struct {
registryMgr registry.Manager
ctl replication.Controller
}
func (h *handler) Handle(event *Event) error {
// Handle ...
func Handle(ctx context.Context, event *Event) error {
if event == nil || event.Resource == nil ||
event.Resource.Metadata == nil ||
len(event.Resource.Metadata.Artifacts) == 0 {
return errors.New("invalid event")
}
var policies []*rep.Policy
var policies []*repctlmodel.Policy
var err error
switch event.Type {
case EventTypeArtifactPush, EventTypeChartUpload, EventTypeTagDelete,
EventTypeArtifactDelete, EventTypeChartDelete:
policies, err = h.getRelatedPolicies(event.Resource)
policies, err = getRelatedPolicies(ctx, event.Resource)
default:
return fmt.Errorf("unsupported event type %s", event.Type)
}
@ -71,7 +53,7 @@ func (h *handler) Handle(event *Event) error {
}
for _, policy := range policies {
id, err := h.ctl.Start(orm.Context(), policy, event.Resource, task.ExecutionTriggerEvent)
id, err := replication.Ctl.Start(ctx, policy, event.Resource, task.ExecutionTriggerEvent)
if err != nil {
return err
}
@ -80,12 +62,12 @@ func (h *handler) Handle(event *Event) error {
return nil
}
func (h *handler) getRelatedPolicies(resource *model.Resource) ([]*rep.Policy, error) {
policies, err := replication.Ctl.ListPolicies(orm.Context(), nil)
func getRelatedPolicies(ctx context.Context, resource *model.Resource) ([]*repctlmodel.Policy, error) {
policies, err := replication.Ctl.ListPolicies(ctx, nil)
if err != nil {
return nil, err
}
result := []*rep.Policy{}
result := []*repctlmodel.Policy{}
for _, policy := range policies {
// disabled
if !policy.Enabled {

View File

@ -19,12 +19,11 @@ import (
"strconv"
"github.com/goharbor/harbor/src/controller/event"
repevent "github.com/goharbor/harbor/src/controller/event/handler/replication/event"
"github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/replication"
repevent "github.com/goharbor/harbor/src/replication/event"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
// Handler ...
@ -40,19 +39,19 @@ func (r *Handler) Name() string {
func (r *Handler) Handle(ctx context.Context, value interface{}) error {
pushArtEvent, ok := value.(*event.PushArtifactEvent)
if ok {
return r.handlePushArtifact(pushArtEvent)
return r.handlePushArtifact(ctx, pushArtEvent)
}
deleteArtEvent, ok := value.(*event.DeleteArtifactEvent)
if ok {
return r.handleDeleteArtifact(deleteArtEvent)
return r.handleDeleteArtifact(ctx, deleteArtEvent)
}
createTagEvent, ok := value.(*event.CreateTagEvent)
if ok {
return r.handleCreateTag(createTagEvent)
return r.handleCreateTag(ctx, createTagEvent)
}
deleteTagEvent, ok := value.(*event.DeleteTagEvent)
if ok {
return r.handleDeleteTag(deleteTagEvent)
return r.handleDeleteTag(ctx, deleteTagEvent)
}
return nil
}
@ -62,7 +61,7 @@ func (r *Handler) IsStateful() bool {
return false
}
func (r *Handler) handlePushArtifact(event *event.PushArtifactEvent) error {
func (r *Handler) handlePushArtifact(ctx context.Context, event *event.PushArtifactEvent) error {
art := event.Artifact
public := false
prj, err := project.Ctl.Get(orm.Context(), art.ProjectID, project.Metadata(true))
@ -92,10 +91,10 @@ func (r *Handler) handlePushArtifact(event *event.PushArtifactEvent) error {
},
},
}
return replication.EventHandler.Handle(e)
return repevent.Handle(ctx, e)
}
func (r *Handler) handleDeleteArtifact(event *event.DeleteArtifactEvent) error {
func (r *Handler) handleDeleteArtifact(ctx context.Context, event *event.DeleteArtifactEvent) error {
art := event.Artifact
e := &repevent.Event{
Type: repevent.EventTypeArtifactDelete,
@ -115,10 +114,10 @@ func (r *Handler) handleDeleteArtifact(event *event.DeleteArtifactEvent) error {
Deleted: true,
},
}
return replication.EventHandler.Handle(e)
return repevent.Handle(ctx, e)
}
func (r *Handler) handleCreateTag(event *event.CreateTagEvent) error {
func (r *Handler) handleCreateTag(ctx context.Context, event *event.CreateTagEvent) error {
art := event.AttachedArtifact
public := false
prj, err := project.Ctl.Get(orm.Context(), art.ProjectID, project.Metadata(true))
@ -148,10 +147,10 @@ func (r *Handler) handleCreateTag(event *event.CreateTagEvent) error {
},
},
}
return replication.EventHandler.Handle(e)
return repevent.Handle(ctx, e)
}
func (r *Handler) handleDeleteTag(event *event.DeleteTagEvent) error {
func (r *Handler) handleDeleteTag(ctx context.Context, event *event.DeleteTagEvent) error {
art := event.AttachedArtifact
e := &repevent.Event{
Type: repevent.EventTypeTagDelete,
@ -172,5 +171,5 @@ func (r *Handler) handleDeleteTag(event *event.DeleteTagEvent) error {
IsDeleteTag: true,
},
}
return replication.EventHandler.Handle(e)
return repevent.Handle(ctx, e)
}

View File

@ -18,8 +18,8 @@ import (
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
rep "github.com/goharbor/harbor/src/replication"
rpModel "github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg"
rpModel "github.com/goharbor/harbor/src/pkg/reg/model"
)
// ReplicationHandler preprocess replication event data
@ -104,14 +104,11 @@ func constructReplicationPayload(event *event.ReplicationEvent) (*model.Payload,
remoteRegID = rpPolicy.DestRegistry.ID
}
remoteRegistry, err := rep.RegistryMgr.Get(remoteRegID)
remoteRegistry, err := reg.Mgr.Get(ctx, remoteRegID)
if err != nil {
log.Errorf("failed to get replication remoteRegistry registry %d: error: %v", remoteRegID, err)
return nil, nil, err
}
if remoteRegistry == nil {
return nil, nil, fmt.Errorf("registry %d not found with replication event", remoteRegID)
}
srcNamespace, srcNameAndTag := getMetadataFromResource(task.SourceResource)
destNamespace, destNameAndTag := getMetadataFromResource(task.DestinationResource)

View File

@ -24,13 +24,10 @@ import (
"github.com/goharbor/harbor/src/controller/event"
"github.com/goharbor/harbor/src/controller/project"
repctl "github.com/goharbor/harbor/src/controller/replication"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/notification"
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
reppkg "github.com/goharbor/harbor/src/pkg/replication"
"github.com/goharbor/harbor/src/replication"
"github.com/goharbor/harbor/src/replication/model"
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
replicationtesting "github.com/goharbor/harbor/src/testing/controller/replication"
"github.com/goharbor/harbor/src/testing/mock"
@ -39,73 +36,26 @@ import (
"github.com/stretchr/testify/require"
)
type fakedReplicationRegistryMgr struct {
}
// Add new registry
func (f *fakedReplicationRegistryMgr) Add(*model.Registry) (int64, error) {
return 0, nil
}
// List registries, returns total count, registry list and error
func (f *fakedReplicationRegistryMgr) List(query *q.Query) (int64, []*model.Registry, error) {
return 0, nil, nil
}
// Get the specified registry
func (f *fakedReplicationRegistryMgr) Get(int64) (*model.Registry, error) {
return &model.Registry{
Type: "harbor",
Credential: &model.Credential{
Type: "local",
},
}, nil
}
// GetByName gets registry by name
func (f *fakedReplicationRegistryMgr) GetByName(name string) (*model.Registry, error) {
return nil, nil
}
// Update the registry, the "props" are the properties of registry
// that need to be updated
func (f *fakedReplicationRegistryMgr) Update(registry *model.Registry, props ...string) error {
return nil
}
// Remove the registry with the specified ID
func (f *fakedReplicationRegistryMgr) Remove(int64) error {
return nil
}
// HealthCheck checks health status of all registries and update result in database
func (f *fakedReplicationRegistryMgr) HealthCheck() error {
return nil
}
func TestReplicationHandler_Handle(t *testing.T) {
common_dao.PrepareTestForPostgresSQL()
config.Init()
PolicyMgr := notification.PolicyMgr
rpRegistry := replication.RegistryMgr
prj := project.Ctl
repCtl := repctl.Ctl
defer func() {
notification.PolicyMgr = PolicyMgr
replication.RegistryMgr = rpRegistry
project.Ctl = prj
repctl.Ctl = repCtl
}()
policyMgrMock := &testingnotification.Manager{}
notification.PolicyMgr = policyMgrMock
replication.RegistryMgr = &fakedReplicationRegistryMgr{}
projectCtl := &projecttesting.Controller{}
project.Ctl = projectCtl
mockRepCtl := &replicationtesting.Controller{}
repctl.Ctl = mockRepCtl
mockRepCtl.On("GetPolicy", mock.Anything, mock.Anything).Return(&reppkg.Policy{ID: 1}, nil)
mockRepCtl.On("GetPolicy", mock.Anything, mock.Anything).Return(&repctlmodel.Policy{ID: 1}, nil)
mockRepCtl.On("GetTask", mock.Anything, mock.Anything).Return(&repctl.Task{}, nil)
mockRepCtl.On("GetExecution", mock.Anything, mock.Anything).Return(&repctl.Execution{}, nil)
policyMgrMock.On("GetRelatedPolices", mock.Anything, mock.Anything, mock.Anything).Return([]*policy_model.Policy{

View File

@ -17,9 +17,10 @@ package proxy
import (
"fmt"
"github.com/docker/distribution"
"github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/registry"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/reg"
"github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"io"
)
@ -37,14 +38,14 @@ type RemoteInterface interface {
type remoteHelper struct {
regID int64
registry adapter.ArtifactRegistry
registryMgr registry.Manager
registryMgr reg.Manager
}
// NewRemoteHelper create a remote interface
func NewRemoteHelper(regID int64) (RemoteInterface, error) {
r := &remoteHelper{
regID: regID,
registryMgr: registry.NewDefaultManager()}
registryMgr: reg.Mgr}
if err := r.init(); err != nil {
return nil, err
}
@ -56,7 +57,7 @@ func (r *remoteHelper) init() error {
if r.registry != nil {
return nil
}
reg, err := r.registryMgr.Get(r.regID)
reg, err := r.registryMgr.Get(orm.Context(), r.regID)
if err != nil {
return err
}

View File

@ -0,0 +1,287 @@
// 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 registry
import (
"context"
"math/rand"
"time"
"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/q"
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/reg"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/replication"
)
// Ctl is a global registry controller instance
var Ctl = NewController()
var regularHealthCheckInterval = 5 * time.Minute
// Controller defines the registry related operations
type Controller interface {
// Create the registry
Create(ctx context.Context, registry *model.Registry) (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.Registry, err error)
// Get the registry specified by ID
Get(ctx context.Context, id int64) (registry *model.Registry, err error)
// Update the specified registry
Update(ctx context.Context, registry *model.Registry, props ...string) (err error)
// Delete the registry specified by ID
Delete(ctx context.Context, id int64) (err error)
// GetInfo returns the basic information and capabilities of the registry
GetInfo(ctx context.Context, id int64) (info *model.RegistryInfo, err error)
// IsHealthy checks whether the provided registry is healthy or not
IsHealthy(ctx context.Context, registry *model.Registry) (healthy bool, err error)
// ListRegistryProviderTypes returns all the registered registry provider type
ListRegistryProviderTypes(ctx context.Context) (types []string, err error)
// ListRegistryProviderInfos returns all the registered registry provider information
ListRegistryProviderInfos(ctx context.Context) (infos map[string]*model.AdapterPattern, err error)
// StartRegularHealthCheck for all registries
StartRegularHealthCheck(ctx context.Context, closing, done chan struct{})
}
// NewController creates an instance of the registry controller
func NewController() Controller {
return &controller{
regMgr: reg.Mgr,
repMgr: replication.Mgr,
proMgr: project.Mgr,
}
}
type controller struct {
regMgr reg.Manager
repMgr replication.Manager
proMgr project.Manager
}
func (c *controller) Create(ctx context.Context, registry *model.Registry) (int64, error) {
if err := c.validate(ctx, registry); err != nil {
return 0, err
}
return c.regMgr.Create(ctx, registry)
}
func (c *controller) validate(ctx context.Context, registry *model.Registry) error {
if len(registry.Name) == 0 {
return errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("name cannot be empty")
}
if len(registry.Name) > 64 {
return errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("the max length of name is 64")
}
url, err := lib.ValidateHTTPURL(registry.URL)
if err != nil {
return err
}
registry.URL = url
healthy, err := c.IsHealthy(ctx, registry)
if err != nil {
return err
}
if !healthy {
return errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("the registry is unhealthy")
}
registry.Status = model.Healthy
return nil
}
func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) {
return c.regMgr.Count(ctx, query)
}
func (c *controller) List(ctx context.Context, query *q.Query) ([]*model.Registry, error) {
return c.regMgr.List(ctx, query)
}
func (c *controller) Get(ctx context.Context, id int64) (*model.Registry, error) {
return c.regMgr.Get(ctx, id)
}
func (c *controller) Update(ctx context.Context, registry *model.Registry, props ...string) error {
if err := c.validate(ctx, registry); err != nil {
return err
}
return c.regMgr.Update(ctx, registry, props...)
}
func (c *controller) Delete(ctx context.Context, id int64) error {
// referenced by replication policy as source registry
count, err := c.repMgr.Count(ctx, &q.Query{
Keywords: map[string]interface{}{
"src_registry_id": id,
},
})
if err != nil {
return err
}
if count > 0 {
return errors.New(nil).WithCode(errors.PreconditionCode).WithMessage("the registry %d is referenced by replication policies, cannot delete it", id)
}
// referenced by replication policy as destination registry
count, err = c.repMgr.Count(ctx, &q.Query{
Keywords: map[string]interface{}{
"dest_registry_id": id,
},
})
if err != nil {
return err
}
if count > 0 {
return errors.New(nil).WithCode(errors.PreconditionCode).WithMessage("the registry %d is referenced by replication policies, cannot delete it", id)
}
// referenced by proxy cache project
count, err = c.proMgr.Count(ctx, &q.Query{
Keywords: map[string]interface{}{
"registry_id": id,
},
})
if err != nil {
return err
}
if count > 0 {
return errors.New(nil).WithCode(errors.PreconditionCode).WithMessage("the registry %d is referenced by proxy cache project, cannot delete it", id)
}
return c.regMgr.Delete(ctx, id)
}
func (c *controller) IsHealthy(ctx context.Context, registry *model.Registry) (bool, error) {
adapter, err := c.regMgr.CreateAdapter(ctx, registry)
if err != nil {
return false, err
}
status, err := adapter.HealthCheck()
if err != nil {
return false, err
}
return status == model.Healthy, nil
}
func (c *controller) GetInfo(ctx context.Context, id int64) (*model.RegistryInfo, error) {
var (
registry *model.Registry
err error
)
registry, err = c.regMgr.Get(ctx, id)
if err != nil {
return nil, err
}
adapter, err := c.regMgr.CreateAdapter(ctx, registry)
if err != nil {
return nil, err
}
info, err := adapter.Info()
if err != nil {
return nil, err
}
// currently, only the local Harbor registry supports the event based trigger, append it here
if id == 0 {
info.SupportedTriggers = append(info.SupportedTriggers, model.TriggerTypeEventBased)
}
info = process(info)
return info, nil
}
func (c *controller) ListRegistryProviderTypes(ctx context.Context) ([]string, error) {
return c.regMgr.ListRegistryProviderTypes(ctx)
}
func (c *controller) ListRegistryProviderInfos(ctx context.Context) (map[string]*model.AdapterPattern, error) {
return c.regMgr.ListRegistryProviderInfos(ctx)
}
func (c *controller) StartRegularHealthCheck(ctx context.Context, closing, done chan struct{}) {
// Wait some random time before starting health checking. If Harbor is deployed in HA mode
// with multiple instances, this will avoid instances check health in the same time.
<-time.After(time.Duration(rand.Int63n(int64(regularHealthCheckInterval))))
ticker := time.NewTicker(regularHealthCheckInterval)
log.Infof("Start regular health check for registries with interval %v", regularHealthCheckInterval)
for {
select {
case <-ticker.C:
registries, err := c.regMgr.List(ctx, nil)
if err != nil {
log.Errorf("failed to list registries: %v", err)
continue
}
for _, registry := range registries {
isHealthy, err := c.IsHealthy(ctx, registry)
if err != nil {
log.Errorf("failed to check health of registry %d: %v", registry.ID, err)
continue
}
status := model.Healthy
if !isHealthy {
status = model.Unhealthy
}
if registry.Status == status {
continue
}
registry.Status = status
if err = c.regMgr.Update(ctx, registry, "Status"); err != nil {
log.Errorf("failed to update the status of registry %d: %v", registry.ID, err)
continue
}
log.Debugf("update the status of registry %d to %s", registry.ID, status)
}
case <-closing:
log.Info("Stop registry health checker")
// No cleanup works to do, signal done directly
close(done)
return
}
}
}
// merge "SupportedResourceTypes" into "SupportedResourceFilters" for UI to render easier
func process(info *model.RegistryInfo) *model.RegistryInfo {
if info == nil {
return nil
}
in := &model.RegistryInfo{
Type: info.Type,
Description: info.Description,
SupportedTriggers: info.SupportedTriggers,
}
filters := []*model.FilterStyle{}
for _, filter := range info.SupportedResourceFilters {
if filter.Type != model.FilterTypeResource {
filters = append(filters, filter)
}
}
values := []string{}
for _, resourceType := range info.SupportedResourceTypes {
values = append(values, string(resourceType))
}
filters = append(filters, &model.FilterStyle{
Type: model.FilterTypeResource,
Style: model.FilterStyleTypeRadio,
Values: values,
})
in.SupportedResourceFilters = filters
return in
}

View File

@ -0,0 +1,178 @@
// 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 registry
import (
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/testing/mock"
"testing"
testingproject "github.com/goharbor/harbor/src/testing/pkg/project"
testingreg "github.com/goharbor/harbor/src/testing/pkg/reg"
testingadapter "github.com/goharbor/harbor/src/testing/pkg/reg/adapter"
testingrep "github.com/goharbor/harbor/src/testing/pkg/replication"
"github.com/stretchr/testify/suite"
)
type registryTestSuite struct {
suite.Suite
ctl *controller
repMgr *testingrep.Manager
regMgr *testingreg.Manager
proMgr *testingproject.Manager
adapter *testingadapter.Adapter
}
func (r *registryTestSuite) SetupTest() {
r.repMgr = &testingrep.Manager{}
r.regMgr = &testingreg.Manager{}
r.proMgr = &testingproject.Manager{}
r.adapter = &testingadapter.Adapter{}
r.ctl = &controller{
repMgr: r.repMgr,
regMgr: r.regMgr,
proMgr: r.proMgr,
}
}
func (r *registryTestSuite) TestValidate() {
// empty name
registry := &model.Registry{
Name: "",
}
err := r.ctl.validate(nil, registry)
r.NotNil(err)
// empty URL
registry = &model.Registry{
Name: "endpoint01",
URL: "",
}
err = r.ctl.validate(nil, registry)
r.NotNil(err)
// invalid HTTP URL
registry = &model.Registry{
Name: "endpoint01",
URL: "ftp://example.com",
}
err = r.ctl.validate(nil, registry)
r.NotNil(err)
// URL without scheme
registry = &model.Registry{
Name: "endpoint01",
URL: "example.com",
}
mock.OnAnything(r.regMgr, "CreateAdapter").Return(r.adapter, nil)
mock.OnAnything(r.adapter, "HealthCheck").Return(model.Healthy, nil)
err = r.ctl.validate(nil, registry)
r.Nil(err)
r.Equal("http://example.com", registry.URL)
r.regMgr.AssertExpectations(r.T())
r.adapter.AssertExpectations(r.T())
r.SetupTest()
// URL with HTTP scheme
registry = &model.Registry{
Name: "endpoint01",
URL: "http://example.com",
}
mock.OnAnything(r.regMgr, "CreateAdapter").Return(r.adapter, nil)
mock.OnAnything(r.adapter, "HealthCheck").Return(model.Healthy, nil)
err = r.ctl.validate(nil, registry)
r.Nil(err)
r.Equal("http://example.com", registry.URL)
r.regMgr.AssertExpectations(r.T())
r.adapter.AssertExpectations(r.T())
r.SetupTest()
// unhealthy
registry = &model.Registry{
Name: "endpoint01",
URL: "http://example.com",
}
mock.OnAnything(r.regMgr, "CreateAdapter").Return(r.adapter, nil)
mock.OnAnything(r.adapter, "HealthCheck").Return(model.Unhealthy, nil)
err = r.ctl.validate(nil, registry)
r.NotNil(err)
r.regMgr.AssertExpectations(r.T())
r.adapter.AssertExpectations(r.T())
r.SetupTest()
// URL with HTTPS scheme
registry = &model.Registry{
Name: "endpoint01",
URL: "https://example.com",
}
mock.OnAnything(r.regMgr, "CreateAdapter").Return(r.adapter, nil)
mock.OnAnything(r.adapter, "HealthCheck").Return(model.Healthy, nil)
err = r.ctl.validate(nil, registry)
r.Nil(err)
r.Equal("https://example.com", registry.URL)
r.regMgr.AssertExpectations(r.T())
r.adapter.AssertExpectations(r.T())
r.SetupTest()
// URL with query string
registry = &model.Registry{
Name: "endpoint01",
URL: "http://example.com/redirect?key=value",
}
mock.OnAnything(r.regMgr, "CreateAdapter").Return(r.adapter, nil)
mock.OnAnything(r.adapter, "HealthCheck").Return(model.Healthy, nil)
err = r.ctl.validate(nil, registry)
r.Nil(err)
r.Equal("http://example.com/redirect", registry.URL)
r.regMgr.AssertExpectations(r.T())
r.adapter.AssertExpectations(r.T())
}
func (r *registryTestSuite) TestDelete() {
// referenced by replication policy
mock.OnAnything(r.repMgr, "Count").Return(int64(1), nil)
err := r.ctl.Delete(nil, 1)
r.NotNil(err)
r.repMgr.AssertExpectations(r.T())
r.SetupTest()
// referenced by proxy cache project
mock.OnAnything(r.repMgr, "Count").Return(int64(0), nil)
mock.OnAnything(r.proMgr, "Count").Return(int64(1), nil)
err = r.ctl.Delete(nil, 1)
r.NotNil(err)
r.repMgr.AssertExpectations(r.T())
r.proMgr.AssertExpectations(r.T())
r.SetupTest()
// pass
mock.OnAnything(r.repMgr, "Count").Return(int64(0), nil)
mock.OnAnything(r.proMgr, "Count").Return(int64(0), nil)
mock.OnAnything(r.regMgr, "Delete").Return(nil)
err = r.ctl.Delete(nil, 1)
r.Nil(err)
r.repMgr.AssertExpectations(r.T())
r.proMgr.AssertExpectations(r.T())
}
func TestRegistryTestSuite(t *testing.T) {
suite.Run(t, &registryTestSuite{})
}

View File

@ -20,6 +20,7 @@ import (
"time"
"github.com/goharbor/harbor/src/controller/replication/flow"
replicationmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/errors"
@ -27,10 +28,10 @@ import (
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/reg"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/replication"
"github.com/goharbor/harbor/src/pkg/scheduler"
"github.com/goharbor/harbor/src/pkg/task"
"github.com/goharbor/harbor/src/replication/model"
)
func init() {
@ -46,17 +47,17 @@ type Controller interface {
// PolicyCount returns the total count of policies according to the query
PolicyCount(ctx context.Context, query *q.Query) (count int64, err error)
// ListPolicies lists the policies according to the query
ListPolicies(ctx context.Context, query *q.Query) (policies []*replication.Policy, err error)
ListPolicies(ctx context.Context, query *q.Query) (policies []*replicationmodel.Policy, err error)
// GetPolicy gets the specific policy
GetPolicy(ctx context.Context, id int64) (policy *replication.Policy, err error)
GetPolicy(ctx context.Context, id int64) (policy *replicationmodel.Policy, err error)
// CreatePolicy creates a policy
CreatePolicy(ctx context.Context, policy *replication.Policy) (id int64, err error)
CreatePolicy(ctx context.Context, policy *replicationmodel.Policy) (id int64, err error)
// UpdatePolicy updates the specific policy
UpdatePolicy(ctx context.Context, policy *replication.Policy, props ...string) (err error)
UpdatePolicy(ctx context.Context, policy *replicationmodel.Policy, props ...string) (err error)
// DeletePolicy deletes the specific policy
DeletePolicy(ctx context.Context, id int64) (err error)
// Start the replication according to the policy
Start(ctx context.Context, policy *replication.Policy, resource *model.Resource, trigger string) (executionID int64, err error)
Start(ctx context.Context, policy *replicationmodel.Policy, resource *model.Resource, trigger string) (executionID int64, err error)
// Stop the replication specified by the execution ID
Stop(ctx context.Context, executionID int64) (err error)
// ExecutionCount returns the total count of executions according to the query
@ -100,7 +101,7 @@ type controller struct {
wp *lib.WorkerPool
}
func (c *controller) Start(ctx context.Context, policy *replication.Policy, resource *model.Resource, trigger string) (int64, error) {
func (c *controller) Start(ctx context.Context, policy *replicationmodel.Policy, resource *model.Resource, trigger string) (int64, error) {
logger := log.GetLogger(ctx)
if !policy.Enabled {
return 0, errors.New(nil).WithCode(errors.PreconditionCode).

View File

@ -20,9 +20,9 @@ import (
"testing"
"time"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/pkg/replication"
"github.com/goharbor/harbor/src/pkg/task"
"github.com/goharbor/harbor/src/pkg/task/dao"
"github.com/goharbor/harbor/src/testing/lib/orm"
@ -68,7 +68,7 @@ func (r *replicationTestSuite) SetupTest() {
func (r *replicationTestSuite) TestStart() {
// policy is disabled
id, err := r.ctl.Start(context.Background(), &replication.Policy{Enabled: false}, nil, task.ExecutionTriggerManual)
id, err := r.ctl.Start(context.Background(), &repctlmodel.Policy{Enabled: false}, nil, task.ExecutionTriggerManual)
r.Require().NotNil(err)
// got error when running the replication flow
@ -78,7 +78,7 @@ func (r *replicationTestSuite) TestStart() {
r.execMgr.On("MarkError", mock.Anything, mock.Anything, mock.Anything).Return(nil)
r.flowCtl.On("Start", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("error"))
r.ormCreator.On("Create").Return(nil)
id, err = r.ctl.Start(context.Background(), &replication.Policy{Enabled: true}, nil, task.ExecutionTriggerManual)
id, err = r.ctl.Start(context.Background(), &repctlmodel.Policy{Enabled: true}, nil, task.ExecutionTriggerManual)
r.Require().Nil(err)
r.Equal(int64(1), id)
time.Sleep(1 * time.Second) // wait the functions called in the goroutine
@ -94,7 +94,7 @@ func (r *replicationTestSuite) TestStart() {
r.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{}, nil)
r.flowCtl.On("Start", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
r.ormCreator.On("Create").Return(nil)
id, err = r.ctl.Start(context.Background(), &replication.Policy{Enabled: true}, nil, task.ExecutionTriggerManual)
id, err = r.ctl.Start(context.Background(), &repctlmodel.Policy{Enabled: true}, nil, task.ExecutionTriggerManual)
r.Require().Nil(err)
r.Equal(int64(1), id)
time.Sleep(1 * time.Second) // wait the functions called in the goroutine

View File

@ -16,9 +16,9 @@ package flow
import (
"context"
"github.com/goharbor/harbor/src/pkg/replication"
"github.com/goharbor/harbor/src/replication/model"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
// Flow defines a specific replication flow
@ -28,7 +28,7 @@ type Flow interface {
// Controller controls the replication flow
type Controller interface {
Start(ctx context.Context, executionID int64, policy *replication.Policy, resource *model.Resource) (err error)
Start(ctx context.Context, executionID int64, policy *repctlmodel.Policy, resource *model.Resource) (err error)
}
// NewController returns an instance of the default flow controller
@ -38,7 +38,7 @@ func NewController() Controller {
type controller struct{}
func (c *controller) Start(ctx context.Context, executionID int64, policy *replication.Policy, resource *model.Resource) error {
func (c *controller) Start(ctx context.Context, executionID int64, policy *repctlmodel.Policy, resource *model.Resource) error {
// deletion flow
if resource != nil && resource.Deleted {
return NewDeletionFlow(executionID, policy, resource).Run(ctx)

View File

@ -17,18 +17,18 @@ package flow
import (
"context"
"encoding/json"
"github.com/goharbor/harbor/src/pkg/replication"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/task"
"github.com/goharbor/harbor/src/replication/model"
)
type copyFlow struct {
executionID int64
resources []*model.Resource
policy *replication.Policy
policy *repctlmodel.Policy
executionMgr task.ExecutionManager
taskMgr task.Manager
}
@ -36,7 +36,7 @@ type copyFlow struct {
// NewCopyFlow returns an instance of the copy flow which replicates the resources from
// the source registry to the destination registry. If the parameter "resources" isn't provided,
// will fetch the resources first
func NewCopyFlow(executionID int64, policy *replication.Policy, resources ...*model.Resource) Flow {
func NewCopyFlow(executionID int64, policy *repctlmodel.Policy, resources ...*model.Resource) Flow {
return &copyFlow{
executionMgr: task.ExecMgr,
taskMgr: task.Mgr,

View File

@ -12,15 +12,15 @@ package flow
import (
"context"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/pkg/replication"
"github.com/goharbor/harbor/src/replication/adapter"
"github.com/stretchr/testify/mock"
"testing"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/task"
"github.com/goharbor/harbor/src/replication/model"
testingTask "github.com/goharbor/harbor/src/testing/pkg/task"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
@ -36,7 +36,7 @@ func (c *copyFlowTestSuite) TestRun() {
adapter.RegisterFactory("TEST_FOR_COPY_FLOW", factory)
adp.On("Info").Return(&model.RegistryInfo{
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeArtifact,
},
}, nil)
@ -61,7 +61,7 @@ func (c *copyFlowTestSuite) TestRun() {
taskMgr := &testingTask.Manager{}
taskMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
policy := &replication.Policy{
policy := &repctlmodel.Policy{
SrcRegistry: &model.Registry{
Type: "TEST_FOR_COPY_FLOW",
},

View File

@ -17,16 +17,16 @@ package flow
import (
"context"
"encoding/json"
"github.com/goharbor/harbor/src/pkg/replication"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/task"
"github.com/goharbor/harbor/src/replication/model"
)
type deletionFlow struct {
executionID int64
policy *replication.Policy
policy *repctlmodel.Policy
executionMgr task.ExecutionManager
taskMgr task.Manager
resources []*model.Resource
@ -34,7 +34,7 @@ type deletionFlow struct {
// NewDeletionFlow returns an instance of the delete flow which deletes the resources
// on the destination registry
func NewDeletionFlow(executionID int64, policy *replication.Policy, resources ...*model.Resource) Flow {
func NewDeletionFlow(executionID int64, policy *repctlmodel.Policy, resources ...*model.Resource) Flow {
return &deletionFlow{
executionMgr: task.ExecMgr,
taskMgr: task.Mgr,

View File

@ -16,10 +16,10 @@ package flow
import (
"context"
"github.com/goharbor/harbor/src/pkg/replication"
"testing"
"github.com/goharbor/harbor/src/replication/model"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/testing/pkg/task"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
@ -33,7 +33,7 @@ func (d *deletionFlowTestSuite) TestRun() {
taskMgr := &task.Manager{}
taskMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
policy := &replication.Policy{
policy := &repctlmodel.Policy{
SrcRegistry: &model.Registry{
Type: model.RegistryTypeHarbor,
},

View File

@ -15,7 +15,7 @@
package flow
import (
"github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter"
)
// define a new interface to combine the two interfaces of adapter for mockery to generate the mocks
@ -25,4 +25,4 @@ type registryAdapter interface {
}
//go:generate mockery --dir . --name registryAdapter --output . --outpkg flow --filename mock_adapter_test.go --structname mockAdapter
//go:generate mockery --dir ../../../replication/adapter --name Factory --output . --outpkg flow --filename mock_adapter_factory_test.go --structname mockFactory
//go:generate mockery --dir ../../../pkg/reg/adapter --name Factory --output . --outpkg flow --filename mock_adapter_factory_test.go --structname mockFactory

View File

@ -3,10 +3,10 @@
package flow
import (
adapter "github.com/goharbor/harbor/src/replication/adapter"
adapter "github.com/goharbor/harbor/src/pkg/reg/adapter"
mock "github.com/stretchr/testify/mock"
model "github.com/goharbor/harbor/src/replication/model"
model "github.com/goharbor/harbor/src/pkg/reg/model"
)
// mockFactory is an autogenerated mock type for the Factory type

View File

@ -9,7 +9,7 @@ import (
mock "github.com/stretchr/testify/mock"
model "github.com/goharbor/harbor/src/replication/model"
model "github.com/goharbor/harbor/src/pkg/reg/model"
)
// mockAdapter is an autogenerated mock type for the registryAdapter type
@ -90,14 +90,14 @@ func (_m *mockAdapter) FetchArtifacts(filters []*model.Filter) ([]*model.Resourc
}
// HealthCheck provides a mock function with given fields:
func (_m *mockAdapter) HealthCheck() (model.HealthStatus, error) {
func (_m *mockAdapter) HealthCheck() (string, error) {
ret := _m.Called()
var r0 model.HealthStatus
if rf, ok := ret.Get(0).(func() model.HealthStatus); ok {
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(model.HealthStatus)
r0 = ret.Get(0).(string)
}
var r1 error

View File

@ -16,16 +16,16 @@ package flow
import (
"fmt"
"github.com/goharbor/harbor/src/pkg/replication"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
)
// get/create the source registry, destination registry, source adapter and destination adapter
func initialize(policy *replication.Policy) (adp.Adapter, adp.Adapter, error) {
func initialize(policy *repctlmodel.Policy) (adp.Adapter, adp.Adapter, error) {
var srcAdapter, dstAdapter adp.Adapter
var err error
@ -53,11 +53,11 @@ func initialize(policy *replication.Policy) (adp.Adapter, adp.Adapter, error) {
}
// fetch resources from the source registry
func fetchResources(adapter adp.Adapter, policy *replication.Policy) ([]*model.Resource, error) {
var resTypes []model.ResourceType
func fetchResources(adapter adp.Adapter, policy *repctlmodel.Policy) ([]*model.Resource, error) {
var resTypes []string
for _, filter := range policy.Filters {
if filter.Type == model.FilterTypeResource {
resTypes = append(resTypes, filter.Value.(model.ResourceType))
resTypes = append(resTypes, filter.Value.(string))
}
}
if len(resTypes) == 0 {
@ -112,7 +112,7 @@ func fetchResources(adapter adp.Adapter, policy *replication.Policy) ([]*model.R
// assemble the source resources by filling the registry information
func assembleSourceResources(resources []*model.Resource,
policy *replication.Policy) []*model.Resource {
policy *repctlmodel.Policy) []*model.Resource {
for _, resource := range resources {
resource.Registry = policy.SrcRegistry
}
@ -122,7 +122,7 @@ func assembleSourceResources(resources []*model.Resource,
// assemble the destination resources by filling the metadata, registry and override properties
func assembleDestinationResources(resources []*model.Resource,
policy *replication.Policy) []*model.Resource {
policy *repctlmodel.Policy) []*model.Resource {
var result []*model.Resource
for _, resource := range resources {
res := &model.Resource{

View File

@ -15,11 +15,11 @@
package flow
import (
"github.com/goharbor/harbor/src/pkg/replication"
"testing"
"github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/stretchr/testify/suite"
)
@ -36,7 +36,7 @@ func (s *stageTestSuite) TestInitialize() {
factory.On("AdapterPattern").Return(nil)
adapter.RegisterFactory(model.RegistryTypeHarbor, factory)
policy := &replication.Policy{
policy := &repctlmodel.Policy{
SrcRegistry: &model.Registry{
Type: model.RegistryTypeHarbor,
},
@ -53,7 +53,7 @@ func (s *stageTestSuite) TestInitialize() {
func (s *stageTestSuite) TestFetchResources() {
adapter := &mockAdapter{}
adapter.On("Info").Return(&model.RegistryInfo{
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeArtifact,
},
}, nil)
@ -61,7 +61,7 @@ func (s *stageTestSuite) TestFetchResources() {
{},
{},
}, nil)
policy := &replication.Policy{}
policy := &repctlmodel.Policy{}
resources, err := fetchResources(adapter, policy)
s.Require().Nil(err)
s.Len(resources, 2)
@ -81,7 +81,7 @@ func (s *stageTestSuite) TestAssembleSourceResources() {
Override: false,
},
}
policy := &replication.Policy{
policy := &repctlmodel.Policy{
SrcRegistry: &model.Registry{
ID: 1,
},
@ -104,7 +104,7 @@ func (s *stageTestSuite) TestAssembleDestinationResources() {
Override: false,
},
}
policy := &replication.Policy{
policy := &repctlmodel.Policy{
DestRegistry: &model.Registry{},
DestNamespace: "test",
Override: true,

View File

@ -7,9 +7,9 @@ import (
mock "github.com/stretchr/testify/mock"
model "github.com/goharbor/harbor/src/replication/model"
model "github.com/goharbor/harbor/src/controller/replication/model"
replication "github.com/goharbor/harbor/src/pkg/replication"
regmodel "github.com/goharbor/harbor/src/pkg/reg/model"
)
// flowController is an autogenerated mock type for the Controller type
@ -18,11 +18,11 @@ type flowController struct {
}
// Start provides a mock function with given fields: ctx, executionID, policy, resource
func (_m *flowController) Start(ctx context.Context, executionID int64, policy *replication.Policy, resource *model.Resource) error {
func (_m *flowController) Start(ctx context.Context, executionID int64, policy *model.Policy, resource *regmodel.Resource) error {
ret := _m.Called(ctx, executionID, policy, resource)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, *replication.Policy, *model.Resource) error); ok {
if rf, ok := ret.Get(0).(func(context.Context, int64, *model.Policy, *regmodel.Resource) error); ok {
r0 = rf(ctx, executionID, policy, resource)
} else {
r0 = ret.Error(0)

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package replication
package model
import (
"encoding/json"
@ -23,8 +23,8 @@ import (
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/replication/dao"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
replicationmodel "github.com/goharbor/harbor/src/pkg/replication/model"
)
// Policy defines the structure of a replication policy
@ -86,7 +86,7 @@ func (p *Policy) Validate() error {
WithMessage("the type of filter value isn't string")
}
if filter.Type == model.FilterTypeResource {
rt := model.ResourceType(value)
rt := value
if !(rt == model.ResourceTypeArtifact || rt == model.ResourceTypeImage || rt == model.ResourceTypeChart) {
return errors.New(nil).WithCode(errors.BadRequestCode).
WithMessage("invalid resource filter: %s", value)
@ -132,8 +132,8 @@ func (p *Policy) Validate() error {
return nil
}
// From converts the DAO model into the Policy
func (p *Policy) From(policy *dao.Policy) error {
// From converts the pkg model into the Policy
func (p *Policy) From(policy *replicationmodel.Policy) error {
if policy == nil {
return nil
}
@ -176,9 +176,9 @@ func (p *Policy) From(policy *dao.Policy) error {
return nil
}
// To converts to DAO model
func (p *Policy) To() (*dao.Policy, error) {
policy := &dao.Policy{
// To converts to pkg model
func (p *Policy) To() (*replicationmodel.Policy, error) {
policy := &replicationmodel.Policy{
ID: p.ID,
Name: p.Name,
Description: p.Description,
@ -217,14 +217,14 @@ func (p *Policy) To() (*dao.Policy, error) {
}
type filter struct {
Type model.FilterType `json:"type"`
Value interface{} `json:"value"`
Kind string `json:"kind"`
Pattern string `json:"pattern"`
Type string `json:"type"`
Value interface{} `json:"value"`
Kind string `json:"kind"`
Pattern string `json:"pattern"`
}
type trigger struct {
Type model.TriggerType `json:"type"`
Type string `json:"type"`
Settings *model.TriggerSettings `json:"trigger_settings"`
Kind string `json:"kind"`
ScheduleParam *scheduleParam `json:"schedule_param"`
@ -287,7 +287,7 @@ func parseFilters(str string) ([]*model.Filter, error) {
// convert the type of value from string to model.ResourceType if the filter
// is a resource type filter
if filter.Type == model.FilterTypeResource {
filter.Value = (model.ResourceType)(filter.Value.(string))
filter.Value = filter.Value.(string)
}
if filter.Type == model.FilterTypeLabel {
labels := []string{}

View File

@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package replication
package model
import (
"testing"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
)
@ -125,29 +125,6 @@ func TestValidate(t *testing.T) {
err = policy.Validate()
assert.True(errors.IsErr(err, errors.BadRequestCode))
// invalid filter
policy = &Policy{
Name: "policy01",
SrcRegistry: &model.Registry{
ID: 0,
},
DestRegistry: &model.Registry{
ID: 1,
},
Filters: []*model.Filter{
{
Type: model.FilterTypeResource,
Value: model.ResourceTypeImage,
},
{
Type: model.FilterTypeTag,
Value: "",
},
},
}
err = policy.Validate()
assert.True(errors.IsErr(err, errors.BadRequestCode))
// invalid trigger
policy = &Policy{
Name: "policy01",

View File

@ -18,15 +18,13 @@ import (
"context"
"strconv"
commonthttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/replication"
pkgmodel "github.com/goharbor/harbor/src/pkg/replication/model"
"github.com/goharbor/harbor/src/pkg/scheduler"
"github.com/goharbor/harbor/src/pkg/task"
"github.com/goharbor/harbor/src/replication/config"
"github.com/goharbor/harbor/src/replication/model"
)
const callbackFuncName = "REPLICATION_CALLBACK"
@ -54,70 +52,81 @@ func (c *controller) PolicyCount(ctx context.Context, query *q.Query) (int64, er
return c.repMgr.Count(ctx, query)
}
func (c *controller) ListPolicies(ctx context.Context, query *q.Query) ([]*replication.Policy, error) {
func (c *controller) ListPolicies(ctx context.Context, query *q.Query) ([]*model.Policy, error) {
policies, err := c.repMgr.List(ctx, query)
if err != nil {
return nil, err
}
var ps []*model.Policy
for _, policy := range policies {
if err := c.populateRegistry(ctx, policy); err != nil {
p, err := c.populateRegistry(ctx, policy)
if err != nil {
return nil, err
}
ps = append(ps, p)
}
return policies, nil
return ps, nil
}
func (c *controller) populateRegistry(ctx context.Context, policy *replication.Policy) error {
if policy.SrcRegistry != nil && policy.SrcRegistry.ID > 0 {
registry, err := c.regMgr.Get(ctx, policy.SrcRegistry.ID)
if err != nil {
return err
}
policy.SrcRegistry = registry
policy.DestRegistry = GetLocalRegistry()
return nil
func (c *controller) populateRegistry(ctx context.Context, p *pkgmodel.Policy) (*model.Policy, error) {
policy := &model.Policy{}
policy.From(p)
var srcRegistryID, destRegistryID int64 = 0, 0
if policy.SrcRegistry != nil && policy.SrcRegistry.ID != 0 {
srcRegistryID = policy.SrcRegistry.ID
destRegistryID = 0
} else {
srcRegistryID = 0
destRegistryID = policy.DestRegistry.ID
}
registry, err := c.regMgr.Get(ctx, policy.DestRegistry.ID)
srcRegistry, err := c.regMgr.Get(ctx, srcRegistryID)
if err != nil {
return err
return nil, err
}
policy.DestRegistry = registry
policy.SrcRegistry = GetLocalRegistry()
return nil
policy.SrcRegistry = srcRegistry
destRegistry, err := c.regMgr.Get(ctx, destRegistryID)
if err != nil {
return nil, err
}
policy.DestRegistry = destRegistry
return policy, nil
}
func (c *controller) GetPolicy(ctx context.Context, id int64) (*replication.Policy, error) {
func (c *controller) GetPolicy(ctx context.Context, id int64) (*model.Policy, error) {
policy, err := c.repMgr.Get(ctx, id)
if err != nil {
return nil, err
}
if err = c.populateRegistry(ctx, policy); err != nil {
return nil, err
}
return policy, nil
return c.populateRegistry(ctx, policy)
}
func (c *controller) CreatePolicy(ctx context.Context, policy *replication.Policy) (int64, error) {
func (c *controller) CreatePolicy(ctx context.Context, policy *model.Policy) (int64, error) {
if err := c.validatePolicy(ctx, policy); err != nil {
return 0, err
}
p, err := policy.To()
if err != nil {
return 0, err
}
// create policy
id, err := c.repMgr.Create(ctx, policy)
id, err := c.repMgr.Create(ctx, p)
if err != nil {
return 0, err
}
// create schedule if needed
if policy.IsScheduledTrigger() {
if _, err = c.scheduler.Schedule(ctx, job.Replication, id, "", policy.Trigger.Settings.Cron,
callbackFuncName, policy.ID, map[string]interface{}{}); err != nil {
callbackFuncName, id, map[string]interface{}{}); err != nil {
return 0, err
}
}
return id, nil
}
func (c *controller) UpdatePolicy(ctx context.Context, policy *replication.Policy, props ...string) error {
func (c *controller) UpdatePolicy(ctx context.Context, policy *model.Policy, props ...string) error {
if err := c.validatePolicy(ctx, policy); err != nil {
return err
}
@ -125,8 +134,13 @@ func (c *controller) UpdatePolicy(ctx context.Context, policy *replication.Polic
if err := c.scheduler.UnScheduleByVendor(ctx, job.Replication, policy.ID); err != nil {
return err
}
p, err := policy.To()
if err != nil {
return err
}
// update the policy
if err := c.repMgr.Update(ctx, policy); err != nil {
if err := c.repMgr.Update(ctx, p); err != nil {
return err
}
// create schedule if needed
@ -139,7 +153,7 @@ func (c *controller) UpdatePolicy(ctx context.Context, policy *replication.Polic
return nil
}
func (c *controller) validatePolicy(ctx context.Context, policy *replication.Policy) error {
func (c *controller) validatePolicy(ctx context.Context, policy *model.Policy) error {
if err := policy.Validate(); err != nil {
return err
}
@ -168,21 +182,3 @@ func (c *controller) DeletePolicy(ctx context.Context, id int64) error {
// delete the policy
return c.repMgr.Delete(ctx, id)
}
// GetLocalRegistry returns the info of the local Harbor registry
// TODO move it into the registry package
func GetLocalRegistry() *model.Registry {
return &model.Registry{
Type: model.RegistryTypeHarbor,
Name: "Local",
URL: config.Config.CoreURL,
TokenServiceURL: config.Config.TokenServiceURL,
Status: "healthy",
Credential: &model.Credential{
Type: model.CredentialTypeSecret,
// use secret to do the auth for the local Harbor
AccessSecret: config.Config.JobserviceSecret,
},
Insecure: !commonthttp.InternalTLSEnabled(),
}
}

View File

@ -15,9 +15,9 @@
package replication
import (
"github.com/goharbor/harbor/src/pkg/replication"
"github.com/goharbor/harbor/src/replication/config"
"github.com/goharbor/harbor/src/replication/model"
repmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
replicationmodel "github.com/goharbor/harbor/src/pkg/replication/model"
"github.com/goharbor/harbor/src/testing/mock"
)
@ -30,18 +30,15 @@ func (r *replicationTestSuite) TestPolicyCount() {
}
func (r *replicationTestSuite) TestListPolicies() {
mock.OnAnything(r.repMgr, "List").Return([]*replication.Policy{
mock.OnAnything(r.repMgr, "List").Return([]*replicationmodel.Policy{
{
ID: 1,
SrcRegistry: &model.Registry{
ID: 1,
},
ID: 1,
SrcRegistryID: 1,
},
}, nil)
mock.OnAnything(r.regMgr, "Get").Return(&model.Registry{
ID: 1,
}, nil)
config.Config = &config.Configuration{}
policies, err := r.ctl.ListPolicies(nil, nil)
r.Require().Nil(err)
r.Require().Len(policies, 1)
@ -51,16 +48,13 @@ func (r *replicationTestSuite) TestListPolicies() {
}
func (r *replicationTestSuite) TestGetPolicy() {
mock.OnAnything(r.repMgr, "Get").Return(&replication.Policy{
ID: 1,
SrcRegistry: &model.Registry{
ID: 1,
},
mock.OnAnything(r.repMgr, "Get").Return(&replicationmodel.Policy{
ID: 1,
SrcRegistryID: 1,
}, nil)
mock.OnAnything(r.regMgr, "Get").Return(&model.Registry{
ID: 1,
}, nil)
config.Config = &config.Configuration{}
policy, err := r.ctl.GetPolicy(nil, 1)
r.Require().Nil(err)
r.Equal(int64(1), policy.ID)
@ -74,7 +68,7 @@ func (r *replicationTestSuite) TestCreatePolicy() {
ID: 1,
}, nil)
mock.OnAnything(r.scheduler, "Schedule").Return(int64(1), nil)
id, err := r.ctl.CreatePolicy(nil, &replication.Policy{
id, err := r.ctl.CreatePolicy(nil, &repmodel.Policy{
Name: "rule",
SrcRegistry: &model.Registry{
ID: 1,
@ -101,7 +95,7 @@ func (r *replicationTestSuite) TestUpdatePolicy() {
mock.OnAnything(r.scheduler, "UnScheduleByVendor").Return(nil)
mock.OnAnything(r.scheduler, "Schedule").Return(int64(1), nil)
mock.OnAnything(r.repMgr, "Update").Return(nil)
err := r.ctl.UpdatePolicy(nil, &replication.Policy{
err := r.ctl.UpdatePolicy(nil, &repmodel.Policy{
ID: 1,
Name: "rule",
SrcRegistry: &model.Registry{

View File

@ -17,10 +17,10 @@ package chart
import (
"errors"
trans "github.com/goharbor/harbor/src/controller/replication/transfer"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
trans "github.com/goharbor/harbor/src/replication/transfer"
"github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
func init() {

View File

@ -20,9 +20,9 @@ import (
"io/ioutil"
"testing"
trans "github.com/goharbor/harbor/src/controller/replication/transfer"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/replication/model"
trans "github.com/goharbor/harbor/src/replication/transfer"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View File

@ -22,17 +22,16 @@ import (
"strings"
"time"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema1"
common_http "github.com/goharbor/harbor/src/common/http"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema2"
common_http "github.com/goharbor/harbor/src/common/http"
trans "github.com/goharbor/harbor/src/controller/replication/transfer"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
trans "github.com/goharbor/harbor/src/replication/transfer"
"github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
var (

View File

@ -16,16 +16,16 @@ package image
import (
"bytes"
"github.com/opencontainers/go-digest"
"io"
"io/ioutil"
"testing"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema2"
trans "github.com/goharbor/harbor/src/controller/replication/transfer"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/replication/model"
trans "github.com/goharbor/harbor/src/replication/transfer"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View File

@ -18,11 +18,11 @@ import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
var (
registry = map[model.ResourceType]Factory{}
registry = map[string]Factory{}
)
// Factory creates a specific Transfer. The "Logger" is used
@ -62,9 +62,9 @@ type Logger interface {
type StopFunc func() bool
// RegisterFactory registers one transfer factory to the registry
func RegisterFactory(name model.ResourceType, factory Factory) error {
if !name.Valid() {
return errors.New("invalid resource transfer factory name")
func RegisterFactory(name string, factory Factory) error {
if len(name) == 0 {
return errors.New("empty name")
}
if factory == nil {
return errors.New("empty resource transfer factory")
@ -78,7 +78,7 @@ func RegisterFactory(name model.ResourceType, factory Factory) error {
}
// GetFactory gets the transfer factory by the specified name
func GetFactory(name model.ResourceType) (Factory, error) {
func GetFactory(name string) (Factory, error) {
factory, exist := registry[name]
if !exist {
return nil, fmt.Errorf("transfer factory for %s not found", name)

View File

@ -17,7 +17,6 @@ package transfer
import (
"testing"
"github.com/goharbor/harbor/src/replication/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -42,7 +41,7 @@ func TestRegisterFactory(t *testing.T) {
}
func TestGetFactory(t *testing.T) {
registry = map[model.ResourceType]Factory{}
registry = map[string]Factory{}
err := RegisterFactory("faked_factory", fakedFactory)
require.Nil(t, err)
// try to get the factory that doesn't exist

View File

@ -18,14 +18,14 @@ import (
"github.com/goharbor/harbor/src/chartserver"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/rbac"
rep_event "github.com/goharbor/harbor/src/controller/event/handler/replication/event"
"github.com/goharbor/harbor/src/controller/event/metadata"
"github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/core/label"
hlog "github.com/goharbor/harbor/src/lib/log"
n_event "github.com/goharbor/harbor/src/pkg/notifier/event"
rep_event "github.com/goharbor/harbor/src/replication/event"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/server/middleware/orm"
)

View File

@ -15,12 +15,8 @@
package api
import (
"os"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
rep_dao "github.com/goharbor/harbor/src/replication/dao"
rep_models "github.com/goharbor/harbor/src/replication/dao/models"
)
const (
@ -83,27 +79,6 @@ func CommonDelProject() {
_ = dao.DeleteProject(commonProject.ProjectID)
}
func CommonAddRegistry() {
endPoint := os.Getenv("REGISTRY_URL")
commonRegistry := &rep_models.Registry{
URL: endPoint,
Name: TestRegistryName,
AccessKey: adminName,
AccessSecret: adminPwd,
}
_, _ = rep_dao.AddRegistry(commonRegistry)
}
func CommonGetRegistry() int {
registry, _ := rep_dao.GetRegistryByName(TestRegistryName)
return int(registry.ID)
}
func CommonDelRegistry() {
registry, _ := rep_dao.GetRegistryByName(TestRegistryName)
_ = rep_dao.DeleteRegistry(registry.ID)
}
func CommonAddRepository() {
commonRepository := &models.RepoRecord{
RepositoryID: 31,

View File

@ -39,7 +39,6 @@ import (
_ "github.com/goharbor/harbor/src/core/auth/db"
_ "github.com/goharbor/harbor/src/core/auth/ldap"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/server/middleware"
"github.com/goharbor/harbor/src/server/middleware/orm"
"github.com/goharbor/harbor/src/server/middleware/security"
@ -107,17 +106,12 @@ func init() {
beego.Router("/api/statistics", &StatisticAPI{})
beego.Router("/api/users/?:id", &UserAPI{})
beego.Router("/api/usergroups/?:ugid([0-9]+)", &UserGroupAPI{})
beego.Router("/api/registries", &RegistryAPI{}, "get:List;post:Post")
beego.Router("/api/registries/ping", &RegistryAPI{}, "post:Ping")
beego.Router("/api/registries/:id([0-9]+)", &RegistryAPI{}, "get:Get;put:Put;delete:Delete")
beego.Router("/api/configurations", &ConfigAPI{})
beego.Router("/api/configs", &ConfigAPI{}, "get:GetInternalConfig")
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
beego.Router("/api/labels", &LabelAPI{}, "post:Post;get:List")
beego.Router("/api/labels/:id([0-9]+", &LabelAPI{}, "get:Get;put:Put;delete:Delete")
beego.Router("/api/replication/adapters", &ReplicationAdapterAPI{}, "get:List")
// Charts are controlled under projects
chartRepositoryAPIType := &ChartRepositoryAPI{}
beego.Router("/api/chartrepo/health", chartRepositoryAPIType, "get:GetHealthStatus")
@ -812,40 +806,6 @@ func (a testapi) DeleteMeta(authInfor usrInfo, projectID int64, name string) (in
return code, string(body), err
}
func (a testapi) RegistryGet(authInfo usrInfo, registryID int64) (*model.Registry, int, error) {
_sling := sling.New().Base(a.basePath).Get(fmt.Sprintf("/api/registries/%d", registryID))
code, body, err := request(_sling, jsonAcceptHeader, authInfo)
if err == nil && code == http.StatusOK {
registry := model.Registry{}
if err := json.Unmarshal(body, &registry); err != nil {
return nil, code, err
}
return &registry, code, nil
}
return nil, code, err
}
func (a testapi) RegistryList(authInfo usrInfo) ([]*model.Registry, int, error) {
_sling := sling.New().Base(a.basePath).Get("/api/registries")
code, body, err := request(_sling, jsonAcceptHeader, authInfo)
if err != nil || code != http.StatusOK {
return nil, code, err
}
var registries []*model.Registry
if err := json.Unmarshal(body, &registries); err != nil {
return nil, code, err
}
return registries, code, nil
}
func (a testapi) RegistryCreate(authInfo usrInfo, registry *model.Registry) (int, error) {
_sling := sling.New().Base(a.basePath).Post("/api/registries").BodyJSON(registry)
code, _, err := request(_sling, jsonAcceptHeader, authInfo)
return code, err
}
type pingReq struct {
ID *int64 `json:"id"`
Type *string `json:"type"`

View File

@ -1,554 +0,0 @@
package api
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/permission/types"
"net/http"
"strconv"
"github.com/goharbor/harbor/src/common/utils"
rep "github.com/goharbor/harbor/src/controller/replication"
"github.com/goharbor/harbor/src/core/api/models"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/replication"
"github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/registry"
)
// RegistryAPI handles requests to /api/registries/{}. It manages registries integrated to Harbor.
type RegistryAPI struct {
BaseController
manager registry.Manager
resource types.Resource
}
// Prepare validates the user
func (t *RegistryAPI) Prepare() {
t.BaseController.Prepare()
if !t.SecurityCtx.IsAuthenticated() {
t.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
t.resource = system.NewNamespace().Resource(rbac.ResourceRegistry)
t.manager = replication.RegistryMgr
}
// Ping checks health status of a registry
func (t *RegistryAPI) Ping() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionRead, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
req := struct {
ID *int64 `json:"id"`
Type *string `json:"type"`
URL *string `json:"url"`
Region *string `json:"region"`
CredentialType *string `json:"credential_type"`
AccessKey *string `json:"access_key"`
AccessSecret *string `json:"access_secret"`
Insecure *bool `json:"insecure"`
}{}
t.DecodeJSONReq(&req)
reg := &model.Registry{}
var err error
if req.ID != nil {
reg, err = t.manager.Get(*req.ID)
if err != nil {
t.SendInternalServerError(fmt.Errorf("failed to get registry %d: %v", *req.ID, err))
return
}
if reg == nil {
t.SendNotFoundError(fmt.Errorf("registry %d not found", *req.ID))
return
}
}
if req.Type != nil {
reg.Type = model.RegistryType(*req.Type)
}
if req.URL != nil {
url, err := utils.ParseEndpoint(*req.URL)
if err != nil {
t.SendBadRequestError(err)
return
}
// Prevent SSRF security issue #3755
reg.URL = url.Scheme + "://" + url.Host + url.Path
}
if req.CredentialType != nil {
if reg.Credential == nil {
reg.Credential = &model.Credential{}
}
reg.Credential.Type = model.CredentialType(*req.CredentialType)
}
if req.AccessKey != nil {
if reg.Credential == nil {
reg.Credential = &model.Credential{}
}
reg.Credential.AccessKey = *req.AccessKey
}
if req.AccessSecret != nil {
if reg.Credential == nil {
reg.Credential = &model.Credential{}
}
reg.Credential.AccessSecret = *req.AccessSecret
}
if req.Insecure != nil {
reg.Insecure = *req.Insecure
}
if len(reg.Type) == 0 || len(reg.URL) == 0 {
t.SendBadRequestError(errors.New("type or url cannot be empty"))
return
}
status := t.getHealthStatus(reg)
if status != model.Healthy {
t.SendBadRequestError(errors.New("the registry is unhealthy"))
return
}
return
}
// Get gets a registry by id.
func (t *RegistryAPI) Get() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionRead, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
id, err := t.GetIDFromURL()
if err != nil {
t.SendBadRequestError(err)
return
}
r, err := t.manager.Get(id)
if err != nil {
log.Errorf("failed to get registry %d: %v", id, err)
t.SendInternalServerError(err)
return
}
if r == nil {
t.SendNotFoundError(fmt.Errorf("registry %d not found", id))
return
}
// Hide access secret
hideAccessSecret(r.Credential)
t.Data["json"] = r
t.ServeJSON()
}
func hideAccessSecret(credential *model.Credential) {
if credential == nil {
return
}
if len(credential.AccessSecret) == 0 {
return
}
credential.AccessSecret = "*****"
}
// List lists all registries
func (t *RegistryAPI) List() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionList, rbac.ResourceRegistry) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
queryStr := t.GetString("q")
// keep backward compatibility for the "name" query
if len(queryStr) == 0 {
name := t.GetString("name")
if len(name) > 0 {
queryStr = fmt.Sprintf("name=~%s", name)
}
}
query, err := q.Build(queryStr, "", 0, 0)
if err != nil {
t.SendError(err)
return
}
_, registries, err := t.manager.List(query)
if err != nil {
t.SendInternalServerError(err)
return
}
// Hide passwords
for _, r := range registries {
hideAccessSecret(r.Credential)
}
t.Data["json"] = registries
t.ServeJSON()
return
}
// Post creates a registry
func (t *RegistryAPI) Post() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionCreate, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
r := &model.Registry{}
isValid, err := t.DecodeJSONReqAndValidate(r)
if !isValid {
t.SendBadRequestError(err)
return
}
reg, err := t.manager.GetByName(r.Name)
if err != nil {
log.Errorf("failed to get registry %s: %v", r.Name, err)
t.SendInternalServerError(err)
return
}
if reg != nil {
t.SendConflictError(fmt.Errorf("name '%s' is already used", r.Name))
return
}
url, err := utils.ParseEndpoint(r.URL)
if err != nil {
t.SendBadRequestError(err)
return
}
// Prevent SSRF security issue #3755
r.URL = url.Scheme + "://" + url.Host + url.Path
status := t.getHealthStatus(r)
if status != model.Healthy {
t.SendBadRequestError(errors.New("the registry is unhealthy"))
return
}
r.Status = model.Healthy
id, err := t.manager.Add(r)
if err != nil {
log.Errorf("Add registry '%s' error: %v", r.URL, err)
t.SendInternalServerError(err)
return
}
t.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
}
func (t *RegistryAPI) getHealthStatus(r *model.Registry) string {
status, err := registry.CheckHealthStatus(r)
if err != nil {
log.Errorf("failed to check the health status of registry %s: %v", r.URL, err)
return model.Unhealthy
}
return string(status)
}
// Put updates a registry
func (t *RegistryAPI) Put() {
if !t.SecurityCtx.Can(t.Context(), rbac.ActionUpdate, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
id, err := t.GetIDFromURL()
if err != nil {
t.SendBadRequestError(err)
return
}
r, err := t.manager.Get(id)
if err != nil {
log.Errorf("Get registry by id %d error: %v", id, err)
t.SendInternalServerError(err)
return
}
if r == nil {
t.SendNotFoundError(fmt.Errorf("Registry %d not found", id))
return
}
req := models.RegistryUpdateRequest{}
if err := t.DecodeJSONReq(&req); err != nil {
t.SendBadRequestError(err)
return
}
originalName := r.Name
if req.Name != nil {
r.Name = *req.Name
}
if req.Description != nil {
r.Description = *req.Description
}
if req.URL != nil {
r.URL = *req.URL
}
if req.CredentialType != nil {
r.Credential.Type = (model.CredentialType)(*req.CredentialType)
}
if req.AccessKey != nil {
r.Credential.AccessKey = *req.AccessKey
}
if req.AccessSecret != nil {
r.Credential.AccessSecret = *req.AccessSecret
}
if req.Insecure != nil {
r.Insecure = *req.Insecure
}
t.Validate(r)
if r.Name != originalName {
reg, err := t.manager.GetByName(r.Name)
if err != nil {
log.Errorf("Get registry by name '%s' error: %v", r.Name, err)
t.SendInternalServerError(err)
return
}
if reg != nil {
t.SendConflictError(errors.New("name is already used"))
return
}
}
status := t.getHealthStatus(r)
if status != model.Healthy {
t.SendBadRequestError(errors.New("the registry is unhealthy"))
return
}
r.Status = model.Healthy
if err := t.manager.Update(r); err != nil {
log.Errorf("Update registry %d error: %v", id, err)
t.SendInternalServerError(err)
return
}
}
// Delete deletes a registry
func (t *RegistryAPI) Delete() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionDelete, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
id, err := t.GetIDFromURL()
if err != nil {
t.SendBadRequestError(err)
return
}
registry, err := t.manager.Get(id)
if err != nil {
msg := fmt.Sprintf("Get registry %d error: %v", id, err)
log.Error(msg)
t.SendInternalServerError(errors.New(msg))
return
}
if registry == nil {
t.SendNotFoundError(fmt.Errorf("Registry %d not found", id))
return
}
// Check whether there are replication policies that use this registry as source registry.
total, err := rep.Ctl.PolicyCount(orm.Context(), &q.Query{
Keywords: map[string]interface{}{
"SrcRegistryID": id,
},
})
if err != nil {
t.SendInternalServerError(fmt.Errorf("List replication policies with source registry %d error: %v", id, err))
return
}
if total > 0 {
msg := fmt.Sprintf("Can't delete registry %d, %d replication policies use it as source registry", id, total)
log.Error(msg)
t.SendPreconditionFailedError(errors.New(msg))
return
}
// Check whether there are replication policies that use this registry as destination registry.
total, err = rep.Ctl.PolicyCount(orm.Context(), &q.Query{
Keywords: map[string]interface{}{
"DestRegistryID": id,
},
})
if err != nil {
t.SendInternalServerError(fmt.Errorf("List replication policies with destination registry %d error: %v", id, err))
return
}
if total > 0 {
msg := fmt.Sprintf("Can't delete registry %d, %d replication policies use it as destination registry", id, total)
log.Error(msg)
t.SendPreconditionFailedError(errors.New(msg))
return
}
// check whether the registry is referenced by any proxy cache projects
count, err := t.ProjectCtl.Count(t.Context(), q.New(q.KeyWords{"registry_id": id}))
if err != nil {
t.SendInternalServerError(fmt.Errorf("failed to list projects: %v", err))
return
}
if count > 0 {
t.SendPreconditionFailedError(fmt.Errorf("Can't delete registry %d, %d proxy cache projects referennce it", id, count))
return
}
if err := t.manager.Remove(id); err != nil {
msg := fmt.Sprintf("Delete registry %d error: %v", id, err)
log.Error(msg)
t.SendPreconditionFailedError(errors.New(msg))
return
}
}
// GetInfo returns the base info and capability declarations of the registry
func (t *RegistryAPI) GetInfo() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionRead, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
id, err := t.GetInt64FromPath(":id")
// "0" is used for the ID of the local Harbor registry
if err != nil || id < 0 {
t.SendBadRequestError(fmt.Errorf("invalid registry ID %s", t.GetString(":id")))
return
}
var registry *model.Registry
if id == 0 {
registry = rep.GetLocalRegistry()
} else {
registry, err = t.manager.Get(id)
if err != nil {
t.SendInternalServerError(fmt.Errorf("failed to get registry %d: %v", id, err))
return
}
if registry == nil {
t.SendNotFoundError(fmt.Errorf("registry %d not found", id))
return
}
}
factory, err := adapter.GetFactory(registry.Type)
if err != nil {
t.SendInternalServerError(fmt.Errorf("failed to get the adapter factory for registry type %s: %v", registry.Type, err))
return
}
adp, err := factory.Create(registry)
if err != nil {
t.SendInternalServerError(fmt.Errorf("failed to create the adapter for registry %d: %v", registry.ID, err))
return
}
info, err := adp.Info()
if err != nil {
t.ParseAndHandleError(fmt.Sprintf("failed to get registry info %d", id), err)
return
}
// currently, only the local Harbor registry supports the event based trigger, append it here
if id == 0 {
info.SupportedTriggers = append(info.SupportedTriggers, model.TriggerTypeEventBased)
}
t.WriteJSONData(process(info))
}
// GetNamespace get the namespace of a registry
// TODO remove
func (t *RegistryAPI) GetNamespace() {
/*
var registry *model.Registry
var err error
id, err := t.GetInt64FromPath(":id")
if err != nil || id < 0 {
t.HandleBadRequest(fmt.Sprintf("invalid registry ID %s", t.GetString(":id")))
return
}
if id > 0 {
registry, err = t.manager.Get(id)
if err != nil {
t.HandleInternalServerError(fmt.Sprintf("failed to get registry %d: %v", id, err))
return
}
} else if id == 0 {
registry = event.GetLocalRegistry()
}
if registry == nil {
t.HandleNotFound(fmt.Sprintf("registry %d not found", id))
return
}
if !adapter.HasFactory(registry.Type) {
t.HandleInternalServerError(fmt.Sprintf("no adapter factory found for %s", registry.Type))
return
}
regFactory, err := adapter.GetFactory(registry.Type)
if err != nil {
t.HandleInternalServerError(fmt.Sprintf("fail to get adapter factory %s", registry.Type))
return
}
regAdapter, err := regFactory(registry)
if err != nil {
t.HandleInternalServerError(fmt.Sprintf("fail to get adapter %s", registry.Type))
return
}
query := &model.NamespaceQuery{
Name: t.GetString("name"),
}
npResults, err := regAdapter.ListNamespaces(query)
if err != nil {
t.HandleInternalServerError(fmt.Sprintf("fail to list namespaces %s %v", registry.Type, err))
return
}
t.Data["json"] = npResults
t.ServeJSON()
*/
}
// merge "SupportedResourceTypes" into "SupportedResourceFilters" for UI to render easier
func process(info *model.RegistryInfo) *model.RegistryInfo {
if info == nil {
return nil
}
in := &model.RegistryInfo{
Type: info.Type,
Description: info.Description,
SupportedTriggers: info.SupportedTriggers,
}
filters := []*model.FilterStyle{}
for _, filter := range info.SupportedResourceFilters {
if filter.Type != model.FilterTypeResource {
filters = append(filters, filter)
}
}
values := []string{}
for _, resourceType := range info.SupportedResourceTypes {
values = append(values, string(resourceType))
}
filters = append(filters, &model.FilterStyle{
Type: model.FilterTypeResource,
Style: model.FilterStyleTypeRadio,
Values: values,
})
in.SupportedResourceFilters = filters
return in
}

View File

@ -1,184 +0,0 @@
package api
import (
"net/http"
"testing"
"github.com/goharbor/harbor/src/core/api/models"
"github.com/goharbor/harbor/src/replication"
"github.com/goharbor/harbor/src/replication/dao"
"github.com/goharbor/harbor/src/replication/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
var (
testRegistry = &model.Registry{
Name: "test1",
URL: "https://registry-1.docker.io",
Type: "harbor",
Credential: nil,
}
testRegistry2 = &model.Registry{
Name: "test2",
URL: "https://registry-1.docker.io",
Type: "harbor",
Credential: nil,
}
)
type RegistrySuite struct {
suite.Suite
testAPI *testapi
defaultRegistry model.Registry
}
func (suite *RegistrySuite) SetupSuite() {
assert := assert.New(suite.T())
assert.Nil(replication.Init(make(chan struct{}), make(chan struct{})))
suite.testAPI = newHarborAPI()
code, err := suite.testAPI.RegistryCreate(*admin, testRegistry)
assert.Nil(err)
assert.Equal(http.StatusCreated, code)
tmp, err := dao.GetRegistryByName(testRegistry.Name)
assert.Nil(err)
assert.NotNil(tmp)
suite.defaultRegistry = *testRegistry
suite.defaultRegistry.ID = tmp.ID
CommonAddUser()
}
func (suite *RegistrySuite) TearDownSuite() {
assert := assert.New(suite.T())
code, err := suite.testAPI.RegistryDelete(*admin, suite.defaultRegistry.ID)
assert.Nil(err)
assert.Equal(http.StatusOK, code)
CommonDelUser()
}
func (suite *RegistrySuite) TestGet() {
assert := assert.New(suite.T())
// Get a non-existed registry
_, code, _ := suite.testAPI.RegistryGet(*admin, 0)
assert.Equal(http.StatusBadRequest, code)
// Get as admin, should succeed
retrieved, code, err := suite.testAPI.RegistryGet(*admin, suite.defaultRegistry.ID)
assert.Nil(err)
assert.NotNil(retrieved)
assert.Equal(http.StatusOK, code)
assert.Equal("test1", retrieved.Name)
// Get as user, should fail
_, code, _ = suite.testAPI.RegistryGet(*testUser, suite.defaultRegistry.ID)
assert.Equal(http.StatusForbidden, code)
}
func (suite *RegistrySuite) TestList() {
assert := assert.New(suite.T())
// List as admin, should succeed
registries, code, err := suite.testAPI.RegistryList(*admin)
assert.Nil(err)
assert.Equal(http.StatusOK, code)
assert.Equal(1, len(registries))
// List as user, should fail
registries, code, err = suite.testAPI.RegistryList(*testUser)
assert.Equal(http.StatusForbidden, code)
assert.Equal(0, len(registries))
}
func (suite *RegistrySuite) TestPost() {
assert := assert.New(suite.T())
// Should conflict when create exited registry
code, err := suite.testAPI.RegistryCreate(*admin, testRegistry)
assert.Nil(err)
assert.Equal(http.StatusConflict, code)
// Create as user, should fail
code, err = suite.testAPI.RegistryCreate(*testUser, testRegistry2)
assert.Nil(err)
assert.Equal(http.StatusForbidden, code)
}
func (suite *RegistrySuite) TestPing() {
assert := assert.New(suite.T())
code, err := suite.testAPI.RegistryPing(*admin, &pingReq{
ID: &suite.defaultRegistry.ID,
})
assert.Nil(err)
assert.Equal(http.StatusOK, code)
var id int64 = -1
code, err = suite.testAPI.RegistryPing(*admin, &pingReq{
ID: &id,
})
assert.Nil(err)
assert.Equal(http.StatusNotFound, code)
code, err = suite.testAPI.RegistryPing(*admin, nil)
assert.Nil(err)
assert.Equal(http.StatusBadRequest, code)
code, err = suite.testAPI.RegistryPing(*testUser, &pingReq{
ID: &suite.defaultRegistry.ID,
})
assert.Nil(err)
assert.Equal(http.StatusForbidden, code)
}
func (suite *RegistrySuite) TestRegistryPut() {
assert := assert.New(suite.T())
// Update as admin, should succeed
description := "foobar"
updateReq := &models.RegistryUpdateRequest{
Description: &description,
}
code, err := suite.testAPI.RegistryUpdate(*admin, suite.defaultRegistry.ID, updateReq)
assert.Nil(err)
assert.Equal(http.StatusOK, code)
updated, code, err := suite.testAPI.RegistryGet(*admin, suite.defaultRegistry.ID)
assert.Nil(err)
assert.Equal(http.StatusOK, code)
assert.Equal("foobar", updated.Description)
// Update as user, should fail
code, err = suite.testAPI.RegistryUpdate(*testUser, suite.defaultRegistry.ID, updateReq)
assert.NotNil(err)
assert.Equal(http.StatusForbidden, code)
}
func (suite *RegistrySuite) TestDelete() {
assert := assert.New(suite.T())
code, err := suite.testAPI.RegistryCreate(*admin, testRegistry2)
assert.Nil(err)
assert.Equal(http.StatusCreated, code)
tmp, err := dao.GetRegistryByName(testRegistry2.Name)
assert.Nil(err)
assert.NotNil(tmp)
// Delete as user, should fail
code, err = suite.testAPI.RegistryDelete(*testUser, tmp.ID)
assert.NotNil(err)
assert.Equal(http.StatusForbidden, code)
// Delete as admin, should succeed
code, err = suite.testAPI.RegistryDelete(*admin, tmp.ID)
assert.Nil(err)
assert.Equal(http.StatusOK, code)
}
func TestRegistrySuite(t *testing.T) {
suite.Run(t, new(RegistrySuite))
}

View File

@ -1,60 +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 (
"errors"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/pkg/permission/types"
"github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
)
// ReplicationAdapterAPI handles the replication adapter requests
type ReplicationAdapterAPI struct {
BaseController
resource types.Resource
}
// Prepare ...
func (r *ReplicationAdapterAPI) Prepare() {
r.BaseController.Prepare()
if !r.SecurityCtx.IsAuthenticated() {
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
r.resource = system.NewNamespace().Resource(rbac.ResourceReplicationAdapter)
}
// List the replication adapters
func (r *ReplicationAdapterAPI) List() {
if !r.SecurityCtx.Can(r.Context(), rbac.ActionList, r.resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return
}
types := []model.RegistryType{}
types = append(types, adapter.ListRegisteredAdapterTypes()...)
r.WriteJSONData(types)
}
// ListAdapterInfos the replication adapter infos
func (r *ReplicationAdapterAPI) ListAdapterInfos() {
if !r.SecurityCtx.Can(r.Context(), rbac.ActionList, r.resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return
}
r.WriteJSONData(adapter.ListAdapterInfos())
}

View File

@ -1,70 +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/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
"github.com/stretchr/testify/require"
)
type fakedFactory struct {
}
func (fakedFactory) Create(*model.Registry) (adapter.Adapter, error) {
return nil, nil
}
func (fakedFactory) AdapterPattern() *model.AdapterPattern {
return nil
}
func TestReplicationAdapterAPIList(t *testing.T) {
err := adapter.RegisterFactory("test", new(fakedFactory))
require.Nil(t, err)
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/adapters",
},
code: http.StatusUnauthorized,
},
// 403
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/adapters",
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 200
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/adapters",
credential: sysAdmin,
},
code: http.StatusOK,
},
}
runCodeCheckingCases(t, cases...)
}

View File

@ -34,6 +34,7 @@ import (
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils"
_ "github.com/goharbor/harbor/src/controller/event/handler"
"github.com/goharbor/harbor/src/controller/registry"
"github.com/goharbor/harbor/src/core/api"
_ "github.com/goharbor/harbor/src/core/auth/authproxy"
_ "github.com/goharbor/harbor/src/core/auth/db"
@ -55,7 +56,6 @@ import (
"github.com/goharbor/harbor/src/pkg/scan"
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
"github.com/goharbor/harbor/src/pkg/version"
"github.com/goharbor/harbor/src/replication"
"github.com/goharbor/harbor/src/server"
)
@ -211,9 +211,8 @@ func main() {
closing := make(chan struct{})
done := make(chan struct{})
go gracefulShutdown(closing, done)
if err := replication.Init(closing, done); err != nil {
log.Fatalf("failed to init for replication: %v", err)
}
// Start health checker for registries
go registry.Ctl.StartRegularHealthCheck(orm.Context(), closing, done)
log.Info("initializing notification...")
notification.Init()

View File

@ -18,46 +18,14 @@ import (
"encoding/json"
"fmt"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/transfer"
// import chart transfer
_ "github.com/goharbor/harbor/src/replication/transfer/chart"
_ "github.com/goharbor/harbor/src/controller/replication/transfer/chart"
// import image transfer
_ "github.com/goharbor/harbor/src/replication/transfer/image"
// register the Harbor adapter
_ "github.com/goharbor/harbor/src/replication/adapter/harbor"
// register the DockerHub adapter
_ "github.com/goharbor/harbor/src/replication/adapter/dockerhub"
// register the Native adapter
_ "github.com/goharbor/harbor/src/replication/adapter/native"
// register the Huawei adapter
_ "github.com/goharbor/harbor/src/replication/adapter/huawei"
// register the Google Gcr adapter
_ "github.com/goharbor/harbor/src/replication/adapter/googlegcr"
// register the AwsEcr adapter
_ "github.com/goharbor/harbor/src/replication/adapter/awsecr"
// register the AzureAcr adapter
_ "github.com/goharbor/harbor/src/replication/adapter/azurecr"
// register the AliACR adapter
_ "github.com/goharbor/harbor/src/replication/adapter/aliacr"
// register the Jfrog Artifactory adapter
_ "github.com/goharbor/harbor/src/replication/adapter/jfrog"
// register the Quay.io adapter
_ "github.com/goharbor/harbor/src/replication/adapter/quay"
// register the Helm Hub adapter
_ "github.com/goharbor/harbor/src/replication/adapter/helmhub"
// register the GitLab adapter
_ "github.com/goharbor/harbor/src/replication/adapter/gitlab"
// register the DTR adapter
_ "github.com/goharbor/harbor/src/replication/adapter/dtr"
// register the Artifact Hub adapter
_ "github.com/goharbor/harbor/src/replication/adapter/artifacthub"
// register the TencentCloud TCR adapter
_ "github.com/goharbor/harbor/src/replication/adapter/tencentcr"
// register the Github Container Registry adapter
_ "github.com/goharbor/harbor/src/replication/adapter/githubcr"
_ "github.com/goharbor/harbor/src/controller/replication/transfer/image"
"github.com/goharbor/harbor/src/controller/replication/transfer"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
// Replication implements the job interface

View File

@ -17,9 +17,9 @@ package replication
import (
"testing"
"github.com/goharbor/harbor/src/controller/replication/transfer"
"github.com/goharbor/harbor/src/jobservice/job/impl"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/transfer"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

46
src/lib/endpoint.go Normal file
View 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 lib
import (
"fmt"
"net/url"
"strings"
"github.com/goharbor/harbor/src/lib/errors"
)
// ValidateHTTPURL checks whether the provided string is a valid HTTP URL.
// If it it, return the URL in format "scheme://host:port" to avoid the SSRF
func ValidateHTTPURL(s string) (string, error) {
s = strings.Trim(s, " ")
s = strings.TrimRight(s, "/")
if len(s) == 0 {
return "", errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("empty string")
}
if !strings.Contains(s, "://") {
s = "http://" + s
}
url, err := url.ParseRequestURI(s)
if err != nil {
return "", errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid URL: %s", err.Error())
}
if url.Scheme != "http" && url.Scheme != "https" {
return "", errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid HTTP scheme: %s", url.Scheme)
}
// To avoid SSRF security issue, refer to #3755 for more detail
return fmt.Sprintf("%s://%s%s", url.Scheme, url.Host, url.Path), nil
}

View File

@ -21,18 +21,17 @@ import (
"sort"
"github.com/docker/distribution"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
// const definition
const (
UserAgentReplication = "harbor-replication-service"
MaxConcurrency = 100
MaxConcurrency = 100
)
var registry = map[model.RegistryType]Factory{}
var registry = map[string]Factory{}
var registryKeys = []string{}
var adapterInfoMap = map[model.RegistryType]*model.AdapterPattern{}
var adapterInfoMap = map[string]*model.AdapterPattern{}
// Factory creates a specific Adapter according to the params
type Factory interface {
@ -48,7 +47,7 @@ type Adapter interface {
// eg: create the namespace or repository
PrepareForPush([]*model.Resource) error
// HealthCheck checks health status of registry
HealthCheck() (model.HealthStatus, error)
HealthCheck() (string, error)
}
// ArtifactRegistry defines the capabilities that an artifact registry should have
@ -74,7 +73,7 @@ type ChartRegistry interface {
}
// RegisterFactory registers one adapter factory to the registry
func RegisterFactory(t model.RegistryType, factory Factory) error {
func RegisterFactory(t string, factory Factory) error {
if len(t) == 0 {
return errors.New("invalid registry type")
}
@ -86,7 +85,7 @@ func RegisterFactory(t model.RegistryType, factory Factory) error {
return fmt.Errorf("adapter factory for %s already exists", t)
}
registry[t] = factory
registryKeys = append(registryKeys, string(t))
registryKeys = append(registryKeys, t)
sort.Strings(registryKeys)
adapterInfo := factory.AdapterPattern()
if adapterInfo != nil {
@ -96,7 +95,7 @@ func RegisterFactory(t model.RegistryType, factory Factory) error {
}
// GetFactory gets the adapter factory by the specified name
func GetFactory(t model.RegistryType) (Factory, error) {
func GetFactory(t string) (Factory, error) {
factory, exist := registry[t]
if !exist {
return nil, fmt.Errorf("adapter factory for %s not found", t)
@ -104,22 +103,12 @@ func GetFactory(t model.RegistryType) (Factory, error) {
return factory, nil
}
// HasFactory checks whether there is given type adapter factory
func HasFactory(t model.RegistryType) bool {
_, ok := registry[t]
return ok
}
// ListRegisteredAdapterTypes lists the registered Adapter type
func ListRegisteredAdapterTypes() []model.RegistryType {
types := []model.RegistryType{}
for _, t := range registryKeys {
types = append(types, model.RegistryType(t))
}
return types
func ListRegisteredAdapterTypes() []string {
return registryKeys
}
// ListAdapterInfos list the adapter infos
func ListAdapterInfos() map[model.RegistryType]*model.AdapterPattern {
// ListRegisteredAdapterInfos list the adapter infos
func ListRegisteredAdapterInfos() map[string]*model.AdapterPattern {
return adapterInfoMap
}

View File

@ -17,7 +17,7 @@ package adapter
import (
"testing"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -45,7 +45,7 @@ func TestRegisterFactory(t *testing.T) {
}
func TestGetFactory(t *testing.T) {
registry = map[model.RegistryType]Factory{}
registry = map[string]Factory{}
require.Nil(t, RegisterFactory("harbor", new(fakedFactory)))
// doesn't exist
_, err := GetFactory("gcr")
@ -56,7 +56,7 @@ func TestGetFactory(t *testing.T) {
}
func TestListRegisteredAdapterTypes(t *testing.T) {
registry = map[model.RegistryType]Factory{}
registry = map[string]Factory{}
registryKeys = []string{}
// not register, got nothing
types := ListRegisteredAdapterTypes()
@ -67,11 +67,11 @@ func TestListRegisteredAdapterTypes(t *testing.T) {
types = ListRegisteredAdapterTypes()
require.Equal(t, 1, len(types))
assert.Equal(t, model.RegistryType("harbor"), types[0])
assert.Equal(t, "harbor", types[0])
}
func TestListRegisteredAdapterTypesOrder(t *testing.T) {
registry = map[model.RegistryType]Factory{}
registry = map[string]Factory{}
registryKeys = []string{}
require.Nil(t, RegisterFactory("a", new(fakedFactory)))
require.Nil(t, RegisterFactory("c", new(fakedFactory)))
@ -79,7 +79,7 @@ func TestListRegisteredAdapterTypesOrder(t *testing.T) {
types := ListRegisteredAdapterTypes()
require.Equal(t, 3, len(types))
require.Equal(t, model.RegistryType("a"), types[0])
require.Equal(t, model.RegistryType("b"), types[1])
require.Equal(t, model.RegistryType("c"), types[2])
require.Equal(t, "a", types[0])
require.Equal(t, "b", types[1])
require.Equal(t, "c", types[2])
}

View File

@ -16,11 +16,11 @@ import (
commonhttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
"github.com/goharbor/harbor/src/pkg/registry/auth/bearer"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
)
func init() {
@ -123,7 +123,7 @@ var _ adp.Adapter = &adapter{}
func (a *adapter) Info() (info *model.RegistryInfo, err error) {
info = &model.RegistryInfo{
Type: model.RegistryTypeAliAcr,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeImage,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -136,7 +136,7 @@ func (a *adapter) Info() (info *model.RegistryInfo, err error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},

View File

@ -10,8 +10,8 @@ import (
"time"
"github.com/goharbor/harbor/src/common/utils/test"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
)

View File

@ -18,8 +18,8 @@ import (
"errors"
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
func init() {
@ -73,7 +73,7 @@ func newAdapter(registry *model.Registry) (*adapter, error) {
func (a *adapter) Info() (*model.RegistryInfo, error) {
return &model.RegistryInfo{
Type: model.RegistryTypeArtifactHub,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeChart,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -86,7 +86,7 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},
@ -98,7 +98,7 @@ func (a *adapter) PrepareForPush(resources []*model.Resource) error {
}
// HealthCheck checks health status of a registry
func (a *adapter) HealthCheck() (model.HealthStatus, error) {
func (a *adapter) HealthCheck() (string, error) {
err := a.client.checkHealthy()
if err == nil {
return model.Healthy, nil

View File

@ -1,8 +1,8 @@
package artifacthub
import (
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
"testing"
)

View File

@ -17,8 +17,8 @@ package artifacthub
import (
"fmt"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/replication/filter"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/filter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"io"
"io/ioutil"
"net/http"

View File

@ -4,8 +4,8 @@ import (
"encoding/json"
"fmt"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
"io/ioutil"
"net/http"
)

View File

@ -21,9 +21,9 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
awsecrapi "github.com/aws/aws-sdk-go/service/ecr"
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
const (
@ -95,7 +95,7 @@ type adapter struct {
func (*adapter) Info() (info *model.RegistryInfo, err error) {
return &model.RegistryInfo{
Type: model.RegistryTypeAwsEcr,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeImage,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -108,7 +108,7 @@ func (*adapter) Info() (info *model.RegistryInfo, err error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},
@ -203,7 +203,7 @@ func getAdapterInfo() *model.AdapterPattern {
}
// HealthCheck checks health status of a registry
func (a *adapter) HealthCheck() (model.HealthStatus, error) {
func (a *adapter) HealthCheck() (string, error) {
if err := a.Ping(); err != nil {
log.Errorf("failed to ping registry %s: %v", a.registry.URL, err)
return model.Unhealthy, nil

View File

@ -14,9 +14,9 @@ import (
"time"
"github.com/goharbor/harbor/src/common/utils/test"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
)

View File

@ -2,9 +2,9 @@ package azurecr
import (
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
func init() {
@ -47,7 +47,7 @@ var (
func (a *adapter) Info() (*model.RegistryInfo, error) {
return &model.RegistryInfo{
Type: model.RegistryTypeAzureAcr,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeImage,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -60,7 +60,7 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},

View File

@ -3,7 +3,7 @@ package azurecr
import (
"testing"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
)

View File

@ -11,10 +11,10 @@ import (
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
)
func init() {
@ -73,7 +73,7 @@ var _ adp.Adapter = (*adapter)(nil)
func (a *adapter) Info() (*model.RegistryInfo, error) {
return &model.RegistryInfo{
Type: model.RegistryTypeDockerHub,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeImage,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -86,7 +86,7 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},
@ -450,7 +450,7 @@ func (a *adapter) getTags(namespace, repo string, page, pageSize int) (*TagsResp
}
// getFilter gets specific type filter value from filters list.
func (a *adapter) getStringFilterValue(filterType model.FilterType, filters []*model.Filter) (string, error) {
func (a *adapter) getStringFilterValue(filterType string, filters []*model.Filter) (string, error) {
for _, f := range filters {
if f.Type == filterType {
v, ok := f.Value.(string)

View File

@ -4,8 +4,8 @@ import (
"fmt"
"testing"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View File

@ -9,8 +9,8 @@ import (
"net/http"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
)
// Client is a client to interact with DockerHub

View File

@ -7,10 +7,10 @@ import (
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/filter"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/filter"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
func init() {
@ -61,7 +61,7 @@ func newAdapter(registry *model.Registry) *adapter {
func (a *adapter) Info() (*model.RegistryInfo, error) {
return &model.RegistryInfo{
Type: model.RegistryTypeAzureAcr,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeImage,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -74,7 +74,7 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},

View File

@ -6,8 +6,8 @@ import (
"testing"
"github.com/goharbor/harbor/src/common/utils/test"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
)

View File

@ -14,8 +14,8 @@ import (
common_http "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
)
// Client is a client to interact with DTR

View File

@ -6,7 +6,7 @@ import (
common_http "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/utils/test"
"github.com/goharbor/harbor/src/replication/util"
"github.com/goharbor/harbor/src/pkg/reg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View File

@ -9,12 +9,12 @@ import (
"github.com/goharbor/harbor/src/common/http/modifier"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/filter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
"github.com/goharbor/harbor/src/pkg/registry/auth/basic"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/filter"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
)
// !!!! Limits:
@ -95,7 +95,7 @@ func newAdapter(registry *model.Registry) *adapter {
func (a *adapter) Info() (info *model.RegistryInfo, err error) {
info = &model.RegistryInfo{
Type: model.RegistryTypeGithubCR,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeImage,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -108,7 +108,7 @@ func (a *adapter) Info() (info *model.RegistryInfo, err error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},

View File

@ -7,7 +7,7 @@ import (
"testing"
"github.com/goharbor/harbor/src/common/utils/test"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View File

@ -2,10 +2,10 @@ package gitlab
import (
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
"strings"
)
@ -60,7 +60,7 @@ func newAdapter(registry *model.Registry) (*adapter, error) {
func (a *adapter) Info() (info *model.RegistryInfo, err error) {
return &model.RegistryInfo{
Type: model.RegistryTypeGitLab,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeImage,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -73,7 +73,7 @@ func (a *adapter) Info() (info *model.RegistryInfo, err error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},

View File

@ -1,8 +1,8 @@
package gitlab
import (
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"io"

View File

@ -6,8 +6,8 @@ import (
"fmt"
"github.com/docker/distribution/registry/client/auth/challenge"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
"io"
"io/ioutil"
"net/http"

View File

@ -3,7 +3,7 @@ package gitlab
import (
common_http "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/utils/test"
"github.com/goharbor/harbor/src/replication/util"
"github.com/goharbor/harbor/src/pkg/reg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/http"

View File

@ -19,9 +19,9 @@ import (
"fmt"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/opencontainers/go-digest"
"io/ioutil"
"net/http"
@ -70,7 +70,7 @@ var _ adp.Adapter = adapter{}
func (adapter) Info() (info *model.RegistryInfo, err error) {
return &model.RegistryInfo{
Type: model.RegistryTypeGoogleGcr,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeImage,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -83,7 +83,7 @@ func (adapter) Info() (info *model.RegistryInfo, err error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},
@ -124,7 +124,7 @@ func getAdapterInfo() *model.AdapterPattern {
}
// HealthCheck checks health status of a registry
func (a adapter) HealthCheck() (model.HealthStatus, error) {
func (a adapter) HealthCheck() (string, error) {
var err error
if a.registry.Credential == nil ||
len(a.registry.Credential.AccessKey) == 0 || len(a.registry.Credential.AccessSecret) == 0 {

View File

@ -3,8 +3,8 @@ package googlegcr
import (
"fmt"
"github.com/goharbor/harbor/src/common/utils/test"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
"io"
"io/ioutil"

View File

@ -16,11 +16,11 @@ package harbor
import (
"github.com/goharbor/harbor/src/lib/log"
adp "github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/adapter/harbor/base"
v1 "github.com/goharbor/harbor/src/replication/adapter/harbor/v1"
v2 "github.com/goharbor/harbor/src/replication/adapter/harbor/v2"
"github.com/goharbor/harbor/src/replication/model"
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/adapter/harbor/base"
v1 "github.com/goharbor/harbor/src/pkg/reg/adapter/harbor/v1"
v2 "github.com/goharbor/harbor/src/pkg/reg/adapter/harbor/v2"
"github.com/goharbor/harbor/src/pkg/reg/model"
)
func init() {

View File

@ -25,10 +25,10 @@ import (
"github.com/goharbor/harbor/src/common/http/modifier"
common_http_auth "github.com/goharbor/harbor/src/common/http/modifier/auth"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/pkg/reg/util"
"github.com/goharbor/harbor/src/pkg/registry/auth/basic"
"github.com/goharbor/harbor/src/replication/adapter/native"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/util"
)
// New creates an instance of the base adapter
@ -97,7 +97,7 @@ func (a *Adapter) GetAPIVersion() string {
func (a *Adapter) Info() (*model.RegistryInfo, error) {
info := &model.RegistryInfo{
Type: model.RegistryTypeHarbor,
SupportedResourceTypes: []model.ResourceType{
SupportedResourceTypes: []string{
model.ResourceTypeImage,
},
SupportedResourceFilters: []*model.FilterStyle{
@ -110,7 +110,7 @@ func (a *Adapter) Info() (*model.RegistryInfo, error) {
Style: model.FilterStyleTypeText,
},
},
SupportedTriggers: []model.TriggerType{
SupportedTriggers: []string{
model.TriggerTypeManual,
model.TriggerTypeScheduled,
},

View File

@ -20,7 +20,7 @@ import (
"testing"
"github.com/goharbor/harbor/src/common/utils/test"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View File

@ -24,8 +24,8 @@ import (
"strings"
common_http "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/replication/filter"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/filter"
"github.com/goharbor/harbor/src/pkg/reg/model"
"net/url"
)

Some files were not shown because too many files have changed in this diff Show More