mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +01:00
refactor notification (#14406)
* Refactor webhook refactor notification to new programming model Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
b2f0a1f0f5
commit
9ef50ed430
@ -1473,300 +1473,6 @@ paths:
|
||||
description: User does not have permission to call this API.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/projects/{project_id}/webhook/policies':
|
||||
get:
|
||||
summary: List project webhook policies.
|
||||
description: |
|
||||
This endpoint returns webhook policies of a project.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: List project webhook policies successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/WebhookPolicy'
|
||||
'400':
|
||||
description: Illegal format of provided ID value.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User have no permission to list webhook policies of the project.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
post:
|
||||
summary: Create project webhook policy.
|
||||
description: |
|
||||
This endpoint create a webhook policy if the project does not have one.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID
|
||||
- name: policy
|
||||
in: body
|
||||
description: Properties "targets" and "event_types" needed.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/WebhookPolicy'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'201':
|
||||
description: Project webhook policy create successfully.
|
||||
headers:
|
||||
Location:
|
||||
type: string
|
||||
description: The URL of the created resource
|
||||
'400':
|
||||
description: Illegal format of provided ID value.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User have no permission to create webhook policy of the project.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/projects/{project_id}/webhook/policies/{policy_id}':
|
||||
get:
|
||||
summary: Get project webhook policy
|
||||
description: |
|
||||
This endpoint returns specified webhook policy of a project.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
description: Relevant project ID.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
- name: policy_id
|
||||
in: path
|
||||
description: The id of webhook policy.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: Get webhook policy successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/WebhookPolicy'
|
||||
'400':
|
||||
description: Illegal format of provided ID value.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User have no permission to get webhook policy of the project.
|
||||
'404':
|
||||
description: Webhook policy ID does not exist.
|
||||
'500':
|
||||
description: Internal server errors.
|
||||
put:
|
||||
summary: Update webhook policy of a project.
|
||||
description: |
|
||||
This endpoint is aimed to update the webhook policy of a project.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
description: Relevant project ID.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
- name: policy_id
|
||||
in: path
|
||||
description: The id of webhook policy.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
- name: policy
|
||||
in: body
|
||||
description: All properties needed except "id", "project_id", "creation_time", "update_time".
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/WebhookPolicy'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: Update webhook policy successfully.
|
||||
'400':
|
||||
description: Illegal format of provided ID value.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User have no permission to update webhook policy of the project.
|
||||
'404':
|
||||
description: Webhook policy ID does not exist.
|
||||
'500':
|
||||
description: Internal server errors.
|
||||
delete:
|
||||
summary: Delete webhook policy of a project
|
||||
description: |
|
||||
This endpoint is aimed to delete webhookpolicy of a project.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
description: Relevant project ID.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
- name: policy_id
|
||||
in: path
|
||||
description: The id of webhook policy.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: Delete webhook policy successfully.
|
||||
'400':
|
||||
description: Illegal format of provided ID value.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User have no permission to delete webhook policy of the project.
|
||||
'404':
|
||||
description: Webhook policy ID does not exist.
|
||||
'500':
|
||||
description: Internal server errors.
|
||||
'/projects/{project_id}/webhook/policies/test':
|
||||
post:
|
||||
summary: Test project webhook connection
|
||||
description: |
|
||||
This endpoint tests webhook connection of a project.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
description: Relevant project ID.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
- name: policy
|
||||
in: body
|
||||
description: Only property "targets" needed.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/WebhookPolicy'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: Test webhook connection successfully.
|
||||
'400':
|
||||
description: Illegal format of provided ID value.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User have no permission to get webhook policy of the project.
|
||||
'500':
|
||||
description: Internal server errors.
|
||||
'/projects/{project_id}/webhook/lasttrigger':
|
||||
get:
|
||||
summary: Get project webhook policy last trigger info
|
||||
description: |
|
||||
This endpoint returns last trigger information of project webhook policy.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
description: Relevant project ID.
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: Test webhook connection successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/WebhookLastTrigger'
|
||||
'400':
|
||||
description: Illegal format of provided ID value.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User have no permission to get webhook policy of the project.
|
||||
'500':
|
||||
description: Internal server errors.
|
||||
'/projects/{project_id}/webhook/jobs':
|
||||
get:
|
||||
summary: List project webhook jobs
|
||||
description: |
|
||||
This endpoint returns webhook jobs of a project.
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: policy_id
|
||||
in: query
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: The policy ID.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: List project webhook jobs successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/WebhookJob'
|
||||
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: Illegal format of provided ID value.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User have no permission to list webhook jobs of the project.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/projects/{project_id}/webhook/events':
|
||||
get:
|
||||
summary: Get supported event types and notify types.
|
||||
description: Get supportted event types and notify types.
|
||||
tags:
|
||||
- Products
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
schema:
|
||||
$ref: '#/definitions/SupportedWebhookEventTypes'
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User have no permission to list webhook jobs of the project.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
responses:
|
||||
OK:
|
||||
description: 'Success'
|
||||
@ -2701,105 +2407,6 @@ definitions:
|
||||
cve_id:
|
||||
type: string
|
||||
description: The ID of the CVE, such as "CVE-2019-10164"
|
||||
WebhookTargetObject:
|
||||
type: object
|
||||
description: The webhook policy target object.
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: The webhook target notify type.
|
||||
address:
|
||||
type: string
|
||||
description: The webhook target address.
|
||||
auth_header:
|
||||
type: string
|
||||
description: The webhook auth header.
|
||||
skip_cert_verify:
|
||||
type: boolean
|
||||
description: Whether or not to skip cert verify.
|
||||
WebhookPolicy:
|
||||
type: object
|
||||
description: The webhook policy object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The webhook policy ID.
|
||||
name:
|
||||
type: string
|
||||
description: The name of webhook policy.
|
||||
description:
|
||||
type: string
|
||||
description: The description of webhook policy.
|
||||
project_id:
|
||||
type: integer
|
||||
description: The project ID of webhook policy.
|
||||
targets:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/WebhookTargetObject'
|
||||
event_types:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
creator:
|
||||
type: string
|
||||
description: The creator of the webhook policy.
|
||||
creation_time:
|
||||
type: string
|
||||
description: The create time of the webhook policy.
|
||||
update_time:
|
||||
type: string
|
||||
description: The update time of the webhook policy.
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Whether the webhook policy is enabled or not.
|
||||
WebhookLastTrigger:
|
||||
type: object
|
||||
description: The webhook policy and last trigger time group by event type.
|
||||
properties:
|
||||
event_type:
|
||||
type: string
|
||||
description: The webhook event type.
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Whether or not the webhook policy enabled.
|
||||
creation_time:
|
||||
type: string
|
||||
description: The creation time of webhook policy.
|
||||
last_trigger_time:
|
||||
type: string
|
||||
description: The last trigger time of webhook policy.
|
||||
WebhookJob:
|
||||
type: object
|
||||
description: The webhook job.
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The webhook job ID.
|
||||
policy_id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The webhook policy ID.
|
||||
event_type:
|
||||
type: string
|
||||
description: The webhook job event type.
|
||||
notify_type:
|
||||
type: string
|
||||
description: The webhook job notify type.
|
||||
status:
|
||||
type: string
|
||||
description: The webhook job status.
|
||||
job_detail:
|
||||
type: string
|
||||
description: The webhook job notify detailed data.
|
||||
creation_time:
|
||||
type: string
|
||||
description: The webhook job creation time.
|
||||
update_time:
|
||||
type: string
|
||||
description: The webhook job update time.
|
||||
|
||||
QuotaSwitcher:
|
||||
type: object
|
||||
@ -2808,27 +2415,6 @@ definitions:
|
||||
type: boolean
|
||||
description: The quota is enable or disable
|
||||
|
||||
SupportedWebhookEventTypes:
|
||||
type: object
|
||||
description: Supportted webhook event types and notify types.
|
||||
properties:
|
||||
event_type:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/EventType'
|
||||
notify_type:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/NotifyType'
|
||||
EventType:
|
||||
type: string
|
||||
description: Webhook supportted event type.
|
||||
example: 'pullImage'
|
||||
NotifyType:
|
||||
type: string
|
||||
description: Webhook supportted notify type.
|
||||
example: 'http'
|
||||
|
||||
parameters:
|
||||
query:
|
||||
name: q
|
||||
|
@ -1973,6 +1973,263 @@ paths:
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/projects/{project_name_or_id}/webhook/policies':
|
||||
get:
|
||||
summary: List project webhook policies.
|
||||
description: |
|
||||
This endpoint returns webhook policies of a project.
|
||||
tags:
|
||||
- webhook
|
||||
operationId: ListWebhookPoliciesOfProject
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- $ref: '#/parameters/sort'
|
||||
- $ref: '#/parameters/query'
|
||||
- $ref: '#/parameters/page'
|
||||
- $ref: '#/parameters/pageSize'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of webhook policies.
|
||||
type: integer
|
||||
Link:
|
||||
description: Link refers to the previous page and next page
|
||||
type: string
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/WebhookPolicy'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
post:
|
||||
summary: Create project webhook policy.
|
||||
description: |
|
||||
This endpoint create a webhook policy if the project does not have one.
|
||||
tags:
|
||||
- webhook
|
||||
operationId: CreateWebhookPolicyOfProject
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- name: policy
|
||||
in: body
|
||||
description: Properties "targets" and "event_types" needed.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/WebhookPolicy'
|
||||
responses:
|
||||
'201':
|
||||
description: Project webhook policy create successfully.
|
||||
headers:
|
||||
X-Request-Id:
|
||||
description: The ID of the corresponding request for the response
|
||||
type: string
|
||||
Location:
|
||||
description: The location of the resource
|
||||
type: string
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/projects/{project_name_or_id}/webhook/policies/{webhook_policy_id}':
|
||||
get:
|
||||
summary: Get project webhook policy
|
||||
description: |
|
||||
This endpoint returns specified webhook policy of a project.
|
||||
tags:
|
||||
- webhook
|
||||
operationId: GetWebhookPolicyOfProject
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- $ref: '#/parameters/webhookPolicyId'
|
||||
responses:
|
||||
'200':
|
||||
description: Get webhook policy successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/WebhookPolicy'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
put:
|
||||
summary: Update webhook policy of a project.
|
||||
description: |
|
||||
This endpoint is aimed to update the webhook policy of a project.
|
||||
tags:
|
||||
- webhook
|
||||
operationId: UpdateWebhookPolicyOfProject
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- $ref: '#/parameters/webhookPolicyId'
|
||||
- name: policy
|
||||
in: body
|
||||
description: All properties needed except "id", "project_id", "creation_time", "update_time".
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/WebhookPolicy'
|
||||
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'
|
||||
delete:
|
||||
summary: Delete webhook policy of a project
|
||||
description: |
|
||||
This endpoint is aimed to delete webhookpolicy of a project.
|
||||
tags:
|
||||
- webhook
|
||||
operationId: DeleteWebhookPolicyOfProject
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- $ref: '#/parameters/webhookPolicyId'
|
||||
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}/webhook/lasttrigger':
|
||||
get:
|
||||
summary: Get project webhook policy last trigger info
|
||||
description: |
|
||||
This endpoint returns last trigger information of project webhook policy.
|
||||
tags:
|
||||
- webhook
|
||||
operationId: LastTrigger
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
responses:
|
||||
'200':
|
||||
description: Test webhook connection successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/WebhookLastTrigger'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/projects/{project_name_or_id}/webhook/jobs':
|
||||
get:
|
||||
summary: List project webhook jobs
|
||||
description: |
|
||||
This endpoint returns webhook jobs of a project.
|
||||
tags:
|
||||
- webhookjob
|
||||
operationId: ListWebhookJobs
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- $ref: '#/parameters/query'
|
||||
- $ref: '#/parameters/sort'
|
||||
- $ref: '#/parameters/page'
|
||||
- $ref: '#/parameters/pageSize'
|
||||
- name: policy_id
|
||||
in: query
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: The policy ID.
|
||||
- name: status
|
||||
in: query
|
||||
description: The status of webhook job.
|
||||
required: false
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: List project webhook jobs successfully.
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of available items
|
||||
type: integer
|
||||
Link:
|
||||
description: Link to previous page and next page
|
||||
type: string
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/WebhookJob'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/projects/{project_name_or_id}/webhook/events':
|
||||
get:
|
||||
summary: Get supported event types and notify types.
|
||||
description: Get supportted event types and notify types.
|
||||
tags:
|
||||
- webhook
|
||||
operationId: GetSupportedEventTypes
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
schema:
|
||||
$ref: '#/definitions/SupportedWebhookEventTypes'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/icons/{digest}:
|
||||
get:
|
||||
summary: Get artifact icon
|
||||
@ -3710,6 +3967,13 @@ parameters:
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
webhookPolicyId:
|
||||
name: webhook_policy_id
|
||||
in: path
|
||||
description: The ID of the webhook policy
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
immutableRuleId:
|
||||
name: immutable_rule_id
|
||||
in: path
|
||||
@ -5824,3 +6088,132 @@ definitions:
|
||||
type: Group
|
||||
import:
|
||||
package: "github.com/goharbor/harbor/src/pkg/ldap/model"
|
||||
SupportedWebhookEventTypes:
|
||||
type: object
|
||||
description: Supportted webhook event types and notify types.
|
||||
properties:
|
||||
event_type:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/EventType'
|
||||
notify_type:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/NotifyType'
|
||||
EventType:
|
||||
type: string
|
||||
description: Webhook supportted event type.
|
||||
example: 'pullImage'
|
||||
NotifyType:
|
||||
type: string
|
||||
description: Webhook supportted notify type.
|
||||
example: 'http'
|
||||
|
||||
WebhookTargetObject:
|
||||
type: object
|
||||
description: The webhook policy target object.
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: The webhook target notify type.
|
||||
address:
|
||||
type: string
|
||||
description: The webhook target address.
|
||||
auth_header:
|
||||
type: string
|
||||
description: The webhook auth header.
|
||||
skip_cert_verify:
|
||||
type: boolean
|
||||
description: Whether or not to skip cert verify.
|
||||
WebhookPolicy:
|
||||
type: object
|
||||
description: The webhook policy object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The webhook policy ID.
|
||||
name:
|
||||
type: string
|
||||
description: The name of webhook policy.
|
||||
description:
|
||||
type: string
|
||||
description: The description of webhook policy.
|
||||
project_id:
|
||||
type: integer
|
||||
description: The project ID of webhook policy.
|
||||
targets:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/WebhookTargetObject'
|
||||
event_types:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
creator:
|
||||
type: string
|
||||
description: The creator of the webhook policy.
|
||||
creation_time:
|
||||
type: string
|
||||
description: The create time of the webhook policy.
|
||||
format: date-time
|
||||
update_time:
|
||||
type: string
|
||||
description: The update time of the webhook policy.
|
||||
format: date-time
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Whether the webhook policy is enabled or not.
|
||||
WebhookLastTrigger:
|
||||
type: object
|
||||
description: The webhook policy and last trigger time group by event type.
|
||||
properties:
|
||||
policy_name:
|
||||
type: string
|
||||
description: The webhook policy name.
|
||||
event_type:
|
||||
type: string
|
||||
description: The webhook event type.
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Whether or not the webhook policy enabled.
|
||||
creation_time:
|
||||
type: string
|
||||
description: The creation time of webhook policy.
|
||||
format: date-time
|
||||
last_trigger_time:
|
||||
type: string
|
||||
description: The last trigger time of webhook policy.
|
||||
format: date-time
|
||||
WebhookJob:
|
||||
type: object
|
||||
description: The webhook job.
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The webhook job ID.
|
||||
policy_id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: The webhook policy ID.
|
||||
event_type:
|
||||
type: string
|
||||
description: The webhook job event type.
|
||||
notify_type:
|
||||
type: string
|
||||
description: The webhook job notify type.
|
||||
status:
|
||||
type: string
|
||||
description: The webhook job status.
|
||||
job_detail:
|
||||
type: string
|
||||
description: The webhook job notify detailed data.
|
||||
creation_time:
|
||||
type: string
|
||||
description: The webhook job creation time.
|
||||
format: date-time
|
||||
update_time:
|
||||
type: string
|
||||
description: The webhook job update time.
|
||||
format: date-time
|
||||
|
@ -1,122 +0,0 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
)
|
||||
|
||||
// UpdateNotificationJob update notification job
|
||||
func UpdateNotificationJob(job *models.NotificationJob, props ...string) (int64, error) {
|
||||
if job == nil {
|
||||
return 0, errors.New("nil job")
|
||||
}
|
||||
|
||||
if job.ID == 0 {
|
||||
return 0, fmt.Errorf("notification job ID is empty")
|
||||
}
|
||||
|
||||
o := dao.GetOrmer()
|
||||
return o.Update(job, props...)
|
||||
}
|
||||
|
||||
// AddNotificationJob insert new notification job to DB
|
||||
func AddNotificationJob(job *models.NotificationJob) (int64, error) {
|
||||
if job == nil {
|
||||
return 0, errors.New("nil job")
|
||||
}
|
||||
o := dao.GetOrmer()
|
||||
if len(job.Status) == 0 {
|
||||
job.Status = models.JobPending
|
||||
}
|
||||
return o.Insert(job)
|
||||
}
|
||||
|
||||
// GetNotificationJob ...
|
||||
func GetNotificationJob(id int64) (*models.NotificationJob, error) {
|
||||
o := dao.GetOrmer()
|
||||
j := &models.NotificationJob{
|
||||
ID: id,
|
||||
}
|
||||
err := o.Read(j)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
|
||||
// GetTotalCountOfNotificationJobs ...
|
||||
func GetTotalCountOfNotificationJobs(query ...*models.NotificationJobQuery) (int64, error) {
|
||||
qs := notificationJobQueryConditions(query...)
|
||||
return qs.Count()
|
||||
}
|
||||
|
||||
// GetNotificationJobs ...
|
||||
func GetNotificationJobs(query ...*models.NotificationJobQuery) ([]*models.NotificationJob, error) {
|
||||
var jobs []*models.NotificationJob
|
||||
|
||||
qs := notificationJobQueryConditions(query...)
|
||||
if len(query) > 0 && query[0] != nil {
|
||||
qs = dao.PaginateForQuerySetter(qs, query[0].Page, query[0].Size)
|
||||
}
|
||||
|
||||
qs = qs.OrderBy("-UpdateTime")
|
||||
|
||||
_, err := qs.All(&jobs)
|
||||
return jobs, err
|
||||
}
|
||||
|
||||
// GetLastTriggerJobsGroupByEventType get notification jobs info of policy, including event type and last trigger time
|
||||
func GetLastTriggerJobsGroupByEventType(policyID int64) ([]*models.NotificationJob, error) {
|
||||
o := dao.GetOrmer()
|
||||
// get jobs last triggered(created) group by event_type. postgres group by usage reference:
|
||||
// https://stackoverflow.com/questions/13325583/postgresql-max-and-group-by
|
||||
sql := `select distinct on (event_type) event_type, id, creation_time, status, notify_type, job_uuid, update_time,
|
||||
creation_time, job_detail from notification_job where policy_id = ?
|
||||
order by event_type, id desc, creation_time, status, notify_type, job_uuid, update_time, creation_time, job_detail`
|
||||
|
||||
jobs := []*models.NotificationJob{}
|
||||
_, err := o.Raw(sql, policyID).QueryRows(&jobs)
|
||||
if err != nil {
|
||||
log.Errorf("query last trigger info group by event type failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
// DeleteNotificationJob ...
|
||||
func DeleteNotificationJob(id int64) error {
|
||||
o := dao.GetOrmer()
|
||||
_, err := o.Delete(&models.NotificationJob{ID: id})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAllNotificationJobsByPolicyID ...
|
||||
func DeleteAllNotificationJobsByPolicyID(policyID int64) (int64, error) {
|
||||
o := dao.GetOrmer()
|
||||
return o.Delete(&models.NotificationJob{PolicyID: policyID}, "policy_id")
|
||||
}
|
||||
|
||||
func notificationJobQueryConditions(query ...*models.NotificationJobQuery) orm.QuerySeter {
|
||||
qs := dao.GetOrmer().QueryTable(&models.NotificationJob{})
|
||||
if len(query) == 0 || query[0] == nil {
|
||||
return qs
|
||||
}
|
||||
|
||||
q := query[0]
|
||||
if q.PolicyID != 0 {
|
||||
qs = qs.Filter("PolicyID", q.PolicyID)
|
||||
}
|
||||
if len(q.Statuses) > 0 {
|
||||
qs = qs.Filter("Status__in", q.Statuses)
|
||||
}
|
||||
if len(q.EventTypes) > 0 {
|
||||
qs = qs.Filter("EventType__in", q.EventTypes)
|
||||
}
|
||||
return qs
|
||||
}
|
@ -1,263 +0,0 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
testJob1 = &models.NotificationJob{
|
||||
PolicyID: 1111,
|
||||
EventType: "pushImage",
|
||||
NotifyType: "http",
|
||||
Status: "pending",
|
||||
JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563536782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}",
|
||||
UUID: "00000000",
|
||||
}
|
||||
testJob2 = &models.NotificationJob{
|
||||
PolicyID: 111,
|
||||
EventType: "pullImage",
|
||||
NotifyType: "http",
|
||||
Status: "",
|
||||
JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563537782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}",
|
||||
UUID: "00000000",
|
||||
}
|
||||
testJob3 = &models.NotificationJob{
|
||||
PolicyID: 111,
|
||||
EventType: "deleteImage",
|
||||
NotifyType: "http",
|
||||
Status: "pending",
|
||||
JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563538782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}",
|
||||
UUID: "00000000",
|
||||
}
|
||||
)
|
||||
|
||||
func TestAddNotificationJob(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
job *models.NotificationJob
|
||||
want int64
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "AddNotificationJob nil", job: nil, wantErr: true},
|
||||
{name: "AddNotificationJob 1", job: testJob1, want: 1},
|
||||
{name: "AddNotificationJob 2", job: testJob2, want: 2},
|
||||
{name: "AddNotificationJob 3", job: testJob3, want: 3},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := AddNotificationJob(tt.job)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "wantErr: %s", err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTotalCountOfNotificationJobs(t *testing.T) {
|
||||
type args struct {
|
||||
query *models.NotificationJobQuery
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int64
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "GetTotalCountOfNotificationJobs 1",
|
||||
args: args{
|
||||
query: &models.NotificationJobQuery{
|
||||
PolicyID: 111,
|
||||
},
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "GetTotalCountOfNotificationJobs 2",
|
||||
args: args{},
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "GetTotalCountOfNotificationJobs 3",
|
||||
args: args{
|
||||
query: &models.NotificationJobQuery{
|
||||
Statuses: []string{"pending"},
|
||||
},
|
||||
},
|
||||
want: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetTotalCountOfNotificationJobs(tt.args.query)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "wantErr: %s", err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLastTriggerJobsGroupByEventType(t *testing.T) {
|
||||
type args struct {
|
||||
policyID int64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*models.NotificationJob
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "GetLastTriggerJobsGroupByEventType",
|
||||
args: args{
|
||||
policyID: 111,
|
||||
},
|
||||
want: []*models.NotificationJob{
|
||||
testJob2,
|
||||
testJob3,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetLastTriggerJobsGroupByEventType(tt.args.policyID)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "wantErr: %s", err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, len(tt.want), len(got))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateNotificationJob(t *testing.T) {
|
||||
type args struct {
|
||||
job *models.NotificationJob
|
||||
props []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int64
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "UpdateNotificationJob Want Error 1", args: args{job: nil}, wantErr: true},
|
||||
{name: "UpdateNotificationJob Want Error 2", args: args{job: &models.NotificationJob{ID: 0}}, wantErr: true},
|
||||
{
|
||||
name: "UpdateNotificationJob 1",
|
||||
args: args{
|
||||
job: &models.NotificationJob{ID: 1, UUID: "111111111111111"},
|
||||
props: []string{"UUID"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "UpdateNotificationJob 2",
|
||||
args: args{
|
||||
job: &models.NotificationJob{ID: 2, UUID: "222222222222222"},
|
||||
props: []string{"UUID"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "UpdateNotificationJob 3",
|
||||
args: args{
|
||||
job: &models.NotificationJob{ID: 3, UUID: "333333333333333"},
|
||||
props: []string{"UUID"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := UpdateNotificationJob(tt.args.job, tt.args.props...)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
gotJob, err := GetNotificationJob(tt.args.job.ID)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, tt.args.job.UUID, gotJob.UUID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteNotificationJob(t *testing.T) {
|
||||
type args struct {
|
||||
id int64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "DeleteNotificationJob 1", args: args{id: 1}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := DeleteNotificationJob(tt.args.id)
|
||||
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
job, err := GetNotificationJob(tt.args.id)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Nil(t, job)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAllNotificationJobs(t *testing.T) {
|
||||
type args struct {
|
||||
policyID int64
|
||||
query []*models.NotificationJobQuery
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "DeleteAllNotificationJobs 1",
|
||||
args: args{
|
||||
policyID: 111,
|
||||
query: []*models.NotificationJobQuery{
|
||||
{PolicyID: 111},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := DeleteAllNotificationJobsByPolicyID(tt.args.policyID)
|
||||
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
jobs, err := GetNotificationJobs(tt.args.query...)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 0, len(jobs))
|
||||
})
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
lib_orm "github.com/goharbor/harbor/src/lib/orm"
|
||||
)
|
||||
|
||||
// GetNotificationPolicy return notification policy by id
|
||||
func GetNotificationPolicy(id int64) (*models.NotificationPolicy, error) {
|
||||
policy := new(models.NotificationPolicy)
|
||||
o := dao.GetOrmer()
|
||||
err := o.QueryTable(policy).Filter("id", id).One(policy)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return policy, err
|
||||
}
|
||||
|
||||
// GetNotificationPolicyByName return notification policy by name
|
||||
func GetNotificationPolicyByName(name string, projectID int64) (*models.NotificationPolicy, error) {
|
||||
policy := new(models.NotificationPolicy)
|
||||
o := dao.GetOrmer()
|
||||
err := o.QueryTable(policy).Filter("name", name).Filter("projectID", projectID).One(policy)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return policy, err
|
||||
}
|
||||
|
||||
// GetNotificationPolicies returns all notification policy in project
|
||||
func GetNotificationPolicies(projectID int64) ([]*models.NotificationPolicy, error) {
|
||||
var policies []*models.NotificationPolicy
|
||||
qs := dao.GetOrmer().QueryTable(new(models.NotificationPolicy)).Filter("ProjectID", projectID).OrderBy("-CreationTime")
|
||||
|
||||
_, err := qs.All(&policies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policies, nil
|
||||
|
||||
}
|
||||
|
||||
// AddNotificationPolicy insert new notification policy to DB
|
||||
func AddNotificationPolicy(policy *models.NotificationPolicy) (int64, error) {
|
||||
if policy == nil {
|
||||
return 0, errors.New("nil policy")
|
||||
}
|
||||
o := dao.GetOrmer()
|
||||
id, err := o.Insert(policy)
|
||||
if err != nil {
|
||||
if e := lib_orm.AsConflictError(err, "notification policy named %s already exists", policy.Name); e != nil {
|
||||
err = e
|
||||
return id, err
|
||||
}
|
||||
err = fmt.Errorf("failed to create the notification policy: %v", err)
|
||||
return id, err
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
// UpdateNotificationPolicy update t specified notification policy
|
||||
func UpdateNotificationPolicy(policy *models.NotificationPolicy) error {
|
||||
if policy == nil {
|
||||
return errors.New("nil policy")
|
||||
}
|
||||
o := dao.GetOrmer()
|
||||
_, err := o.Update(policy)
|
||||
if err != nil {
|
||||
if e := lib_orm.AsConflictError(err, "notification policy named %s already exists", policy.Name); e != nil {
|
||||
return e
|
||||
}
|
||||
err = fmt.Errorf("failed to update the notification policy: %v", err)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteNotificationPolicy delete notification policy by id
|
||||
func DeleteNotificationPolicy(id int64) error {
|
||||
o := dao.GetOrmer()
|
||||
_, err := o.Delete(&models.NotificationPolicy{ID: id})
|
||||
return err
|
||||
}
|
@ -1,291 +0,0 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
testPly1 = &models.NotificationPolicy{
|
||||
Name: "webhook test policy1",
|
||||
Description: "webhook test policy1 description",
|
||||
ProjectID: 111,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
testPly2 = &models.NotificationPolicy{
|
||||
Name: "webhook test policy2",
|
||||
Description: "webhook test policy2 description",
|
||||
ProjectID: 222,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
testPly3 = &models.NotificationPolicy{
|
||||
Name: "webhook test policy3",
|
||||
Description: "webhook test policy3 description",
|
||||
ProjectID: 333,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
func TestAddNotificationPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
policy *models.NotificationPolicy
|
||||
want int64
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "AddNotificationPolicy nil", policy: nil, wantErr: true},
|
||||
{name: "AddNotificationPolicy 1", policy: testPly1, want: 1},
|
||||
{name: "AddNotificationPolicy 2", policy: testPly2, want: 2},
|
||||
{name: "AddNotificationPolicy 3", policy: testPly3, want: 3},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := AddNotificationPolicy(tt.policy)
|
||||
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "wantErr: %s", err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNotificationPolicies(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
projectID int64
|
||||
wantPolicies []*models.NotificationPolicy
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "GetNotificationPolicies nil", projectID: 0, wantPolicies: []*models.NotificationPolicy{}},
|
||||
{name: "GetNotificationPolicies 1", projectID: 111, wantPolicies: []*models.NotificationPolicy{testPly1}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotPolicies, err := GetNotificationPolicies(tt.projectID)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "wantErr: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
for i, gotPolicy := range gotPolicies {
|
||||
assert.Equal(t, tt.wantPolicies[i].Name, gotPolicy.Name)
|
||||
assert.Equal(t, tt.wantPolicies[i].ID, gotPolicy.ID)
|
||||
assert.Equal(t, tt.wantPolicies[i].EventTypesDB, gotPolicy.EventTypesDB)
|
||||
assert.Equal(t, tt.wantPolicies[i].TargetsDB, gotPolicy.TargetsDB)
|
||||
assert.Equal(t, tt.wantPolicies[i].Creator, gotPolicy.Creator)
|
||||
assert.Equal(t, tt.wantPolicies[i].Enabled, gotPolicy.Enabled)
|
||||
assert.Equal(t, tt.wantPolicies[i].Description, gotPolicy.Description)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNotificationPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
id int64
|
||||
wantPolicy *models.NotificationPolicy
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "GetRepPolicy 1", id: 1, wantPolicy: testPly1},
|
||||
{name: "GetRepPolicy 2", id: 2, wantPolicy: testPly2},
|
||||
{name: "GetRepPolicy 3", id: 3, wantPolicy: testPly3},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotPolicy, err := GetNotificationPolicy(tt.id)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "wantErr: %s", err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, tt.wantPolicy.Name, gotPolicy.Name)
|
||||
assert.Equal(t, tt.wantPolicy.ID, gotPolicy.ID)
|
||||
assert.Equal(t, tt.wantPolicy.EventTypesDB, gotPolicy.EventTypesDB)
|
||||
assert.Equal(t, tt.wantPolicy.TargetsDB, gotPolicy.TargetsDB)
|
||||
assert.Equal(t, tt.wantPolicy.Creator, gotPolicy.Creator)
|
||||
assert.Equal(t, tt.wantPolicy.Enabled, gotPolicy.Enabled)
|
||||
assert.Equal(t, tt.wantPolicy.Description, gotPolicy.Description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNotificationPolicyByName(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
projectID int64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantPolicy *models.NotificationPolicy
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "GetNotificationPolicyByName 1", args: args{name: testPly1.Name, projectID: testPly1.ProjectID}, wantPolicy: testPly1},
|
||||
{name: "GetNotificationPolicyByName 2", args: args{name: testPly2.Name, projectID: testPly2.ProjectID}, wantPolicy: testPly2},
|
||||
{name: "GetNotificationPolicyByName 3", args: args{name: testPly3.Name, projectID: testPly3.ProjectID}, wantPolicy: testPly3},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotPolicy, err := GetNotificationPolicyByName(tt.args.name, tt.args.projectID)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "wantErr: %s", err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, tt.wantPolicy.Name, gotPolicy.Name)
|
||||
assert.Equal(t, tt.wantPolicy.ID, gotPolicy.ID)
|
||||
assert.Equal(t, tt.wantPolicy.EventTypesDB, gotPolicy.EventTypesDB)
|
||||
assert.Equal(t, tt.wantPolicy.TargetsDB, gotPolicy.TargetsDB)
|
||||
assert.Equal(t, tt.wantPolicy.Creator, gotPolicy.Creator)
|
||||
assert.Equal(t, tt.wantPolicy.Enabled, gotPolicy.Enabled)
|
||||
assert.Equal(t, tt.wantPolicy.Description, gotPolicy.Description)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateNotificationPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
policy *models.NotificationPolicy
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "UpdateNotificationPolicy nil",
|
||||
args: args{
|
||||
policy: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "UpdateNotificationPolicy 1",
|
||||
args: args{
|
||||
policy: &models.NotificationPolicy{
|
||||
ID: 1,
|
||||
Name: "webhook test policy1 new",
|
||||
Description: "webhook test policy1 description new",
|
||||
ProjectID: 111,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "UpdateNotificationPolicy 2",
|
||||
args: args{
|
||||
policy: &models.NotificationPolicy{
|
||||
ID: 2,
|
||||
Name: "webhook test policy2 new",
|
||||
Description: "webhook test policy2 description new",
|
||||
ProjectID: 222,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "UpdateNotificationPolicy 3",
|
||||
args: args{
|
||||
policy: &models.NotificationPolicy{
|
||||
ID: 3,
|
||||
Name: "webhook test policy3 new",
|
||||
Description: "webhook test policy3 description new",
|
||||
ProjectID: 333,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := UpdateNotificationPolicy(tt.args.policy)
|
||||
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
gotPolicy, err := GetNotificationPolicy(tt.args.policy.ID)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, tt.args.policy.Description, gotPolicy.Description)
|
||||
assert.Equal(t, tt.args.policy.Name, gotPolicy.Name)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteNotificationPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
id int64
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "DeleteNotificationPolicy 1", id: 1, wantErr: false},
|
||||
{name: "DeleteNotificationPolicy 2", id: 2, wantErr: false},
|
||||
{name: "DeleteNotificationPolicy 3", id: 3, wantErr: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := DeleteNotificationPolicy(tt.id)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "wantErr: %s", err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
policy, err := GetNotificationPolicy(tt.id)
|
||||
require.Nil(t, err)
|
||||
assert.Nil(t, policy)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
dao.PrepareTestForPostgresSQL()
|
||||
os.Exit(m.Run())
|
||||
}
|
@ -31,8 +31,6 @@ func init() {
|
||||
new(UserGroup),
|
||||
new(JobLog),
|
||||
new(OIDCUser),
|
||||
new(NotificationPolicy),
|
||||
new(NotificationJob),
|
||||
new(ProjectBlob),
|
||||
new(ArtifactAndBlob),
|
||||
)
|
||||
|
@ -16,10 +16,8 @@ package auditlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
beegorm "github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/audit"
|
||||
am "github.com/goharbor/harbor/src/pkg/audit/model"
|
||||
)
|
||||
@ -39,8 +37,7 @@ func (h *Handler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (h *Handler) Handle(value interface{}) error {
|
||||
ctx := orm.NewContext(context.Background(), beegorm.NewOrm())
|
||||
func (h *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||
var auditLog *am.AuditLog
|
||||
switch v := value.(type) {
|
||||
case *event.PushArtifactEvent, *event.PullArtifactEvent, *event.DeleteArtifactEvent,
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/event/handler/webhook/scan"
|
||||
"github.com/goharbor/harbor/src/controller/event/metadata"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
@ -25,14 +24,14 @@ func init() {
|
||||
notifier.Subscribe(event.TopicPushArtifact, &artifact.Handler{})
|
||||
notifier.Subscribe(event.TopicPullArtifact, &artifact.Handler{})
|
||||
notifier.Subscribe(event.TopicDeleteArtifact, &artifact.Handler{})
|
||||
notifier.Subscribe(event.TopicUploadChart, &chart.Handler{Context: orm.Context})
|
||||
notifier.Subscribe(event.TopicDeleteChart, &chart.Handler{Context: orm.Context})
|
||||
notifier.Subscribe(event.TopicDownloadChart, &chart.Handler{Context: orm.Context})
|
||||
notifier.Subscribe(event.TopicUploadChart, &chart.Handler{})
|
||||
notifier.Subscribe(event.TopicDeleteChart, &chart.Handler{})
|
||||
notifier.Subscribe(event.TopicDownloadChart, &chart.Handler{})
|
||||
notifier.Subscribe(event.TopicQuotaExceed, "a.Handler{})
|
||||
notifier.Subscribe(event.TopicQuotaWarning, "a.Handler{})
|
||||
notifier.Subscribe(event.TopicScanningFailed, &scan.Handler{})
|
||||
notifier.Subscribe(event.TopicScanningCompleted, &scan.Handler{})
|
||||
notifier.Subscribe(event.TopicDeleteArtifact, &scan.DelArtHandler{Context: orm.Context})
|
||||
notifier.Subscribe(event.TopicDeleteArtifact, &scan.DelArtHandler{})
|
||||
notifier.Subscribe(event.TopicReplication, &artifact.ReplicationHandler{})
|
||||
notifier.Subscribe(event.TopicTagRetention, &artifact.RetentionHandler{})
|
||||
|
||||
@ -43,9 +42,9 @@ func init() {
|
||||
notifier.Subscribe(event.TopicDeleteTag, &replication.Handler{})
|
||||
|
||||
// p2p preheat
|
||||
notifier.Subscribe(event.TopicPushArtifact, &p2p.Handler{Context: orm.Context})
|
||||
notifier.Subscribe(event.TopicScanningCompleted, &p2p.Handler{Context: orm.Context})
|
||||
notifier.Subscribe(event.TopicArtifactLabeled, &p2p.Handler{Context: orm.Context})
|
||||
notifier.Subscribe(event.TopicPushArtifact, &p2p.Handler{})
|
||||
notifier.Subscribe(event.TopicScanningCompleted, &p2p.Handler{})
|
||||
notifier.Subscribe(event.TopicArtifactLabeled, &p2p.Handler{})
|
||||
|
||||
// audit logs
|
||||
notifier.Subscribe(event.TopicPushArtifact, &auditlog.Handler{})
|
||||
@ -58,8 +57,8 @@ func init() {
|
||||
notifier.Subscribe(event.TopicDeleteTag, &auditlog.Handler{})
|
||||
|
||||
// internal
|
||||
notifier.Subscribe(event.TopicPullArtifact, &internal.Handler{Context: orm.Context})
|
||||
notifier.Subscribe(event.TopicPushArtifact, &internal.Handler{Context: orm.Context})
|
||||
notifier.Subscribe(event.TopicPullArtifact, &internal.Handler{})
|
||||
notifier.Subscribe(event.TopicPushArtifact, &internal.Handler{})
|
||||
|
||||
task.RegisterTaskStatusChangePostFunc(job.Replication, func(ctx context.Context, taskID int64, status string) error {
|
||||
notification.AddEvent(ctx, &metadata.ReplicationMetaData{
|
||||
|
@ -28,7 +28,6 @@ import (
|
||||
|
||||
// Handler preprocess artifact event data
|
||||
type Handler struct {
|
||||
Context func() context.Context
|
||||
}
|
||||
|
||||
// Name ...
|
||||
@ -37,12 +36,12 @@ func (a *Handler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (a *Handler) Handle(value interface{}) error {
|
||||
func (a *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case *event.PullArtifactEvent:
|
||||
return a.onPull(a.Context(), v.ArtifactEvent)
|
||||
return a.onPull(ctx, v.ArtifactEvent)
|
||||
case *event.PushArtifactEvent:
|
||||
return a.onPush(a.Context(), v.ArtifactEvent)
|
||||
return a.onPush(ctx, v.ArtifactEvent)
|
||||
default:
|
||||
log.Errorf("Can not handler this event type! %#v", v)
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ import (
|
||||
|
||||
// Handler ...
|
||||
type Handler struct {
|
||||
Context func() context.Context
|
||||
}
|
||||
|
||||
// Name ...
|
||||
@ -38,17 +37,17 @@ func (p *Handler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (p *Handler) Handle(value interface{}) error {
|
||||
func (p *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||
switch value.(type) {
|
||||
case *event.PushArtifactEvent:
|
||||
pushArtEvent, _ := value.(*event.PushArtifactEvent)
|
||||
return p.handlePushArtifact(pushArtEvent)
|
||||
return p.handlePushArtifact(ctx, pushArtEvent)
|
||||
case *event.ScanImageEvent:
|
||||
scanImageEvent, _ := value.(*event.ScanImageEvent)
|
||||
return p.handleImageScanned(scanImageEvent)
|
||||
return p.handleImageScanned(ctx, scanImageEvent)
|
||||
case *event.ArtifactLabeledEvent:
|
||||
artifactLabeledEvent, _ := value.(*event.ArtifactLabeledEvent)
|
||||
return p.handleArtifactLabeled(artifactLabeledEvent)
|
||||
return p.handleArtifactLabeled(ctx, artifactLabeledEvent)
|
||||
default:
|
||||
return errors.New("unsupported type")
|
||||
}
|
||||
@ -59,7 +58,7 @@ func (p *Handler) IsStateful() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Handler) handlePushArtifact(event *event.PushArtifactEvent) error {
|
||||
func (p *Handler) handlePushArtifact(ctx context.Context, event *event.PushArtifactEvent) error {
|
||||
if event.Artifact.Type != image.ArtifactTypeImage {
|
||||
return nil
|
||||
}
|
||||
@ -71,7 +70,7 @@ func (p *Handler) handlePushArtifact(event *event.PushArtifactEvent) error {
|
||||
|
||||
log.Debugf("preheat: artifact pushed %s:%s@%s", event.Artifact.RepositoryName, event.Tags, event.Artifact.Digest)
|
||||
|
||||
art, err := artifact.Ctl.Get(p.Context(), event.Artifact.ID, &artifact.Option{
|
||||
art, err := artifact.Ctl.Get(ctx, event.Artifact.ID, &artifact.Option{
|
||||
WithTag: true,
|
||||
WithLabel: true,
|
||||
})
|
||||
@ -89,16 +88,16 @@ func (p *Handler) handlePushArtifact(event *event.PushArtifactEvent) error {
|
||||
}
|
||||
art.Tags = pt
|
||||
|
||||
_, err = preheat.Enf.PreheatArtifact(p.Context(), art)
|
||||
_, err = preheat.Enf.PreheatArtifact(ctx, art)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Handler) handleImageScanned(event *event.ScanImageEvent) error {
|
||||
func (p *Handler) handleImageScanned(ctx context.Context, event *event.ScanImageEvent) error {
|
||||
// TODO: If the scan is targeting an manifest list, here the artifacts we get are all the children
|
||||
// artifacts of the manifest list. The children artifacts are high probably untagged ones that
|
||||
// will be definitely ignored by the tag filter. We need to find a way to resolve this issue.
|
||||
log.Debugf("preheat: image scanned %s:%s", event.Artifact.Repository, event.Artifact.Tag)
|
||||
art, err := artifact.Ctl.GetByReference(p.Context(), event.Artifact.Repository, event.Artifact.Digest,
|
||||
art, err := artifact.Ctl.GetByReference(ctx, event.Artifact.Repository, event.Artifact.Digest,
|
||||
&artifact.Option{
|
||||
WithTag: true,
|
||||
WithLabel: true,
|
||||
@ -106,12 +105,12 @@ func (p *Handler) handleImageScanned(event *event.ScanImageEvent) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = preheat.Enf.PreheatArtifact(p.Context(), art)
|
||||
_, err = preheat.Enf.PreheatArtifact(ctx, art)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Handler) handleArtifactLabeled(event *event.ArtifactLabeledEvent) error {
|
||||
art, err := artifact.Ctl.Get(p.Context(), event.ArtifactID, &artifact.Option{
|
||||
func (p *Handler) handleArtifactLabeled(ctx context.Context, event *event.ArtifactLabeledEvent) error {
|
||||
art, err := artifact.Ctl.Get(ctx, event.ArtifactID, &artifact.Option{
|
||||
WithTag: true,
|
||||
WithLabel: true,
|
||||
})
|
||||
@ -126,6 +125,6 @@ func (p *Handler) handleArtifactLabeled(event *event.ArtifactLabeledEvent) error
|
||||
}
|
||||
log.Debugf("preheat: artifact labeled %s:%s", art.Artifact.RepositoryName, art.Artifact.Digest)
|
||||
|
||||
_, err = preheat.Enf.PreheatArtifact(p.Context(), art)
|
||||
_, err = preheat.Enf.PreheatArtifact(ctx, art)
|
||||
return err
|
||||
}
|
||||
|
@ -58,9 +58,7 @@ func (suite *PreheatTestSuite) SetupSuite() {
|
||||
artifact.Ctl = fakeArtifactCtl
|
||||
suite.preheatEnf = preheat.Enf
|
||||
preheat.Enf = fakeEnforcer
|
||||
suite.handler = &Handler{
|
||||
Context: context.TODO,
|
||||
}
|
||||
suite.handler = &Handler{}
|
||||
}
|
||||
|
||||
// TearDownSuite cleans the testing env
|
||||
@ -138,7 +136,7 @@ func (suite *PreheatTestSuite) TestHandle() {
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
err := suite.handler.Handle(tt.args.data)
|
||||
err := suite.handler.Handle(context.TODO(), tt.args.data)
|
||||
if tt.wantErr {
|
||||
suite.Error(err, tt.name)
|
||||
} else {
|
||||
|
@ -15,6 +15,7 @@
|
||||
package replication
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
@ -36,7 +37,7 @@ func (r *Handler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (r *Handler) Handle(value interface{}) error {
|
||||
func (r *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||
pushArtEvent, ok := value.(*event.PushArtifactEvent)
|
||||
if ok {
|
||||
return r.handlePushArtifact(pushArtEvent)
|
||||
|
@ -3,18 +3,17 @@ package util
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/distribution"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
notifyModel "github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SendHookWithPolicies send hook by publishing topic of specified target type(notify type)
|
||||
func SendHookWithPolicies(policies []*models.NotificationPolicy, payload *notifyModel.Payload, eventType string) error {
|
||||
func SendHookWithPolicies(policies []*policy_model.Policy, payload *notifyModel.Payload, eventType string) error {
|
||||
// if global notification configured disabled, return directly
|
||||
if !config.NotificationEnable() {
|
||||
log.Debug("notification feature is not enabled")
|
||||
|
@ -41,14 +41,14 @@ func (a *Handler) Name() string {
|
||||
}
|
||||
|
||||
// Handle preprocess artifact event data and then publish hook event
|
||||
func (a *Handler) Handle(value interface{}) error {
|
||||
func (a *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case *event.PushArtifactEvent:
|
||||
return a.handle(v.ArtifactEvent)
|
||||
return a.handle(ctx, v.ArtifactEvent)
|
||||
case *event.PullArtifactEvent:
|
||||
return a.handle(v.ArtifactEvent)
|
||||
return a.handle(ctx, v.ArtifactEvent)
|
||||
case *event.DeleteArtifactEvent:
|
||||
return a.handle(v.ArtifactEvent)
|
||||
return a.handle(ctx, v.ArtifactEvent)
|
||||
default:
|
||||
log.Errorf("Can not handler this event type! %#v", v)
|
||||
}
|
||||
@ -60,18 +60,19 @@ func (a *Handler) IsStateful() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *Handler) handle(event *event.ArtifactEvent) error {
|
||||
prj, err := project.Ctl.Get(orm.Context(), event.Artifact.ProjectID, project.Metadata(true))
|
||||
func (a *Handler) handle(ctx context.Context, event *event.ArtifactEvent) error {
|
||||
prj, err := project.Ctl.Get(ctx, event.Artifact.ProjectID, project.Metadata(true))
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project: %d, error: %v", event.Artifact.ProjectID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(prj.ProjectID, event.EventType)
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(ctx, prj.ProjectID, event.EventType)
|
||||
if err != nil {
|
||||
log.Errorf("failed to find policy for %s event: %v", event.EventType, err)
|
||||
return err
|
||||
}
|
||||
log.Info(policies)
|
||||
if len(policies) == 0 {
|
||||
log.Debugf("cannot find policy for %s event: %v", event.EventType, event)
|
||||
return nil
|
||||
|
@ -1,6 +1,7 @@
|
||||
package artifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@ -31,7 +32,7 @@ func (r *ReplicationHandler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (r *ReplicationHandler) Handle(value interface{}) error {
|
||||
func (r *ReplicationHandler) Handle(ctx context.Context, value interface{}) error {
|
||||
if !config.NotificationEnable() {
|
||||
log.Debug("notification feature is not enabled")
|
||||
return nil
|
||||
@ -50,7 +51,7 @@ func (r *ReplicationHandler) Handle(value interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(project.ProjectID, rpEvent.EventType)
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(orm.Context(), project.ProjectID, rpEvent.EventType)
|
||||
if err != nil {
|
||||
log.Errorf("failed to find policy for %s event: %v", rpEvent.EventType, err)
|
||||
return err
|
||||
|
@ -15,6 +15,7 @@
|
||||
package artifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -26,65 +27,21 @@ import (
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
reppkg "github.com/goharbor/harbor/src/pkg/replication"
|
||||
"github.com/goharbor/harbor/src/replication"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||
replicationtesting "github.com/goharbor/harbor/src/testing/controller/replication"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
testingnotification "github.com/goharbor/harbor/src/testing/pkg/notification/policy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakedNotificationPolicyMgr struct {
|
||||
}
|
||||
|
||||
type fakedReplicationRegistryMgr struct {
|
||||
}
|
||||
|
||||
func (f *fakedNotificationPolicyMgr) Create(*models.NotificationPolicy) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// List the policies, returns the policy list and error
|
||||
func (f *fakedNotificationPolicyMgr) List(int64) ([]*models.NotificationPolicy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get policy with specified ID
|
||||
func (f *fakedNotificationPolicyMgr) Get(int64) (*models.NotificationPolicy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetByNameAndProjectID get policy by the name and projectID
|
||||
func (f *fakedNotificationPolicyMgr) GetByNameAndProjectID(string, int64) (*models.NotificationPolicy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Update the specified policy
|
||||
func (f *fakedNotificationPolicyMgr) Update(*models.NotificationPolicy) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the specified policy
|
||||
func (f *fakedNotificationPolicyMgr) Delete(int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test the specified policy
|
||||
func (f *fakedNotificationPolicyMgr) Test(*models.NotificationPolicy) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRelatedPolices get event type related policies in project
|
||||
func (f *fakedNotificationPolicyMgr) GetRelatedPolices(int64, string) ([]*models.NotificationPolicy, error) {
|
||||
return []*models.NotificationPolicy{
|
||||
{
|
||||
ID: 0,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Add new registry
|
||||
func (f *fakedReplicationRegistryMgr) Add(*model.Registry) (int64, error) {
|
||||
return 0, nil
|
||||
@ -141,7 +98,8 @@ func TestReplicationHandler_Handle(t *testing.T) {
|
||||
project.Ctl = prj
|
||||
repctl.Ctl = repCtl
|
||||
}()
|
||||
notification.PolicyMgr = &fakedNotificationPolicyMgr{}
|
||||
policyMgrMock := &testingnotification.Manager{}
|
||||
notification.PolicyMgr = policyMgrMock
|
||||
replication.RegistryMgr = &fakedReplicationRegistryMgr{}
|
||||
projectCtl := &projecttesting.Controller{}
|
||||
project.Ctl = projectCtl
|
||||
@ -150,6 +108,11 @@ func TestReplicationHandler_Handle(t *testing.T) {
|
||||
mockRepCtl.On("GetPolicy", mock.Anything, mock.Anything).Return(&reppkg.Policy{ID: 1}, nil)
|
||||
mockRepCtl.On("GetTask", mock.Anything, mock.Anything).Return(&repctl.Task{}, nil)
|
||||
mockRepCtl.On("GetExecution", mock.Anything, mock.Anything).Return(&repctl.Execution{}, nil)
|
||||
policyMgrMock.On("GetRelatedPolices", mock.Anything, mock.Anything, mock.Anything).Return([]*policy_model.Policy{
|
||||
{
|
||||
ID: 0,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
mock.OnAnything(projectCtl, "GetByName").Return(&models.Project{ProjectID: 1}, nil)
|
||||
|
||||
@ -192,7 +155,7 @@ func TestReplicationHandler_Handle(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := handler.Handle(tt.args.data)
|
||||
err := handler.Handle(context.TODO(), tt.args.data)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
|
@ -1,6 +1,7 @@
|
||||
package artifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/controller/retention"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
@ -26,7 +27,7 @@ func (r *RetentionHandler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (r *RetentionHandler) Handle(value interface{}) error {
|
||||
func (r *RetentionHandler) Handle(ctx context.Context, value interface{}) error {
|
||||
if !config.NotificationEnable() {
|
||||
log.Debug("notification feature is not enabled")
|
||||
return nil
|
||||
@ -54,7 +55,7 @@ func (r *RetentionHandler) Handle(value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(project, trEvent.EventType)
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(ctx, project, trEvent.EventType)
|
||||
if err != nil {
|
||||
log.Errorf("failed to find policy for %s event: %v", trEvent.EventType, err)
|
||||
return err
|
||||
|
@ -1,21 +1,23 @@
|
||||
package artifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/controller/retention"
|
||||
ret "github.com/goharbor/harbor/src/pkg/retention"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
"github.com/goharbor/harbor/src/controller/retention"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/lib/selector"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
ret "github.com/goharbor/harbor/src/pkg/retention"
|
||||
retentiontesting "github.com/goharbor/harbor/src/testing/controller/retention"
|
||||
testingnotification "github.com/goharbor/harbor/src/testing/pkg/notification/policy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRetentionHandler_Handle(t *testing.T) {
|
||||
@ -29,7 +31,8 @@ func TestRetentionHandler_Handle(t *testing.T) {
|
||||
notification.PolicyMgr = policyMgr
|
||||
retention.Ctl = oldretentionCtl
|
||||
}()
|
||||
notification.PolicyMgr = &fakedNotificationPolicyMgr{}
|
||||
policyMgrMock := &testingnotification.Manager{}
|
||||
notification.PolicyMgr = policyMgrMock
|
||||
retentionCtl := &retentiontesting.Controller{}
|
||||
retention.Ctl = retentionCtl
|
||||
retentionCtl.On("GetRetentionExecTask", mock.Anything, mock.Anything).
|
||||
@ -49,6 +52,11 @@ func TestRetentionHandler_Handle(t *testing.T) {
|
||||
StartTime: time.Now(),
|
||||
EndTime: time.Now(),
|
||||
}, nil)
|
||||
policyMgrMock.On("GetRelatedPolices", mock.Anything, mock.Anything, mock.Anything).Return([]*policy_model.Policy{
|
||||
{
|
||||
ID: 0,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
type args struct {
|
||||
data interface{}
|
||||
@ -87,7 +95,7 @@ func TestRetentionHandler_Handle(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := handler.Handle(tt.args.data)
|
||||
err := handler.Handle(context.TODO(), tt.args.data)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
|
@ -31,7 +31,6 @@ import (
|
||||
|
||||
// Handler preprocess chart event data
|
||||
type Handler struct {
|
||||
Context func() context.Context
|
||||
}
|
||||
|
||||
// Name ...
|
||||
@ -40,7 +39,7 @@ func (cph *Handler) Name() string {
|
||||
}
|
||||
|
||||
// Handle preprocess chart event data and then publish hook event
|
||||
func (cph *Handler) Handle(value interface{}) error {
|
||||
func (cph *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||
chartEvent, ok := value.(*event.ChartEvent)
|
||||
if !ok {
|
||||
return errors.New("invalid chart event type")
|
||||
@ -50,12 +49,12 @@ func (cph *Handler) Handle(value interface{}) error {
|
||||
return fmt.Errorf("data miss in chart event: %v", chartEvent)
|
||||
}
|
||||
|
||||
prj, err := project.Ctl.Get(cph.Context(), chartEvent.ProjectName, project.Metadata(true))
|
||||
prj, err := project.Ctl.Get(ctx, chartEvent.ProjectName, project.Metadata(true))
|
||||
if err != nil {
|
||||
log.Errorf("failed to find project[%s] for chart event: %v", chartEvent.ProjectName, err)
|
||||
return err
|
||||
}
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(prj.ProjectID, chartEvent.EventType)
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(ctx, prj.ProjectID, chartEvent.EventType)
|
||||
if err != nil {
|
||||
log.Errorf("failed to find policy for %s event: %v", chartEvent.EventType, err)
|
||||
return err
|
||||
|
@ -23,9 +23,10 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/project"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
testingnotification "github.com/goharbor/harbor/src/testing/pkg/notification"
|
||||
testingnotification "github.com/goharbor/harbor/src/testing/pkg/notification/policy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -35,7 +36,8 @@ func TestChartPreprocessHandler_Handle(t *testing.T) {
|
||||
defer func() {
|
||||
notification.PolicyMgr = PolicyMgr
|
||||
}()
|
||||
notification.PolicyMgr = &testingnotification.FakedPolicyMgr{}
|
||||
policyMgrMock := &testingnotification.Manager{}
|
||||
notification.PolicyMgr = policyMgrMock
|
||||
|
||||
ProjectCtl := project.Ctl
|
||||
defer func() {
|
||||
@ -58,8 +60,30 @@ func TestChartPreprocessHandler_Handle(t *testing.T) {
|
||||
}
|
||||
}, nil)
|
||||
projectCtl.On("Get")
|
||||
policyMgrMock.On("GetRelatedPolices", mock.Anything, mock.Anything, mock.Anything).Return([]*model.Policy{
|
||||
{
|
||||
ID: 1,
|
||||
EventTypes: []string{
|
||||
event.TopicUploadChart,
|
||||
event.TopicDownloadChart,
|
||||
event.TopicDeleteChart,
|
||||
event.TopicPushArtifact,
|
||||
event.TopicPullArtifact,
|
||||
event.TopicDeleteArtifact,
|
||||
event.TopicScanningFailed,
|
||||
event.TopicScanningCompleted,
|
||||
event.TopicQuotaExceed,
|
||||
},
|
||||
Targets: []model.EventTarget{
|
||||
{
|
||||
Type: "http",
|
||||
Address: "http://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
handler := &Handler{Context: context.TODO}
|
||||
handler := &Handler{}
|
||||
config.Init()
|
||||
|
||||
type args struct {
|
||||
@ -127,7 +151,7 @@ func TestChartPreprocessHandler_Handle(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := handler.Handle(tt.args.data)
|
||||
err := handler.Handle(context.TODO(), tt.args.data)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
|
@ -15,6 +15,7 @@
|
||||
package quota
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
@ -39,7 +40,7 @@ func (qp *Handler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (qp *Handler) Handle(value interface{}) error {
|
||||
func (qp *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||
quotaEvent, ok := value.(*event.QuotaEvent)
|
||||
if !ok {
|
||||
return errors.New("invalid quota event type")
|
||||
@ -54,7 +55,7 @@ func (qp *Handler) Handle(value interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(prj.ProjectID, quotaEvent.EventType)
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(ctx, prj.ProjectID, quotaEvent.EventType)
|
||||
if err != nil {
|
||||
log.Errorf("failed to find policy for %s event: %v", quotaEvent.EventType, err)
|
||||
return err
|
||||
|
@ -15,8 +15,11 @@
|
||||
package quota
|
||||
|
||||
import (
|
||||
"context"
|
||||
common_dao "github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -27,7 +30,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
testing_notification "github.com/goharbor/harbor/src/testing/pkg/notification"
|
||||
testing_notification "github.com/goharbor/harbor/src/testing/pkg/notification/policy"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@ -69,8 +72,13 @@ func (suite *QuotaPreprocessHandlerSuite) SetupSuite() {
|
||||
}
|
||||
|
||||
suite.om = notification.PolicyMgr
|
||||
mp := &testing_notification.FakedPolicyMgr{}
|
||||
mp := &testing_notification.Manager{}
|
||||
notification.PolicyMgr = mp
|
||||
mp.On("GetRelatedPolices", mock.Anything, mock.Anything, mock.Anything).Return([]*policy_model.Policy{
|
||||
{
|
||||
ID: 1,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
h := &MockHandler{}
|
||||
|
||||
@ -86,7 +94,7 @@ func (suite *QuotaPreprocessHandlerSuite) TearDownSuite() {
|
||||
// TestHandle ...
|
||||
func (suite *QuotaPreprocessHandlerSuite) TestHandle() {
|
||||
handler := &Handler{}
|
||||
err := handler.Handle(suite.evt)
|
||||
err := handler.Handle(context.TODO(), suite.evt)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
@ -99,7 +107,7 @@ func (m *MockHandler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (m *MockHandler) Handle(value interface{}) error {
|
||||
func (m *MockHandler) Handle(ctx context.Context, value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
|
||||
// DelArtHandler is a handler to listen to the internal delete image event.
|
||||
type DelArtHandler struct {
|
||||
Context func() context.Context
|
||||
}
|
||||
|
||||
// Name ...
|
||||
@ -36,7 +35,7 @@ func (o *DelArtHandler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (o *DelArtHandler) Handle(value interface{}) error {
|
||||
func (o *DelArtHandler) Handle(ctx context.Context, value interface{}) error {
|
||||
if value == nil {
|
||||
return errors.New("delete image event handler: nil value ")
|
||||
}
|
||||
@ -48,7 +47,6 @@ func (o *DelArtHandler) Handle(value interface{}) error {
|
||||
|
||||
log.Debugf("clear the scan reports as receiving event %s", evt.EventType)
|
||||
|
||||
ctx := o.Context()
|
||||
// Check if it is safe to delete the reports.
|
||||
count, err := artifact.Ctl.Count(ctx, q.New(q.KeyWords{"digest": evt.Artifact.Digest}))
|
||||
if err != nil {
|
||||
|
@ -54,11 +54,11 @@ func (suite *DelArtHandlerTestSuite) TeardownSuite() {
|
||||
}
|
||||
|
||||
func (suite *DelArtHandlerTestSuite) TestHandle() {
|
||||
o := DelArtHandler{Context: context.TODO}
|
||||
o := DelArtHandler{}
|
||||
|
||||
suite.Error(o.Handle(nil))
|
||||
suite.Error(o.Handle(context.TODO(), nil))
|
||||
|
||||
suite.Error(o.Handle("string"))
|
||||
suite.Error(o.Handle(context.TODO(), "string"))
|
||||
|
||||
art := &artifact.Artifact{}
|
||||
art.Digest = "digest"
|
||||
@ -67,18 +67,18 @@ func (suite *DelArtHandlerTestSuite) TestHandle() {
|
||||
value := &event.DeleteArtifactEvent{ArtifactEvent: ev}
|
||||
|
||||
suite.artifactCtl.On("Count", context.TODO(), q.New(q.KeyWords{"digest": "digest"})).Return(int64(0), fmt.Errorf("failed")).Once()
|
||||
suite.Require().NoError(o.Handle(value))
|
||||
suite.Require().NoError(o.Handle(context.TODO(), value))
|
||||
|
||||
suite.artifactCtl.On("Count", context.TODO(), q.New(q.KeyWords{"digest": "digest"})).Return(int64(1), nil).Once()
|
||||
suite.Require().NoError(o.Handle(value))
|
||||
suite.Require().NoError(o.Handle(context.TODO(), value))
|
||||
|
||||
suite.artifactCtl.On("Count", context.TODO(), q.New(q.KeyWords{"digest": "digest"})).Return(int64(0), nil).Once()
|
||||
suite.scanCtl.On("DeleteReports", context.TODO(), "digest").Return(fmt.Errorf("failed")).Once()
|
||||
suite.Require().Error(o.Handle(value))
|
||||
suite.Require().Error(o.Handle(context.TODO(), value))
|
||||
|
||||
suite.artifactCtl.On("Count", context.TODO(), q.New(q.KeyWords{"digest": "digest"})).Return(int64(0), nil).Once()
|
||||
suite.scanCtl.On("DeleteReports", context.TODO(), "digest").Return(nil).Once()
|
||||
suite.Require().NoError(o.Handle(value))
|
||||
suite.Require().NoError(o.Handle(context.TODO(), value))
|
||||
}
|
||||
|
||||
func TestDelArtHandlerTestSuite(t *testing.T) {
|
||||
|
@ -43,7 +43,7 @@ func (si *Handler) Name() string {
|
||||
}
|
||||
|
||||
// Handle preprocess chart event data and then publish hook event
|
||||
func (si *Handler) Handle(value interface{}) error {
|
||||
func (si *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||
if value == nil {
|
||||
return errors.New("empty scan artifact event")
|
||||
}
|
||||
@ -53,7 +53,7 @@ func (si *Handler) Handle(value interface{}) error {
|
||||
return errors.New("invalid scan artifact event type")
|
||||
}
|
||||
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(e.Artifact.NamespaceID, e.EventType)
|
||||
policies, err := notification.PolicyMgr.GetRelatedPolices(ctx, e.Artifact.NamespaceID, e.EventType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scan preprocess handler")
|
||||
}
|
||||
|
@ -15,8 +15,10 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
common_dao "github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -34,7 +36,7 @@ import (
|
||||
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
|
||||
scantesting "github.com/goharbor/harbor/src/testing/controller/scan"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
notificationtesting "github.com/goharbor/harbor/src/testing/pkg/notification"
|
||||
notificationtesting "github.com/goharbor/harbor/src/testing/pkg/notification/policy"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@ -108,8 +110,13 @@ func (suite *ScanImagePreprocessHandlerSuite) SetupSuite() {
|
||||
artifact.Ctl = artifactCtl
|
||||
|
||||
suite.om = notification.PolicyMgr
|
||||
mp := ¬ificationtesting.FakedPolicyMgr{}
|
||||
mp := ¬ificationtesting.Manager{}
|
||||
notification.PolicyMgr = mp
|
||||
mp.On("GetRelatedPolices", mock.Anything, mock.Anything, mock.Anything).Return([]*policy_model.Policy{
|
||||
{
|
||||
ID: 1,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
h := &MockHTTPHandler{}
|
||||
|
||||
@ -128,7 +135,7 @@ func (suite *ScanImagePreprocessHandlerSuite) TearDownSuite() {
|
||||
func (suite *ScanImagePreprocessHandlerSuite) TestHandle() {
|
||||
handler := &Handler{}
|
||||
|
||||
err := handler.Handle(suite.evt)
|
||||
err := handler.Handle(context.TODO(), suite.evt)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
@ -143,7 +150,7 @@ func (m *MockHTTPHandler) Name() string {
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (m *MockHTTPHandler) Handle(value interface{}) error {
|
||||
func (m *MockHTTPHandler) Handle(ctx context.Context, value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,6 @@ import (
|
||||
_ "github.com/goharbor/harbor/src/core/auth/db"
|
||||
_ "github.com/goharbor/harbor/src/core/auth/ldap"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
"github.com/goharbor/harbor/src/server/middleware/orm"
|
||||
@ -119,11 +118,6 @@ func init() {
|
||||
|
||||
beego.Router("/api/replication/adapters", &ReplicationAdapterAPI{}, "get:List")
|
||||
|
||||
beego.Router("/api/projects/:pid([0-9]+)/webhook/policies", &NotificationPolicyAPI{}, "get:List;post:Post")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/webhook/policies/:id([0-9]+)", &NotificationPolicyAPI{})
|
||||
beego.Router("/api/projects/:pid([0-9]+)/webhook/policies/test", &NotificationPolicyAPI{}, "post:Test")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/webhook/lasttrigger", &NotificationPolicyAPI{}, "get:ListGroupByEventType")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/webhook/jobs/", &NotificationJobAPI{}, "get:List")
|
||||
// Charts are controlled under projects
|
||||
chartRepositoryAPIType := &ChartRepositoryAPI{}
|
||||
beego.Router("/api/chartrepo/health", chartRepositoryAPIType, "get:GetHealthStatus")
|
||||
@ -153,9 +147,6 @@ func init() {
|
||||
unknownUsr = &usrInfo{"unknown", "unknown"}
|
||||
testUser = &usrInfo{TestUserName, TestUserPwd}
|
||||
|
||||
// Init notification related check map
|
||||
notification.Init()
|
||||
|
||||
// Init mock jobservice
|
||||
mockServer := test.NewJobServiceServer()
|
||||
defer mockServer.Close()
|
||||
|
@ -1,100 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
)
|
||||
|
||||
// NotificationJobAPI ...
|
||||
type NotificationJobAPI struct {
|
||||
BaseController
|
||||
project *models.Project
|
||||
}
|
||||
|
||||
// Prepare ...
|
||||
func (w *NotificationJobAPI) Prepare() {
|
||||
w.BaseController.Prepare()
|
||||
if !w.SecurityCtx.IsAuthenticated() {
|
||||
w.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
pid, err := w.GetInt64FromPath(":pid")
|
||||
if err != nil {
|
||||
w.SendBadRequestError(fmt.Errorf("failed to get project ID: %v", err))
|
||||
return
|
||||
}
|
||||
if pid <= 0 {
|
||||
w.SendBadRequestError(fmt.Errorf("invalid project ID: %d", pid))
|
||||
return
|
||||
}
|
||||
|
||||
project, err := w.ProjectCtl.Get(w.Context(), pid)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundErr(err) {
|
||||
w.SendNotFoundError(fmt.Errorf("project %d not found", pid))
|
||||
} else {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to get project %d: %v", pid, err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.project = project
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (w *NotificationJobAPI) List() {
|
||||
if !w.validateRBAC(rbac.ActionList, w.project.ProjectID) {
|
||||
return
|
||||
}
|
||||
|
||||
policyID, err := w.GetInt64("policy_id")
|
||||
if err != nil || policyID <= 0 {
|
||||
w.SendBadRequestError(fmt.Errorf("invalid policy_id: %s", w.GetString("policy_id")))
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := notification.PolicyMgr.Get(policyID)
|
||||
if err != nil {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to get policy %d: %v", policyID, err))
|
||||
return
|
||||
}
|
||||
if policy == nil {
|
||||
w.SendBadRequestError(fmt.Errorf("policy %d not found", policyID))
|
||||
return
|
||||
}
|
||||
|
||||
query := &models.NotificationJobQuery{
|
||||
PolicyID: policyID,
|
||||
}
|
||||
|
||||
query.Statuses = w.GetStrings("status")
|
||||
|
||||
query.Page, query.Size, err = w.GetPaginationParams()
|
||||
if err != nil {
|
||||
w.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
total, jobs, err := notification.JobMgr.List(query)
|
||||
if err != nil {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to list notification jobs: %v", err))
|
||||
return
|
||||
}
|
||||
w.SetPaginationHeader(total, query.Page, query.Size)
|
||||
w.WriteJSONData(jobs)
|
||||
}
|
||||
|
||||
func (w *NotificationJobAPI) validateRBAC(action rbac.Action, projectID int64) bool {
|
||||
resource := system.NewNamespace().Resource(rbac.ResourceNotificationPolicy)
|
||||
if w.SecurityCtx.Can(w.Context(), action, resource) {
|
||||
return true
|
||||
}
|
||||
|
||||
return w.RequireProjectAccess(projectID, action, rbac.ResourceNotificationPolicy)
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
)
|
||||
|
||||
type fakedNotificationJobMgr struct {
|
||||
}
|
||||
|
||||
func (f *fakedNotificationJobMgr) Create(job *models.NotificationJob) (int64, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (f *fakedNotificationJobMgr) List(...*models.NotificationJobQuery) (int64, []*models.NotificationJob, error) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func (f *fakedNotificationJobMgr) Update(job *models.NotificationJob, props ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakedNotificationJobMgr) ListJobsGroupByEventType(policyID int64) ([]*models.NotificationJob, error) {
|
||||
return []*models.NotificationJob{
|
||||
{
|
||||
EventType: event.TopicPullArtifact,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
{
|
||||
EventType: event.TopicDeleteArtifact,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestNotificationJobAPI_List(t *testing.T) {
|
||||
policyMgr := notification.PolicyMgr
|
||||
jobMgr := notification.JobMgr
|
||||
defer func() {
|
||||
notification.PolicyMgr = policyMgr
|
||||
notification.JobMgr = jobMgr
|
||||
}()
|
||||
notification.PolicyMgr = &fakedNotificationPlyMgr{}
|
||||
notification.JobMgr = &fakedNotificationJobMgr{}
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/jobs?policy_id=1",
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/jobs?policy_id=1",
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 400 policyID invalid
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/jobs?policy_id=0",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 policyID not found
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/jobs?policy_id=123",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 404 project not found
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/123/webhook/jobs?policy_id=1",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/jobs?policy_id=1",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
@ -1,428 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
)
|
||||
|
||||
// NotificationPolicyAPI ...
|
||||
type NotificationPolicyAPI struct {
|
||||
BaseController
|
||||
project *models.Project
|
||||
supportedEvents map[string]struct{}
|
||||
}
|
||||
|
||||
// notificationPolicyForUI defines the structure of notification policy info display in UI
|
||||
type notificationPolicyForUI struct {
|
||||
PolicyName string `json:"policy_name"`
|
||||
EventType string `json:"event_type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CreationTime *time.Time `json:"creation_time"`
|
||||
LastTriggerTime *time.Time `json:"last_trigger_time,omitempty"`
|
||||
}
|
||||
|
||||
type notificationSupportedEventTypes struct {
|
||||
EventType []string `json:"event_type"`
|
||||
NotifyType []string `json:"notify_type"`
|
||||
}
|
||||
|
||||
// Prepare ...
|
||||
func (w *NotificationPolicyAPI) Prepare() {
|
||||
w.BaseController.Prepare()
|
||||
if !w.SecurityCtx.IsAuthenticated() {
|
||||
w.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
pid, err := w.GetInt64FromPath(":pid")
|
||||
if err != nil {
|
||||
w.SendBadRequestError(fmt.Errorf("failed to get project ID: %v", err))
|
||||
return
|
||||
}
|
||||
if pid <= 0 {
|
||||
w.SendBadRequestError(fmt.Errorf("invalid project ID: %d", pid))
|
||||
return
|
||||
}
|
||||
|
||||
project, err := w.ProjectCtl.Get(w.Context(), pid)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundErr(err) {
|
||||
w.SendNotFoundError(fmt.Errorf("project %d not found", pid))
|
||||
} else {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to get project %d: %v", pid, err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.project = project
|
||||
w.supportedEvents = initSupportedEvents()
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (w *NotificationPolicyAPI) Get() {
|
||||
if !w.validateRBAC(rbac.ActionRead, w.project.ProjectID) {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := w.GetIDFromURL()
|
||||
if err != nil {
|
||||
w.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := notification.PolicyMgr.Get(id)
|
||||
if err != nil {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to get the notification policy %d: %v", id, err))
|
||||
return
|
||||
}
|
||||
if policy == nil {
|
||||
w.SendNotFoundError(fmt.Errorf("notification policy %d not found", id))
|
||||
return
|
||||
}
|
||||
|
||||
if w.project.ProjectID != policy.ProjectID {
|
||||
w.SendBadRequestError(fmt.Errorf("notification policy %d with projectID %d not belong to project %d in URL", id, policy.ProjectID, w.project.ProjectID))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJSONData(policy)
|
||||
}
|
||||
|
||||
// Post ...
|
||||
func (w *NotificationPolicyAPI) Post() {
|
||||
if !w.validateRBAC(rbac.ActionCreate, w.project.ProjectID) {
|
||||
return
|
||||
}
|
||||
|
||||
policy := &models.NotificationPolicy{}
|
||||
isValid, err := w.DecodeJSONReqAndValidate(policy)
|
||||
if !isValid {
|
||||
w.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !w.validateTargets(policy) {
|
||||
return
|
||||
}
|
||||
|
||||
if !w.validateEventTypes(policy) {
|
||||
return
|
||||
}
|
||||
|
||||
if policy.ID != 0 {
|
||||
w.SendBadRequestError(fmt.Errorf("cannot accept policy creating request with ID: %d", policy.ID))
|
||||
return
|
||||
}
|
||||
|
||||
policy.Creator = w.SecurityCtx.GetUsername()
|
||||
policy.ProjectID = w.project.ProjectID
|
||||
|
||||
id, err := notification.PolicyMgr.Create(policy)
|
||||
if err != nil {
|
||||
w.SendError(err)
|
||||
return
|
||||
}
|
||||
w.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
|
||||
}
|
||||
|
||||
// Put ...
|
||||
func (w *NotificationPolicyAPI) Put() {
|
||||
if !w.validateRBAC(rbac.ActionUpdate, w.project.ProjectID) {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := w.GetIDFromURL()
|
||||
if id < 0 || err != nil {
|
||||
w.SendBadRequestError(errors.New("invalid notification policy ID"))
|
||||
return
|
||||
}
|
||||
|
||||
oriPolicy, err := notification.PolicyMgr.Get(id)
|
||||
if err != nil {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to get the notification policy %d: %v", id, err))
|
||||
return
|
||||
}
|
||||
if oriPolicy == nil {
|
||||
w.SendNotFoundError(fmt.Errorf("notification policy %d not found", id))
|
||||
return
|
||||
}
|
||||
|
||||
policy := &models.NotificationPolicy{}
|
||||
isValid, err := w.DecodeJSONReqAndValidate(policy)
|
||||
if !isValid {
|
||||
w.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !w.validateTargets(policy) {
|
||||
return
|
||||
}
|
||||
|
||||
if !w.validateEventTypes(policy) {
|
||||
return
|
||||
}
|
||||
|
||||
if w.project.ProjectID != oriPolicy.ProjectID {
|
||||
w.SendBadRequestError(fmt.Errorf("notification policy %d with projectID %d not belong to project %d in URL", id, oriPolicy.ProjectID, w.project.ProjectID))
|
||||
return
|
||||
}
|
||||
|
||||
policy.ID = id
|
||||
policy.ProjectID = w.project.ProjectID
|
||||
|
||||
if err = notification.PolicyMgr.Update(policy); err != nil {
|
||||
w.SendError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (w *NotificationPolicyAPI) List() {
|
||||
projectID := w.project.ProjectID
|
||||
if !w.validateRBAC(rbac.ActionList, projectID) {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := notification.PolicyMgr.List(projectID)
|
||||
if err != nil {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to list notification policies by projectID %d: %v", projectID, err))
|
||||
return
|
||||
}
|
||||
|
||||
policies := []*models.NotificationPolicy{}
|
||||
if res != nil {
|
||||
for _, policy := range res {
|
||||
policies = append(policies, policy)
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteJSONData(policies)
|
||||
}
|
||||
|
||||
// ListGroupByEventType lists notification policy trigger info grouped by event type for UI,
|
||||
// displays event type, status(enabled/disabled), create time, last trigger time
|
||||
func (w *NotificationPolicyAPI) ListGroupByEventType() {
|
||||
projectID := w.project.ProjectID
|
||||
if !w.validateRBAC(rbac.ActionList, projectID) {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := notification.PolicyMgr.List(projectID)
|
||||
if err != nil {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to list notification policies by projectID %d: %v", projectID, err))
|
||||
return
|
||||
}
|
||||
|
||||
policies, err := constructPolicyWithTriggerTime(res)
|
||||
if err != nil {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to list the notification policy trigger information: %v", err))
|
||||
return
|
||||
}
|
||||
w.WriteJSONData(policies)
|
||||
}
|
||||
|
||||
// Delete ...
|
||||
func (w *NotificationPolicyAPI) Delete() {
|
||||
projectID := w.project.ProjectID
|
||||
if !w.validateRBAC(rbac.ActionDelete, projectID) {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := w.GetIDFromURL()
|
||||
if id < 0 || err != nil {
|
||||
w.SendBadRequestError(errors.New("invalid notification policy ID"))
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := notification.PolicyMgr.Get(id)
|
||||
if err != nil {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to get the notification policy %d: %v", id, err))
|
||||
return
|
||||
}
|
||||
if policy == nil {
|
||||
w.SendNotFoundError(fmt.Errorf("notification policy %d not found", id))
|
||||
return
|
||||
}
|
||||
|
||||
if projectID != policy.ProjectID {
|
||||
w.SendBadRequestError(fmt.Errorf("notification policy %d with projectID %d not belong to project %d in URL", id, policy.ProjectID, projectID))
|
||||
return
|
||||
}
|
||||
|
||||
if err = notification.PolicyMgr.Delete(id); err != nil {
|
||||
w.SendInternalServerError(fmt.Errorf("failed to delete notification policy %d: %v", id, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetSupportedEventTypes get supported trigger event types and notify types in module notification
|
||||
func (w *NotificationPolicyAPI) GetSupportedEventTypes() {
|
||||
projectID := w.project.ProjectID
|
||||
if !w.validateRBAC(rbac.ActionList, projectID) {
|
||||
return
|
||||
}
|
||||
|
||||
var notificationTypes = notificationSupportedEventTypes{}
|
||||
for key := range notification.SupportedNotifyTypes {
|
||||
notificationTypes.NotifyType = append(notificationTypes.NotifyType, key)
|
||||
}
|
||||
|
||||
for key := range w.supportedEvents {
|
||||
notificationTypes.EventType = append(notificationTypes.EventType, key)
|
||||
}
|
||||
w.WriteJSONData(notificationTypes)
|
||||
}
|
||||
|
||||
// Test ...
|
||||
func (w *NotificationPolicyAPI) Test() {
|
||||
projectID := w.project.ProjectID
|
||||
if !w.validateRBAC(rbac.ActionCreate, projectID) {
|
||||
return
|
||||
}
|
||||
|
||||
policy := &models.NotificationPolicy{}
|
||||
isValid, err := w.DecodeJSONReqAndValidate(policy)
|
||||
if !isValid {
|
||||
w.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !w.validateTargets(policy) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := notification.PolicyMgr.Test(policy); err != nil {
|
||||
log.Errorf("notification policy %s test failed: %v", policy.Name, err)
|
||||
w.SendBadRequestError(fmt.Errorf("notification policy %s test failed", policy.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (w *NotificationPolicyAPI) validateRBAC(action rbac.Action, projectID int64) bool {
|
||||
if w.SecurityCtx.IsSysAdmin() {
|
||||
return true
|
||||
}
|
||||
|
||||
return w.RequireProjectAccess(projectID, action, rbac.ResourceNotificationPolicy)
|
||||
}
|
||||
|
||||
func (w *NotificationPolicyAPI) validateTargets(policy *models.NotificationPolicy) bool {
|
||||
if len(policy.Targets) == 0 {
|
||||
w.SendBadRequestError(fmt.Errorf("empty notification target with policy %s", policy.Name))
|
||||
return false
|
||||
}
|
||||
|
||||
for _, target := range policy.Targets {
|
||||
url, err := utils.ParseEndpoint(target.Address)
|
||||
if err != nil {
|
||||
w.SendBadRequestError(err)
|
||||
return false
|
||||
}
|
||||
// Prevent SSRF security issue #3755
|
||||
target.Address = url.Scheme + "://" + url.Host + url.Path
|
||||
|
||||
_, ok := notification.SupportedNotifyTypes[target.Type]
|
||||
if !ok {
|
||||
w.SendBadRequestError(fmt.Errorf("unsupport target type %s with policy %s", target.Type, policy.Name))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *NotificationPolicyAPI) validateEventTypes(policy *models.NotificationPolicy) bool {
|
||||
if len(policy.EventTypes) == 0 {
|
||||
w.SendBadRequestError(errors.New("empty event type"))
|
||||
return false
|
||||
}
|
||||
|
||||
for _, eventType := range policy.EventTypes {
|
||||
_, ok := w.supportedEvents[eventType]
|
||||
if !ok {
|
||||
w.SendBadRequestError(fmt.Errorf("unsupport event type %s", eventType))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getLastTriggerTimeGroupByEventType(eventType string, policyID int64) (time.Time, error) {
|
||||
jobs, err := notification.JobMgr.ListJobsGroupByEventType(policyID)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
if eventType == job.EventType {
|
||||
return job.CreationTime, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
func initSupportedEvents() map[string]struct{} {
|
||||
eventTypes := []string{
|
||||
event.TopicPushArtifact,
|
||||
event.TopicPullArtifact,
|
||||
event.TopicDeleteArtifact,
|
||||
event.TopicUploadChart,
|
||||
event.TopicDeleteChart,
|
||||
event.TopicDownloadChart,
|
||||
event.TopicQuotaExceed,
|
||||
event.TopicQuotaWarning,
|
||||
event.TopicScanningFailed,
|
||||
event.TopicScanningCompleted,
|
||||
event.TopicReplication,
|
||||
event.TopicTagRetention,
|
||||
}
|
||||
|
||||
var supportedEventTypes = make(map[string]struct{})
|
||||
for _, eventType := range eventTypes {
|
||||
supportedEventTypes[eventType] = struct{}{}
|
||||
}
|
||||
|
||||
return supportedEventTypes
|
||||
}
|
||||
|
||||
// constructPolicyWithTriggerTime construct notification policy information displayed in UI
|
||||
// including event type, enabled, creation time, last trigger time
|
||||
func constructPolicyWithTriggerTime(policies []*models.NotificationPolicy) ([]*notificationPolicyForUI, error) {
|
||||
res := []*notificationPolicyForUI{}
|
||||
if policies != nil {
|
||||
for _, policy := range policies {
|
||||
for _, t := range policy.EventTypes {
|
||||
ply := ¬ificationPolicyForUI{
|
||||
PolicyName: policy.Name,
|
||||
EventType: t,
|
||||
Enabled: policy.Enabled,
|
||||
CreationTime: &policy.CreationTime,
|
||||
}
|
||||
if !policy.CreationTime.IsZero() {
|
||||
ply.CreationTime = &policy.CreationTime
|
||||
}
|
||||
|
||||
ltTime, err := getLastTriggerTimeGroupByEventType(t, policy.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ltTime.IsZero() {
|
||||
ply.LastTriggerTime = <Time
|
||||
}
|
||||
res = append(res, ply)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
@ -1,636 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
)
|
||||
|
||||
type fakedNotificationPlyMgr struct {
|
||||
}
|
||||
|
||||
func (f *fakedNotificationPlyMgr) Create(*models.NotificationPolicy) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (f *fakedNotificationPlyMgr) List(id int64) ([]*models.NotificationPolicy, error) {
|
||||
return []*models.NotificationPolicy{
|
||||
{
|
||||
ID: 1,
|
||||
EventTypes: []string{
|
||||
event.TopicPullArtifact,
|
||||
event.TopicPushArtifact,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakedNotificationPlyMgr) Get(id int64) (*models.NotificationPolicy, error) {
|
||||
switch id {
|
||||
case 1:
|
||||
return &models.NotificationPolicy{ID: 1, ProjectID: 1}, nil
|
||||
case 2:
|
||||
return &models.NotificationPolicy{ID: 2, ProjectID: 222}, nil
|
||||
case 3:
|
||||
return nil, errors.New("")
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakedNotificationPlyMgr) GetByNameAndProjectID(string, int64) (*models.NotificationPolicy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakedNotificationPlyMgr) Update(*models.NotificationPolicy) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakedNotificationPlyMgr) Delete(int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakedNotificationPlyMgr) Test(*models.NotificationPolicy) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakedNotificationPlyMgr) GetRelatedPolices(int64, string) ([]*models.NotificationPolicy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestNotificationPolicyAPI_List(t *testing.T) {
|
||||
policyCtl := notification.PolicyMgr
|
||||
defer func() {
|
||||
notification.PolicyMgr = policyCtl
|
||||
}()
|
||||
|
||||
notification.PolicyMgr = &fakedNotificationPlyMgr{}
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/123/webhook/policies",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
|
||||
}
|
||||
|
||||
func TestNotificationPolicyAPI_Post(t *testing.T) {
|
||||
policyCtl := notification.PolicyMgr
|
||||
defer func() {
|
||||
notification.PolicyMgr = policyCtl
|
||||
}()
|
||||
|
||||
notification.PolicyMgr = &fakedNotificationPlyMgr{}
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 400 invalid json body
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: "invalid json body",
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 empty targets
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
Targets: []models.EventTarget{},
|
||||
}},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 invalid event target address
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Address: "tcp://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
}},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 invalid event target type
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Type: "smn",
|
||||
Address: "http://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
}},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 invalid event type
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
EventTypes: []string{"invalidType"},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Address: "tcp://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
}},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 policy ID != 0
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
ID: 111,
|
||||
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Type: "http",
|
||||
Address: "http://10.173.32.58:9009",
|
||||
AuthHeader: "xxxxxxxxx",
|
||||
SkipCertVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 201
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Type: "http",
|
||||
Address: "http://10.173.32.58:9009",
|
||||
AuthHeader: "xxxxxxxxx",
|
||||
SkipCertVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code: http.StatusCreated,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestNotificationPolicyAPI_Get(t *testing.T) {
|
||||
policyCtl := notification.PolicyMgr
|
||||
defer func() {
|
||||
notification.PolicyMgr = policyCtl
|
||||
}()
|
||||
|
||||
notification.PolicyMgr = &fakedNotificationPlyMgr{}
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/policies/111",
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/policies/111",
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/policies/1234",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 400 projectID not match with projectID in URL
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/policies/2",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 500
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/policies/3",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusInternalServerError,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/policies/1",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestNotificationPolicyAPI_Put(t *testing.T) {
|
||||
policyCtl := notification.PolicyMgr
|
||||
defer func() {
|
||||
notification.PolicyMgr = policyCtl
|
||||
}()
|
||||
|
||||
notification.PolicyMgr = &fakedNotificationPlyMgr{}
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: "/api/projects/1/webhook/policies/111",
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: "/api/projects/1/webhook/policies/111",
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: "/api/projects/1/webhook/policies/1234",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 400 invalid json body
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: "/api/projects/1/webhook/policies/1",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: "invalidJSONBody",
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 empty targets
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: "/api/projects/1/webhook/policies/1",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
|
||||
Targets: []models.EventTarget{},
|
||||
}},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 invalid event target address
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: "/api/projects/1/webhook/policies/1",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Address: "tcp://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
}},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 invalid event target type
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: "/api/projects/1/webhook/policies/1",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Type: "smn",
|
||||
Address: "http://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
}},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 invalid event type
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: "/api/projects/1/webhook/policies/1",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
EventTypes: []string{"invalidType"},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Address: "tcp://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
}},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: "/api/projects/1/webhook/policies/1",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
Name: "imagePolicyTest",
|
||||
EventTypes: []string{"PULL_ARTIFACT", "PUSH_ARTIFACT", "DELETE_ARTIFACT"},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Type: "http",
|
||||
Address: "http://10.173.32.58:9009",
|
||||
AuthHeader: "xxxxxxxxx",
|
||||
SkipCertVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestNotificationPolicyAPI_Test(t *testing.T) {
|
||||
policyCtl := notification.PolicyMgr
|
||||
defer func() {
|
||||
notification.PolicyMgr = policyCtl
|
||||
}()
|
||||
|
||||
notification.PolicyMgr = &fakedNotificationPlyMgr{}
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies/test",
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies/test",
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/123/webhook/policies/test",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 400 invalid json body
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies/test",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: 1234125,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/webhook/policies/test",
|
||||
credential: sysAdmin,
|
||||
bodyJSON: &models.NotificationPolicy{
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Type: "http",
|
||||
Address: "http://10.173.32.58:9009",
|
||||
AuthHeader: "xxxxxxxxx",
|
||||
SkipCertVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestNotificationPolicyAPI_ListGroupByEventType(t *testing.T) {
|
||||
policyCtl := notification.PolicyMgr
|
||||
jobMgr := notification.JobMgr
|
||||
defer func() {
|
||||
notification.PolicyMgr = policyCtl
|
||||
notification.JobMgr = jobMgr
|
||||
}()
|
||||
|
||||
notification.PolicyMgr = &fakedNotificationPlyMgr{}
|
||||
notification.JobMgr = &fakedNotificationJobMgr{}
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/lasttrigger",
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/lasttrigger",
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/123/webhook/lasttrigger",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/webhook/lasttrigger",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestNotificationPolicyAPI_Delete(t *testing.T) {
|
||||
policyCtl := notification.PolicyMgr
|
||||
defer func() {
|
||||
notification.PolicyMgr = policyCtl
|
||||
}()
|
||||
|
||||
notification.PolicyMgr = &fakedNotificationPlyMgr{}
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: "/api/projects/1/webhook/policies/111",
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: "/api/projects/1/webhook/policies/111",
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: "/api/projects/1/webhook/policies/1234",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 400 projectID not match
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: "/api/projects/1/webhook/policies/2",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 500 failed to get policy
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: "/api/projects/1/webhook/policies/3",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusInternalServerError,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: "/api/projects/1/webhook/policies/1",
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
@ -16,6 +16,7 @@ package jobs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/job"
|
||||
@ -24,6 +25,7 @@ import (
|
||||
jjob "github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job/model"
|
||||
)
|
||||
|
||||
var statusMap = map[string]string{
|
||||
@ -87,7 +89,7 @@ func (h *Handler) Prepare() {
|
||||
// HandleNotificationJob handles the hook of notification job
|
||||
func (h *Handler) HandleNotificationJob() {
|
||||
log.Debugf("received notification job status update event: job-%d, status-%s", h.id, h.status)
|
||||
if err := notification.JobMgr.Update(&models.NotificationJob{
|
||||
if err := notification.JobMgr.Update(orm.Context(), &model.Job{
|
||||
ID: h.id,
|
||||
Status: h.status,
|
||||
UpdateTime: time.Now(),
|
||||
|
@ -1,6 +1,7 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
@ -12,13 +13,13 @@ import (
|
||||
"github.com/goharbor/harbor/src/core/utils"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job/manager"
|
||||
job_model "github.com/goharbor/harbor/src/pkg/notification/job/model"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
)
|
||||
|
||||
// Manager send hook
|
||||
type Manager interface {
|
||||
StartHook(*model.HookEvent, *models.JobData) error
|
||||
StartHook(context.Context, *model.HookEvent, *models.JobData) error
|
||||
}
|
||||
|
||||
// DefaultManager ...
|
||||
@ -30,20 +31,20 @@ type DefaultManager struct {
|
||||
// NewHookManager ...
|
||||
func NewHookManager() *DefaultManager {
|
||||
return &DefaultManager{
|
||||
jobMgr: manager.NewDefaultManager(),
|
||||
jobMgr: job.NewManager(),
|
||||
client: utils.GetJobServiceClient(),
|
||||
}
|
||||
}
|
||||
|
||||
// StartHook create a notification job record in database, and submit it to jobservice
|
||||
func (hm *DefaultManager) StartHook(event *model.HookEvent, data *models.JobData) error {
|
||||
func (hm *DefaultManager) StartHook(ctx context.Context, event *model.HookEvent, data *models.JobData) error {
|
||||
payload, err := json.Marshal(event.Payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
id, err := hm.jobMgr.Create(&cModels.NotificationJob{
|
||||
id, err := hm.jobMgr.Create(ctx, &job_model.Job{
|
||||
PolicyID: event.PolicyID,
|
||||
EventType: event.EventType,
|
||||
NotifyType: event.Target.Type,
|
||||
@ -64,7 +65,7 @@ func (hm *DefaultManager) StartHook(event *model.HookEvent, data *models.JobData
|
||||
jobUUID, err := hm.client.SubmitJob(data)
|
||||
if err != nil {
|
||||
log.Errorf("failed to submit job with notification event: %v", err)
|
||||
e := hm.jobMgr.Update(&cModels.NotificationJob{
|
||||
e := hm.jobMgr.Update(ctx, &job_model.Job{
|
||||
ID: id,
|
||||
Status: cModels.JobError,
|
||||
}, "Status")
|
||||
@ -74,7 +75,7 @@ func (hm *DefaultManager) StartHook(event *model.HookEvent, data *models.JobData
|
||||
return err
|
||||
}
|
||||
|
||||
if err = hm.jobMgr.Update(&cModels.NotificationJob{
|
||||
if err = hm.jobMgr.Update(ctx, &job_model.Job{
|
||||
ID: id,
|
||||
UUID: jobUUID,
|
||||
}, "UUID"); err != nil {
|
||||
|
185
src/pkg/notification/job/dao/dao.go
Normal file
185
src/pkg/notification/job/dao/dao.go
Normal file
@ -0,0 +1,185 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job/model"
|
||||
)
|
||||
|
||||
// DAO defines the interface to access the robot data model
|
||||
type DAO interface {
|
||||
// Create ...
|
||||
Create(ctx context.Context, n *model.Job) (int64, error)
|
||||
|
||||
// Update ...
|
||||
Update(ctx context.Context, n *model.Job, props ...string) error
|
||||
|
||||
// Get ...
|
||||
Get(ctx context.Context, id int64) (*model.Job, error)
|
||||
|
||||
// Count ...
|
||||
Count(ctx context.Context, query *q.Query) (total int64, err error)
|
||||
|
||||
// List ...
|
||||
List(ctx context.Context, query *q.Query) ([]*model.Job, error)
|
||||
|
||||
// Delete ...
|
||||
Delete(ctx context.Context, id int64) error
|
||||
|
||||
// GetLastTriggerJobsGroupByEventType ...
|
||||
GetLastTriggerJobsGroupByEventType(ctx context.Context, policyID int64) ([]*model.Job, error)
|
||||
|
||||
// DeleteByPolicyID
|
||||
DeleteByPolicyID(ctx context.Context, policyID int64) error
|
||||
}
|
||||
|
||||
// New creates a default implementation for Dao
|
||||
func New() DAO {
|
||||
return &dao{}
|
||||
}
|
||||
|
||||
type dao struct{}
|
||||
|
||||
// UpdateNotificationJob update notification job
|
||||
func (d *dao) Update(ctx context.Context, job *model.Job, props ...string) error {
|
||||
if job == nil {
|
||||
return errors.New("nil job")
|
||||
}
|
||||
|
||||
if job.ID == 0 {
|
||||
return fmt.Errorf("notification job ID is empty")
|
||||
}
|
||||
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := ormer.Update(job, props...)
|
||||
if n == 0 {
|
||||
return errors.NotFoundError(nil).WithMessage("notification %d not found", job.ID)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create insert new notification job to DB
|
||||
func (d *dao) Create(ctx context.Context, job *model.Job) (int64, error) {
|
||||
if job == nil {
|
||||
return 0, errors.New("nil job")
|
||||
}
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(job.Status) == 0 {
|
||||
job.Status = models.JobPending
|
||||
}
|
||||
return ormer.Insert(job)
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (d *dao) Get(ctx context.Context, id int64) (*model.Job, error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
j := &model.Job{
|
||||
ID: id,
|
||||
}
|
||||
if err := ormer.Read(j); err != nil {
|
||||
if e := orm.AsNotFoundError(err, "notificationJob %d not found", id); e != nil {
|
||||
err = e
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
|
||||
// Count ...
|
||||
func (d *dao) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
qs, err := orm.QuerySetterForCount(ctx, &model.Job{}, query)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return qs.Count()
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (d *dao) List(ctx context.Context, query *q.Query) ([]*model.Job, error) {
|
||||
jobs := []*model.Job{}
|
||||
|
||||
qs, err := orm.QuerySetter(ctx, &model.Job{}, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = qs.All(&jobs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
// GetLastTriggerJobsGroupByEventType get notification jobs info of policy, including event type and last trigger time
|
||||
func (d *dao) GetLastTriggerJobsGroupByEventType(ctx context.Context, policyID int64) ([]*model.Job, error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// get jobs last triggered(created) group by event_type. postgres group by usage reference:
|
||||
// https://stackoverflow.com/questions/13325583/postgresql-max-and-group-by
|
||||
sql := `select distinct on (event_type) event_type, id, creation_time, status, notify_type, job_uuid, update_time,
|
||||
creation_time, job_detail from notification_job where policy_id = ?
|
||||
order by event_type, id desc, creation_time, status, notify_type, job_uuid, update_time, creation_time, job_detail`
|
||||
|
||||
jobs := []*model.Job{}
|
||||
_, err = ormer.Raw(sql, policyID).QueryRows(&jobs)
|
||||
if err != nil {
|
||||
log.Errorf("query last trigger info group by event type failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func (d *dao) Delete(ctx context.Context, id int64) error {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := ormer.Delete(&model.Job{
|
||||
ID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return errors.NotFoundError(nil).WithMessage("notificationJob %d not found", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteByPolicyID ...
|
||||
func (d *dao) DeleteByPolicyID(ctx context.Context, policyID int64) error {
|
||||
qs, err := orm.QuerySetter(ctx, &model.Job{}, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"policy_id": policyID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := qs.Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return errors.NotFoundError(nil).WithMessage("notificationJob %d not found", policyID)
|
||||
}
|
||||
return nil
|
||||
}
|
187
src/pkg/notification/job/dao/dao_test.go
Normal file
187
src/pkg/notification/job/dao/dao_test.go
Normal file
@ -0,0 +1,187 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job/model"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
testJob1 = &model.Job{
|
||||
PolicyID: 1111,
|
||||
EventType: "pushImage",
|
||||
NotifyType: "http",
|
||||
Status: "pending",
|
||||
JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563536782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}",
|
||||
UUID: "00000000",
|
||||
}
|
||||
testJob2 = &model.Job{
|
||||
PolicyID: 111,
|
||||
EventType: "pullImage",
|
||||
NotifyType: "http",
|
||||
Status: "",
|
||||
JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563537782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}",
|
||||
UUID: "00000000",
|
||||
}
|
||||
testJob3 = &model.Job{
|
||||
PolicyID: 111,
|
||||
EventType: "deleteImage",
|
||||
NotifyType: "http",
|
||||
Status: "pending",
|
||||
JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563538782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}",
|
||||
UUID: "00000000",
|
||||
}
|
||||
)
|
||||
|
||||
type DaoTestSuite struct {
|
||||
htesting.Suite
|
||||
dao DAO
|
||||
|
||||
jobID1 int64
|
||||
jobID2 int64
|
||||
jobID3 int64
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) SetupSuite() {
|
||||
suite.Suite.SetupSuite()
|
||||
suite.dao = New()
|
||||
suite.Suite.ClearTables = []string{"notification_job"}
|
||||
suite.jobs()
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) jobs() {
|
||||
var err error
|
||||
suite.jobID1, err = suite.dao.Create(orm.Context(), testJob1)
|
||||
suite.Nil(err)
|
||||
|
||||
suite.jobID2, err = suite.dao.Create(orm.Context(), testJob2)
|
||||
suite.Nil(err)
|
||||
|
||||
suite.jobID3, err = suite.dao.Create(orm.Context(), testJob3)
|
||||
suite.Nil(err)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestCreate() {
|
||||
_, err := suite.dao.Create(orm.Context(), nil)
|
||||
suite.NotNil(err)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestDelete() {
|
||||
err := suite.dao.Delete(orm.Context(), 1234)
|
||||
suite.Require().NotNil(err)
|
||||
suite.True(errors.IsErr(err, errors.NotFoundCode))
|
||||
|
||||
err = suite.dao.Delete(orm.Context(), suite.jobID2)
|
||||
suite.Nil(err)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestList() {
|
||||
jobs, err := suite.dao.List(orm.Context(), &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"EventType": "pushImage",
|
||||
},
|
||||
})
|
||||
suite.Require().Nil(err)
|
||||
suite.Equal(len(jobs), 1)
|
||||
suite.Equal(suite.jobID1, jobs[0].ID)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestGet() {
|
||||
_, err := suite.dao.Get(orm.Context(), 1234)
|
||||
suite.Require().NotNil(err)
|
||||
suite.True(errors.IsErr(err, errors.NotFoundCode))
|
||||
|
||||
id, err := suite.dao.Create(orm.Context(), &model.Job{
|
||||
PolicyID: 2222,
|
||||
EventType: "pushChart",
|
||||
NotifyType: "http",
|
||||
Status: "pending",
|
||||
JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563536782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}",
|
||||
UUID: "00000000",
|
||||
})
|
||||
suite.Nil(err)
|
||||
|
||||
r, err := suite.dao.Get(orm.Context(), id)
|
||||
suite.Nil(err)
|
||||
suite.Equal("pushChart", r.EventType)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestUpdate() {
|
||||
j := &model.Job{
|
||||
ID: suite.jobID1,
|
||||
Status: "success",
|
||||
}
|
||||
|
||||
err := suite.dao.Update(orm.Context(), j)
|
||||
suite.Nil(err)
|
||||
|
||||
r1, err := suite.dao.Get(orm.Context(), j.ID)
|
||||
suite.Equal("success", r1.Status)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestCount() {
|
||||
// nil query
|
||||
total, err := suite.dao.Count(orm.Context(), nil)
|
||||
suite.Nil(err)
|
||||
suite.True(total > 0)
|
||||
|
||||
// query by name
|
||||
total, err = suite.dao.Count(orm.Context(), &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"EventType": "deleteImage",
|
||||
},
|
||||
})
|
||||
suite.Nil(err)
|
||||
suite.Equal(int64(1), total)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestDeleteByPolicyID() {
|
||||
jobs, err := suite.dao.List(orm.Context(), &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"PolicyID": 111,
|
||||
},
|
||||
})
|
||||
suite.True(len(jobs) > 0)
|
||||
|
||||
err = suite.dao.DeleteByPolicyID(orm.Context(), 111)
|
||||
suite.Nil(err)
|
||||
|
||||
jobs, err = suite.dao.List(orm.Context(), &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"PolicyID": 111,
|
||||
},
|
||||
})
|
||||
suite.Equal(0, len(jobs))
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestGetLastTriggerJobsGroupByEventType() {
|
||||
_, err := suite.dao.Create(orm.Context(), &model.Job{
|
||||
PolicyID: 3333,
|
||||
EventType: "pushChart",
|
||||
NotifyType: "http",
|
||||
Status: "pending",
|
||||
JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563536782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}",
|
||||
UUID: "00000000",
|
||||
})
|
||||
suite.Nil(err)
|
||||
_, err = suite.dao.Create(orm.Context(), &model.Job{
|
||||
PolicyID: 3333,
|
||||
EventType: "pullChart",
|
||||
NotifyType: "http",
|
||||
Status: "pending",
|
||||
JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563536782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}",
|
||||
UUID: "00000000",
|
||||
})
|
||||
suite.Nil(err)
|
||||
jobs, err := suite.dao.GetLastTriggerJobsGroupByEventType(orm.Context(), 3333)
|
||||
suite.Nil(err)
|
||||
suite.Equal(2, len(jobs))
|
||||
}
|
||||
|
||||
func TestDaoTestSuite(t *testing.T) {
|
||||
suite.Run(t, &DaoTestSuite{})
|
||||
}
|
@ -1,20 +1,69 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job/model"
|
||||
)
|
||||
|
||||
var (
|
||||
// Mgr is a global variable for the default notification job
|
||||
Mgr = NewManager()
|
||||
)
|
||||
|
||||
// Manager manages notification jobs recorded in database
|
||||
type Manager interface {
|
||||
// Create create a notification job
|
||||
Create(job *models.NotificationJob) (int64, error)
|
||||
Create(ctx context.Context, job *model.Job) (int64, error)
|
||||
|
||||
// List list notification jobs
|
||||
List(...*models.NotificationJobQuery) (int64, []*models.NotificationJob, error)
|
||||
List(ctx context.Context, query *q.Query) ([]*model.Job, error)
|
||||
|
||||
// Update update notification job
|
||||
Update(job *models.NotificationJob, props ...string) error
|
||||
Update(ctx context.Context, job *model.Job, props ...string) error
|
||||
|
||||
// ListJobsGroupByEventType lists last triggered jobs group by event type
|
||||
ListJobsGroupByEventType(policyID int64) ([]*models.NotificationJob, error)
|
||||
ListJobsGroupByEventType(ctx context.Context, policyID int64) ([]*model.Job, error)
|
||||
|
||||
// Count ...
|
||||
Count(ctx context.Context, query *q.Query) (total int64, err error)
|
||||
}
|
||||
|
||||
var _ Manager = &manager{}
|
||||
|
||||
type manager struct {
|
||||
dao dao.DAO
|
||||
}
|
||||
|
||||
// NewManager ...
|
||||
func NewManager() Manager {
|
||||
return &manager{
|
||||
dao: dao.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (d *manager) Create(ctx context.Context, job *model.Job) (int64, error) {
|
||||
return d.dao.Create(ctx, job)
|
||||
}
|
||||
|
||||
// Count ...
|
||||
func (d *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
return d.dao.Count(ctx, query)
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (d *manager) List(ctx context.Context, query *q.Query) ([]*model.Job, error) {
|
||||
return d.dao.List(ctx, query)
|
||||
}
|
||||
|
||||
// Update ...
|
||||
func (d *manager) Update(ctx context.Context, job *model.Job, props ...string) error {
|
||||
return d.dao.Update(ctx, job, props...)
|
||||
}
|
||||
|
||||
// ListJobsGroupByEventType lists last triggered jobs group by event type
|
||||
func (d *manager) ListJobsGroupByEventType(ctx context.Context, policyID int64) ([]*model.Job, error) {
|
||||
return d.dao.GetLastTriggerJobsGroupByEventType(ctx, policyID)
|
||||
}
|
||||
|
@ -1,55 +0,0 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao/notification"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job"
|
||||
)
|
||||
|
||||
// DefaultManager ..
|
||||
type DefaultManager struct {
|
||||
}
|
||||
|
||||
// NewDefaultManager ...
|
||||
func NewDefaultManager() job.Manager {
|
||||
return &DefaultManager{}
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (d *DefaultManager) Create(job *models.NotificationJob) (int64, error) {
|
||||
return notification.AddNotificationJob(job)
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (d *DefaultManager) List(query ...*models.NotificationJobQuery) (int64, []*models.NotificationJob, error) {
|
||||
total, err := notification.GetTotalCountOfNotificationJobs(query...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
executions, err := notification.GetNotificationJobs(query...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return total, executions, nil
|
||||
}
|
||||
|
||||
// Update ...
|
||||
func (d *DefaultManager) Update(job *models.NotificationJob, props ...string) error {
|
||||
n, err := notification.UpdateNotificationJob(job, props...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return fmt.Errorf("execution %d not found", job.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListJobsGroupByEventType lists last triggered jobs group by event type
|
||||
func (d *DefaultManager) ListJobsGroupByEventType(policyID int64) ([]*models.NotificationJob, error) {
|
||||
return notification.GetLastTriggerJobsGroupByEventType(policyID)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewDefaultManger(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want *DefaultManager
|
||||
}{
|
||||
{want: &DefaultManager{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NewDefaultManager(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewDefaultManager() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
81
src/pkg/notification/job/manager_test.go
Normal file
81
src/pkg/notification/job/manager_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job/model"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"github.com/goharbor/harbor/src/testing/pkg/notification/job/dao"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type managerTestSuite struct {
|
||||
suite.Suite
|
||||
mgr *manager
|
||||
dao *dao.DAO
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) SetupTest() {
|
||||
m.dao = &dao.DAO{}
|
||||
m.mgr = &manager{
|
||||
dao: m.dao,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestCreate() {
|
||||
m.dao.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
_, err := m.mgr.Create(context.Background(), &model.Job{})
|
||||
m.Nil(err)
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestUpdate() {
|
||||
m.dao.On("Update", mock.Anything, mock.Anything).Return(nil)
|
||||
err := m.mgr.Update(context.Background(), &model.Job{})
|
||||
m.Nil(err)
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestCount() {
|
||||
m.dao.On("Count", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
n, err := m.mgr.Count(context.Background(), nil)
|
||||
m.Nil(err)
|
||||
m.Equal(int64(1), n)
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestList() {
|
||||
m.dao.On("List", mock.Anything, mock.Anything).Return([]*model.Job{
|
||||
{
|
||||
ID: 1,
|
||||
EventType: "test_job",
|
||||
},
|
||||
}, nil)
|
||||
rpers, err := m.mgr.List(context.Background(), nil)
|
||||
m.Nil(err)
|
||||
m.Equal(1, len(rpers))
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestListJobsGroupByEventType() {
|
||||
m.dao.On("GetLastTriggerJobsGroupByEventType", mock.Anything, mock.Anything).Return([]*model.Job{
|
||||
{
|
||||
ID: 1,
|
||||
EventType: "test_job",
|
||||
PolicyID: 1,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
EventType: "test_job",
|
||||
PolicyID: 1,
|
||||
},
|
||||
}, nil)
|
||||
rpers, err := m.mgr.ListJobsGroupByEventType(context.Background(), 1)
|
||||
m.Nil(err)
|
||||
m.Equal(2, len(rpers))
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
suite.Run(t, &managerTestSuite{})
|
||||
}
|
28
src/pkg/notification/job/model/model.go
Normal file
28
src/pkg/notification/job/model/model.go
Normal file
@ -0,0 +1,28 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(&Job{})
|
||||
}
|
||||
|
||||
// Job is the model for a notification job
|
||||
type Job struct {
|
||||
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||
PolicyID int64 `orm:"column(policy_id)" json:"policy_id"`
|
||||
EventType string `orm:"column(event_type)" json:"event_type"`
|
||||
NotifyType string `orm:"column(notify_type)" json:"notify_type"`
|
||||
Status string `orm:"column(status)" json:"status"`
|
||||
JobDetail string `orm:"column(job_detail)" json:"job_detail"`
|
||||
UUID string `orm:"column(job_uuid)" json:"-"`
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time" sort:"default:desc"`
|
||||
}
|
||||
|
||||
// TableName set table name for ORM.
|
||||
func (j *Job) TableName() string {
|
||||
return "notification_job"
|
||||
}
|
@ -3,15 +3,14 @@ package notification
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
notifier_model "github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/hook"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job"
|
||||
jobMgr "github.com/goharbor/harbor/src/pkg/notification/job/manager"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy/manager"
|
||||
n_event "github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -34,20 +33,40 @@ var (
|
||||
// Init ...
|
||||
func Init() {
|
||||
// init notification policy manager
|
||||
PolicyMgr = manager.NewDefaultManger()
|
||||
PolicyMgr = policy.Mgr
|
||||
// init hook manager
|
||||
HookManager = hook.NewHookManager()
|
||||
// init notification job manager
|
||||
JobMgr = jobMgr.NewDefaultManager()
|
||||
JobMgr = job.Mgr
|
||||
|
||||
SupportedNotifyTypes = make(map[string]struct{})
|
||||
|
||||
initSupportedNotifyType(model.NotifyTypeHTTP, model.NotifyTypeSlack)
|
||||
initSupportedNotifyType()
|
||||
|
||||
log.Info("notification initialization completed")
|
||||
}
|
||||
|
||||
func initSupportedNotifyType(notifyTypes ...string) {
|
||||
func initSupportedNotifyType() {
|
||||
SupportedEventTypes = make(map[string]struct{}, 0)
|
||||
SupportedNotifyTypes = make(map[string]struct{}, 0)
|
||||
|
||||
eventTypes := []string{
|
||||
event.TopicPushArtifact,
|
||||
event.TopicPullArtifact,
|
||||
event.TopicDeleteArtifact,
|
||||
event.TopicUploadChart,
|
||||
event.TopicDeleteChart,
|
||||
event.TopicDownloadChart,
|
||||
event.TopicQuotaExceed,
|
||||
event.TopicQuotaWarning,
|
||||
event.TopicScanningFailed,
|
||||
event.TopicScanningCompleted,
|
||||
event.TopicReplication,
|
||||
event.TopicTagRetention,
|
||||
}
|
||||
for _, eventType := range eventTypes {
|
||||
SupportedEventTypes[eventType] = struct{}{}
|
||||
}
|
||||
|
||||
notifyTypes := []string{notifier_model.NotifyTypeHTTP, notifier_model.NotifyTypeSlack}
|
||||
for _, notifyType := range notifyTypes {
|
||||
SupportedNotifyTypes[notifyType] = struct{}{}
|
||||
}
|
||||
|
141
src/pkg/notification/policy/dao/dao.go
Normal file
141
src/pkg/notification/policy/dao/dao.go
Normal file
@ -0,0 +1,141 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
)
|
||||
|
||||
// DAO defines the interface to access the notification policy data model
|
||||
type DAO interface {
|
||||
// Create ...
|
||||
Create(ctx context.Context, n *model.Policy) (int64, error)
|
||||
|
||||
// Update ...
|
||||
Update(ctx context.Context, n *model.Policy) error
|
||||
|
||||
// Get ...
|
||||
Get(ctx context.Context, id int64) (*model.Policy, error)
|
||||
|
||||
// Count returns the total count of robots according to the query
|
||||
Count(ctx context.Context, query *q.Query) (total int64, err error)
|
||||
|
||||
// List ...
|
||||
List(ctx context.Context, query *q.Query) ([]*model.Policy, error)
|
||||
|
||||
// Delete ...
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
// New creates a default implementation for Dao
|
||||
func New() DAO {
|
||||
return &dao{}
|
||||
}
|
||||
|
||||
type dao struct{}
|
||||
|
||||
// Get ...
|
||||
func (d *dao) Get(ctx context.Context, id int64) (*model.Policy, error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
j := &model.Policy{
|
||||
ID: id,
|
||||
}
|
||||
if err := ormer.Read(j); err != nil {
|
||||
if e := orm.AsNotFoundError(err, "notificationPolicy %d not found", id); e != nil {
|
||||
err = e
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (d *dao) Create(ctx context.Context, policy *model.Policy) (int64, error) {
|
||||
if policy == nil {
|
||||
return 0, errors.New("nil policy")
|
||||
}
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
id, err := ormer.Insert(policy)
|
||||
if err != nil {
|
||||
if e := orm.AsConflictError(err, "notification policy named %s already exists", policy.Name); e != nil {
|
||||
err = e
|
||||
return id, err
|
||||
}
|
||||
err = fmt.Errorf("failed to create the notification policy: %v", err)
|
||||
return id, err
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
// Update ...
|
||||
func (d *dao) Update(ctx context.Context, policy *model.Policy) error {
|
||||
if policy == nil {
|
||||
return errors.New("nil policy")
|
||||
}
|
||||
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := ormer.Update(policy)
|
||||
if n == 0 {
|
||||
if e := orm.AsConflictError(err, "notification policy named %s already exists", policy.Name); e != nil {
|
||||
err = e
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count ...
|
||||
func (d *dao) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
qs, err := orm.QuerySetterForCount(ctx, &model.Policy{}, query)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return qs.Count()
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (d *dao) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) {
|
||||
policies := []*model.Policy{}
|
||||
|
||||
qs, err := orm.QuerySetter(ctx, &model.Policy{}, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = qs.All(&policies); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// Delete delete notification policy by id
|
||||
func (d *dao) Delete(ctx context.Context, id int64) error {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := ormer.Delete(&model.Policy{
|
||||
ID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return errors.NotFoundError(nil).WithMessage("notificationPolicy %d not found", id)
|
||||
}
|
||||
return nil
|
||||
}
|
162
src/pkg/notification/policy/dao/dao_test.go
Normal file
162
src/pkg/notification/policy/dao/dao_test.go
Normal file
@ -0,0 +1,162 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
testPly1 = &model.Policy{
|
||||
Name: "webhook test policy1",
|
||||
Description: "webhook test policy1 description",
|
||||
ProjectID: 111,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
testPly2 = &model.Policy{
|
||||
Name: "webhook test policy2",
|
||||
Description: "webhook test policy2 description",
|
||||
ProjectID: 222,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
testPly3 = &model.Policy{
|
||||
Name: "webhook test policy3",
|
||||
Description: "webhook test policy3 description",
|
||||
ProjectID: 333,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
type DaoTestSuite struct {
|
||||
htesting.Suite
|
||||
dao DAO
|
||||
|
||||
jobID1 int64
|
||||
jobID2 int64
|
||||
jobID3 int64
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) SetupSuite() {
|
||||
suite.Suite.SetupSuite()
|
||||
suite.dao = New()
|
||||
suite.Suite.ClearTables = []string{"notification_policy"}
|
||||
suite.policies()
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) policies() {
|
||||
var err error
|
||||
suite.jobID1, err = suite.dao.Create(orm.Context(), testPly1)
|
||||
suite.Nil(err)
|
||||
|
||||
suite.jobID2, err = suite.dao.Create(orm.Context(), testPly2)
|
||||
suite.Nil(err)
|
||||
|
||||
suite.jobID3, err = suite.dao.Create(orm.Context(), testPly3)
|
||||
suite.Nil(err)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestCreate() {
|
||||
_, err := suite.dao.Create(orm.Context(), nil)
|
||||
suite.NotNil(err)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestDelete() {
|
||||
err := suite.dao.Delete(orm.Context(), 1234)
|
||||
suite.Require().NotNil(err)
|
||||
suite.True(errors.IsErr(err, errors.NotFoundCode))
|
||||
|
||||
err = suite.dao.Delete(orm.Context(), suite.jobID2)
|
||||
suite.Nil(err)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestList() {
|
||||
jobs, err := suite.dao.List(orm.Context(), &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ProjectID": 333,
|
||||
},
|
||||
})
|
||||
suite.Require().Nil(err)
|
||||
suite.Equal(len(jobs), 1)
|
||||
suite.Equal(suite.jobID3, jobs[0].ID)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestGet() {
|
||||
_, err := suite.dao.Get(orm.Context(), 1234)
|
||||
suite.Require().NotNil(err)
|
||||
suite.True(errors.IsErr(err, errors.NotFoundCode))
|
||||
|
||||
id, err := suite.dao.Create(orm.Context(), &model.Policy{
|
||||
Name: "webhook test policy4",
|
||||
Description: "webhook test policy4 description",
|
||||
ProjectID: 444,
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]",
|
||||
Creator: "no one",
|
||||
CreationTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Enabled: true,
|
||||
})
|
||||
suite.Nil(err)
|
||||
|
||||
r, err := suite.dao.Get(orm.Context(), id)
|
||||
suite.Nil(err)
|
||||
suite.Equal("webhook test policy4", r.Name)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestUpdate() {
|
||||
j := &model.Policy{
|
||||
ID: suite.jobID1,
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
err := suite.dao.Update(orm.Context(), j)
|
||||
suite.Nil(err)
|
||||
|
||||
r1, err := suite.dao.Get(orm.Context(), j.ID)
|
||||
suite.False(r1.Enabled)
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestCount() {
|
||||
// nil query
|
||||
total, err := suite.dao.Count(orm.Context(), nil)
|
||||
suite.Nil(err)
|
||||
suite.True(total > 0)
|
||||
|
||||
total, err = suite.dao.Count(orm.Context(), &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ProjectID": 111,
|
||||
},
|
||||
})
|
||||
suite.Nil(err)
|
||||
suite.Equal(int64(1), total)
|
||||
}
|
||||
|
||||
func TestDaoTestSuite(t *testing.T) {
|
||||
suite.Run(t, &DaoTestSuite{})
|
||||
}
|
@ -1,25 +1,197 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"context"
|
||||
"fmt"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
notifier_model "github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// Mgr is a global variable for the default notification policies
|
||||
Mgr = NewManager()
|
||||
)
|
||||
|
||||
// Manager manages the notification policies
|
||||
type Manager interface {
|
||||
// Create new policy
|
||||
Create(*models.NotificationPolicy) (int64, error)
|
||||
Create(ctx context.Context, policy *model.Policy) (int64, error)
|
||||
// List the policies, returns the policy list and error
|
||||
List(int64) ([]*models.NotificationPolicy, error)
|
||||
List(ctx context.Context, query *q.Query) ([]*model.Policy, error)
|
||||
// Count the policies, returns the policy count and error
|
||||
Count(ctx context.Context, query *q.Query) (int64, error)
|
||||
// Get policy with specified ID
|
||||
Get(int64) (*models.NotificationPolicy, error)
|
||||
Get(ctx context.Context, id int64) (*model.Policy, error)
|
||||
// GetByNameAndProjectID get policy by the name and projectID
|
||||
GetByNameAndProjectID(string, int64) (*models.NotificationPolicy, error)
|
||||
GetByNameAndProjectID(ctx context.Context, name string, projectID int64) (*model.Policy, error)
|
||||
// Update the specified policy
|
||||
Update(*models.NotificationPolicy) error
|
||||
Update(ctx context.Context, policy *model.Policy) error
|
||||
// Delete the specified policy
|
||||
Delete(int64) error
|
||||
Delete(ctx context.Context, policyID int64) error
|
||||
// Test the specified policy
|
||||
Test(*models.NotificationPolicy) error
|
||||
Test(policy *model.Policy) error
|
||||
// GetRelatedPolices get event type related policies in project
|
||||
GetRelatedPolices(int64, string) ([]*models.NotificationPolicy, error)
|
||||
GetRelatedPolices(ctx context.Context, projectID int64, eventType string) ([]*model.Policy, error)
|
||||
}
|
||||
|
||||
var _ Manager = &manager{}
|
||||
|
||||
type manager struct {
|
||||
dao dao.DAO
|
||||
}
|
||||
|
||||
// NewManager ...
|
||||
func NewManager() Manager {
|
||||
return &manager{
|
||||
dao: dao.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Create notification policy
|
||||
func (m *manager) Create(ctx context.Context, policy *model.Policy) (int64, error) {
|
||||
t := time.Now()
|
||||
policy.CreationTime = t
|
||||
policy.UpdateTime = t
|
||||
|
||||
err := policy.ConvertToDBModel()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return m.dao.Create(ctx, policy)
|
||||
}
|
||||
|
||||
// List the notification policies, returns the policy list and error
|
||||
func (m *manager) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) {
|
||||
policies := []*model.Policy{}
|
||||
persisPolicies, err := m.dao.List(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, policy := range persisPolicies {
|
||||
err := policy.ConvertFromDBModel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policies = append(policies, policy)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// Count the notification policies, returns the count and error
|
||||
func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
return m.dao.Count(ctx, query)
|
||||
}
|
||||
|
||||
// Get notification policy with specified ID
|
||||
func (m *manager) Get(ctx context.Context, id int64) (*model.Policy, error) {
|
||||
policy, err := m.dao.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if policy == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if err := policy.ConvertFromDBModel(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policy, err
|
||||
}
|
||||
|
||||
// GetByNameAndProjectID notification policy by the name and projectID
|
||||
func (m *manager) GetByNameAndProjectID(ctx context.Context, name string, projectID int64) (*model.Policy, error) {
|
||||
query := q.New(q.KeyWords{"name": name, "project_id": projectID})
|
||||
policies, err := m.dao.List(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(policies) == 0 {
|
||||
return nil, errors.New(nil).WithCode(errors.NotFoundCode).WithMessage("no notification policy found")
|
||||
}
|
||||
policy := policies[0]
|
||||
if err := policy.ConvertFromDBModel(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policy, err
|
||||
}
|
||||
|
||||
// Update the specified notification policy
|
||||
func (m *manager) Update(ctx context.Context, policy *model.Policy) error {
|
||||
policy.UpdateTime = time.Now()
|
||||
err := policy.ConvertToDBModel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.dao.Update(ctx, policy)
|
||||
}
|
||||
|
||||
// Delete the specified notification policy
|
||||
func (m *manager) Delete(ctx context.Context, policyID int64) error {
|
||||
return m.dao.Delete(ctx, policyID)
|
||||
}
|
||||
|
||||
// Test the specified notification policy, just test for network connection without request body
|
||||
func (m *manager) Test(policy *model.Policy) error {
|
||||
for _, target := range policy.Targets {
|
||||
switch target.Type {
|
||||
case notifier_model.NotifyTypeHTTP, notifier_model.NotifyTypeSlack:
|
||||
return m.policyHTTPTest(target.Address, target.SkipCertVerify)
|
||||
default:
|
||||
return fmt.Errorf("invalid policy target type: %s", target.Type)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) policyHTTPTest(address string, skipCertVerify bool) error {
|
||||
req, err := http.NewRequest(http.MethodPost, address, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
client := http.Client{
|
||||
Transport: commonhttp.GetHTTPTransportByInsecure(skipCertVerify),
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
log.Debugf("policy test success with address %s, skip cert verify :%v", address, skipCertVerify)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRelatedPolices get policies including event type in project
|
||||
func (m *manager) GetRelatedPolices(ctx context.Context, projectID int64, eventType string) ([]*model.Policy, error) {
|
||||
policies, err := m.List(ctx, q.New(q.KeyWords{"project_id": projectID}))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get notification policies with projectID %d: %v", projectID, err)
|
||||
}
|
||||
|
||||
var result []*model.Policy
|
||||
|
||||
for _, ply := range policies {
|
||||
if !ply.Enabled {
|
||||
continue
|
||||
}
|
||||
for _, t := range ply.EventTypes {
|
||||
if t != eventType {
|
||||
continue
|
||||
}
|
||||
result = append(result, ply)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
@ -1,149 +0,0 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao/notification"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
)
|
||||
|
||||
// DefaultManager ...
|
||||
type DefaultManager struct {
|
||||
}
|
||||
|
||||
// NewDefaultManger ...
|
||||
func NewDefaultManger() *DefaultManager {
|
||||
return &DefaultManager{}
|
||||
}
|
||||
|
||||
// Create notification policy
|
||||
func (m *DefaultManager) Create(policy *models.NotificationPolicy) (int64, error) {
|
||||
t := time.Now()
|
||||
policy.CreationTime = t
|
||||
policy.UpdateTime = t
|
||||
|
||||
err := policy.ConvertToDBModel()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return notification.AddNotificationPolicy(policy)
|
||||
}
|
||||
|
||||
// List the notification policies, returns the policy list and error
|
||||
func (m *DefaultManager) List(projectID int64) ([]*models.NotificationPolicy, error) {
|
||||
policies := []*models.NotificationPolicy{}
|
||||
persisPolicies, err := notification.GetNotificationPolicies(projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, policy := range persisPolicies {
|
||||
err := policy.ConvertFromDBModel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policies = append(policies, policy)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// Get notification policy with specified ID
|
||||
func (m *DefaultManager) Get(id int64) (*models.NotificationPolicy, error) {
|
||||
policy, err := notification.GetNotificationPolicy(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if policy == nil {
|
||||
return nil, nil
|
||||
}
|
||||
err = policy.ConvertFromDBModel()
|
||||
return policy, err
|
||||
}
|
||||
|
||||
// GetByNameAndProjectID notification policy by the name and projectID
|
||||
func (m *DefaultManager) GetByNameAndProjectID(name string, projectID int64) (*models.NotificationPolicy, error) {
|
||||
policy, err := notification.GetNotificationPolicyByName(name, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = policy.ConvertFromDBModel()
|
||||
return policy, err
|
||||
}
|
||||
|
||||
// Update the specified notification policy
|
||||
func (m *DefaultManager) Update(policy *models.NotificationPolicy) error {
|
||||
policy.UpdateTime = time.Now()
|
||||
err := policy.ConvertToDBModel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return notification.UpdateNotificationPolicy(policy)
|
||||
}
|
||||
|
||||
// Delete the specified notification policy
|
||||
func (m *DefaultManager) Delete(policyID int64) error {
|
||||
return notification.DeleteNotificationPolicy(policyID)
|
||||
}
|
||||
|
||||
// Test the specified notification policy, just test for network connection without request body
|
||||
func (m *DefaultManager) Test(policy *models.NotificationPolicy) error {
|
||||
for _, target := range policy.Targets {
|
||||
switch target.Type {
|
||||
case model.NotifyTypeHTTP, model.NotifyTypeSlack:
|
||||
return m.policyHTTPTest(target.Address, target.SkipCertVerify)
|
||||
default:
|
||||
return fmt.Errorf("invalid policy target type: %s", target.Type)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *DefaultManager) policyHTTPTest(address string, skipCertVerify bool) error {
|
||||
req, err := http.NewRequest(http.MethodPost, address, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
client := http.Client{
|
||||
Transport: commonhttp.GetHTTPTransportByInsecure(skipCertVerify),
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
log.Debugf("policy test success with address %s, skip cert verify :%v", address, skipCertVerify)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRelatedPolices get policies including event type in project
|
||||
func (m *DefaultManager) GetRelatedPolices(projectID int64, eventType string) ([]*models.NotificationPolicy, error) {
|
||||
policies, err := m.List(projectID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get notification policies with projectID %d: %v", projectID, err)
|
||||
}
|
||||
|
||||
var result []*models.NotificationPolicy
|
||||
|
||||
for _, ply := range policies {
|
||||
if !ply.Enabled {
|
||||
continue
|
||||
}
|
||||
for _, t := range ply.EventTypes {
|
||||
if t != eventType {
|
||||
continue
|
||||
}
|
||||
result = append(result, ply)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewDefaultManger(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want *DefaultManager
|
||||
}{
|
||||
{want: &DefaultManager{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NewDefaultManger(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewDefaultManger() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
116
src/pkg/notification/policy/manager_test.go
Normal file
116
src/pkg/notification/policy/manager_test.go
Normal file
@ -0,0 +1,116 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"github.com/goharbor/harbor/src/testing/pkg/notification/policy/dao"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type managerTestSuite struct {
|
||||
suite.Suite
|
||||
mgr *manager
|
||||
dao *dao.DAO
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) SetupTest() {
|
||||
m.dao = &dao.DAO{}
|
||||
m.mgr = &manager{
|
||||
dao: m.dao,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestCreate() {
|
||||
m.dao.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
_, err := m.mgr.Create(context.Background(), &model.Policy{})
|
||||
m.Nil(err)
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestDelete() {
|
||||
m.dao.On("Delete", mock.Anything, mock.Anything).Return(nil)
|
||||
err := m.mgr.Delete(context.Background(), 1)
|
||||
m.Nil(err)
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestUpdate() {
|
||||
m.dao.On("Update", mock.Anything, mock.Anything).Return(nil)
|
||||
err := m.mgr.Update(context.Background(), &model.Policy{})
|
||||
m.Nil(err)
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestGet() {
|
||||
m.dao.On("Get", mock.Anything, mock.Anything).Return(&model.Policy{
|
||||
Name: "test_policy",
|
||||
}, nil)
|
||||
policy, err := m.mgr.Get(context.Background(), 1)
|
||||
m.Nil(err)
|
||||
m.Equal("test_policy", policy.Name)
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestList() {
|
||||
m.dao.On("List", mock.Anything, mock.Anything).Return([]*model.Policy{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "policy",
|
||||
},
|
||||
}, nil)
|
||||
rpers, err := m.mgr.List(context.Background(), nil)
|
||||
m.Nil(err)
|
||||
m.Equal(1, len(rpers))
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestGetByNameAndProjectID404() {
|
||||
m.dao.On("List", mock.Anything, mock.Anything).Return([]*model.Policy{}, nil)
|
||||
_, err := m.mgr.GetByNameAndProjectID(context.Background(), "test_policy", 1)
|
||||
m.NotNil(err)
|
||||
m.True(errors.IsNotFoundErr(err))
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestGetByNameAndProjectID() {
|
||||
m.dao.On("List", mock.Anything, mock.Anything).Return([]*model.Policy{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "test_policy",
|
||||
ProjectID: 1,
|
||||
},
|
||||
}, nil)
|
||||
policy, err := m.mgr.GetByNameAndProjectID(context.Background(), "test_policy", 1)
|
||||
m.Nil(err)
|
||||
m.Equal("test_policy", policy.Name)
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestGetRelatedPolices() {
|
||||
m.dao.On("List", mock.Anything, mock.Anything).Return([]*model.Policy{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "policy",
|
||||
ProjectID: 1,
|
||||
Enabled: true,
|
||||
EventTypesDB: "[\"PULL_IMAGE\",\"PUSH_CHART\"]",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Name: "policy",
|
||||
ProjectID: 1,
|
||||
Enabled: true,
|
||||
EventTypesDB: "[\"PULL_IMAGE\",\"PUSH_CHART\"]",
|
||||
},
|
||||
}, nil)
|
||||
rpers, err := m.mgr.GetRelatedPolices(context.Background(), 1, "PULL_IMAGE")
|
||||
m.Nil(err)
|
||||
m.Equal(2, len(rpers))
|
||||
m.dao.AssertExpectations(m.T())
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
suite.Run(t, &managerTestSuite{})
|
||||
}
|
52
src/common/models/hook_notification.go → src/pkg/notification/policy/model/model.go
Executable file → Normal file
52
src/common/models/hook_notification.go → src/pkg/notification/policy/model/model.go
Executable file → Normal file
@ -1,19 +1,17 @@
|
||||
package models
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/astaxie/beego/orm"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// NotificationPolicyTable is table name for notification policies
|
||||
NotificationPolicyTable = "notification_policy"
|
||||
// NotificationJobTable is table name for notification job
|
||||
NotificationJobTable = "notification_job"
|
||||
)
|
||||
func init() {
|
||||
orm.RegisterModel(&Policy{})
|
||||
}
|
||||
|
||||
// NotificationPolicy is the model for a notification policy.
|
||||
type NotificationPolicy struct {
|
||||
// Policy ...
|
||||
type Policy struct {
|
||||
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
Description string `orm:"column(description)" json:"description"`
|
||||
@ -23,18 +21,18 @@ type NotificationPolicy struct {
|
||||
EventTypesDB string `orm:"column(event_types)" json:"-"`
|
||||
EventTypes []string `orm:"-" json:"event_types"`
|
||||
Creator string `orm:"column(creator)" json:"creator"`
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time" sort:"default:desc"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now_add" json:"update_time"`
|
||||
Enabled bool `orm:"column(enabled)" json:"enabled"`
|
||||
}
|
||||
|
||||
// TableName set table name for ORM.
|
||||
func (w *NotificationPolicy) TableName() string {
|
||||
return NotificationPolicyTable
|
||||
func (w *Policy) TableName() string {
|
||||
return "notification_policy"
|
||||
}
|
||||
|
||||
// ConvertToDBModel convert struct data in notification policy to DB model data
|
||||
func (w *NotificationPolicy) ConvertToDBModel() error {
|
||||
func (w *Policy) ConvertToDBModel() error {
|
||||
if len(w.Targets) != 0 {
|
||||
targets, err := json.Marshal(w.Targets)
|
||||
if err != nil {
|
||||
@ -54,7 +52,7 @@ func (w *NotificationPolicy) ConvertToDBModel() error {
|
||||
}
|
||||
|
||||
// ConvertFromDBModel convert from DB model data to struct data
|
||||
func (w *NotificationPolicy) ConvertFromDBModel() error {
|
||||
func (w *Policy) ConvertFromDBModel() error {
|
||||
targets := []EventTarget{}
|
||||
if len(w.TargetsDB) != 0 {
|
||||
err := json.Unmarshal([]byte(w.TargetsDB), &targets)
|
||||
@ -76,32 +74,6 @@ func (w *NotificationPolicy) ConvertFromDBModel() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotificationJob is the model for a notification job
|
||||
type NotificationJob struct {
|
||||
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||
PolicyID int64 `orm:"column(policy_id)" json:"policy_id"`
|
||||
EventType string `orm:"column(event_type)" json:"event_type"`
|
||||
NotifyType string `orm:"column(notify_type)" json:"notify_type"`
|
||||
Status string `orm:"column(status)" json:"status"`
|
||||
JobDetail string `orm:"column(job_detail)" json:"job_detail"`
|
||||
UUID string `orm:"column(job_uuid)" json:"-"`
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||
}
|
||||
|
||||
// TableName set table name for ORM.
|
||||
func (w *NotificationJob) TableName() string {
|
||||
return NotificationJobTable
|
||||
}
|
||||
|
||||
// NotificationJobQuery holds query conditions for notification job
|
||||
type NotificationJobQuery struct {
|
||||
PolicyID int64
|
||||
Statuses []string
|
||||
EventTypes []string
|
||||
Pagination
|
||||
}
|
||||
|
||||
// EventTarget defines the structure of target a notification send to
|
||||
type EventTarget struct {
|
||||
Type string `json:"type"`
|
@ -1,40 +1,39 @@
|
||||
package models
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNotificationPolicy_ConvertFromDBModel(t *testing.T) {
|
||||
func TestPolicy_ConvertFromDBModel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
policy *NotificationPolicy
|
||||
want *NotificationPolicy
|
||||
policy *Policy
|
||||
want *Policy
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ConvertFromDBModel want error 1",
|
||||
policy: &NotificationPolicy{
|
||||
policy: &Policy{
|
||||
TargetsDB: "[{{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\"}]",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ConvertFromDBModel want error 2",
|
||||
policy: &NotificationPolicy{
|
||||
policy: &Policy{
|
||||
EventTypesDB: "[{\"pushImage\",\"pullImage\",\"deleteImage\"]",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ConvertFromDBModel 1",
|
||||
policy: &NotificationPolicy{
|
||||
policy: &Policy{
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\"}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\"]",
|
||||
},
|
||||
want: &NotificationPolicy{
|
||||
want: &Policy{
|
||||
Targets: []EventTarget{
|
||||
{
|
||||
Type: "http",
|
||||
@ -60,16 +59,16 @@ func TestNotificationPolicy_ConvertFromDBModel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationPolicy_ConvertToDBModel(t *testing.T) {
|
||||
func TestPolicy_ConvertToDBModel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
policy *NotificationPolicy
|
||||
want *NotificationPolicy
|
||||
policy *Policy
|
||||
want *Policy
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ConvertToDBModel 1",
|
||||
policy: &NotificationPolicy{
|
||||
policy: &Policy{
|
||||
Targets: []EventTarget{
|
||||
{
|
||||
Type: "http",
|
||||
@ -79,7 +78,7 @@ func TestNotificationPolicy_ConvertToDBModel(t *testing.T) {
|
||||
},
|
||||
EventTypes: []string{"pushImage", "pullImage", "deleteImage"},
|
||||
},
|
||||
want: &NotificationPolicy{
|
||||
want: &Policy{
|
||||
TargetsDB: "[{\"type\":\"http\",\"address\":\"http://127.0.0.1\",\"skip_cert_verify\":false}]",
|
||||
EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\"]",
|
||||
},
|
||||
@ -99,16 +98,3 @@ func TestNotificationPolicy_ConvertToDBModel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationJob_TableName(t *testing.T) {
|
||||
job := &NotificationJob{}
|
||||
got := job.TableName()
|
||||
assert.Equal(t, NotificationJobTable, got)
|
||||
}
|
||||
|
||||
func TestNotificationPolicy_TableName(t *testing.T) {
|
||||
policy := &NotificationPolicy{}
|
||||
got := policy.TableName()
|
||||
assert.Equal(t, NotificationPolicyTable, got)
|
||||
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
)
|
||||
@ -40,7 +40,7 @@ type Metadata interface {
|
||||
type HookMetaData struct {
|
||||
PolicyID int64
|
||||
EventType string
|
||||
Target *models.EventTarget
|
||||
Target *policy_model.EventTarget
|
||||
Payload *model.Payload
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
notifierModel "github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -25,7 +25,7 @@ func TestHookEvent_Build(t *testing.T) {
|
||||
hookMetadata: &HookMetaData{
|
||||
PolicyID: 1,
|
||||
EventType: "pushImage",
|
||||
Target: &models.EventTarget{
|
||||
Target: &policy_model.EventTarget{
|
||||
Type: "http",
|
||||
Address: "http://127.0.0.1",
|
||||
},
|
||||
|
@ -1,10 +1,10 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
@ -21,7 +21,7 @@ func (h *HTTPHandler) Name() string {
|
||||
}
|
||||
|
||||
// Handle handles http event
|
||||
func (h *HTTPHandler) Handle(value interface{}) error {
|
||||
func (h *HTTPHandler) Handle(ctx context.Context, value interface{}) error {
|
||||
if value == nil {
|
||||
return errors.New("HTTPHandler cannot handle nil value")
|
||||
}
|
||||
@ -30,8 +30,7 @@ func (h *HTTPHandler) Handle(value interface{}) error {
|
||||
if !ok || event == nil {
|
||||
return errors.New("invalid notification http event")
|
||||
}
|
||||
|
||||
return h.process(event)
|
||||
return h.process(ctx, event)
|
||||
}
|
||||
|
||||
// IsStateful ...
|
||||
@ -39,7 +38,7 @@ func (h *HTTPHandler) IsStateful() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) process(event *model.HookEvent) error {
|
||||
func (h *HTTPHandler) process(ctx context.Context, event *model.HookEvent) error {
|
||||
j := &models.JobData{
|
||||
Metadata: &models.JobMetadata{
|
||||
JobKind: job.KindGeneric,
|
||||
@ -60,5 +59,5 @@ func (h *HTTPHandler) process(event *model.HookEvent) error {
|
||||
"auth_header": event.Target.AuthHeader,
|
||||
"skip_cert_verify": event.Target.SkipCertVerify,
|
||||
}
|
||||
return notification.HookManager.StartHook(event, j)
|
||||
return notification.HookManager.StartHook(ctx, event, j)
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
cModels "github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -17,7 +18,7 @@ import (
|
||||
type fakedHookManager struct {
|
||||
}
|
||||
|
||||
func (f *fakedHookManager) StartHook(event *model.HookEvent, job *models.JobData) error {
|
||||
func (f *fakedHookManager) StartHook(ctx context.Context, event *model.HookEvent, job *models.JobData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -66,7 +67,7 @@ func TestHTTPHandler_Handle(t *testing.T) {
|
||||
Data: &model.HookEvent{
|
||||
PolicyID: 1,
|
||||
EventType: "pushImage",
|
||||
Target: &cModels.EventTarget{
|
||||
Target: &policy_model.EventTarget{
|
||||
Type: "http",
|
||||
Address: "http://127.0.0.1:8080",
|
||||
},
|
||||
@ -82,7 +83,7 @@ func TestHTTPHandler_Handle(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := handler.Handle(tt.args.event.Data)
|
||||
err := handler.Handle(context.TODO(), tt.args.event.Data)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
|
@ -1,11 +1,11 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
@ -70,7 +70,7 @@ func (s *SlackHandler) Name() string {
|
||||
}
|
||||
|
||||
// Handle handles event to slack
|
||||
func (s *SlackHandler) Handle(value interface{}) error {
|
||||
func (s *SlackHandler) Handle(ctx context.Context, value interface{}) error {
|
||||
if value == nil {
|
||||
return errors.New("SlackHandler cannot handle nil value")
|
||||
}
|
||||
@ -80,7 +80,7 @@ func (s *SlackHandler) Handle(value interface{}) error {
|
||||
return errors.New("invalid notification slack event")
|
||||
}
|
||||
|
||||
return s.process(event)
|
||||
return s.process(ctx, event)
|
||||
}
|
||||
|
||||
// IsStateful ...
|
||||
@ -88,7 +88,7 @@ func (s *SlackHandler) IsStateful() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SlackHandler) process(event *model.HookEvent) error {
|
||||
func (s *SlackHandler) process(ctx context.Context, event *model.HookEvent) error {
|
||||
j := &models.JobData{
|
||||
Metadata: &models.JobMetadata{
|
||||
JobKind: job.KindGeneric,
|
||||
@ -108,7 +108,7 @@ func (s *SlackHandler) process(event *model.HookEvent) error {
|
||||
"address": event.Target.Address,
|
||||
"skip_cert_verify": event.Target.SkipCertVerify,
|
||||
}
|
||||
return notification.HookManager.StartHook(event, j)
|
||||
return notification.HookManager.StartHook(ctx, event, j)
|
||||
}
|
||||
|
||||
func (s *SlackHandler) convert(payLoad *model.Payload) (string, error) {
|
||||
|
@ -1,19 +1,21 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
cModels "github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSlackHandler_Handle(t *testing.T) {
|
||||
dao.PrepareTestForPostgresSQL()
|
||||
hookMgr := notification.HookManager
|
||||
defer func() {
|
||||
notification.HookManager = hookMgr
|
||||
@ -58,7 +60,7 @@ func TestSlackHandler_Handle(t *testing.T) {
|
||||
Data: &model.HookEvent{
|
||||
PolicyID: 1,
|
||||
EventType: "pushImage",
|
||||
Target: &cModels.EventTarget{
|
||||
Target: &policy_model.EventTarget{
|
||||
Type: "slack",
|
||||
Address: "http://127.0.0.1:8080",
|
||||
},
|
||||
@ -86,7 +88,7 @@ func TestSlackHandler_Handle(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := handler.Handle(tt.args.event.Data)
|
||||
err := handler.Handle(context.TODO(), tt.args.event.Data)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Error: %s", err)
|
||||
return
|
||||
|
@ -1,15 +1,15 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/controller/event/model"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
)
|
||||
|
||||
// HookEvent is hook related event data to publish
|
||||
type HookEvent struct {
|
||||
PolicyID int64
|
||||
EventType string
|
||||
Target *models.EventTarget
|
||||
Target *policy_model.EventTarget
|
||||
Payload *Payload
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package notifier
|
||||
|
||||
import "context"
|
||||
|
||||
// NotificationHandler defines what operations a notification handler
|
||||
// should have.
|
||||
type NotificationHandler interface {
|
||||
@ -8,7 +10,7 @@ type NotificationHandler interface {
|
||||
|
||||
// Handle the event when it coming.
|
||||
// value might be optional, it depends on usages.
|
||||
Handle(value interface{}) error
|
||||
Handle(ctx context.Context, value interface{}) error
|
||||
|
||||
// IsStateful returns whether the handler is stateful or not.
|
||||
// If handler is stateful, it will not be triggered in parallel.
|
||||
|
@ -3,6 +3,7 @@ package notifier
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -197,7 +198,7 @@ func (nw *NotificationWatcher) Notify(notification Notification) error {
|
||||
<-ch
|
||||
}
|
||||
}()
|
||||
if err := hd.Handle(notification.Value); err != nil {
|
||||
if err := hd.Handle(orm.Context(), notification.Value); err != nil {
|
||||
// Currently, we just log the error
|
||||
log.Errorf("Error occurred when triggering handler %s of topic %s: %s\n", reflect.TypeOf(hd).String(), notification.Topic, err.Error())
|
||||
} else {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@ -21,7 +23,7 @@ func (fsh *fakeStatefulHandler) IsStateful() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (fsh *fakeStatefulHandler) Handle(v interface{}) error {
|
||||
func (fsh *fakeStatefulHandler) Handle(ctx context.Context, v interface{}) error {
|
||||
increment := 0
|
||||
if v != nil && reflect.TypeOf(v).Kind() == reflect.Int {
|
||||
increment = v.(int)
|
||||
@ -40,7 +42,7 @@ func (fsh *fakeStatelessHandler) Name() string {
|
||||
return "fakeStateless"
|
||||
}
|
||||
|
||||
func (fsh *fakeStatelessHandler) Handle(v interface{}) error {
|
||||
func (fsh *fakeStatelessHandler) Handle(ctx context.Context, v interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -116,6 +118,7 @@ func TestSubscribeAndUnSubscribe(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPublish(t *testing.T) {
|
||||
dao.PrepareTestForPostgresSQL()
|
||||
count := len(notificationWatcher.handlers)
|
||||
err := Subscribe("topic1", &fakeStatefulHandler{0})
|
||||
if err != nil {
|
||||
@ -157,6 +160,7 @@ func TestPublish(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConcurrentPublish(t *testing.T) {
|
||||
dao.PrepareTestForPostgresSQL()
|
||||
count := len(notificationWatcher.handlers)
|
||||
err := Subscribe("topic1", &fakeStatefulHandler{0})
|
||||
if err != nil {
|
||||
|
@ -50,6 +50,8 @@ func New() http.Handler {
|
||||
GCAPI: newGCAPI(),
|
||||
QuotaAPI: newQuotaAPI(),
|
||||
RetentionAPI: newRetentionAPI(),
|
||||
WebhookAPI: newNotificationPolicyAPI(),
|
||||
WebhookjobAPI: newNotificationJobAPI(),
|
||||
ImmutableAPI: newImmutableAPI(),
|
||||
OidcAPI: newOIDCAPI(),
|
||||
SystemCVEAllowlistAPI: newSystemCVEAllowListAPI(),
|
||||
|
33
src/server/v2.0/handler/model/notification_job.go
Normal file
33
src/server/v2.0/handler/model/notification_job.go
Normal file
@ -0,0 +1,33 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
)
|
||||
|
||||
// NotificationJob ...
|
||||
type NotificationJob struct {
|
||||
*model.Job
|
||||
}
|
||||
|
||||
// ToSwagger ...
|
||||
func (n *NotificationJob) ToSwagger() *models.WebhookJob {
|
||||
return &models.WebhookJob{
|
||||
ID: n.ID,
|
||||
EventType: n.EventType,
|
||||
JobDetail: n.JobDetail,
|
||||
NotifyType: n.NotifyType,
|
||||
PolicyID: n.PolicyID,
|
||||
Status: n.Status,
|
||||
CreationTime: strfmt.DateTime(n.CreationTime),
|
||||
UpdateTime: strfmt.DateTime(n.UpdateTime),
|
||||
}
|
||||
}
|
||||
|
||||
// NewNotificationJob ...
|
||||
func NewNotificationJob(j *model.Job) *NotificationJob {
|
||||
return &NotificationJob{
|
||||
Job: j,
|
||||
}
|
||||
}
|
49
src/server/v2.0/handler/model/notification_policy.go
Normal file
49
src/server/v2.0/handler/model/notification_policy.go
Normal file
@ -0,0 +1,49 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
)
|
||||
|
||||
// NotifiactionPolicy ...
|
||||
type NotifiactionPolicy struct {
|
||||
*model.Policy
|
||||
}
|
||||
|
||||
// ToSwagger ...
|
||||
func (n *NotifiactionPolicy) ToSwagger() *models.WebhookPolicy {
|
||||
return &models.WebhookPolicy{
|
||||
ID: n.ID,
|
||||
CreationTime: strfmt.DateTime(n.CreationTime),
|
||||
UpdateTime: strfmt.DateTime(n.UpdateTime),
|
||||
Creator: n.Creator,
|
||||
Description: n.Description,
|
||||
Enabled: n.Enabled,
|
||||
EventTypes: n.EventTypes,
|
||||
Name: n.Name,
|
||||
ProjectID: n.ProjectID,
|
||||
Targets: n.ToTargets(),
|
||||
}
|
||||
}
|
||||
|
||||
// ToTargets ...
|
||||
func (n *NotifiactionPolicy) ToTargets() []*models.WebhookTargetObject {
|
||||
var results []*models.WebhookTargetObject
|
||||
for _, t := range n.Targets {
|
||||
results = append(results, &models.WebhookTargetObject{
|
||||
Type: t.Type,
|
||||
Address: t.Address,
|
||||
AuthHeader: t.AuthHeader,
|
||||
SkipCertVerify: t.SkipCertVerify,
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// NewNotifiactionPolicy ...
|
||||
func NewNotifiactionPolicy(p *model.Policy) *NotifiactionPolicy {
|
||||
return &NotifiactionPolicy{
|
||||
Policy: p,
|
||||
}
|
||||
}
|
67
src/server/v2.0/handler/notification_job.go
Normal file
67
src/server/v2.0/handler/notification_job.go
Normal file
@ -0,0 +1,67 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/restapi/operations/webhookjob"
|
||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/webhookjob"
|
||||
)
|
||||
|
||||
func newNotificationJobAPI() *notificationJobAPI {
|
||||
return ¬ificationJobAPI{
|
||||
webhookjobMgr: job.Mgr,
|
||||
webhookPolicyMgr: policy.Mgr,
|
||||
}
|
||||
}
|
||||
|
||||
type notificationJobAPI struct {
|
||||
BaseAPI
|
||||
webhookjobMgr job.Manager
|
||||
webhookPolicyMgr policy.Manager
|
||||
}
|
||||
|
||||
func (n *notificationJobAPI) ListWebhookJobs(ctx context.Context, params webhookjob.ListWebhookJobsParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := n.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionList, rbac.ResourceNotificationPolicy); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
policy, err := n.webhookPolicyMgr.Get(ctx, params.PolicyID)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
query, err := n.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
query.Keywords["PolicyID"] = policy.ID
|
||||
if len(params.Status) != 0 {
|
||||
query.Keywords["Status"] = params.Status
|
||||
}
|
||||
|
||||
total, err := n.webhookjobMgr.Count(ctx, query)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
jobs, err := n.webhookjobMgr.List(ctx, query)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
var results []*models.WebhookJob
|
||||
for _, j := range jobs {
|
||||
results = append(results, model.NewNotificationJob(j).ToSwagger())
|
||||
}
|
||||
|
||||
return operation.NewListWebhookJobsOK().
|
||||
WithXTotalCount(total).
|
||||
WithLink(n.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
||||
WithPayload(results)
|
||||
}
|
285
src/server/v2.0/handler/notification_policy.go
Normal file
285
src/server/v2.0/handler/notification_policy.go
Normal file
@ -0,0 +1,285 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"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/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/job"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy"
|
||||
policy_model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/restapi/operations/webhook"
|
||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/webhook"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newNotificationPolicyAPI() *notificationPolicyAPI {
|
||||
return ¬ificationPolicyAPI{
|
||||
webhookjobMgr: job.Mgr,
|
||||
webhookPolicyMgr: policy.Mgr,
|
||||
}
|
||||
}
|
||||
|
||||
type notificationPolicyAPI struct {
|
||||
BaseAPI
|
||||
webhookjobMgr job.Manager
|
||||
webhookPolicyMgr policy.Manager
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) ListWebhookPoliciesOfProject(ctx context.Context, params webhook.ListWebhookPoliciesOfProjectParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := n.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionList, rbac.ResourceNotificationPolicy); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
projectID, err := getProjectID(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
query, err := n.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
query.Keywords["ProjectID"] = projectID
|
||||
|
||||
total, err := n.webhookPolicyMgr.Count(ctx, query)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
policies, err := n.webhookPolicyMgr.List(ctx, query)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
var results []*models.WebhookPolicy
|
||||
for _, p := range policies {
|
||||
results = append(results, model.NewNotifiactionPolicy(p).ToSwagger())
|
||||
}
|
||||
|
||||
return operation.NewListWebhookPoliciesOfProjectOK().
|
||||
WithXTotalCount(total).
|
||||
WithLink(n.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
||||
WithPayload(results)
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) CreateWebhookPolicyOfProject(ctx context.Context, params webhook.CreateWebhookPolicyOfProjectParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := n.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionCreate, rbac.ResourceNotificationPolicy); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
policy := &policy_model.Policy{}
|
||||
lib.JSONCopy(policy, params.Policy)
|
||||
|
||||
if ok, err := n.validateEventTypes(policy); !ok {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
if ok, err := n.validateTargets(policy); !ok {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
projectID, err := getProjectID(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
policy.ProjectID = projectID
|
||||
id, err := n.webhookPolicyMgr.Create(ctx, policy)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
location := fmt.Sprintf("%s/%d", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), id)
|
||||
return operation.NewCreateWebhookPolicyOfProjectCreated().WithLocation(location)
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) UpdateWebhookPolicyOfProject(ctx context.Context, params webhook.UpdateWebhookPolicyOfProjectParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := n.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionUpdate, rbac.ResourceNotificationPolicy); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
policy := &policy_model.Policy{}
|
||||
lib.JSONCopy(policy, params.Policy)
|
||||
|
||||
if ok, err := n.validateEventTypes(policy); !ok {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
if ok, err := n.validateTargets(policy); !ok {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
projectID, err := getProjectID(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
policy.ProjectID = projectID
|
||||
if err := n.webhookPolicyMgr.Update(ctx, policy); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewUpdateWebhookPolicyOfProjectOK()
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) DeleteWebhookPolicyOfProject(ctx context.Context, params webhook.DeleteWebhookPolicyOfProjectParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := n.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionDelete, rbac.ResourceNotificationPolicy); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := n.webhookPolicyMgr.Delete(ctx, params.WebhookPolicyID); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewDeleteWebhookPolicyOfProjectOK()
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) GetWebhookPolicyOfProject(ctx context.Context, params webhook.GetWebhookPolicyOfProjectParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := n.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead, rbac.ResourceNotificationPolicy); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
policy, err := n.webhookPolicyMgr.Get(ctx, params.WebhookPolicyID)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewGetWebhookPolicyOfProjectOK().WithPayload(model.NewNotifiactionPolicy(policy).ToSwagger())
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) LastTrigger(ctx context.Context, params webhook.LastTriggerParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := n.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead, rbac.ResourceNotificationPolicy); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
projectID, err := getProjectID(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
query := &q.Query{
|
||||
Keywords: q.KeyWords{
|
||||
"ProjectID": projectID,
|
||||
},
|
||||
}
|
||||
policies, err := n.webhookPolicyMgr.List(ctx, query)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
triggers, err := n.constructPolicyWithTriggerTime(ctx, policies)
|
||||
if err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewLastTriggerOK().WithPayload(triggers)
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) GetSupportedEventTypes(ctx context.Context, params webhook.GetSupportedEventTypesParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := n.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead, rbac.ResourceNotificationPolicy); err != nil {
|
||||
return n.SendError(ctx, err)
|
||||
}
|
||||
|
||||
var notificationTypes = &models.SupportedWebhookEventTypes{}
|
||||
for key := range notification.SupportedNotifyTypes {
|
||||
notificationTypes.NotifyType = append(notificationTypes.NotifyType, models.NotifyType(key))
|
||||
}
|
||||
|
||||
for key := range notification.SupportedEventTypes {
|
||||
notificationTypes.EventType = append(notificationTypes.EventType, models.EventType(key))
|
||||
}
|
||||
|
||||
return operation.NewGetSupportedEventTypesOK().WithPayload(notificationTypes)
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) getLastTriggerTimeGroupByEventType(ctx context.Context, eventType string, policyID int64) (time.Time, error) {
|
||||
jobs, err := n.webhookjobMgr.ListJobsGroupByEventType(ctx, policyID)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
if eventType == job.EventType {
|
||||
return job.CreationTime, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) validateTargets(policy *policy_model.Policy) (bool, error) {
|
||||
if len(policy.Targets) == 0 {
|
||||
return false, errors.New(nil).WithMessage("empty notification target with policy %s", policy.Name).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
for _, target := range policy.Targets {
|
||||
url, err := utils.ParseEndpoint(target.Address)
|
||||
if err != nil {
|
||||
return false, errors.New(err).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
// Prevent SSRF security issue #3755
|
||||
target.Address = url.Scheme + "://" + url.Host + url.Path
|
||||
|
||||
_, ok := notification.SupportedNotifyTypes[target.Type]
|
||||
if !ok {
|
||||
return false, errors.New(nil).WithMessage("unsupported target type %s with policy %s", target.Type, policy.Name).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *notificationPolicyAPI) validateEventTypes(policy *policy_model.Policy) (bool, error) {
|
||||
if len(policy.EventTypes) == 0 {
|
||||
return false, errors.New(nil).WithMessage("empty event type").WithCode(errors.BadRequestCode)
|
||||
}
|
||||
for _, eventType := range policy.EventTypes {
|
||||
_, ok := notification.SupportedEventTypes[eventType]
|
||||
if !ok {
|
||||
return false, errors.New(nil).WithMessage("unsupported event type %s", eventType).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// constructPolicyWithTriggerTime construct notification policy information displayed in UI
|
||||
// including event type, enabled, creation time, last trigger time
|
||||
func (n *notificationPolicyAPI) constructPolicyWithTriggerTime(ctx context.Context, policies []*policy_model.Policy) ([]*models.WebhookLastTrigger, error) {
|
||||
res := []*models.WebhookLastTrigger{}
|
||||
if policies != nil {
|
||||
for _, policy := range policies {
|
||||
for _, t := range policy.EventTypes {
|
||||
ply := &models.WebhookLastTrigger{
|
||||
PolicyName: policy.Name,
|
||||
EventType: t,
|
||||
Enabled: policy.Enabled,
|
||||
CreationTime: strfmt.DateTime(policy.CreationTime),
|
||||
}
|
||||
if !policy.CreationTime.IsZero() {
|
||||
ply.CreationTime = strfmt.DateTime(policy.CreationTime)
|
||||
}
|
||||
|
||||
ltTime, err := n.getLastTriggerTimeGroupByEventType(ctx, t, policy.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ltTime.IsZero() {
|
||||
ply.LastTriggerTime = strfmt.DateTime(ltTime)
|
||||
}
|
||||
res = append(res, ply)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
@ -15,13 +15,16 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/controller/project"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
)
|
||||
@ -113,3 +116,19 @@ func parseProjectNameOrID(str string, isResourceName *bool) interface{} {
|
||||
|
||||
return v // projectID
|
||||
}
|
||||
|
||||
func getProjectID(ctx context.Context, projectNameOrID interface{}) (int64, error) {
|
||||
projectName, ok := projectNameOrID.(string)
|
||||
if ok {
|
||||
p, err := project.Ctl.Get(ctx, projectName, project.Metadata(false))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p.ProjectID, nil
|
||||
}
|
||||
projectID, ok := projectNameOrID.(int64)
|
||||
if ok {
|
||||
return projectID, nil
|
||||
}
|
||||
return 0, errors.New("unknown project identifier type")
|
||||
}
|
||||
|
@ -40,12 +40,6 @@ func registerLegacyRoutes() {
|
||||
beego.Router("/api/"+version+"/replication/adapters", &api.ReplicationAdapterAPI{}, "get:List")
|
||||
beego.Router("/api/"+version+"/replication/adapterinfos", &api.ReplicationAdapterAPI{}, "get:ListAdapterInfos")
|
||||
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/webhook/policies", &api.NotificationPolicyAPI{}, "get:List;post:Post")
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/webhook/policies/:id([0-9]+)", &api.NotificationPolicyAPI{})
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/webhook/lasttrigger", &api.NotificationPolicyAPI{}, "get:ListGroupByEventType")
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/webhook/events", &api.NotificationPolicyAPI{}, "get:GetSupportedEventTypes")
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/webhook/jobs/", &api.NotificationJobAPI{}, "get:List")
|
||||
|
||||
beego.Router("/api/"+version+"/configurations", &api.ConfigAPI{}, "get:Get;put:Put")
|
||||
beego.Router("/api/"+version+"/statistics", &api.StatisticAPI{})
|
||||
beego.Router("/api/"+version+"/labels", &api.LabelAPI{}, "post:Post;get:List")
|
||||
|
178
src/testing/pkg/notification/job/dao/dao.go
Normal file
178
src/testing/pkg/notification/job/dao/dao.go
Normal file
@ -0,0 +1,178 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/goharbor/harbor/src/pkg/notification/job/model"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// DAO is an autogenerated mock type for the DAO type
|
||||
type DAO struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Count provides a mock function with given fields: ctx, query
|
||||
func (_m *DAO) 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, n
|
||||
func (_m *DAO) Create(ctx context.Context, n *model.Job) (int64, error) {
|
||||
ret := _m.Called(ctx, n)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Job) int64); ok {
|
||||
r0 = rf(ctx, n)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *model.Job) error); ok {
|
||||
r1 = rf(ctx, n)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, id
|
||||
func (_m *DAO) Delete(ctx context.Context, id int64) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteByPolicyID provides a mock function with given fields: ctx, policyID
|
||||
func (_m *DAO) DeleteByPolicyID(ctx context.Context, policyID int64) error {
|
||||
ret := _m.Called(ctx, policyID)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, policyID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, id
|
||||
func (_m *DAO) Get(ctx context.Context, id int64) (*model.Job, error) {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
var r0 *model.Job
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) *model.Job); ok {
|
||||
r0 = rf(ctx, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Job)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetLastTriggerJobsGroupByEventType provides a mock function with given fields: ctx, policyID
|
||||
func (_m *DAO) GetLastTriggerJobsGroupByEventType(ctx context.Context, policyID int64) ([]*model.Job, error) {
|
||||
ret := _m.Called(ctx, policyID)
|
||||
|
||||
var r0 []*model.Job
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) []*model.Job); ok {
|
||||
r0 = rf(ctx, policyID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Job)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, policyID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, query
|
||||
func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Job, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 []*model.Job
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.Job); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Job)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, n, props
|
||||
func (_m *DAO) Update(ctx context.Context, n *model.Job, props ...string) error {
|
||||
_va := make([]interface{}, len(props))
|
||||
for _i := range props {
|
||||
_va[_i] = props[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, n)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Job, ...string) error); ok {
|
||||
r0 = rf(ctx, n, props...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
)
|
||||
|
||||
type FakedPolicyMgr struct {
|
||||
}
|
||||
|
||||
func (f *FakedPolicyMgr) Create(*models.NotificationPolicy) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (f *FakedPolicyMgr) List(id int64) ([]*models.NotificationPolicy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *FakedPolicyMgr) Get(id int64) (*models.NotificationPolicy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *FakedPolicyMgr) GetByNameAndProjectID(string, int64) (*models.NotificationPolicy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *FakedPolicyMgr) Update(*models.NotificationPolicy) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakedPolicyMgr) Delete(int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakedPolicyMgr) Test(*models.NotificationPolicy) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakedPolicyMgr) GetRelatedPolices(id int64, eventType string) ([]*models.NotificationPolicy, error) {
|
||||
return []*models.NotificationPolicy{
|
||||
{
|
||||
ID: 1,
|
||||
EventTypes: []string{
|
||||
event.TopicUploadChart,
|
||||
event.TopicDownloadChart,
|
||||
event.TopicDeleteChart,
|
||||
event.TopicPushArtifact,
|
||||
event.TopicPullArtifact,
|
||||
event.TopicDeleteArtifact,
|
||||
event.TopicScanningFailed,
|
||||
event.TopicScanningCompleted,
|
||||
event.TopicQuotaExceed,
|
||||
},
|
||||
Targets: []models.EventTarget{
|
||||
{
|
||||
Type: "http",
|
||||
Address: "http://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
134
src/testing/pkg/notification/policy/dao/dao.go
Normal file
134
src/testing/pkg/notification/policy/dao/dao.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// DAO is an autogenerated mock type for the DAO type
|
||||
type DAO struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Count provides a mock function with given fields: ctx, query
|
||||
func (_m *DAO) 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, n
|
||||
func (_m *DAO) Create(ctx context.Context, n *model.Policy) (int64, error) {
|
||||
ret := _m.Called(ctx, n)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) int64); ok {
|
||||
r0 = rf(ctx, n)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *model.Policy) error); ok {
|
||||
r1 = rf(ctx, n)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, id
|
||||
func (_m *DAO) Delete(ctx context.Context, id int64) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, id
|
||||
func (_m *DAO) Get(ctx context.Context, id int64) (*model.Policy, error) {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
var r0 *model.Policy
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) *model.Policy); ok {
|
||||
r0 = rf(ctx, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Policy)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, query
|
||||
func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 []*model.Policy
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.Policy); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Policy)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, n
|
||||
func (_m *DAO) Update(ctx context.Context, n *model.Policy) error {
|
||||
ret := _m.Called(ctx, n)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) error); ok {
|
||||
r0 = rf(ctx, n)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
193
src/testing/pkg/notification/policy/manager.go
Normal file
193
src/testing/pkg/notification/policy/manager.go
Normal file
@ -0,0 +1,193 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
model "github.com/goharbor/harbor/src/pkg/notification/policy/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// Manager is an autogenerated mock type for the Manager type
|
||||
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, _a1
|
||||
func (_m *Manager) Create(ctx context.Context, _a1 *model.Policy) (int64, error) {
|
||||
ret := _m.Called(ctx, _a1)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) int64); ok {
|
||||
r0 = rf(ctx, _a1)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *model.Policy) error); ok {
|
||||
r1 = rf(ctx, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, policyID
|
||||
func (_m *Manager) Delete(ctx context.Context, policyID int64) error {
|
||||
ret := _m.Called(ctx, policyID)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, policyID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, id
|
||||
func (_m *Manager) Get(ctx context.Context, id int64) (*model.Policy, error) {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
var r0 *model.Policy
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) *model.Policy); ok {
|
||||
r0 = rf(ctx, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Policy)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetByNameAndProjectID provides a mock function with given fields: ctx, name, projectID
|
||||
func (_m *Manager) GetByNameAndProjectID(ctx context.Context, name string, projectID int64) (*model.Policy, error) {
|
||||
ret := _m.Called(ctx, name, projectID)
|
||||
|
||||
var r0 *model.Policy
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, int64) *model.Policy); ok {
|
||||
r0 = rf(ctx, name, projectID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Policy)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
|
||||
r1 = rf(ctx, name, projectID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRelatedPolices provides a mock function with given fields: ctx, projectID, eventType
|
||||
func (_m *Manager) GetRelatedPolices(ctx context.Context, projectID int64, eventType string) ([]*model.Policy, error) {
|
||||
ret := _m.Called(ctx, projectID, eventType)
|
||||
|
||||
var r0 []*model.Policy
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) []*model.Policy); ok {
|
||||
r0 = rf(ctx, projectID, eventType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Policy)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
|
||||
r1 = rf(ctx, projectID, eventType)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, query
|
||||
func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 []*model.Policy
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.Policy); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Policy)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Test provides a mock function with given fields: _a0
|
||||
func (_m *Manager) Test(_a0 *model.Policy) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Policy) error); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, _a1
|
||||
func (_m *Manager) Update(ctx context.Context, _a1 *model.Policy) error {
|
||||
ret := _m.Called(ctx, _a1)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) error); ok {
|
||||
r0 = rf(ctx, _a1)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
@ -33,6 +33,9 @@ package pkg
|
||||
//go:generate mockery --case snake --dir ../../pkg/robot --name Manager --output ./robot --outpkg robot
|
||||
//go:generate mockery --case snake --dir ../../pkg/robot/dao --name DAO --output ./robot/dao --outpkg dao
|
||||
//go:generate mockery --case snake --dir ../../pkg/repository/dao --name DAO --output ./repository/dao --outpkg dao
|
||||
//go:generate mockery --case snake --dir ../../pkg/notification/job/dao --name DAO --output ./notification/job/dao --outpkg dao
|
||||
//go:generate mockery --case snake --dir ../../pkg/notification/policy/dao --name DAO --output ./notification/policy/dao --outpkg dao
|
||||
//go:generate mockery --case snake --dir ../../pkg/notification/policy --name Manager --output ./notification/policy --outpkg notification
|
||||
//go:generate mockery --case snake --dir ../../pkg/immutable/dao --name DAO --output ./immutable/dao --outpkg dao
|
||||
//go:generate mockery --case snake --dir ../../pkg/ldap --name Manager --output ./ldap --outpkg ldap
|
||||
//go:generate mockery --case snake --dir ../../pkg/allowlist --name Manager --output ./allowlist --outpkg robot
|
||||
|
Loading…
Reference in New Issue
Block a user