mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-26 02:28:06 +01:00
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:
parent
70165be3f0
commit
28596c3ffb
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
@ -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 (
|
@ -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 {
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
||||
|
287
src/controller/registry/controller.go
Normal file
287
src/controller/registry/controller.go
Normal 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
|
||||
}
|
178
src/controller/registry/controller_test.go
Normal file
178
src/controller/registry/controller_test.go
Normal 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, ®istryTestSuite{})
|
||||
}
|
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 ©Flow{
|
||||
executionMgr: task.ExecMgr,
|
||||
taskMgr: task.Mgr,
|
||||
|
@ -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",
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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{}
|
@ -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",
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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() {
|
@ -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"
|
||||
)
|
@ -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 (
|
@ -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"
|
||||
)
|
@ -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)
|
@ -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
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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, ®istry); err != nil {
|
||||
return nil, code, err
|
||||
}
|
||||
return ®istry, 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, ®istries); 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"`
|
||||
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
@ -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())
|
||||
}
|
@ -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...)
|
||||
}
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
46
src/lib/endpoint.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package 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
|
||||
}
|
@ -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
|
||||
}
|
@ -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])
|
||||
}
|
@ -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,
|
||||
},
|
@ -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"
|
||||
)
|
||||
|
@ -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
|
@ -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"
|
||||
)
|
@ -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"
|
@ -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"
|
||||
)
|
@ -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
|
@ -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"
|
||||
)
|
||||
|
@ -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,
|
||||
},
|
@ -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"
|
||||
)
|
||||
|
@ -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)
|
@ -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"
|
||||
)
|
@ -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
|
@ -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,
|
||||
},
|
@ -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"
|
||||
)
|
||||
|
@ -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
|
@ -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"
|
||||
)
|
@ -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,
|
||||
},
|
@ -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"
|
||||
)
|
@ -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,
|
||||
},
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -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 {
|
@ -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"
|
@ -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() {
|
@ -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,
|
||||
},
|
@ -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"
|
||||
)
|
@ -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
Loading…
Reference in New Issue
Block a user