mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 10:15:35 +01:00
refactor: generate scanner APIs by go-swagger
Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
9161a3fbdf
commit
ef0bdf6954
@ -2220,343 +2220,6 @@ paths:
|
||||
description: User have no permission to delete immutable tags of the project.
|
||||
'500':
|
||||
description: Internal server errors.
|
||||
'/scanners':
|
||||
get:
|
||||
summary: List scanner registrations
|
||||
description: |
|
||||
Returns a list of currently configured scanner registrations.
|
||||
tags:
|
||||
- Products
|
||||
- Scanners
|
||||
responses:
|
||||
'200':
|
||||
description: A list of scanner registrations.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of available items
|
||||
type: integer
|
||||
Link:
|
||||
description: Link to previous page and next page
|
||||
type: string
|
||||
'400':
|
||||
description: Bad query paramters
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed, system role required
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The page number.
|
||||
- name: page_size
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The size of per page.
|
||||
post:
|
||||
summary: Create a scanner registration
|
||||
description: |
|
||||
Creats a new scanner registration with the given data.
|
||||
tags:
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: registration
|
||||
in: body
|
||||
description: A scanner registration to be created.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistrationReq'
|
||||
responses:
|
||||
'201':
|
||||
description: Created successfully
|
||||
headers:
|
||||
Location:
|
||||
type: string
|
||||
description: The URL of the created resource
|
||||
'400':
|
||||
description: Bad registration request
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed, system role required
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
'/scanners/ping':
|
||||
post:
|
||||
summary: Tests scanner registration settings
|
||||
description: |
|
||||
Pings scanner adapter to test endpoint URL and authorization settings.
|
||||
tags:
|
||||
- Products
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: settings
|
||||
in: body
|
||||
description: A scanner registration settings to be tested.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistrationSettings'
|
||||
responses:
|
||||
'200':
|
||||
description: Test succeeded
|
||||
'400':
|
||||
description: Bad registration settings
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed, system role required
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
'/scanners/{registration_id}':
|
||||
get:
|
||||
summary: Get a scanner registration details
|
||||
description: |
|
||||
Retruns the details of the specified scanner registration.
|
||||
tags:
|
||||
- Products
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: registration_id
|
||||
in: path
|
||||
description: The scanner registration identifer.
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: The details of the scanner registration.
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed, system role required
|
||||
'404':
|
||||
description: The requested object is not found
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
put:
|
||||
summary: Update a scanner registration
|
||||
description: |
|
||||
Updates the specified scanner registration.
|
||||
tags:
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: registration_id
|
||||
in: path
|
||||
description: The scanner registration identifier.
|
||||
required: true
|
||||
type: string
|
||||
- name: registration
|
||||
in: body
|
||||
required: true
|
||||
description: A scanner registraiton to be updated.
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistrationReq'
|
||||
responses:
|
||||
'200':
|
||||
description: Updated successfully
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed, system role required
|
||||
'404':
|
||||
description: The requested object is not found
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
delete:
|
||||
summary: Delete a scanner registration
|
||||
description: |
|
||||
Deletes the specified scanner registration.
|
||||
tags:
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: registration_id
|
||||
in: path
|
||||
description: The scanner registration identifier.
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Deleted successfully and return the deleted registration
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed, system role required or registration is immutable
|
||||
'404':
|
||||
description: The requested object is not found
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
patch:
|
||||
summary: Set system default scanner registration
|
||||
description: |
|
||||
Set the specified scanner registration as the system default one.
|
||||
tags:
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: registration_id
|
||||
in: path
|
||||
description: The scanner registration identifier.
|
||||
required: true
|
||||
type: string
|
||||
- name: payload
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/IsDefault'
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully set the specified scanner registration as system default
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
'/scanners/{registration_id}/metadata':
|
||||
get:
|
||||
summary: Get the metadata of the specified scanner registration
|
||||
description: |
|
||||
Get the metadata of the specified scanner registration, including the capabilities and customzied properties.
|
||||
tags:
|
||||
- Products
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: registration_id
|
||||
in: path
|
||||
required: true
|
||||
description: The scanner registration identifier.
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: The metadata of the specified scanner adapter
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerAdapterMetadata'
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
'/projects/{project_id}/scanner':
|
||||
get:
|
||||
summary: Get project level scanner
|
||||
description: Get the scanner registration of the specified project. If no scanner registration is configured for the specified project, the system default scanner registration will be returned.
|
||||
tags:
|
||||
- Products
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
required: true
|
||||
description: The project identifier.
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: The details of the scanner registration.
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
'400':
|
||||
description: Bad project ID
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed
|
||||
'404':
|
||||
description: The requested object is not found
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
put:
|
||||
summary: Configure scanner for the specified project
|
||||
description: Set one of the system configured scanner registration as the indepndent scanner of the specified project.
|
||||
tags:
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
required: true
|
||||
description: The project identifier.
|
||||
type: integer
|
||||
format: int64
|
||||
- name: payload
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/ProjectScanner'
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully set the project level scanner
|
||||
'400':
|
||||
description: Bad project ID
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed
|
||||
'404':
|
||||
description: The requested object is not found
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
'/projects/{project_id}/scanner/candidates':
|
||||
get:
|
||||
summary: Get scanner registration candidates for configurating project level scanner
|
||||
description: |
|
||||
Retrieve the system configured scanner registrations as candidates of setting project level scanner.
|
||||
tags:
|
||||
- Products
|
||||
- Scanners
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The page number.
|
||||
- name: page_size
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The size of per page.
|
||||
- name: project_id
|
||||
in: path
|
||||
required: true
|
||||
description: The project identifier.
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: A list of scanner registrations.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of available items
|
||||
type: integer
|
||||
Link:
|
||||
description: Link to previous page and next page
|
||||
type: string
|
||||
'400':
|
||||
description: Bad project ID or query parameters
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
responses:
|
||||
OK:
|
||||
description: 'Success'
|
||||
@ -3769,199 +3432,6 @@ definitions:
|
||||
enabled:
|
||||
type: boolean
|
||||
description: The quota is enable or disable
|
||||
ScannerRegistration:
|
||||
type: object
|
||||
description: |
|
||||
Registration represents a named configuration for invoking a scanner via its adapter.
|
||||
properties:
|
||||
uuid:
|
||||
type: string
|
||||
description: The unique identifier of this registration.
|
||||
name:
|
||||
type: string
|
||||
example: Clair
|
||||
description: The name of this registration.
|
||||
description:
|
||||
type: string
|
||||
description: An optional description of this registration.
|
||||
example: |
|
||||
A free-to-use tool that scans container images for package vulnerabilities.
|
||||
url:
|
||||
type: string
|
||||
format: url
|
||||
description: A base URL of the scanner adapter
|
||||
example: http://harbor-scanner-clair:8080
|
||||
disabled:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate whether the registration is enabled or not
|
||||
is_default:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate if the registration is set as the system default one
|
||||
health:
|
||||
type: string
|
||||
default: ""
|
||||
description: Indicate the healthy of the registration
|
||||
example: "healthy"
|
||||
auth:
|
||||
type: string
|
||||
default: ""
|
||||
description: |
|
||||
Specify what authentication approach is adopted for the HTTP communications.
|
||||
Supported types Basic", "Bearer" and api key header "X-ScannerAdapter-API-Key"
|
||||
example: "Bearer"
|
||||
access_credential:
|
||||
type: string
|
||||
description: |
|
||||
An optional value of the HTTP Authorization header sent with each request to the Scanner Adapter API.
|
||||
example: "Bearer: JWTTOKENGOESHERE"
|
||||
skip_certVerify:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate if skip the certificate verification when sending HTTP requests
|
||||
use_internal_addr:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate whether use internal registry addr for the scanner to pull content or not
|
||||
adapter:
|
||||
type: string
|
||||
description: Optional property to describe the name of the scanner registration
|
||||
example: "Clair"
|
||||
vendor:
|
||||
type: string
|
||||
description: Optional property to describe the vendor of the scanner registration
|
||||
example: "CentOS"
|
||||
version:
|
||||
type: string
|
||||
description: Optional property to describe the version of the scanner registration
|
||||
example: "1.0.1"
|
||||
|
||||
ScannerRegistrationReq:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The name of this registration
|
||||
example: Clair
|
||||
description:
|
||||
type: string
|
||||
description: An optional description of this registration.
|
||||
example: |
|
||||
A free-to-use tool that scans container images for package vulnerabilities.
|
||||
url:
|
||||
type: string
|
||||
format: url
|
||||
description: A base URL of the scanner adapter.
|
||||
example: http://harbor-scanner-clair:8080
|
||||
auth:
|
||||
type: string
|
||||
default: ""
|
||||
description: |
|
||||
Specify what authentication approach is adopted for the HTTP communications.
|
||||
Supported types Basic", "Bearer" and api key header "X-ScannerAdapter-API-Key"
|
||||
example: "Bearer"
|
||||
access_credential:
|
||||
type: string
|
||||
description: |
|
||||
An optional value of the HTTP Authorization header sent with each request to the Scanner Adapter API.
|
||||
example: "Bearer: JWTTOKENGOESHERE"
|
||||
skip_certVerify:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate if skip the certificate verification when sending HTTP requests
|
||||
use_internal_addr:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate whether use internal registry addr for the scanner to pull content or not
|
||||
disabled:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate whether the registration is enabled or not
|
||||
|
||||
ScannerRegistrationSettings:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The name of this registration
|
||||
example: Clair
|
||||
url:
|
||||
type: string
|
||||
format: url
|
||||
description: A base URL of the scanner adapter.
|
||||
example: http://harbor-scanner-clair:8080
|
||||
auth:
|
||||
type: string
|
||||
default: ""
|
||||
description: |
|
||||
Specify what authentication approach is adopted for the HTTP communications.
|
||||
Supported types Basic", "Bearer" and api key header "X-ScannerAdapter-API-Key"
|
||||
access_credential:
|
||||
type: string
|
||||
description: |
|
||||
An optional value of the HTTP Authorization header sent with each request to the Scanner Adapter API.
|
||||
example: "Bearer: JWTTOKENGOESHERE"
|
||||
|
||||
IsDefault:
|
||||
type: object
|
||||
properties:
|
||||
is_default:
|
||||
type: boolean
|
||||
description: A flag indicating whether a scanner registration is default.
|
||||
Scanner:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Name of the scanner
|
||||
example: "Clair"
|
||||
vendor:
|
||||
type: string
|
||||
description: Name of the scanner provider
|
||||
example: "CentOS"
|
||||
version:
|
||||
type: string
|
||||
description: Version of the scanner adapter
|
||||
example: "1.0.1"
|
||||
|
||||
ScannerCapability:
|
||||
type: object
|
||||
properties:
|
||||
consumes_mime_types:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: "application/vnd.docker.distribution.manifest.v2+json"
|
||||
produces_mime_types:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: "application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
|
||||
|
||||
ScannerAdapterMetadata:
|
||||
type: object
|
||||
description: The metadata info of the scanner adapter
|
||||
properties:
|
||||
name:
|
||||
$ref: '#/definitions/Scanner'
|
||||
capabilities:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ScannerCapability'
|
||||
properties:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
example:
|
||||
'harbor.scanner-adapter/registry-authorization-type': 'Bearer'
|
||||
|
||||
ProjectScanner:
|
||||
type: object
|
||||
properties:
|
||||
uuid:
|
||||
type: string
|
||||
description: The identifier of the scanner registration
|
||||
|
||||
SupportedWebhookEventTypes:
|
||||
type: object
|
||||
|
@ -834,6 +834,97 @@ paths:
|
||||
$ref: '#/responses/409'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/projects/{project_name_or_id}/scanner':
|
||||
get:
|
||||
summary: Get project level scanner
|
||||
description: Get the scanner registration of the specified project. If no scanner registration is configured for the specified project, the system default scanner registration will be returned.
|
||||
tags:
|
||||
- project
|
||||
operationId: getScannerOfProject
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
responses:
|
||||
'200':
|
||||
description: The details of the scanner registration.
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
'400':
|
||||
description: Bad project ID
|
||||
'401':
|
||||
description: Unauthorized request
|
||||
'403':
|
||||
description: Request is not allowed
|
||||
'404':
|
||||
description: The requested object is not found
|
||||
'500':
|
||||
description: Internal server error happened
|
||||
put:
|
||||
summary: Configure scanner for the specified project
|
||||
description: Set one of the system configured scanner registration as the indepndent scanner of the specified project.
|
||||
tags:
|
||||
- project
|
||||
operationId: setScannerOfProject
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- name: payload
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/ProjectScanner'
|
||||
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'
|
||||
'/projects/{project_name_or_id}/scanner/candidates':
|
||||
get:
|
||||
summary: Get scanner registration candidates for configurating project level scanner
|
||||
description: Retrieve the system configured scanner registrations as candidates of setting project level scanner.
|
||||
tags:
|
||||
- project
|
||||
operationId: listScannerCandidatesOfProject
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- $ref: '#/parameters/query'
|
||||
- $ref: '#/parameters/sort'
|
||||
- $ref: '#/parameters/page'
|
||||
- $ref: '#/parameters/pageSize'
|
||||
responses:
|
||||
'200':
|
||||
description: A list of scanner registrations.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of available items
|
||||
type: integer
|
||||
Link:
|
||||
description: Link to previous page and next page
|
||||
type: string
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/audit-logs:
|
||||
get:
|
||||
summary: Get recent logs of the projects which the user is a member of
|
||||
@ -2764,6 +2855,239 @@ paths:
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
|
||||
'/scanners':
|
||||
get:
|
||||
summary: List scanner registrations
|
||||
description: |
|
||||
Returns a list of currently configured scanner registrations.
|
||||
tags:
|
||||
- scanner
|
||||
operationId: listScanners
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/query'
|
||||
- $ref: '#/parameters/sort'
|
||||
- $ref: '#/parameters/page'
|
||||
- $ref: '#/parameters/pageSize'
|
||||
responses:
|
||||
'200':
|
||||
description: A list of scanner registrations.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of available items
|
||||
type: integer
|
||||
Link:
|
||||
description: Link to previous page and next page
|
||||
type: string
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
post:
|
||||
summary: Create a scanner registration
|
||||
description: |
|
||||
Creats a new scanner registration with the given data.
|
||||
tags:
|
||||
- scanner
|
||||
operationId: createScanner
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: registration
|
||||
in: body
|
||||
description: A scanner registration to be created.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistrationReq'
|
||||
responses:
|
||||
'201':
|
||||
description: Created successfully
|
||||
headers:
|
||||
Location:
|
||||
type: string
|
||||
description: The URL of the created resource
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/scanners/ping':
|
||||
post:
|
||||
summary: Tests scanner registration settings
|
||||
description: |
|
||||
Pings scanner adapter to test endpoint URL and authorization settings.
|
||||
tags:
|
||||
- scanner
|
||||
operationId: pingScanner
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: settings
|
||||
in: body
|
||||
description: A scanner registration settings to be tested.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistrationSettings'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/responses/200'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/scanners/{registration_id}':
|
||||
get:
|
||||
summary: Get a scanner registration details
|
||||
description: |
|
||||
Retruns the details of the specified scanner registration.
|
||||
tags:
|
||||
- scanner
|
||||
operationId: getScanner
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: registration_id
|
||||
in: path
|
||||
description: The scanner registration identifer.
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: The details of the scanner registration.
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
put:
|
||||
summary: Update a scanner registration
|
||||
description: |
|
||||
Updates the specified scanner registration.
|
||||
tags:
|
||||
- scanner
|
||||
operationId: updateScanner
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: registration_id
|
||||
in: path
|
||||
description: The scanner registration identifier.
|
||||
required: true
|
||||
type: string
|
||||
- name: registration
|
||||
in: body
|
||||
required: true
|
||||
description: A scanner registraiton to be updated.
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistrationReq'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/responses/200'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
delete:
|
||||
summary: Delete a scanner registration
|
||||
description: |
|
||||
Deletes the specified scanner registration.
|
||||
tags:
|
||||
- scanner
|
||||
operationId: deleteScanner
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: registration_id
|
||||
in: path
|
||||
description: The scanner registration identifier.
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Deleted successfully and return the deleted registration
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerRegistration'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
patch:
|
||||
summary: Set system default scanner registration
|
||||
description: |
|
||||
Set the specified scanner registration as the system default one.
|
||||
tags:
|
||||
- scanner
|
||||
operationId: setScannerAsDefault
|
||||
parameters:
|
||||
- name: registration_id
|
||||
in: path
|
||||
description: The scanner registration identifier.
|
||||
required: true
|
||||
type: string
|
||||
- name: payload
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/IsDefault'
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully set the specified scanner registration as system default
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/scanners/{registration_id}/metadata':
|
||||
get:
|
||||
summary: Get the metadata of the specified scanner registration
|
||||
description: |
|
||||
Get the metadata of the specified scanner registration, including the capabilities and customized properties.
|
||||
tags:
|
||||
- scanner
|
||||
operationId: getScannerMetadata
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: registration_id
|
||||
in: path
|
||||
required: true
|
||||
description: The scanner registration identifier.
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: The metadata of the specified scanner adapter
|
||||
schema:
|
||||
$ref: '#/definitions/ScannerAdapterMetadata'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
|
||||
parameters:
|
||||
query:
|
||||
name: q
|
||||
@ -2853,6 +3177,12 @@ parameters:
|
||||
description: The size of per page
|
||||
default: 10
|
||||
maximum: 100
|
||||
sort:
|
||||
name: sort
|
||||
in: query
|
||||
type: string
|
||||
required: false
|
||||
description: The order by fields of the query, the format is '+field1,-field2'.
|
||||
instanceName:
|
||||
name: preheat_instance_name
|
||||
in: path
|
||||
@ -3705,6 +4035,14 @@ definitions:
|
||||
used:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The used status of the quota
|
||||
ProjectScanner:
|
||||
type: object
|
||||
required:
|
||||
- uuid
|
||||
properties:
|
||||
uuid:
|
||||
type: string
|
||||
description: The identifier of the scanner registration
|
||||
CVEAllowlist:
|
||||
type: object
|
||||
description: The CVE Allowlist for system or project
|
||||
@ -4442,15 +4780,18 @@ definitions:
|
||||
retained:
|
||||
type: integer
|
||||
x-omitempty: false
|
||||
|
||||
QuotaUpdateReq:
|
||||
type: object
|
||||
properties:
|
||||
hard:
|
||||
$ref: "#/definitions/ResourceList"
|
||||
description: The new hard limits for the quota
|
||||
|
||||
QuotaRefObject:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
|
||||
Quota:
|
||||
type: object
|
||||
description: The quota object
|
||||
@ -4477,3 +4818,200 @@ definitions:
|
||||
type: string
|
||||
format: date-time
|
||||
description: the update time of the quota
|
||||
|
||||
ScannerRegistration:
|
||||
type: object
|
||||
description: |
|
||||
Registration represents a named configuration for invoking a scanner via its adapter.
|
||||
properties:
|
||||
uuid:
|
||||
type: string
|
||||
description: The unique identifier of this registration.
|
||||
name:
|
||||
type: string
|
||||
example: Trivy
|
||||
description: The name of this registration.
|
||||
description:
|
||||
type: string
|
||||
description: An optional description of this registration.
|
||||
example: |
|
||||
A free-to-use tool that scans container images for package vulnerabilities.
|
||||
x-omitempty: false
|
||||
url:
|
||||
type: string
|
||||
format: url
|
||||
description: A base URL of the scanner adapter
|
||||
example: http://harbor-scanner-trivy:8080
|
||||
disabled:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate whether the registration is enabled or not
|
||||
x-omitempty: false
|
||||
is_default:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate if the registration is set as the system default one
|
||||
x-omitempty: false
|
||||
auth:
|
||||
type: string
|
||||
default: ""
|
||||
description: |
|
||||
Specify what authentication approach is adopted for the HTTP communications.
|
||||
Supported types Basic", "Bearer" and api key header "X-ScannerAdapter-API-Key"
|
||||
example: "Bearer"
|
||||
x-omitempty: false
|
||||
access_credential:
|
||||
type: string
|
||||
description: |
|
||||
An optional value of the HTTP Authorization header sent with each request to the Scanner Adapter API.
|
||||
example: "Bearer: JWTTOKENGOESHERE"
|
||||
x-omitempty: false
|
||||
skip_certVerify:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate if skip the certificate verification when sending HTTP requests
|
||||
x-omitempty: false
|
||||
use_internal_addr:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate whether use internal registry addr for the scanner to pull content or not
|
||||
x-omitempty: false
|
||||
create_time:
|
||||
type: string
|
||||
format: date-time
|
||||
description: The creation time of this registration
|
||||
update_time:
|
||||
type: string
|
||||
format: date-time
|
||||
description: The update time of this registration
|
||||
adapter:
|
||||
type: string
|
||||
description: Optional property to describe the name of the scanner registration
|
||||
example: "Clair"
|
||||
vendor:
|
||||
type: string
|
||||
description: Optional property to describe the vendor of the scanner registration
|
||||
example: "CentOS"
|
||||
version:
|
||||
type: string
|
||||
description: Optional property to describe the version of the scanner registration
|
||||
example: "1.0.1"
|
||||
health:
|
||||
type: string
|
||||
default: ""
|
||||
description: Indicate the healthy of the registration
|
||||
example: "healthy"
|
||||
|
||||
ScannerRegistrationReq:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- url
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The name of this registration
|
||||
example: Trivy
|
||||
description:
|
||||
type: string
|
||||
description: An optional description of this registration.
|
||||
example: |
|
||||
A free-to-use tool that scans container images for package vulnerabilities.
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
description: A base URL of the scanner adapter.
|
||||
example: http://harbor-scanner-trivy:8080
|
||||
auth:
|
||||
type: string
|
||||
description: |
|
||||
Specify what authentication approach is adopted for the HTTP communications.
|
||||
Supported types Basic", "Bearer" and api key header "X-ScannerAdapter-API-Key"
|
||||
example: "Bearer"
|
||||
access_credential:
|
||||
type: string
|
||||
description: |
|
||||
An optional value of the HTTP Authorization header sent with each request to the Scanner Adapter API.
|
||||
example: "Bearer: JWTTOKENGOESHERE"
|
||||
skip_certVerify:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate if skip the certificate verification when sending HTTP requests
|
||||
use_internal_addr:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate whether use internal registry addr for the scanner to pull content or not
|
||||
disabled:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Indicate whether the registration is enabled or not
|
||||
|
||||
ScannerRegistrationSettings:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- url
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The name of this registration
|
||||
example: Trivy
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
description: A base URL of the scanner adapter.
|
||||
example: http://harbor-scanner-trivy:8080
|
||||
auth:
|
||||
type: string
|
||||
default: ""
|
||||
description: |
|
||||
Specify what authentication approach is adopted for the HTTP communications.
|
||||
Supported types Basic", "Bearer" and api key header "X-ScannerAdapter-API-Key"
|
||||
access_credential:
|
||||
type: string
|
||||
description: |
|
||||
An optional value of the HTTP Authorization header sent with each request to the Scanner Adapter API.
|
||||
example: "Bearer: JWTTOKENGOESHERE"
|
||||
|
||||
IsDefault:
|
||||
type: object
|
||||
properties:
|
||||
is_default:
|
||||
type: boolean
|
||||
description: A flag indicating whether a scanner registration is default.
|
||||
|
||||
ScannerCapability:
|
||||
type: object
|
||||
properties:
|
||||
consumes_mime_types:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: "application/vnd.docker.distribution.manifest.v2+json"
|
||||
produces_mime_types:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: "application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
|
||||
|
||||
ScannerAdapterMetadata:
|
||||
type: object
|
||||
description: The metadata info of the scanner adapter
|
||||
properties:
|
||||
scanner:
|
||||
$ref: '#/definitions/Scanner'
|
||||
capabilities:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ScannerCapability'
|
||||
properties:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
example:
|
||||
'harbor.scanner-adapter/registry-authorization-type': 'Bearer'
|
||||
x-go-type:
|
||||
type: ScannerAdapterMetadata
|
||||
import:
|
||||
package: "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
alias: v1
|
||||
|
@ -65,6 +65,11 @@ func (bc *basicController) ListRegistrations(ctx context.Context, query *q.Query
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Count returns the total count of scanner registrations according to the query.
|
||||
func (bc *basicController) GetTotalOfRegistrations(ctx context.Context, query *q.Query) (int64, error) {
|
||||
return bc.manager.Count(ctx, query)
|
||||
}
|
||||
|
||||
// CreateRegistration ...
|
||||
func (bc *basicController) CreateRegistration(ctx context.Context, registration *scanner.Registration) (string, error) {
|
||||
if isReservedName(registration.Name) {
|
||||
@ -324,6 +329,10 @@ func (bc *basicController) GetMetadata(ctx context.Context, registrationUUID str
|
||||
return nil, errors.Wrap(err, "scanner controller: get metadata")
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
return nil, errors.NotFoundError(nil).WithMessage("registration %s not found", registrationUUID)
|
||||
}
|
||||
|
||||
return bc.Ping(ctx, r)
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,9 @@ import (
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
)
|
||||
|
||||
// Registration ...
|
||||
type Registration = scanner.Registration
|
||||
|
||||
// Controller provides the related operations of scanner for the upper API.
|
||||
// All the capabilities of the scanner are defined here.
|
||||
type Controller interface {
|
||||
@ -37,6 +40,9 @@ type Controller interface {
|
||||
// error : non nil error if any errors occurred
|
||||
ListRegistrations(ctx context.Context, query *q.Query) ([]*scanner.Registration, error)
|
||||
|
||||
// GetTotalOfRegistrations returns the total count of scanner registrations according to the query.
|
||||
GetTotalOfRegistrations(ctx context.Context, query *q.Query) (int64, error)
|
||||
|
||||
// CreateRegistration creates a new scanner registration with the given data.
|
||||
// Returns the scanner registration identifier.
|
||||
//
|
||||
|
@ -160,18 +160,6 @@ func init() {
|
||||
beego.Router("/api/internal/switchquota", &InternalAPI{}, "put:SwitchQuota")
|
||||
beego.Router("/api/internal/syncquota", &InternalAPI{}, "post:SyncQuota")
|
||||
|
||||
// Add routes for plugin scanner management
|
||||
scannerAPI := &ScannerAPI{}
|
||||
beego.Router("/api/scanners", scannerAPI, "post:Create;get:List")
|
||||
beego.Router("/api/scanners/:uuid", scannerAPI, "get:Get;delete:Delete;put:Update;patch:SetAsDefault")
|
||||
beego.Router("/api/scanners/:uuid/metadata", scannerAPI, "get:Metadata")
|
||||
beego.Router("/api/scanners/ping", scannerAPI, "post:Ping")
|
||||
|
||||
// Add routes for project level scanner
|
||||
proScannerAPI := &ProjectScannerAPI{}
|
||||
beego.Router("/api/projects/:pid([0-9]+)/scanner", proScannerAPI, "get:GetProjectScanner;put:SetProjectScanner")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/scanner/candidates", proScannerAPI, "get:GetProScannerCandidates")
|
||||
|
||||
// Init user Info
|
||||
admin = &usrInfo{adminName, adminPwd}
|
||||
unknownUsr = &usrInfo{"unknown", "unknown"}
|
||||
|
@ -1,143 +0,0 @@
|
||||
// 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 api
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// ProjectScannerAPI provides rest API for managing the project level scanner(s).
|
||||
type ProjectScannerAPI struct {
|
||||
// The base controller to provide common utilities
|
||||
BaseController
|
||||
// Scanner controller for operating scanner registrations.
|
||||
c scanner.Controller
|
||||
// ID of the project
|
||||
pid int64
|
||||
}
|
||||
|
||||
// Prepare sth. for the subsequent actions
|
||||
func (sa *ProjectScannerAPI) Prepare() {
|
||||
// Call super prepare method
|
||||
sa.BaseController.Prepare()
|
||||
|
||||
// Check access permissions
|
||||
if !sa.RequireAuthenticated() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get ID of the project
|
||||
pid, err := sa.GetInt64FromPath(":pid")
|
||||
if err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "project scanner API"))
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the project exists
|
||||
exists, err := sa.ProjectCtl.Exists(sa.Context(), pid)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "project scanner API"))
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
sa.SendNotFoundError(errors.Errorf("project with id %d", sa.pid))
|
||||
return
|
||||
}
|
||||
|
||||
sa.pid = pid
|
||||
|
||||
sa.c = scanner.DefaultController
|
||||
}
|
||||
|
||||
// GetProjectScanner gets the project level scanner
|
||||
func (sa *ProjectScannerAPI) GetProjectScanner() {
|
||||
// Check access permissions
|
||||
if !sa.RequireProjectAccess(sa.pid, rbac.ActionRead, rbac.ResourceScanner) {
|
||||
return
|
||||
}
|
||||
|
||||
r, err := sa.c.GetRegistrationByProject(sa.Context(), sa.pid)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: get project scanners"))
|
||||
return
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
sa.Data["json"] = r
|
||||
} else {
|
||||
sa.Data["json"] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
sa.ServeJSON()
|
||||
}
|
||||
|
||||
// SetProjectScanner sets the project level scanner
|
||||
func (sa *ProjectScannerAPI) SetProjectScanner() {
|
||||
// Check access permissions
|
||||
if !sa.RequireProjectAccess(sa.pid, rbac.ActionCreate, rbac.ResourceScanner) {
|
||||
return
|
||||
}
|
||||
|
||||
body := make(map[string]string)
|
||||
if err := sa.DecodeJSONReq(&body); err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: set project scanners"))
|
||||
return
|
||||
}
|
||||
|
||||
uuid, ok := body["uuid"]
|
||||
if !ok || len(uuid) == 0 {
|
||||
sa.SendBadRequestError(errors.New("missing scanner uuid when setting project scanner"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := sa.c.SetRegistrationByProject(sa.Context(), sa.pid, uuid); err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: set project scanners"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetProScannerCandidates gets the candidates for setting project level scanner.
|
||||
func (sa *ProjectScannerAPI) GetProScannerCandidates() {
|
||||
// Check access permissions
|
||||
// Same permission with project level scanner set action
|
||||
if !sa.RequireProjectAccess(sa.pid, rbac.ActionCreate, rbac.ResourceScanner) {
|
||||
return
|
||||
}
|
||||
|
||||
p, pz, err := sa.GetPaginationParams()
|
||||
if err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: get project scanner candidates"))
|
||||
return
|
||||
}
|
||||
|
||||
query := &q.Query{
|
||||
PageSize: pz,
|
||||
PageNumber: p,
|
||||
}
|
||||
|
||||
all, err := sa.c.ListRegistrations(sa.Context(), query)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: get project scanner candidates"))
|
||||
return
|
||||
}
|
||||
|
||||
// Response to the client
|
||||
sa.Data["json"] = all
|
||||
sa.ServeJSON()
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
// 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 api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
sc "github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
scannertesting "github.com/goharbor/harbor/src/testing/controller/scanner"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
// ProScannerAPITestSuite is test suite for testing the project scanner API
|
||||
type ProScannerAPITestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
originC sc.Controller
|
||||
mockC *scannertesting.Controller
|
||||
}
|
||||
|
||||
// TestProScannerAPI is the entry of ProScannerAPITestSuite
|
||||
func TestProScannerAPI(t *testing.T) {
|
||||
suite.Run(t, new(ProScannerAPITestSuite))
|
||||
}
|
||||
|
||||
// SetupSuite prepares testing env
|
||||
func (suite *ProScannerAPITestSuite) SetupTest() {
|
||||
suite.originC = sc.DefaultController
|
||||
m := &scannertesting.Controller{}
|
||||
sc.DefaultController = m
|
||||
|
||||
suite.mockC = m
|
||||
}
|
||||
|
||||
// TearDownTest clears test case env
|
||||
func (suite *ProScannerAPITestSuite) TearDownTest() {
|
||||
// Restore
|
||||
sc.DefaultController = suite.originC
|
||||
}
|
||||
|
||||
// TestScannerAPIProjectScanner tests the API of getting/setting project level scanner
|
||||
func (suite *ProScannerAPITestSuite) TestScannerAPIProjectScanner() {
|
||||
suite.mockC.On("SetRegistrationByProject", mock.Anything, int64(1), "uuid").Return(nil)
|
||||
|
||||
// Set
|
||||
body := make(map[string]interface{}, 1)
|
||||
body["uuid"] = "uuid"
|
||||
runCodeCheckingCases(suite.T(), &codeCheckingCase{
|
||||
request: &testingRequest{
|
||||
url: fmt.Sprintf("/api/projects/%d/scanner", 1),
|
||||
method: http.MethodPut,
|
||||
credential: projAdmin,
|
||||
bodyJSON: body,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
})
|
||||
|
||||
r := &scanner.Registration{
|
||||
ID: 1004,
|
||||
UUID: "uuid",
|
||||
Name: "TestScannerAPIProjectScanner",
|
||||
Description: "JUST FOR TEST",
|
||||
URL: "https://a.b.c",
|
||||
}
|
||||
suite.mockC.On("GetRegistrationByProject", mock.Anything, int64(1)).Return(r, nil)
|
||||
|
||||
// Get
|
||||
rr := &scanner.Registration{}
|
||||
err := handleAndParse(&testingRequest{
|
||||
url: fmt.Sprintf("/api/projects/%d/scanner", 1),
|
||||
method: http.MethodGet,
|
||||
credential: projAdmin,
|
||||
}, rr)
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
assert.Equal(suite.T(), r.Name, rr.Name)
|
||||
assert.Equal(suite.T(), r.UUID, rr.UUID)
|
||||
}
|
||||
|
||||
// TestScannerAPIGetScannerCandidates ...
|
||||
func (suite *ProScannerAPITestSuite) TestScannerAPIGetScannerCandidates() {
|
||||
query := &q.Query{
|
||||
PageNumber: 1,
|
||||
PageSize: 500,
|
||||
}
|
||||
|
||||
ll := []*scanner.Registration{
|
||||
{
|
||||
ID: 1005,
|
||||
UUID: "uuid",
|
||||
Name: "TestScannerAPIGetScannerCandidates",
|
||||
Description: "JUST FOR TEST",
|
||||
URL: "https://a.b.c",
|
||||
}}
|
||||
suite.mockC.On("ListRegistrations", mock.Anything, query).Return(ll, nil)
|
||||
|
||||
// Get
|
||||
l := make([]*scanner.Registration, 0)
|
||||
err := handleAndParse(&testingRequest{
|
||||
url: fmt.Sprintf("/api/projects/%d/scanner/candidates", 1),
|
||||
method: http.MethodGet,
|
||||
credential: projAdmin,
|
||||
}, &l)
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
assert.Equal(suite.T(), 1, len(l))
|
||||
assert.Equal(suite.T(), "uuid", l[0].UUID)
|
||||
}
|
@ -1,368 +0,0 @@
|
||||
// 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 api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
|
||||
s "github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
)
|
||||
|
||||
// ScannerAPI provides the API for managing the plugin scanners
|
||||
type ScannerAPI struct {
|
||||
// The base controller to provide common utilities
|
||||
BaseController
|
||||
|
||||
// Controller for the plug scanners
|
||||
c s.Controller
|
||||
|
||||
resource types.Resource
|
||||
}
|
||||
|
||||
// Prepare sth. for the subsequent actions
|
||||
func (sa *ScannerAPI) Prepare() {
|
||||
// Call super prepare method
|
||||
sa.BaseController.Prepare()
|
||||
|
||||
// Check access permissions
|
||||
if !sa.SecurityCtx.IsAuthenticated() {
|
||||
sa.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
sa.resource = system.NewNamespace().Resource(rbac.ResourceScanner)
|
||||
|
||||
// Use the default controller
|
||||
sa.c = s.DefaultController
|
||||
}
|
||||
|
||||
// Get the specified scanner
|
||||
func (sa *ScannerAPI) Get() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionRead, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
if r := sa.get(); r != nil {
|
||||
// Response to the client
|
||||
sa.Data["json"] = r
|
||||
sa.ServeJSON()
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata returns the metadata of the given scanner.
|
||||
func (sa *ScannerAPI) Metadata() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionRead, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
uuid := sa.GetStringFromPath(":uuid")
|
||||
|
||||
meta, err := sa.c.GetMetadata(sa.Context(), uuid)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: get metadata"))
|
||||
return
|
||||
}
|
||||
|
||||
// Response to the client
|
||||
sa.Data["json"] = meta
|
||||
sa.ServeJSON()
|
||||
}
|
||||
|
||||
// List all the scanners
|
||||
func (sa *ScannerAPI) List() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionList, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
p, pz, err := sa.GetPaginationParams()
|
||||
if err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: list all"))
|
||||
return
|
||||
}
|
||||
|
||||
query := &q.Query{
|
||||
PageSize: pz,
|
||||
PageNumber: p,
|
||||
}
|
||||
|
||||
// Get query key words
|
||||
kws := make(map[string]interface{})
|
||||
properties := []string{"name", "description", "url", "ex_name", "ex_url"}
|
||||
for _, k := range properties {
|
||||
kw := sa.GetString(k)
|
||||
if len(kw) > 0 {
|
||||
kws[k] = kw
|
||||
}
|
||||
}
|
||||
|
||||
if len(kws) > 0 {
|
||||
query.Keywords = kws
|
||||
}
|
||||
|
||||
all, err := sa.c.ListRegistrations(sa.Context(), query)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: list all"))
|
||||
return
|
||||
}
|
||||
|
||||
// Response to the client
|
||||
sa.Data["json"] = all
|
||||
sa.ServeJSON()
|
||||
}
|
||||
|
||||
// Create a new scanner
|
||||
func (sa *ScannerAPI) Create() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionCreate, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r := &scanner.Registration{}
|
||||
|
||||
if err := sa.DecodeJSONReq(r); err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: create"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.Validate(false); err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: create"))
|
||||
return
|
||||
}
|
||||
|
||||
// Explicitly check if conflict
|
||||
if !sa.checkDuplicated("name", r.Name) ||
|
||||
!sa.checkDuplicated("url", r.URL) {
|
||||
return
|
||||
}
|
||||
|
||||
// All newly created should be non default one except the 1st one
|
||||
r.IsDefault = false
|
||||
|
||||
uuid, err := sa.c.CreateRegistration(sa.Context(), r)
|
||||
if err != nil {
|
||||
sa.SendError(errors.Wrap(err, "scanner API: create"))
|
||||
return
|
||||
}
|
||||
|
||||
location := fmt.Sprintf("%s/%s", sa.Ctx.Request.RequestURI, uuid)
|
||||
sa.Ctx.ResponseWriter.Header().Add("Location", location)
|
||||
|
||||
resp := make(map[string]string, 1)
|
||||
resp["uuid"] = uuid
|
||||
|
||||
// Response to the client
|
||||
sa.Ctx.ResponseWriter.WriteHeader(http.StatusCreated)
|
||||
sa.Data["json"] = resp
|
||||
sa.ServeJSON()
|
||||
}
|
||||
|
||||
// Update a scanner
|
||||
func (sa *ScannerAPI) Update() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionUpdate, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r := sa.get()
|
||||
if r == nil {
|
||||
// meet error
|
||||
return
|
||||
}
|
||||
|
||||
// Immutable registration is not allowed
|
||||
if r.Immutable {
|
||||
sa.SendForbiddenError(errors.Errorf("registration %s is not allowed to update as it is immutable: scanner API: update", r.Name))
|
||||
return
|
||||
}
|
||||
|
||||
// full dose updated
|
||||
rr := &scanner.Registration{}
|
||||
if err := sa.DecodeJSONReq(rr); err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: update"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.Validate(true); err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: update"))
|
||||
return
|
||||
}
|
||||
|
||||
// Name changed?
|
||||
if r.Name != rr.Name {
|
||||
if !sa.checkDuplicated("name", rr.Name) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// URL changed?
|
||||
if r.URL != rr.URL {
|
||||
if !sa.checkDuplicated("url", rr.URL) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
getChanges(r, rr)
|
||||
|
||||
if err := sa.c.UpdateRegistration(sa.Context(), r); err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: update"))
|
||||
return
|
||||
}
|
||||
|
||||
location := fmt.Sprintf("%s/%s", sa.Ctx.Request.RequestURI, r.UUID)
|
||||
sa.Ctx.ResponseWriter.Header().Add("Location", location)
|
||||
|
||||
// Response to the client
|
||||
sa.Data["json"] = r
|
||||
sa.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete the scanner
|
||||
func (sa *ScannerAPI) Delete() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionDelete, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r := sa.get()
|
||||
if r == nil {
|
||||
// meet error
|
||||
return
|
||||
}
|
||||
|
||||
// Immutable registration is not allowed
|
||||
if r.Immutable {
|
||||
sa.SendForbiddenError(errors.Errorf("registration %s is not allowed to delete as it is immutable: scanner API: delete", r.Name))
|
||||
return
|
||||
}
|
||||
|
||||
deleted, err := sa.c.DeleteRegistration(sa.Context(), r.UUID)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: delete"))
|
||||
return
|
||||
}
|
||||
|
||||
sa.Data["json"] = deleted
|
||||
sa.ServeJSON()
|
||||
}
|
||||
|
||||
// SetAsDefault sets the given registration as default one
|
||||
func (sa *ScannerAPI) SetAsDefault() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionCreate, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
uid := sa.GetStringFromPath(":uuid")
|
||||
|
||||
m := make(map[string]interface{})
|
||||
if err := sa.DecodeJSONReq(&m); err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: set as default"))
|
||||
return
|
||||
}
|
||||
|
||||
if v, ok := m["is_default"]; ok {
|
||||
if isDefault, y := v.(bool); y && isDefault {
|
||||
if err := sa.c.SetDefaultRegistration(sa.Context(), uid); err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: set as default"))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Not supported
|
||||
sa.SendForbiddenError(errors.Errorf("not supported: %#v", m))
|
||||
}
|
||||
|
||||
// Ping the registration.
|
||||
func (sa *ScannerAPI) Ping() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionRead, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r := &scanner.Registration{}
|
||||
|
||||
if err := sa.DecodeJSONReq(r); err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: ping"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.Validate(false); err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: ping"))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := sa.c.Ping(sa.Context(), r); err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: ping"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// get the specified scanner
|
||||
func (sa *ScannerAPI) get() *scanner.Registration {
|
||||
uid := sa.GetStringFromPath(":uuid")
|
||||
|
||||
r, err := sa.c.GetRegistration(sa.Context(), uid)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: get"))
|
||||
return nil
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
// NOT found
|
||||
sa.SendNotFoundError(errors.Errorf("scanner: %s", uid))
|
||||
return nil
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (sa *ScannerAPI) checkDuplicated(property, value string) bool {
|
||||
// Explicitly check if conflict
|
||||
kw := make(map[string]interface{})
|
||||
kw[property] = value
|
||||
|
||||
query := &q.Query{
|
||||
Keywords: kw,
|
||||
}
|
||||
|
||||
l, err := sa.c.ListRegistrations(sa.Context(), query)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scanner API: check existence"))
|
||||
return false
|
||||
}
|
||||
|
||||
if len(l) > 0 {
|
||||
sa.SendConflictError(errors.Errorf("duplicated entries: %s:%s", property, value))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getChanges(e *scanner.Registration, eChange *scanner.Registration) {
|
||||
e.Name = eChange.Name
|
||||
e.Description = eChange.Description
|
||||
e.URL = eChange.URL
|
||||
e.Auth = eChange.Auth
|
||||
e.AccessCredential = eChange.AccessCredential
|
||||
e.Disabled = eChange.Disabled
|
||||
e.SkipCertVerify = eChange.SkipCertVerify
|
||||
e.UseInternalAddr = eChange.UseInternalAddr
|
||||
}
|
@ -1,276 +0,0 @@
|
||||
// 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 api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
sc "github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
scannertesting "github.com/goharbor/harbor/src/testing/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const (
|
||||
rootRoute = "/api/scanners"
|
||||
)
|
||||
|
||||
// ScannerAPITestSuite is test suite for testing the scanner API
|
||||
type ScannerAPITestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
originC sc.Controller
|
||||
mockC *scannertesting.Controller
|
||||
}
|
||||
|
||||
// TestScannerAPI is the entry of ScannerAPITestSuite
|
||||
func TestScannerAPI(t *testing.T) {
|
||||
suite.Run(t, new(ScannerAPITestSuite))
|
||||
}
|
||||
|
||||
// SetupSuite prepares testing env
|
||||
func (suite *ScannerAPITestSuite) SetupTest() {
|
||||
suite.originC = sc.DefaultController
|
||||
m := &scannertesting.Controller{}
|
||||
sc.DefaultController = m
|
||||
|
||||
suite.mockC = m
|
||||
}
|
||||
|
||||
// TearDownTest clears test case env
|
||||
func (suite *ScannerAPITestSuite) TearDownTest() {
|
||||
// Restore
|
||||
sc.DefaultController = suite.originC
|
||||
}
|
||||
|
||||
// TestScannerAPICreate tests the post request to create new one
|
||||
func (suite *ScannerAPITestSuite) TestScannerAPIBase() {
|
||||
// Including general cases
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: rootRoute,
|
||||
method: http.MethodPost,
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: rootRoute,
|
||||
method: http.MethodPost,
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: rootRoute,
|
||||
method: http.MethodPost,
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &scanner.Registration{
|
||||
URL: "http://a.b.c",
|
||||
},
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
runCodeCheckingCases(suite.T(), cases...)
|
||||
}
|
||||
|
||||
// TestScannerAPIGet tests api get
|
||||
func (suite *ScannerAPITestSuite) TestScannerAPIGet() {
|
||||
res := &scanner.Registration{
|
||||
ID: 1000,
|
||||
UUID: "uuid",
|
||||
Name: "TestScannerAPIGet",
|
||||
Description: "JUST FOR TEST",
|
||||
URL: "https://a.b.c",
|
||||
}
|
||||
suite.mockC.On("GetRegistration", mock.Anything, "uuid").Return(res, nil)
|
||||
|
||||
// Get
|
||||
rr := &scanner.Registration{}
|
||||
err := handleAndParse(&testingRequest{
|
||||
url: fmt.Sprintf("%s/%s", rootRoute, "uuid"),
|
||||
method: http.MethodGet,
|
||||
credential: sysAdmin,
|
||||
}, rr)
|
||||
require.NoError(suite.T(), err)
|
||||
require.NotNil(suite.T(), rr)
|
||||
assert.Equal(suite.T(), res.Name, rr.Name)
|
||||
assert.Equal(suite.T(), res.UUID, rr.UUID)
|
||||
}
|
||||
|
||||
// TestScannerAPICreate tests create.
|
||||
func (suite *ScannerAPITestSuite) TestScannerAPICreate() {
|
||||
r := &scanner.Registration{
|
||||
Name: "TestScannerAPICreate",
|
||||
Description: "JUST FOR TEST",
|
||||
URL: "https://a.b.c",
|
||||
}
|
||||
|
||||
suite.mockQuery(r)
|
||||
suite.mockC.On("CreateRegistration", mock.Anything, r).Return("uuid", nil)
|
||||
|
||||
// Create
|
||||
res := make(map[string]string, 1)
|
||||
err := handleAndParse(
|
||||
&testingRequest{
|
||||
url: rootRoute,
|
||||
method: http.MethodPost,
|
||||
credential: sysAdmin,
|
||||
bodyJSON: r,
|
||||
}, &res)
|
||||
require.NoError(suite.T(), err)
|
||||
require.Condition(suite.T(), func() (success bool) {
|
||||
success = res["uuid"] == "uuid"
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// TestScannerAPIList tests list
|
||||
func (suite *ScannerAPITestSuite) TestScannerAPIList() {
|
||||
query := &q.Query{
|
||||
PageNumber: 1,
|
||||
PageSize: 500,
|
||||
}
|
||||
ll := []*scanner.Registration{
|
||||
{
|
||||
ID: 1001,
|
||||
UUID: "uuid",
|
||||
Name: "TestScannerAPIList",
|
||||
Description: "JUST FOR TEST",
|
||||
URL: "https://a.b.c",
|
||||
}}
|
||||
suite.mockC.On("ListRegistrations", mock.Anything, query).Return(ll, nil)
|
||||
|
||||
// List
|
||||
l := make([]*scanner.Registration, 0)
|
||||
err := handleAndParse(&testingRequest{
|
||||
url: rootRoute,
|
||||
method: http.MethodGet,
|
||||
credential: sysAdmin,
|
||||
}, &l)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Condition(suite.T(), func() (success bool) {
|
||||
success = len(l) > 0 && l[0].Name == ll[0].Name
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// TestScannerAPIUpdate tests the update API
|
||||
func (suite *ScannerAPITestSuite) TestScannerAPIUpdate() {
|
||||
before := &scanner.Registration{
|
||||
ID: 1002,
|
||||
UUID: "uuid",
|
||||
Name: "TestScannerAPIUpdate_before",
|
||||
Description: "JUST FOR TEST",
|
||||
URL: "https://a.b.c",
|
||||
}
|
||||
|
||||
updated := &scanner.Registration{
|
||||
ID: 1002,
|
||||
UUID: "uuid",
|
||||
Name: "TestScannerAPIUpdate",
|
||||
Description: "JUST FOR TEST",
|
||||
URL: "https://a.b.c",
|
||||
}
|
||||
|
||||
suite.mockQuery(updated)
|
||||
suite.mockC.On("UpdateRegistration", mock.Anything, updated).Return(nil)
|
||||
suite.mockC.On("GetRegistration", mock.Anything, "uuid").Return(before, nil)
|
||||
|
||||
rr := &scanner.Registration{}
|
||||
err := handleAndParse(&testingRequest{
|
||||
url: fmt.Sprintf("%s/%s", rootRoute, "uuid"),
|
||||
method: http.MethodPut,
|
||||
credential: sysAdmin,
|
||||
bodyJSON: updated,
|
||||
}, rr)
|
||||
require.NoError(suite.T(), err)
|
||||
require.NotNil(suite.T(), rr)
|
||||
|
||||
assert.Equal(suite.T(), updated.Name, rr.Name)
|
||||
assert.Equal(suite.T(), updated.UUID, rr.UUID)
|
||||
}
|
||||
|
||||
//
|
||||
func (suite *ScannerAPITestSuite) TestScannerAPIDelete() {
|
||||
r := &scanner.Registration{
|
||||
ID: 1003,
|
||||
UUID: "uuid",
|
||||
Name: "TestScannerAPIDelete",
|
||||
Description: "JUST FOR TEST",
|
||||
URL: "https://a.b.c",
|
||||
}
|
||||
|
||||
suite.mockC.On("GetRegistration", mock.Anything, "uuid").Return(r, nil)
|
||||
suite.mockC.On("DeleteRegistration", mock.Anything, "uuid").Return(r, nil)
|
||||
|
||||
deleted := &scanner.Registration{}
|
||||
err := handleAndParse(&testingRequest{
|
||||
url: fmt.Sprintf("%s/%s", rootRoute, "uuid"),
|
||||
method: http.MethodDelete,
|
||||
credential: sysAdmin,
|
||||
}, deleted)
|
||||
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), r.UUID, deleted.UUID)
|
||||
assert.Equal(suite.T(), r.Name, deleted.Name)
|
||||
}
|
||||
|
||||
// TestScannerAPISetDefault tests the set default
|
||||
func (suite *ScannerAPITestSuite) TestScannerAPISetDefault() {
|
||||
suite.mockC.On("SetDefaultRegistration", mock.Anything, "uuid").Return(nil)
|
||||
|
||||
body := make(map[string]interface{}, 1)
|
||||
body["is_default"] = true
|
||||
runCodeCheckingCases(suite.T(), &codeCheckingCase{
|
||||
request: &testingRequest{
|
||||
url: fmt.Sprintf("%s/%s", rootRoute, "uuid"),
|
||||
method: http.MethodPatch,
|
||||
credential: sysAdmin,
|
||||
bodyJSON: body,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *ScannerAPITestSuite) mockQuery(r *scanner.Registration) {
|
||||
kw := make(map[string]interface{}, 1)
|
||||
kw["name"] = r.Name
|
||||
query := &q.Query{
|
||||
Keywords: kw,
|
||||
}
|
||||
emptyL := make([]*scanner.Registration, 0)
|
||||
suite.mockC.On("ListRegistrations", mock.Anything, query).Return(emptyL, nil)
|
||||
|
||||
kw2 := make(map[string]interface{}, 1)
|
||||
kw2["url"] = r.URL
|
||||
query2 := &q.Query{
|
||||
Keywords: kw2,
|
||||
}
|
||||
suite.mockC.On("ListRegistrations", mock.Anything, query2).Return(emptyL, nil)
|
||||
}
|
@ -16,8 +16,6 @@ package scanner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
@ -28,6 +26,20 @@ func init() {
|
||||
orm.RegisterModel(new(Registration))
|
||||
}
|
||||
|
||||
// GetTotalOfRegistrations returns the total count of scanner registrations according to the query.
|
||||
func GetTotalOfRegistrations(ctx context.Context, query *q.Query) (int64, error) {
|
||||
query = q.MustClone(query)
|
||||
query.Sorting = ""
|
||||
query.PageNumber = 0
|
||||
query.PageSize = 0
|
||||
|
||||
qs, err := orm.QuerySetter(ctx, &Registration{}, query)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return qs.Count()
|
||||
}
|
||||
|
||||
// AddRegistration adds a new registration
|
||||
func AddRegistration(ctx context.Context, r *Registration) (int64, error) {
|
||||
o, err := orm.FromContext(ctx)
|
||||
@ -108,39 +120,22 @@ func DeleteRegistration(ctx context.Context, UUID string) error {
|
||||
|
||||
// ListRegistrations lists all the existing registrations
|
||||
func ListRegistrations(ctx context.Context, query *q.Query) ([]*Registration, error) {
|
||||
o, err := orm.FromContext(ctx)
|
||||
query = q.MustClone(query)
|
||||
|
||||
qs, err := orm.QuerySetter(ctx, &Registration{}, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qt := o.QueryTable(new(Registration))
|
||||
|
||||
if query != nil {
|
||||
if len(query.Keywords) > 0 {
|
||||
for k, v := range query.Keywords {
|
||||
if strings.HasPrefix(k, "ex_") {
|
||||
kk := strings.TrimPrefix(k, "ex_")
|
||||
qt = qt.Filter(kk, v)
|
||||
continue
|
||||
}
|
||||
if s, ok := v.(string); ok {
|
||||
v = orm.Escape(s)
|
||||
}
|
||||
|
||||
qt = qt.Filter(fmt.Sprintf("%s__icontains", k), v)
|
||||
}
|
||||
}
|
||||
|
||||
if query.PageNumber > 0 && query.PageSize > 0 {
|
||||
qt = qt.Limit(query.PageSize, (query.PageNumber-1)*query.PageSize)
|
||||
}
|
||||
// Order the list
|
||||
if query.Sorting != "" {
|
||||
qs = qs.OrderBy(query.Sorting)
|
||||
} else {
|
||||
qs = qs.OrderBy("-is_default", "-create_time")
|
||||
}
|
||||
|
||||
// Order the list
|
||||
qt = qt.OrderBy("-is_default", "-create_time")
|
||||
|
||||
l := make([]*Registration, 0)
|
||||
_, err = qt.All(&l)
|
||||
_, err = qs.All(&l)
|
||||
|
||||
return l, err
|
||||
}
|
||||
@ -164,7 +159,7 @@ func SetDefaultRegistration(ctx context.Context, UUID string) error {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return errors.Errorf("set default for %s failed", UUID)
|
||||
return errors.NotFoundError(nil).WithMessage("registration %s not found", UUID)
|
||||
}
|
||||
|
||||
qt2 := o.QueryTable(new(Registration))
|
||||
|
@ -108,7 +108,7 @@ func (suite *RegistrationDAOTestSuite) TestList() {
|
||||
|
||||
// with query and found items
|
||||
keywords := make(map[string]interface{})
|
||||
keywords["description"] = "sample"
|
||||
keywords["description"] = &q.FuzzyMatchValue{Value: "sample"}
|
||||
l, err = ListRegistrations(suite.Context(), &q.Query{
|
||||
PageSize: 5,
|
||||
PageNumber: 1,
|
||||
@ -118,7 +118,7 @@ func (suite *RegistrationDAOTestSuite) TestList() {
|
||||
require.Equal(suite.T(), 1, len(l))
|
||||
|
||||
// With query and not found items
|
||||
keywords["description"] = "not_exist"
|
||||
keywords["description"] = &q.FuzzyMatchValue{Value: "not_exist"}
|
||||
l, err = ListRegistrations(suite.Context(), &q.Query{
|
||||
Keywords: keywords,
|
||||
})
|
||||
@ -127,14 +127,14 @@ func (suite *RegistrationDAOTestSuite) TestList() {
|
||||
|
||||
// Exact match
|
||||
exactKeywords := make(map[string]interface{})
|
||||
exactKeywords["ex_name"] = "forUT"
|
||||
exactKeywords["name"] = "forUT"
|
||||
l, err = ListRegistrations(suite.Context(), &q.Query{
|
||||
Keywords: exactKeywords,
|
||||
})
|
||||
require.NoError(suite.T(), err)
|
||||
require.Equal(suite.T(), 1, len(l))
|
||||
|
||||
exactKeywords["ex_name"] = "forU"
|
||||
exactKeywords["name"] = "forU"
|
||||
l, err = ListRegistrations(suite.Context(), &q.Query{
|
||||
Keywords: exactKeywords,
|
||||
})
|
||||
|
@ -38,7 +38,7 @@ func EnsureScanners(ctx context.Context, wantedScanners []scanner.Registration)
|
||||
names[i] = ws.Name
|
||||
}
|
||||
|
||||
list, err := scannerManager.List(ctx, q.New(q.KeyWords{"ex_name__in": names}))
|
||||
list, err := scannerManager.List(ctx, q.New(q.KeyWords{"name__in": names}))
|
||||
if err != nil {
|
||||
return errors.Errorf("listing scanners: %v", err)
|
||||
}
|
||||
@ -79,7 +79,7 @@ func EnsureDefaultScanner(ctx context.Context, scannerName string) (err error) {
|
||||
log.Infof("Skipped setting %s as the default scanner. The default scanner is already set to %s", scannerName, defaultScanner.URL)
|
||||
return
|
||||
}
|
||||
scanners, err := scannerManager.List(ctx, q.New(q.KeyWords{"ex_name": scannerName}))
|
||||
scanners, err := scannerManager.List(ctx, q.New(q.KeyWords{"name": scannerName}))
|
||||
if err != nil {
|
||||
err = errors.Errorf("listing scanners: %v", err)
|
||||
return
|
||||
@ -99,7 +99,7 @@ func RemoveImmutableScanners(ctx context.Context, names []string) error {
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
query := q.New(q.KeyWords{"ex_immutable": true, "ex_name__in": names})
|
||||
query := q.New(q.KeyWords{"immutable": true, "name__in": names})
|
||||
|
||||
// TODO Instead of executing 1 to N SQL queries we might want to delete multiple rows with scannerManager.DeleteByImmutableAndURLIn(true, []string{})
|
||||
registrations, err := scannerManager.List(ctx, query)
|
||||
|
@ -39,7 +39,7 @@ func TestEnsureScanners(t *testing.T) {
|
||||
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ex_name__in": []string{"scanner"},
|
||||
"name__in": []string{"scanner"},
|
||||
},
|
||||
}).Return(nil, errors.New("DB error"))
|
||||
|
||||
@ -57,7 +57,7 @@ func TestEnsureScanners(t *testing.T) {
|
||||
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ex_name__in": []string{
|
||||
"name__in": []string{
|
||||
"trivy",
|
||||
},
|
||||
},
|
||||
@ -81,7 +81,7 @@ func TestEnsureScanners(t *testing.T) {
|
||||
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ex_name__in": []string{
|
||||
"name__in": []string{
|
||||
"trivy",
|
||||
},
|
||||
},
|
||||
@ -135,7 +135,7 @@ func TestEnsureDefaultScanner(t *testing.T) {
|
||||
|
||||
mgr.On("GetDefault", mock.Anything).Return(nil, nil)
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{"ex_name": "trivy"},
|
||||
Keywords: map[string]interface{}{"name": "trivy"},
|
||||
}).Return(nil, errors.New("DB error"))
|
||||
|
||||
err := EnsureDefaultScanner(context.TODO(), "trivy")
|
||||
@ -149,7 +149,7 @@ func TestEnsureDefaultScanner(t *testing.T) {
|
||||
|
||||
mgr.On("GetDefault", mock.Anything).Return(nil, nil)
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{"ex_name": "trivy"},
|
||||
Keywords: map[string]interface{}{"name": "trivy"},
|
||||
}).Return([]*scanner.Registration{
|
||||
{Name: "trivy"},
|
||||
{Name: "trivy"},
|
||||
@ -166,7 +166,7 @@ func TestEnsureDefaultScanner(t *testing.T) {
|
||||
|
||||
mgr.On("GetDefault", mock.Anything).Return(nil, nil)
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{"ex_name": "trivy"},
|
||||
Keywords: map[string]interface{}{"name": "trivy"},
|
||||
}).Return([]*scanner.Registration{
|
||||
{
|
||||
Name: "trivy",
|
||||
@ -187,7 +187,7 @@ func TestEnsureDefaultScanner(t *testing.T) {
|
||||
|
||||
mgr.On("GetDefault", mock.Anything).Return(nil, nil)
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{"ex_name": "trivy"},
|
||||
Keywords: map[string]interface{}{"name": "trivy"},
|
||||
}).Return([]*scanner.Registration{
|
||||
{
|
||||
Name: "trivy",
|
||||
@ -221,8 +221,8 @@ func TestRemoveImmutableScanners(t *testing.T) {
|
||||
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ex_immutable": true,
|
||||
"ex_name__in": []string{"scanner"},
|
||||
"immutable": true,
|
||||
"name__in": []string{"scanner"},
|
||||
},
|
||||
}).Return(nil, errors.New("DB error"))
|
||||
|
||||
@ -249,8 +249,8 @@ func TestRemoveImmutableScanners(t *testing.T) {
|
||||
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ex_immutable": true,
|
||||
"ex_name__in": []string{
|
||||
"immutable": true,
|
||||
"name__in": []string{
|
||||
"scanner-1",
|
||||
"scanner-2",
|
||||
},
|
||||
@ -285,8 +285,8 @@ func TestRemoveImmutableScanners(t *testing.T) {
|
||||
|
||||
mgr.On("List", mock.Anything, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ex_immutable": true,
|
||||
"ex_name__in": []string{
|
||||
"immutable": true,
|
||||
"name__in": []string{
|
||||
"scanner-1",
|
||||
"scanner-2",
|
||||
},
|
||||
|
@ -25,6 +25,9 @@ import (
|
||||
|
||||
// Manager defines the related scanner API endpoints
|
||||
type Manager interface {
|
||||
// Count returns the total count of scanner registrations according to the query.
|
||||
Count(ctx context.Context, query *q.Query) (int64, error)
|
||||
|
||||
// List returns a list of currently configured scanner registrations.
|
||||
// Query parameters are optional
|
||||
List(ctx context.Context, query *q.Query) ([]*scanner.Registration, error)
|
||||
@ -58,6 +61,10 @@ func New() Manager {
|
||||
return &basicManager{}
|
||||
}
|
||||
|
||||
func (bm *basicManager) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
return scanner.GetTotalOfRegistrations(ctx, query)
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (bm *basicManager) Create(ctx context.Context, registration *scanner.Registration) (string, error) {
|
||||
if registration == nil {
|
||||
|
@ -18,12 +18,13 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
rbac_project "github.com/goharbor/harbor/src/common/rbac/project"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
rbac_project "github.com/goharbor/harbor/src/common/rbac/project"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
@ -136,7 +137,7 @@ func (b *BaseAPI) RequireAuthenticated(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// BuildQuery builds the query model according to the query string
|
||||
func (b *BaseAPI) BuildQuery(ctx context.Context, query *string, pageNumber, pageSize *int64) (*q.Query, error) {
|
||||
func (b *BaseAPI) BuildQuery(ctx context.Context, query *string, pageNumber, pageSize *int64, sorts ...*string) (*q.Query, error) {
|
||||
var (
|
||||
qs string
|
||||
pn int64
|
||||
@ -151,7 +152,17 @@ func (b *BaseAPI) BuildQuery(ctx context.Context, query *string, pageNumber, pag
|
||||
if pageSize != nil {
|
||||
ps = *pageSize
|
||||
}
|
||||
return q.Build(qs, pn, ps)
|
||||
|
||||
r, err := q.Build(qs, pn, ps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(sorts) > 0 {
|
||||
r.Sorting = lib.StringValue(sorts[0])
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Links return Links based on the provided pagination information
|
||||
|
@ -34,6 +34,7 @@ func New() http.Handler {
|
||||
ArtifactAPI: newArtifactAPI(),
|
||||
RepositoryAPI: newRepositoryAPI(),
|
||||
AuditlogAPI: newAuditLogAPI(),
|
||||
ScannerAPI: newScannerAPI(),
|
||||
ScanAPI: newScanAPI(),
|
||||
ScanAllAPI: newScanAllAPI(),
|
||||
ProjectAPI: newProjectAPI(),
|
||||
|
59
src/server/v2.0/handler/model/scanner.go
Normal file
59
src/server/v2.0/handler/model/scanner.go
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
)
|
||||
|
||||
// ScannerRegistration ...
|
||||
type ScannerRegistration struct {
|
||||
*scanner.Registration
|
||||
}
|
||||
|
||||
// ToSwagger ...
|
||||
func (s *ScannerRegistration) ToSwagger(ctx context.Context) *models.ScannerRegistration {
|
||||
if s.Registration == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &models.ScannerRegistration{
|
||||
UUID: s.UUID,
|
||||
Name: s.Name,
|
||||
URL: s.URL,
|
||||
Description: s.Description,
|
||||
Auth: s.Auth,
|
||||
AccessCredential: s.AccessCredential,
|
||||
SkipCertVerify: &s.SkipCertVerify,
|
||||
UseInternalAddr: &s.UseInternalAddr,
|
||||
IsDefault: &s.IsDefault,
|
||||
Disabled: &s.Disabled,
|
||||
CreateTime: strfmt.DateTime(s.CreateTime),
|
||||
UpdateTime: strfmt.DateTime(s.UpdateTime),
|
||||
Adapter: s.Adapter,
|
||||
Vendor: s.Vendor,
|
||||
Version: s.Version,
|
||||
Health: s.Health,
|
||||
}
|
||||
}
|
||||
|
||||
// NewScannerRegistration ...
|
||||
func NewScannerRegistration(scanner *scanner.Registration) *ScannerRegistration {
|
||||
return &ScannerRegistration{Registration: scanner}
|
||||
}
|
@ -33,6 +33,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/quota"
|
||||
"github.com/goharbor/harbor/src/controller/repository"
|
||||
"github.com/goharbor/harbor/src/controller/retention"
|
||||
"github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
@ -66,6 +67,7 @@ func newProjectAPI() *projectAPI {
|
||||
robotMgr: robot.Mgr,
|
||||
preheatCtl: preheat.Ctl,
|
||||
retentionCtl: retention.Ctl,
|
||||
scannerCtl: scanner.DefaultController,
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +82,7 @@ type projectAPI struct {
|
||||
robotMgr robot.Manager
|
||||
preheatCtl preheat.Controller
|
||||
retentionCtl retention.Controller
|
||||
scannerCtl scanner.Controller
|
||||
}
|
||||
|
||||
func (a *projectAPI) CreateProject(ctx context.Context, params operation.CreateProjectParams) middleware.Responder {
|
||||
@ -502,6 +505,75 @@ func (a *projectAPI) UpdateProject(ctx context.Context, params operation.UpdateP
|
||||
return operation.NewUpdateProjectOK()
|
||||
}
|
||||
|
||||
func (a *projectAPI) GetScannerOfProject(ctx context.Context, params operation.GetScannerOfProjectParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead, rbac.ResourceScanner); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
p, err := a.projectCtl.Get(ctx, projectNameOrID, project.Metadata(false))
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
scanner, err := a.scannerCtl.GetRegistrationByProject(ctx, p.ProjectID)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewGetScannerOfProjectOK().WithPayload(model.NewScannerRegistration(scanner).ToSwagger(ctx))
|
||||
}
|
||||
|
||||
func (a *projectAPI) ListScannerCandidatesOfProject(ctx context.Context, params operation.ListScannerCandidatesOfProjectParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionCreate, rbac.ResourceScanner); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
query, err := a.BuildQuery(ctx, params.Q, params.Page, params.PageSize, params.Sort)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
total, err := a.scannerCtl.GetTotalOfRegistrations(ctx, query)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
scanners, err := a.scannerCtl.ListRegistrations(ctx, query)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
payload := make([]*models.ScannerRegistration, len(scanners))
|
||||
for i, scanner := range scanners {
|
||||
payload[i] = model.NewScannerRegistration(scanner).ToSwagger(ctx)
|
||||
}
|
||||
|
||||
return operation.NewListScannerCandidatesOfProjectOK().
|
||||
WithXTotalCount(total).
|
||||
WithLink(a.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
||||
WithPayload(payload)
|
||||
}
|
||||
|
||||
func (a *projectAPI) SetScannerOfProject(ctx context.Context, params operation.SetScannerOfProjectParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionCreate, rbac.ResourceScanner); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
p, err := a.projectCtl.Get(ctx, projectNameOrID, project.Metadata(false))
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := a.scannerCtl.SetRegistrationByProject(ctx, p.ProjectID, *params.Payload.UUID); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewSetScannerOfProjectOK()
|
||||
}
|
||||
|
||||
func (a *projectAPI) deletable(ctx context.Context, projectNameOrID interface{}) (*project.Project, *models.ProjectDeletable, error) {
|
||||
p, err := a.getProject(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
|
201
src/server/v2.0/handler/project_test.go
Normal file
201
src/server/v2.0/handler/project_test.go
Normal file
@ -0,0 +1,201 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/project/models"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/restapi"
|
||||
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||
scannertesting "github.com/goharbor/harbor/src/testing/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
htesting "github.com/goharbor/harbor/src/testing/server/v2.0/handler"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ProjectTestSuite struct {
|
||||
htesting.Suite
|
||||
|
||||
projectCtl *projecttesting.Controller
|
||||
scannerCtl *scannertesting.Controller
|
||||
project *models.Project
|
||||
reg *scanner.Registration
|
||||
}
|
||||
|
||||
func (suite *ProjectTestSuite) SetupSuite() {
|
||||
suite.project = &models.Project{
|
||||
ProjectID: 1,
|
||||
Name: "library",
|
||||
}
|
||||
|
||||
suite.reg = &scanner.Registration{
|
||||
Name: "reg",
|
||||
URL: "http://reg:8080",
|
||||
UUID: "uuid",
|
||||
}
|
||||
|
||||
suite.projectCtl = &projecttesting.Controller{}
|
||||
suite.scannerCtl = &scannertesting.Controller{}
|
||||
|
||||
suite.Config = &restapi.Config{
|
||||
ProjectAPI: &projectAPI{
|
||||
projectCtl: suite.projectCtl,
|
||||
scannerCtl: suite.scannerCtl,
|
||||
},
|
||||
}
|
||||
|
||||
suite.Suite.SetupSuite()
|
||||
}
|
||||
|
||||
func (suite *ProjectTestSuite) TestGetScannerOfProject() {
|
||||
times := 4
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// get project failed
|
||||
mock.OnAnything(suite.projectCtl, "Get").Return(nil, fmt.Errorf("failed to get project")).Once()
|
||||
|
||||
res, err := suite.Get("/projects/1/scanner")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// scanner not found
|
||||
mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(nil, nil).Once()
|
||||
|
||||
res, err := suite.Get("/projects/1/scanner")
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(suite.reg, nil).Once()
|
||||
|
||||
var scanner scanner.Registration
|
||||
res, err := suite.GetJSON("/projects/1/scanner", &scanner)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Equal(suite.reg.UUID, scanner.UUID)
|
||||
}
|
||||
|
||||
{
|
||||
mock.OnAnything(projectCtlMock, "GetByName").Return(suite.project, nil).Once()
|
||||
mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(suite.reg, nil).Once()
|
||||
|
||||
var scanner scanner.Registration
|
||||
res, err := suite.GetJSON("/projects/library/scanner", &scanner)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Equal(suite.reg.UUID, scanner.UUID)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ProjectTestSuite) TestListScannerCandidatesOfProject() {
|
||||
times := 4
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// list scanners failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetTotalOfRegistrations").Return(int64(0), fmt.Errorf("failed to count scanners")).Once()
|
||||
|
||||
res, err := suite.Get("/projects/1/scanner/candidates")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// list scanners failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetTotalOfRegistrations").Return(int64(1), nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return(nil, fmt.Errorf("failed to list scanners")).Once()
|
||||
|
||||
res, err := suite.Get("/projects/1/scanner/candidates")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// scanners not found
|
||||
mock.OnAnything(suite.scannerCtl, "GetTotalOfRegistrations").Return(int64(0), nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return(nil, nil).Once()
|
||||
|
||||
var scanners []interface{}
|
||||
res, err := suite.GetJSON("/projects/1/scanner/candidates", &scanners)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Len(scanners, 0)
|
||||
}
|
||||
|
||||
{
|
||||
// scanners found
|
||||
mock.OnAnything(suite.scannerCtl, "GetTotalOfRegistrations").Return(int64(3), nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{suite.reg}, nil).Once()
|
||||
|
||||
var scanners []interface{}
|
||||
res, err := suite.GetJSON("/projects/1/scanner/candidates?page_size=1&page=2&name=n&description=d&url=u&ex_name=n&ex_url=u", &scanners)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Len(scanners, 1)
|
||||
suite.Equal("3", res.Header.Get("X-Total-Count"))
|
||||
suite.Contains(res.Header, "Link")
|
||||
suite.Equal(`</api/v2.0/projects/1/scanner/candidates?description=d&ex_name=n&ex_url=u&name=n&page=1&page_size=1&url=u>; rel="prev" , </api/v2.0/projects/1/scanner/candidates?description=d&ex_name=n&ex_url=u&name=n&page=3&page_size=1&url=u>; rel="next"`, res.Header.Get("Link"))
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ProjectTestSuite) TestSetScannerOfProject() {
|
||||
times := 3
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// get project failed
|
||||
mock.OnAnything(suite.projectCtl, "Get").Return(nil, fmt.Errorf("failed to get project")).Once()
|
||||
|
||||
res, err := suite.PutJSON("/projects/1/scanner", map[string]interface{}{"uuid": "uuid"})
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "SetRegistrationByProject").Return(nil).Once()
|
||||
|
||||
res, err := suite.PutJSON("/projects/1/scanner", map[string]interface{}{"uuid": "uuid"})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
mock.OnAnything(projectCtlMock, "GetByName").Return(suite.project, nil).Once()
|
||||
mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "SetRegistrationByProject").Return(nil).Once()
|
||||
|
||||
res, err := suite.PutJSON("/projects/library/scanner", map[string]interface{}{"uuid": "uuid"})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectTestSuite(t *testing.T) {
|
||||
suite.Run(t, &ProjectTestSuite{})
|
||||
}
|
247
src/server/v2.0/handler/scanner.go
Normal file
247
src/server/v2.0/handler/scanner.go
Normal file
@ -0,0 +1,247 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/scanner"
|
||||
)
|
||||
|
||||
func newScannerAPI() *scannerAPI {
|
||||
return &scannerAPI{
|
||||
scannerCtl: scanner.DefaultController,
|
||||
}
|
||||
}
|
||||
|
||||
type scannerAPI struct {
|
||||
BaseAPI
|
||||
scannerCtl scanner.Controller
|
||||
}
|
||||
|
||||
func (s *scannerAPI) CreateScanner(ctx context.Context, params operation.CreateScannerParams) middleware.Responder {
|
||||
if err := s.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourceScanner); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
r := &scanner.Registration{IsDefault: false}
|
||||
copyToScannerRegistration(r, params.Registration)
|
||||
|
||||
if err := r.Validate(false); err != nil {
|
||||
return s.SendError(ctx, errors.BadRequestError(nil).WithMessage(err.Error()))
|
||||
}
|
||||
|
||||
uuid, err := s.scannerCtl.CreateRegistration(ctx, r)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
location := fmt.Sprintf("%s/%s", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), uuid)
|
||||
return operation.NewCreateScannerCreated().WithLocation(location)
|
||||
}
|
||||
|
||||
func (s *scannerAPI) DeleteScanner(ctx context.Context, params operation.DeleteScannerParams) middleware.Responder {
|
||||
if err := s.RequireSystemAccess(ctx, rbac.ActionDelete, rbac.ResourceScanner); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
r, err := s.scannerCtl.GetRegistration(ctx, params.RegistrationID)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
return s.SendError(ctx, errors.NotFoundError(nil).WithMessage("scanner %s not found", params.RegistrationID))
|
||||
}
|
||||
|
||||
// Immutable registration is not allowed
|
||||
if r.Immutable {
|
||||
format := "registration %s is not allowed to delete as it is immutable: scanner API: delete"
|
||||
return s.SendError(ctx, errors.ForbiddenError(nil).WithMessage(format, r.Name))
|
||||
}
|
||||
|
||||
deleted, err := s.scannerCtl.DeleteRegistration(ctx, r.UUID)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewDeleteScannerOK().WithPayload(model.NewScannerRegistration(deleted).ToSwagger(ctx))
|
||||
}
|
||||
|
||||
func (s *scannerAPI) GetScanner(ctx context.Context, params operation.GetScannerParams) middleware.Responder {
|
||||
if err := s.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceScanner); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
r, err := s.scannerCtl.GetRegistration(ctx, params.RegistrationID)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
return s.SendError(ctx, errors.NotFoundError(nil).WithMessage("scanner %s not found", params.RegistrationID))
|
||||
}
|
||||
|
||||
return operation.NewGetScannerOK().WithPayload(model.NewScannerRegistration(r).ToSwagger(ctx))
|
||||
}
|
||||
|
||||
func (s *scannerAPI) GetScannerMetadata(ctx context.Context, params operation.GetScannerMetadataParams) middleware.Responder {
|
||||
if err := s.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceScanner); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
meta, err := s.scannerCtl.GetMetadata(ctx, params.RegistrationID)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewGetScannerMetadataOK().WithPayload(meta)
|
||||
}
|
||||
|
||||
func (s *scannerAPI) ListScanners(ctx context.Context, params operation.ListScannersParams) middleware.Responder {
|
||||
if err := s.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceScanner); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
query, err := s.BuildQuery(ctx, params.Q, params.Page, params.PageSize, params.Sort)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
// compatible with previous version list scanners API
|
||||
values := params.HTTPRequest.URL.Query()
|
||||
for _, k := range []string{"name", "description", "url"} {
|
||||
if v := values.Get(k); v != "" {
|
||||
query.Keywords[k] = &q.FuzzyMatchValue{Value: v}
|
||||
}
|
||||
}
|
||||
|
||||
for _, k := range []string{"ex_name", "ex_url"} {
|
||||
if v := values.Get(k); v != "" {
|
||||
query.Keywords[strings.TrimPrefix(k, "ex_")] = v
|
||||
}
|
||||
}
|
||||
|
||||
total, err := s.scannerCtl.GetTotalOfRegistrations(ctx, query)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
scanners, err := s.scannerCtl.ListRegistrations(ctx, query)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
payload := make([]*models.ScannerRegistration, len(scanners))
|
||||
for i, scanner := range scanners {
|
||||
payload[i] = model.NewScannerRegistration(scanner).ToSwagger(ctx)
|
||||
}
|
||||
|
||||
return operation.NewListScannersOK().
|
||||
WithXTotalCount(total).
|
||||
WithLink(s.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
||||
WithPayload(payload)
|
||||
}
|
||||
|
||||
func (s *scannerAPI) PingScanner(ctx context.Context, params operation.PingScannerParams) middleware.Responder {
|
||||
if err := s.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceScanner); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
r := &scanner.Registration{
|
||||
Name: lib.StringValue(params.Settings.Name),
|
||||
URL: lib.StringValue((*string)(params.Settings.URL)),
|
||||
Auth: params.Settings.Auth,
|
||||
AccessCredential: params.Settings.AccessCredential,
|
||||
}
|
||||
|
||||
if err := r.Validate(false); err != nil {
|
||||
return s.SendError(ctx, errors.BadRequestError(nil).WithMessage(err.Error()))
|
||||
}
|
||||
|
||||
if _, err := s.scannerCtl.Ping(ctx, r); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewPingScannerOK()
|
||||
}
|
||||
|
||||
func (s *scannerAPI) SetScannerAsDefault(ctx context.Context, params operation.SetScannerAsDefaultParams) middleware.Responder {
|
||||
if err := s.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceScanner); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if params.Payload.IsDefault {
|
||||
if err := s.scannerCtl.SetDefaultRegistration(ctx, params.RegistrationID); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
return operation.NewSetScannerAsDefaultOK()
|
||||
}
|
||||
|
||||
func (s *scannerAPI) UpdateScanner(ctx context.Context, params operation.UpdateScannerParams) middleware.Responder {
|
||||
if err := s.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceScanner); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
r, err := s.scannerCtl.GetRegistration(ctx, params.RegistrationID)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
return s.SendError(ctx, errors.NotFoundError(nil).WithMessage("scanner %s not found", params.RegistrationID))
|
||||
}
|
||||
|
||||
// Immutable registration is not allowed
|
||||
if r.Immutable {
|
||||
format := "registration %s is not allowed to update as it is immutable: scanner API: update"
|
||||
return s.SendError(ctx, errors.ForbiddenError(nil).WithMessage(format, r.Name))
|
||||
}
|
||||
|
||||
copyToScannerRegistration(r, params.Registration)
|
||||
|
||||
if err := r.Validate(true); err != nil {
|
||||
return s.SendError(ctx, errors.BadRequestError(nil).WithMessage(err.Error()))
|
||||
}
|
||||
|
||||
if err := s.scannerCtl.UpdateRegistration(ctx, r); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewUpdateScannerOK()
|
||||
}
|
||||
|
||||
func copyToScannerRegistration(r *scanner.Registration, req *models.ScannerRegistrationReq) {
|
||||
r.Name = lib.StringValue(req.Name)
|
||||
r.URL = lib.StringValue((*string)(req.URL))
|
||||
r.Description = req.Description
|
||||
r.Disabled = lib.BoolValue(req.Disabled)
|
||||
r.SkipCertVerify = lib.BoolValue(req.SkipCertVerify)
|
||||
r.UseInternalAddr = lib.BoolValue(req.UseInternalAddr)
|
||||
r.Auth = req.Auth
|
||||
r.AccessCredential = req.AccessCredential
|
||||
}
|
532
src/server/v2.0/handler/scanner_test.go
Normal file
532
src/server/v2.0/handler/scanner_test.go
Normal file
@ -0,0 +1,532 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/restapi"
|
||||
scannertesting "github.com/goharbor/harbor/src/testing/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
htesting "github.com/goharbor/harbor/src/testing/server/v2.0/handler"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ScannerTestSuite struct {
|
||||
htesting.Suite
|
||||
|
||||
scannerCtl *scannertesting.Controller
|
||||
reg *scanner.Registration
|
||||
|
||||
metadata v1.ScannerAdapterMetadata
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) SetupSuite() {
|
||||
suite.reg = &scanner.Registration{
|
||||
Name: "reg",
|
||||
URL: "http://reg:8080",
|
||||
UUID: "uuid",
|
||||
}
|
||||
|
||||
suite.metadata = v1.ScannerAdapterMetadata{
|
||||
Scanner: &v1.Scanner{
|
||||
Name: "reg",
|
||||
},
|
||||
}
|
||||
|
||||
suite.scannerCtl = &scannertesting.Controller{}
|
||||
|
||||
suite.Config = &restapi.Config{
|
||||
ScannerAPI: &scannerAPI{
|
||||
scannerCtl: suite.scannerCtl,
|
||||
},
|
||||
}
|
||||
|
||||
suite.Suite.SetupSuite()
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestAuthorization() {
|
||||
newBody := func(body interface{}) io.Reader {
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(body)
|
||||
suite.Require().NoError(err)
|
||||
return bytes.NewBuffer(buf)
|
||||
}
|
||||
|
||||
reqs := []struct {
|
||||
method string
|
||||
url string
|
||||
body interface{}
|
||||
}{
|
||||
{http.MethodGet, "/scanners", nil},
|
||||
{http.MethodPost, "/scanners", suite.reg},
|
||||
{http.MethodPost, "/scanners/ping", suite.reg},
|
||||
{http.MethodGet, "/scanners/uuid1", nil},
|
||||
{http.MethodPut, "/scanners/uuid1", suite.reg},
|
||||
{http.MethodDelete, "/scanners/uuid1", nil},
|
||||
{http.MethodPatch, "/scanners/uuid1", map[string]interface{}{"is_default": true}},
|
||||
{http.MethodGet, "/scanners/uuid1/metadata", nil},
|
||||
}
|
||||
|
||||
for _, req := range reqs {
|
||||
{
|
||||
// authorized required
|
||||
suite.Security.On("IsAuthenticated").Return(false).Once()
|
||||
|
||||
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
|
||||
suite.NoError(err)
|
||||
suite.Equal(401, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// permission required
|
||||
suite.Security.On("IsAuthenticated").Return(true).Once()
|
||||
suite.Security.On("GetUsername").Return("username").Once()
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once()
|
||||
|
||||
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
|
||||
suite.NoError(err)
|
||||
suite.Equal(403, res.StatusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestCreateScannerWithInvalidBody() {
|
||||
{
|
||||
// empty body
|
||||
res, err := suite.PostJSON("/scanners", nil)
|
||||
suite.NoError(err)
|
||||
suite.Equal(422, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// name missing
|
||||
res, err := suite.PostJSON("/scanners", map[string]interface{}{
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(422, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// url missing
|
||||
res, err := suite.PostJSON("/scanners", map[string]interface{}{
|
||||
"name": "reg",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(422, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// invalid url
|
||||
res, err := suite.PostJSON("/scanners", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "invalid url",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(422, res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestCreateScanner() {
|
||||
times := 4
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
mock.OnAnything(suite.scannerCtl, "CreateRegistration").Return("", fmt.Errorf("failed to create registration")).Once()
|
||||
res, err := suite.PostJSON("/scanners", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
mock.OnAnything(suite.scannerCtl, "CreateRegistration").Return("uuid", nil).Once()
|
||||
res, err := suite.PostJSON("/scanners", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(201, res.StatusCode)
|
||||
suite.Equal("/api/v2.0/scanners/uuid", res.Header.Get("Location"))
|
||||
}
|
||||
|
||||
{
|
||||
// access_credential missing
|
||||
res, err := suite.PostJSON("/scanners", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
"auth": "Basic",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(400, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
mock.OnAnything(suite.scannerCtl, "CreateRegistration").Return("uuid", nil).Once()
|
||||
res, err := suite.PostJSON("/scanners", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
"auth": "Basic",
|
||||
"access_credential": "username:password",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(201, res.StatusCode)
|
||||
suite.Equal("/api/v2.0/scanners/uuid", res.Header.Get("Location"))
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestDeleteScanner() {
|
||||
times := 5
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// get scanner failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(nil, fmt.Errorf("failed to get registration")).Once()
|
||||
res, err := suite.Delete("/scanners/uuid")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// scanner not found
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(nil, nil).Once()
|
||||
res, err := suite.Delete("/scanners/uuid")
|
||||
suite.NoError(err)
|
||||
suite.Equal(404, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// immutable scanner
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(&scanner.Registration{Immutable: true}, nil).Once()
|
||||
res, err := suite.Delete("/scanners/uuid")
|
||||
suite.NoError(err)
|
||||
suite.Equal(403, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// delete scanner failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(suite.reg, nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "DeleteRegistration").Return(nil, fmt.Errorf("failed to delete registration")).Once()
|
||||
res, err := suite.Delete("/scanners/uuid")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// delete scanner
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(suite.reg, nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "DeleteRegistration").Return(suite.reg, nil).Once()
|
||||
res, err := suite.Delete("/scanners/uuid")
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestGetScanner() {
|
||||
times := 3
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// get scanner failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(nil, fmt.Errorf("failed to get registration")).Once()
|
||||
|
||||
res, err := suite.Get("/scanners/uuid")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// scanner not found
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(nil, nil).Once()
|
||||
|
||||
var scanner map[string]interface{}
|
||||
res, err := suite.GetJSON("/scanners/uuid", &scanner)
|
||||
suite.NoError(err)
|
||||
suite.Equal(404, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// scanner found
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(suite.reg, nil).Once()
|
||||
|
||||
var scanner map[string]interface{}
|
||||
res, err := suite.GetJSON("/scanners/uuid", &scanner)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Equal("uuid", scanner["uuid"])
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestGetScannerMetadata() {
|
||||
times := 3
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// get metadata failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(nil, fmt.Errorf("failed to get metadata")).Once()
|
||||
|
||||
res, err := suite.Get("/scanners/uuid/metadata")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(&suite.metadata, nil).Once()
|
||||
|
||||
var md v1.ScannerAdapterMetadata
|
||||
res, err := suite.GetJSON("/scanners/uuid/metadata", &md)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Equal(suite.metadata, md)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestListScanners() {
|
||||
times := 4
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// list scanners failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetTotalOfRegistrations").Return(int64(0), fmt.Errorf("failed to count scanners")).Once()
|
||||
|
||||
res, err := suite.Get("/scanners")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// list scanners failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetTotalOfRegistrations").Return(int64(1), nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return(nil, fmt.Errorf("failed to list scanners")).Once()
|
||||
|
||||
res, err := suite.Get("/scanners")
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// scanners not found
|
||||
mock.OnAnything(suite.scannerCtl, "GetTotalOfRegistrations").Return(int64(0), nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return(nil, nil).Once()
|
||||
|
||||
var scanners []interface{}
|
||||
res, err := suite.GetJSON("/scanners", &scanners)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Len(scanners, 0)
|
||||
}
|
||||
|
||||
{
|
||||
// scanners found
|
||||
mock.OnAnything(suite.scannerCtl, "GetTotalOfRegistrations").Return(int64(3), nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{suite.reg}, nil).Once()
|
||||
|
||||
var scanners []interface{}
|
||||
res, err := suite.GetJSON("/scanners?page_size=1&page=2", &scanners)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Len(scanners, 1)
|
||||
suite.Equal("3", res.Header.Get("X-Total-Count"))
|
||||
suite.Contains(res.Header, "Link")
|
||||
suite.Equal(`</api/v2.0/scanners?page=1&page_size=1>; rel="prev" , </api/v2.0/scanners?page=3&page_size=1>; rel="next"`, res.Header.Get("Link"))
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestPingScanner() {
|
||||
times := 3
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// bad req
|
||||
res, err := suite.PostJSON("/scanners/ping", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
"auth": "Basic",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(400, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// ping failed
|
||||
mock.OnAnything(suite.scannerCtl, "Ping").Return(nil, fmt.Errorf("failed to ping scanner")).Once()
|
||||
|
||||
res, err := suite.PostJSON("/scanners/ping", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// ping
|
||||
mock.OnAnything(suite.scannerCtl, "Ping").Return(&suite.metadata, nil).Once()
|
||||
|
||||
res, err := suite.PostJSON("/scanners/ping", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestSetScannerAsDefault() {
|
||||
times := 3
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
res, err := suite.PatchJSON("/scanners/uuid", map[string]interface{}{
|
||||
"is_default": false,
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// set default failed
|
||||
mock.OnAnything(suite.scannerCtl, "SetDefaultRegistration").Return(fmt.Errorf("failed to set default")).Once()
|
||||
|
||||
res, err := suite.PatchJSON("/scanners/uuid", map[string]interface{}{
|
||||
"is_default": true,
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// set default
|
||||
mock.OnAnything(suite.scannerCtl, "SetDefaultRegistration").Return(nil).Once()
|
||||
|
||||
res, err := suite.PatchJSON("/scanners/uuid", map[string]interface{}{
|
||||
"is_default": true,
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ScannerTestSuite) TestUpdateScanner() {
|
||||
times := 7
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
|
||||
{
|
||||
// update scanner no body
|
||||
res, err := suite.Put("/scanners/uuid", nil)
|
||||
suite.NoError(err)
|
||||
suite.Equal(422, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// get scanner failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(nil, fmt.Errorf("failed to get registration")).Once()
|
||||
|
||||
res, err := suite.PutJSON("/scanners/uuid", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// scanner not found
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(nil, nil).Once()
|
||||
|
||||
res, err := suite.PutJSON("/scanners/uuid", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(404, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// immutable scanner
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(&scanner.Registration{Immutable: true}, nil).Once()
|
||||
|
||||
res, err := suite.PutJSON("/scanners/uuid", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(403, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// bad req
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(suite.reg, nil).Once()
|
||||
|
||||
res, err := suite.PutJSON("/scanners/uuid", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
"auth": "Basic",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(400, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// update scanner failed
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(suite.reg, nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "UpdateRegistration").Return(fmt.Errorf("failed to update the scanner")).Once()
|
||||
|
||||
res, err := suite.PutJSON("/scanners/uuid", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(500, res.StatusCode)
|
||||
}
|
||||
|
||||
{
|
||||
// update scanner
|
||||
mock.OnAnything(suite.scannerCtl, "GetRegistration").Return(suite.reg, nil).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "UpdateRegistration").Return(nil).Once()
|
||||
|
||||
res, err := suite.PutJSON("/scanners/uuid", map[string]interface{}{
|
||||
"name": "reg",
|
||||
"url": "http://reg:8080",
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScannerTestSuite(t *testing.T) {
|
||||
suite.Run(t, &ScannerTestSuite{})
|
||||
}
|
@ -81,16 +81,4 @@ func registerLegacyRoutes() {
|
||||
beego.Router("/api/"+version+"/chartrepo/:repo/charts/:name/:version/labels", chartLabelAPIType, "get:GetLabels;post:MarkLabel")
|
||||
beego.Router("/api/"+version+"/chartrepo/:repo/charts/:name/:version/labels/:id([0-9]+)", chartLabelAPIType, "delete:RemoveLabel")
|
||||
}
|
||||
|
||||
// Add routes for plugin scanner management
|
||||
scannerAPI := &api.ScannerAPI{}
|
||||
beego.Router("/api/"+version+"/scanners", scannerAPI, "post:Create;get:List")
|
||||
beego.Router("/api/"+version+"/scanners/:uuid", scannerAPI, "get:Get;delete:Delete;put:Update;patch:SetAsDefault")
|
||||
beego.Router("/api/"+version+"/scanners/:uuid/metadata", scannerAPI, "get:Metadata")
|
||||
beego.Router("/api/"+version+"/scanners/ping", scannerAPI, "post:Ping")
|
||||
|
||||
// Add routes for project level scanner
|
||||
proScannerAPI := &api.ProjectScannerAPI{}
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/scanner", proScannerAPI, "get:GetProjectScanner;put:SetProjectScanner")
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/scanner/candidates", proScannerAPI, "get:GetProScannerCandidates")
|
||||
}
|
||||
|
@ -140,6 +140,27 @@ func (_m *Controller) GetRegistrationByProject(ctx context.Context, projectID in
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetTotalOfRegistrations provides a mock function with given fields: ctx, query
|
||||
func (_m *Controller) GetTotalOfRegistrations(ctx context.Context, query *q.Query) (int64, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) int64); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
|
||||
r1 = rf(ctx, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ListRegistrations provides a mock function with given fields: ctx, query
|
||||
func (_m *Controller) ListRegistrations(ctx context.Context, query *q.Query) ([]*scanner.Registration, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
@ -5,10 +5,10 @@ package scanner
|
||||
import (
|
||||
context "context"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
daoscanner "github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
scanner "github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// Manager is an autogenerated mock type for the Manager type
|
||||
@ -16,19 +16,40 @@ type Manager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Count provides a mock function with given fields: ctx, query
|
||||
func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) int64); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
|
||||
r1 = rf(ctx, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, registration
|
||||
func (_m *Manager) Create(ctx context.Context, registration *scanner.Registration) (string, error) {
|
||||
func (_m *Manager) Create(ctx context.Context, registration *daoscanner.Registration) (string, error) {
|
||||
ret := _m.Called(ctx, registration)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) string); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *daoscanner.Registration) string); ok {
|
||||
r0 = rf(ctx, registration)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *scanner.Registration) error); ok {
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *daoscanner.Registration) error); ok {
|
||||
r1 = rf(ctx, registration)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
@ -52,15 +73,15 @@ func (_m *Manager) Delete(ctx context.Context, registrationUUID string) error {
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, registrationUUID
|
||||
func (_m *Manager) Get(ctx context.Context, registrationUUID string) (*scanner.Registration, error) {
|
||||
func (_m *Manager) Get(ctx context.Context, registrationUUID string) (*daoscanner.Registration, error) {
|
||||
ret := _m.Called(ctx, registrationUUID)
|
||||
|
||||
var r0 *scanner.Registration
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *scanner.Registration); ok {
|
||||
var r0 *daoscanner.Registration
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *daoscanner.Registration); ok {
|
||||
r0 = rf(ctx, registrationUUID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scanner.Registration)
|
||||
r0 = ret.Get(0).(*daoscanner.Registration)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,15 +96,15 @@ func (_m *Manager) Get(ctx context.Context, registrationUUID string) (*scanner.R
|
||||
}
|
||||
|
||||
// GetDefault provides a mock function with given fields: ctx
|
||||
func (_m *Manager) GetDefault(ctx context.Context) (*scanner.Registration, error) {
|
||||
func (_m *Manager) GetDefault(ctx context.Context) (*daoscanner.Registration, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 *scanner.Registration
|
||||
if rf, ok := ret.Get(0).(func(context.Context) *scanner.Registration); ok {
|
||||
var r0 *daoscanner.Registration
|
||||
if rf, ok := ret.Get(0).(func(context.Context) *daoscanner.Registration); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scanner.Registration)
|
||||
r0 = ret.Get(0).(*daoscanner.Registration)
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,15 +119,15 @@ func (_m *Manager) GetDefault(ctx context.Context) (*scanner.Registration, error
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, query
|
||||
func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*scanner.Registration, error) {
|
||||
func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*daoscanner.Registration, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 []*scanner.Registration
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*scanner.Registration); ok {
|
||||
var r0 []*daoscanner.Registration
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*daoscanner.Registration); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*scanner.Registration)
|
||||
r0 = ret.Get(0).([]*daoscanner.Registration)
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,11 +156,11 @@ func (_m *Manager) SetAsDefault(ctx context.Context, registrationUUID string) er
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, registration
|
||||
func (_m *Manager) Update(ctx context.Context, registration *scanner.Registration) error {
|
||||
func (_m *Manager) Update(ctx context.Context, registration *daoscanner.Registration) error {
|
||||
ret := _m.Called(ctx, registration)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) error); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *daoscanner.Registration) error); ok {
|
||||
r0 = rf(ctx, registration)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
|
@ -28,7 +28,7 @@ def get_endpoint():
|
||||
|
||||
def _create_client(server, credential, debug, api_type="products"):
|
||||
cfg = None
|
||||
if api_type in ('projectv2', 'artifact', 'repository', 'scan', 'scanall', 'preheat', 'quota', 'replication', 'robot', 'gc', 'retention'):
|
||||
if api_type in ('projectv2', 'artifact', 'repository', 'scanner', 'scan', 'scanall', 'preheat', 'quota', 'replication', 'robot', 'gc', 'retention'):
|
||||
cfg = v2_swagger_client.Configuration()
|
||||
else:
|
||||
cfg = swagger_client.Configuration()
|
||||
@ -60,7 +60,7 @@ def _create_client(server, credential, debug, api_type="products"):
|
||||
"repository": v2_swagger_client.RepositoryApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"scan": v2_swagger_client.ScanApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"scanall": v2_swagger_client.ScanAllApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"scanner": swagger_client.ScannersApi(swagger_client.ApiClient(cfg)),
|
||||
"scanner": v2_swagger_client.ScannerApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"replication": v2_swagger_client.ReplicationApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"robot": v2_swagger_client.RobotApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"gc": v2_swagger_client.GcApi(v2_swagger_client.ApiClient(cfg)),
|
||||
|
@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import base
|
||||
import swagger_client
|
||||
from swagger_client.rest import ApiException
|
||||
|
||||
class Scanner(base.Base, object):
|
||||
def __init__(self):
|
||||
super(Scanner,self).__init__(api_type = "scanner")
|
||||
|
||||
def scanners_get(self, **kwargs):
|
||||
client = self._get_client(**kwargs)
|
||||
return client.scanners_get()
|
||||
|
||||
def scanners_get_uuid(self, is_default = False, **kwargs):
|
||||
scanners = self.scanners_get(**kwargs)
|
||||
for scanner in scanners:
|
||||
if scanner.is_default == is_default:
|
||||
return scanner.uuid
|
||||
|
||||
def scanners_registration_id_patch(self, registration_id, is_default = True, **kwargs):
|
||||
client = self._get_client(**kwargs)
|
||||
isdefault = swagger_client.IsDefault(is_default)
|
||||
client.scanners_registration_id_patch(registration_id, isdefault)
|
||||
|
@ -10,7 +10,6 @@ from library.user import User
|
||||
from library.repository import Repository
|
||||
from library.repository import push_image_to_project
|
||||
from library.artifact import Artifact
|
||||
from library.scanner import Scanner
|
||||
from library.configurations import Configurations
|
||||
from library.projectV2 import ProjectV2
|
||||
|
||||
@ -89,4 +88,4 @@ class TestAssignRoleToLdapGroup(unittest.TestCase):
|
||||
self.repo.delete_repository(project_name, repo_name_dev.split('/')[1], **USER_ADMIN)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
@ -12,7 +12,6 @@ from library.user import User
|
||||
from library.repository import Repository
|
||||
from library.repository import push_self_build_image_to_project
|
||||
from library.artifact import Artifact
|
||||
from library.scanner import Scanner
|
||||
from library.docker_api import list_image_tags
|
||||
from library.docker_api import list_repositories
|
||||
import os
|
||||
|
@ -57,8 +57,7 @@ class TestScan(unittest.TestCase):
|
||||
4. Get private project of user(UA), user(UA) can see only one private project which is project(PA);
|
||||
5. Create a new repository(RA) and tag(TA) in project(PA) by user(UA);
|
||||
6. Send scan image command and get tag(TA) information to check scan result, it should be finished;
|
||||
7. Swith Scanner;
|
||||
8. Send scan another image command and get tag(TA) information to check scan result, it should be finished.
|
||||
7. Send scan another image command and get tag(TA) information to check scan result, it should be finished.
|
||||
Tear down:
|
||||
1. Delete repository(RA) by user(UA);
|
||||
2. Delete project(PA);
|
||||
@ -93,8 +92,7 @@ class TestScan(unittest.TestCase):
|
||||
4. Get private project of user(UA), user(UA) can see only one private project which is project(PA);
|
||||
5. Create a new repository(RA) and tag(TA) in project(PA) by user(UA);
|
||||
6. Send scan image command and get tag(TA) information to check scan result, it should be finished;
|
||||
7. Swith Scanner;
|
||||
8. Send scan another image command and get tag(TA) information to check scan result, it should be finished.
|
||||
7. Send scan another image command and get tag(TA) information to check scan result, it should be finished.
|
||||
Tear down:
|
||||
1. Delete repository(RA) by user(UA);
|
||||
2. Delete project(PA);
|
||||
|
Loading…
Reference in New Issue
Block a user