feat: [AH-933]: implement webhook execution (#3444)

* feat: [AH-933]: implement webhook execution
* feat: [AH-933]: implement webhook execution
This commit is contained in:
Tudor Macari 2025-03-11 21:08:21 +00:00 committed by Harness
parent 3a2428f6b8
commit 6da9821bc7
83 changed files with 2992 additions and 667 deletions

View File

@ -19,6 +19,7 @@ import (
"fmt"
"github.com/harness/gitness/app/auth"
webhooksservice "github.com/harness/gitness/app/services/webhook"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
@ -35,7 +36,11 @@ func (c *Controller) RetriggerExecutionRepo(
if err != nil {
return nil, fmt.Errorf("failed to acquire access to the repo: %w", err)
}
return c.webhookService.RetriggerExecution(
executionCore, err := c.webhookService.RetriggerExecution(
ctx, repo.ID, enum.WebhookParentRepo, webhookIdentifier, webhookExecutionID)
if err != nil {
return nil, err
}
execution := webhooksservice.CoreWebhookExecutionToGitnessWebhookExecution(executionCore)
return execution, nil
}

View File

@ -19,6 +19,7 @@ import (
"fmt"
"github.com/harness/gitness/app/auth"
webhooksservice "github.com/harness/gitness/app/services/webhook"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
@ -36,6 +37,11 @@ func (c *Controller) RetriggerExecutionSpace(
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
return c.webhookService.RetriggerExecution(
executionCore, err := c.webhookService.RetriggerExecution(
ctx, space.ID, enum.WebhookParentSpace, webhookIdentifier, webhookExecutionID)
if err != nil {
return nil, err
}
execution := webhooksservice.CoreWebhookExecutionToGitnessWebhookExecution(executionCore)
return execution, nil
}

View File

@ -63,6 +63,11 @@ func InternalError(ctx context.Context, w http.ResponseWriter) {
UserError(ctx, w, usererror.ErrInternal)
}
// InternalErrorf writes the json-encoded message with internal server error status code.
func InternalErrorf(ctx context.Context, w http.ResponseWriter, format string, args ...interface{}) {
UserError(ctx, w, usererror.Newf(http.StatusInternalServerError, format, args...))
}
// UserError writes the json-encoded user error.
func UserError(ctx context.Context, w http.ResponseWriter, err *usererror.Error) {
log.Ctx(ctx).Debug().Err(err).Msgf("operation resulted in user facing error")

View File

@ -44,7 +44,7 @@ func (s *Service) triggerForEventWithRepo(
repoID int64,
createBodyFn func(*types.Principal, *types.Repository) (any, error),
) error {
principal, err := s.findPrincipalForEvent(ctx, principalID)
principal, err := s.WebhookExecutor.FindPrincipalForEvent(ctx, principalID)
if err != nil {
return err
}
@ -65,7 +65,7 @@ func (s *Service) triggerForEventWithRepo(
return fmt.Errorf("failed to get webhook parent info for parents: %w", err)
}
return s.triggerForEvent(ctx, eventID, parents, triggerType, body)
return s.WebhookExecutor.TriggerForEvent(ctx, eventID, parents, triggerType, body)
}
// triggerForEventWithPullReq triggers all webhooks for the given repo and triggerType
@ -73,11 +73,15 @@ func (s *Service) triggerForEventWithRepo(
// The method tries to find the pullreq, principal, target repo, and source repo
// and provides all to the bodyFn to generate the body.
// NOTE: technically we could avoid this call if we send the data via the event (though then events will get big).
func (s *Service) triggerForEventWithPullReq(ctx context.Context,
func (s *Service) triggerForEventWithPullReq(
ctx context.Context,
triggerType enum.WebhookTrigger, eventID string, principalID int64, prID int64,
createBodyFn func(principal *types.Principal, pr *types.PullReq,
targetRepo *types.Repository, sourceRepo *types.Repository) (any, error)) error {
principal, err := s.findPrincipalForEvent(ctx, principalID)
createBodyFn func(
principal *types.Principal, pr *types.PullReq,
targetRepo *types.Repository, sourceRepo *types.Repository,
) (any, error),
) error {
principal, err := s.WebhookExecutor.FindPrincipalForEvent(ctx, principalID)
if err != nil {
return err
}
@ -111,7 +115,7 @@ func (s *Service) triggerForEventWithPullReq(ctx context.Context,
return fmt.Errorf("failed to get webhook parent info: %w", err)
}
return s.triggerForEvent(ctx, eventID, parents, triggerType, body)
return s.WebhookExecutor.TriggerForEvent(ctx, eventID, parents, triggerType, body)
}
// findRepositoryForEvent finds the repository for the provided repoID.
@ -146,9 +150,9 @@ func (s *Service) findPullReqForEvent(ctx context.Context, prID int64) (*types.P
return pr, nil
}
// findPrincipalForEvent finds the principal for the provided principalID.
func (s *Service) findPrincipalForEvent(ctx context.Context, principalID int64) (*types.Principal, error) {
principal, err := s.principalStore.Find(ctx, principalID)
// FindPrincipalForEvent finds the principal for the provided principalID.
func (w *WebhookExecutor) FindPrincipalForEvent(ctx context.Context, principalID int64) (*types.Principal, error) {
principal, err := w.principalStore.Find(ctx, principalID)
if err != nil && errors.Is(err, store.ErrResourceNotFound) {
// this should never happen (as we won't delete principals) - discard event
@ -162,9 +166,9 @@ func (s *Service) findPrincipalForEvent(ctx context.Context, principalID int64)
return principal, nil
}
// triggerForEvent triggers all webhooks for the given parentType/ID and triggerType
// TriggerForEvent triggers all webhooks for the given parentType/ID and triggerType
// using the eventID to generate a deterministic triggerID and sending the provided body as payload.
func (s *Service) triggerForEvent(
func (w *WebhookExecutor) TriggerForEvent(
ctx context.Context,
eventID string,
parents []types.WebhookParentInfo,
@ -173,7 +177,7 @@ func (s *Service) triggerForEvent(
) error {
triggerID := generateTriggerIDFromEventID(eventID)
results, err := s.triggerWebhooksFor(ctx, parents, triggerID, triggerType, body)
results, err := w.triggerWebhooksFor(ctx, parents, triggerID, triggerType, body)
// return all errors and force the event to be reprocessed (it's not webhook execution specific!)
if err != nil {

View File

@ -78,7 +78,7 @@ func (s *Service) RetriggerExecution(
parentType enum.WebhookParent,
webhookIdentifier string,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
) (*types.WebhookExecutionCore, error) {
webhook, err := s.GetWebhookVerifyOwnership(
ctx, parentID, parentType, webhookIdentifier)
if err != nil {
@ -91,7 +91,7 @@ func (s *Service) RetriggerExecution(
return nil, err
}
executionResult, err := s.RetriggerWebhookExecution(ctx, webhookExecution.ID)
executionResult, err := s.WebhookExecutor.RetriggerWebhookExecution(ctx, webhookExecution.ID)
if err != nil {
return nil, fmt.Errorf("failed to retrigger webhook execution: %w", err)
}

View File

@ -41,8 +41,10 @@ type PullReqCreatedPayload struct {
// handleEventPullReqCreated handles created events for pull requests
// and triggers pullreq created webhooks for the source repo.
func (s *Service) handleEventPullReqCreated(ctx context.Context,
event *events.Event[*pullreqevents.CreatedPayload]) error {
func (s *Service) handleEventPullReqCreated(
ctx context.Context,
event *events.Event[*pullreqevents.CreatedPayload],
) error {
return s.triggerForEventWithPullReq(ctx, enum.WebhookTriggerPullReqCreated,
event.ID, event.Payload.PrincipalID, event.Payload.PullReqID,
func(principal *types.Principal, pr *types.PullReq, targetRepo, sourceRepo *types.Repository) (any, error) {
@ -89,8 +91,10 @@ type PullReqReopenedPayload PullReqCreatedPayload
// handleEventPullReqReopened handles reopened events for pull requests
// and triggers pullreq reopened webhooks for the source repo.
func (s *Service) handleEventPullReqReopened(ctx context.Context,
event *events.Event[*pullreqevents.ReopenedPayload]) error {
func (s *Service) handleEventPullReqReopened(
ctx context.Context,
event *events.Event[*pullreqevents.ReopenedPayload],
) error {
return s.triggerForEventWithPullReq(ctx, enum.WebhookTriggerPullReqReopened,
event.ID, event.Payload.PrincipalID, event.Payload.PullReqID,
func(principal *types.Principal, pr *types.PullReq, targetRepo, sourceRepo *types.Repository) (any, error) {
@ -144,8 +148,10 @@ type PullReqBranchUpdatedPayload struct {
// handleEventPullReqBranchUpdated handles branch updated events for pull requests
// and triggers pullreq branch updated webhooks for the source repo.
func (s *Service) handleEventPullReqBranchUpdated(ctx context.Context,
event *events.Event[*pullreqevents.BranchUpdatedPayload]) error {
func (s *Service) handleEventPullReqBranchUpdated(
ctx context.Context,
event *events.Event[*pullreqevents.BranchUpdatedPayload],
) error {
return s.triggerForEventWithPullReq(ctx, enum.WebhookTriggerPullReqBranchUpdated,
event.ID, event.Payload.PrincipalID, event.Payload.PullReqID,
func(principal *types.Principal, pr *types.PullReq, targetRepo, sourceRepo *types.Repository) (any, error) {
@ -204,8 +210,10 @@ type PullReqClosedPayload struct {
ReferenceDetailsSegment
}
func (s *Service) handleEventPullReqClosed(ctx context.Context,
event *events.Event[*pullreqevents.ClosedPayload]) error {
func (s *Service) handleEventPullReqClosed(
ctx context.Context,
event *events.Event[*pullreqevents.ClosedPayload],
) error {
return s.triggerForEventWithPullReq(ctx, enum.WebhookTriggerPullReqClosed,
event.ID, event.Payload.PrincipalID, event.Payload.PullReqID,
func(principal *types.Principal, pr *types.PullReq, targetRepo, sourceRepo *types.Repository) (any, error) {
@ -255,8 +263,10 @@ type PullReqMergedPayload struct {
ReferenceDetailsSegment
}
func (s *Service) handleEventPullReqMerged(ctx context.Context,
event *events.Event[*pullreqevents.MergedPayload]) error {
func (s *Service) handleEventPullReqMerged(
ctx context.Context,
event *events.Event[*pullreqevents.MergedPayload],
) error {
return s.triggerForEventWithPullReq(ctx, enum.WebhookTriggerPullReqMerged,
event.ID, event.Payload.PrincipalID, event.Payload.PullReqID,
func(principal *types.Principal, pr *types.PullReq, targetRepo, sourceRepo *types.Repository) (any, error) {
@ -654,7 +664,7 @@ func (s *Service) handleEventPullReqReviewSubmitted(
targetRepoInfo := repositoryInfoFrom(ctx, targetRepo, s.urlProvider)
sourceRepoInfo := repositoryInfoFrom(ctx, sourceRepo, s.urlProvider)
reviewer, err := s.findPrincipalForEvent(ctx, event.Payload.ReviewerID)
reviewer, err := s.WebhookExecutor.FindPrincipalForEvent(ctx, event.Payload.ReviewerID)
if err != nil {
return nil, fmt.Errorf("failed to get reviewer by id for reviewer id %d: %w", event.Payload.ReviewerID, err)
}

View File

@ -37,7 +37,7 @@ func newHTTPClient(allowLoopback bool, allowPrivateNetwork bool, disableSSLVerif
}
// Clone http.DefaultTransport (used by http.DefaultClient)
tr := http.DefaultTransport.(*http.Transport).Clone()
tr := http.DefaultTransport.(*http.Transport).Clone() //nolint:errcheck
tr.TLSClientConfig.InsecureSkipVerify = disableSSLVerification

View File

@ -22,7 +22,7 @@ import (
"github.com/harness/gitness/types/enum"
)
// Listreturns the webhooks from the provided scope.
// List returns the webhooks from the provided scope.
func (s *Service) List(
ctx context.Context,
parentID int64,

View File

@ -0,0 +1,101 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package webhook
import (
"context"
gitnessstore "github.com/harness/gitness/app/store"
"github.com/harness/gitness/types"
)
type GitnessWebhookExecutorStore struct {
webhookStore gitnessstore.WebhookStore
webhookExecutionStore gitnessstore.WebhookExecutionStore
}
func (s *GitnessWebhookExecutorStore) Find(ctx context.Context, id int64) (*types.WebhookExecutionCore, error) {
execution, err := s.webhookExecutionStore.Find(ctx, id)
if err != nil {
return nil, err
}
executionCore := GitnessWebhookExecutionToWebhookExecutionCore(execution)
return executionCore, nil
}
func (s *GitnessWebhookExecutorStore) ListWebhooks(
ctx context.Context,
parents []types.WebhookParentInfo,
) ([]*types.WebhookCore, error) {
webhooks, err := s.webhookStore.List(ctx, parents, &types.WebhookFilter{})
if err != nil {
return nil, err
}
webhooksCore := GitnessWebhooksToWebhooksCore(webhooks)
return webhooksCore, nil
}
func (s *GitnessWebhookExecutorStore) ListForTrigger(
ctx context.Context,
triggerID string,
) ([]*types.WebhookExecutionCore, error) {
executions, err := s.webhookExecutionStore.ListForTrigger(ctx, triggerID)
if err != nil {
return nil, err
}
webhookExecutionsCore := make([]*types.WebhookExecutionCore, 0)
for _, e := range executions {
executionCore := GitnessWebhookExecutionToWebhookExecutionCore(e)
webhookExecutionsCore = append(webhookExecutionsCore, executionCore)
}
return webhookExecutionsCore, nil
}
func (s *GitnessWebhookExecutorStore) CreateWebhookExecution(
ctx context.Context,
hook *types.WebhookExecutionCore,
) error {
webhookExecution := CoreWebhookExecutionToGitnessWebhookExecution(hook)
return s.webhookExecutionStore.Create(ctx, webhookExecution)
}
func (s *GitnessWebhookExecutorStore) UpdateOptLock(
ctx context.Context, hook *types.WebhookCore,
execution *types.WebhookExecutionCore,
) (*types.WebhookCore, error) {
webhook := CoreWebhookToGitnessWebhook(hook)
fn := func(hook *types.Webhook) error {
hook.LatestExecutionResult = &execution.Result
return nil
}
gitnessWebhook, err := s.webhookStore.UpdateOptLock(ctx, webhook, fn)
if err != nil {
return nil, err
}
webhookCore := GitnessWebhookToWebhookCore(gitnessWebhook)
return webhookCore, err
}
func (s *GitnessWebhookExecutorStore) FindWebhook(
ctx context.Context,
id int64,
) (*types.WebhookCore, error) {
webhook, err := s.webhookStore.Find(ctx, id)
if err != nil {
return nil, err
}
webhookCore := GitnessWebhookToWebhookCore(webhook)
return webhookCore, nil
}

View File

@ -29,8 +29,10 @@ import (
"github.com/harness/gitness/encrypt"
"github.com/harness/gitness/events"
"github.com/harness/gitness/git"
"github.com/harness/gitness/secret"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/stream"
"github.com/harness/gitness/types"
)
const (
@ -59,16 +61,16 @@ func (c *Config) Prepare() error {
return errors.New("config is required")
}
if c.EventReaderName == "" {
return errors.New("config.EventReaderName is required")
return errors.New("Config.EventReaderName is required")
}
if c.UserAgentIdentity == "" {
return errors.New("config.UserAgentIdentity is required")
return errors.New("Config.UserAgentIdentity is required")
}
if c.Concurrency < 1 {
return errors.New("config.Concurrency has to be a positive number")
return errors.New("Config.Concurrency has to be a positive number")
}
if c.MaxRetries < 0 {
return errors.New("config.MaxRetries can't be negative")
return errors.New("Config.MaxRetries can't be negative")
}
// Backfill data
@ -79,10 +81,76 @@ func (c *Config) Prepare() error {
return nil
}
type WebhookExecutorStore interface {
Find(ctx context.Context, id int64) (*types.WebhookExecutionCore, error)
ListWebhooks(
ctx context.Context,
parents []types.WebhookParentInfo,
) ([]*types.WebhookCore, error)
UpdateOptLock(
ctx context.Context, hook *types.WebhookCore,
execution *types.WebhookExecutionCore,
) (*types.WebhookCore, error)
FindWebhook(
ctx context.Context,
id int64,
) (*types.WebhookCore, error)
ListForTrigger(
ctx context.Context,
triggerID string,
) ([]*types.WebhookExecutionCore, error)
CreateWebhookExecution(ctx context.Context, hook *types.WebhookExecutionCore) error
}
type WebhookExecutor struct {
secureHTTPClient *http.Client
insecureHTTPClient *http.Client
secureHTTPClientInternal *http.Client
insecureHTTPClientInternal *http.Client
config Config
webhookURLProvider URLProvider
encrypter encrypt.Encrypter
spacePathStore store.SpacePathStore
secretService secret.Service
principalStore store.PrincipalStore
webhookExecutorStore WebhookExecutorStore
source string
}
func NewWebhookExecutor(
config Config,
webhookURLProvider URLProvider,
encrypter encrypt.Encrypter,
spacePathStore store.SpacePathStore,
secretService secret.Service,
principalStore store.PrincipalStore,
webhookExecutorStore WebhookExecutorStore,
source string,
) *WebhookExecutor {
return &WebhookExecutor{
webhookExecutorStore: webhookExecutorStore,
secureHTTPClient: newHTTPClient(config.AllowLoopback, config.AllowPrivateNetwork, false),
insecureHTTPClient: newHTTPClient(config.AllowLoopback, config.AllowPrivateNetwork, true),
secureHTTPClientInternal: newHTTPClient(config.AllowLoopback, true, false),
insecureHTTPClientInternal: newHTTPClient(config.AllowLoopback, true, true),
config: config,
webhookURLProvider: webhookURLProvider,
encrypter: encrypter,
spacePathStore: spacePathStore,
secretService: secretService,
principalStore: principalStore,
source: source,
}
}
// Service is responsible for processing webhook events.
type Service struct {
tx dbtx.Transactor
WebhookExecutor *WebhookExecutor
tx dbtx.Transactor
webhookStore store.WebhookStore
webhookExecutionStore store.WebhookExecutionStore
urlProvider url.Provider
@ -95,17 +163,8 @@ type Service struct {
labelStore store.LabelStore
labelValueStore store.LabelValueStore
encrypter encrypt.Encrypter
secureHTTPClient *http.Client
insecureHTTPClient *http.Client
secureHTTPClientInternal *http.Client
insecureHTTPClientInternal *http.Client
config Config
webhookURLProvider URLProvider
sseStreamer sse.Streamer
config Config
sseStreamer sse.Streamer
}
func NewService(
@ -128,11 +187,21 @@ func NewService(
webhookURLProvider URLProvider,
labelValueStore store.LabelValueStore,
sseStreamer sse.Streamer,
secretService secret.Service,
spacePathStore store.SpacePathStore,
) (*Service, error) {
if err := config.Prepare(); err != nil {
return nil, fmt.Errorf("provided webhook service config is invalid: %w", err)
return nil, fmt.Errorf("provided webhook service Config is invalid: %w", err)
}
webhookExecutorStore := &GitnessWebhookExecutorStore{
webhookStore: webhookStore,
webhookExecutionStore: webhookExecutionStore,
}
executor := NewWebhookExecutor(config, webhookURLProvider, encrypter, spacePathStore,
secretService, principalStore, webhookExecutorStore, RepoTrigger)
service := &Service{
WebhookExecutor: executor,
tx: tx,
webhookStore: webhookStore,
webhookExecutionStore: webhookExecutionStore,
@ -144,20 +213,10 @@ func NewService(
principalStore: principalStore,
git: git,
encrypter: encrypter,
secureHTTPClient: newHTTPClient(config.AllowLoopback, config.AllowPrivateNetwork, false),
insecureHTTPClient: newHTTPClient(config.AllowLoopback, config.AllowPrivateNetwork, true),
secureHTTPClientInternal: newHTTPClient(config.AllowLoopback, true, false),
insecureHTTPClientInternal: newHTTPClient(config.AllowLoopback, true, true),
config: config,
labelStore: labelStore,
labelValueStore: labelValueStore,
webhookURLProvider: webhookURLProvider,
sseStreamer: sseStreamer,
config: config,
labelStore: labelStore,
labelValueStore: labelValueStore,
sseStreamer: sseStreamer,
}
_, err := gitReaderFactory.Launch(ctx, eventsReaderGroupName, config.EventReaderName,

View File

@ -25,7 +25,9 @@ import (
"net/http"
"time"
gitnessstore "github.com/harness/gitness/app/store"
"github.com/harness/gitness/crypto"
"github.com/harness/gitness/secret"
"github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -46,6 +48,11 @@ const (
responseBodyBytesLimit = 1024
)
const (
RepoTrigger = "Trigger"
ArtifactRegistryTrigger = "Artifact-Registry-Trigger"
)
var (
// ErrWebhookNotRetriggerable is returned in case the webhook can't be retriggered due to an incomplete execution.
// This should only occur if we failed to generate the request body (most likely out of memory).
@ -55,8 +62,8 @@ var (
type TriggerResult struct {
TriggerID string
TriggerType enum.WebhookTrigger
Webhook *types.Webhook
Execution *types.WebhookExecution
Webhook *types.WebhookCore
Execution *types.WebhookExecutionCore
Err error
}
@ -64,31 +71,32 @@ func (r *TriggerResult) Skipped() bool {
return r.Execution == nil
}
func (s *Service) triggerWebhooksFor(
func (w *WebhookExecutor) triggerWebhooksFor(
ctx context.Context,
parents []types.WebhookParentInfo,
triggerID string,
triggerType enum.WebhookTrigger,
body any,
) ([]TriggerResult, error) {
webhooks, err := s.webhookStore.List(ctx, parents, &types.WebhookFilter{})
webhooks, err := w.webhookExecutorStore.ListWebhooks(ctx, parents)
if err != nil {
return nil, fmt.Errorf("failed to list webhooks for: %w", err)
}
return s.triggerWebhooks(ctx, webhooks, triggerID, triggerType, body)
return w.triggerWebhooks(ctx, webhooks, triggerID, triggerType, body)
}
//nolint:gocognit // refactor if needed
func (s *Service) triggerWebhooks(ctx context.Context, webhooks []*types.Webhook,
triggerID string, triggerType enum.WebhookTrigger, body any) ([]TriggerResult, error) {
func (w *WebhookExecutor) triggerWebhooks(
ctx context.Context, webhooks []*types.WebhookCore,
triggerID string, triggerType enum.WebhookTrigger, body any,
) ([]TriggerResult, error) {
// return immediately if webhooks are empty
if len(webhooks) == 0 {
return []TriggerResult{}, nil
}
// get all previous execution for the same trigger
executions, err := s.webhookExecutionStore.ListForTrigger(ctx, triggerID)
executions, err := w.webhookExecutorStore.ListForTrigger(ctx, triggerID)
if err != nil && !errors.Is(err, store.ErrResourceNotFound) {
return nil, fmt.Errorf("failed to get executions for trigger '%s'", triggerID)
}
@ -134,15 +142,18 @@ func (s *Service) triggerWebhooks(ctx context.Context, webhooks []*types.Webhook
}
// execute trigger and store output in result
results[i].Execution, results[i].Err = s.executeWebhook(ctx, webhook, triggerID, triggerType, body, nil)
results[i].Execution, results[i].Err = w.executeWebhook(ctx, webhook, triggerID, triggerType, body, nil)
}
return results, nil
}
func (s *Service) RetriggerWebhookExecution(ctx context.Context, webhookExecutionID int64) (*TriggerResult, error) {
func (w *WebhookExecutor) RetriggerWebhookExecution(
ctx context.Context,
webhookExecutionID int64,
) (*TriggerResult, error) {
// find execution
webhookExecution, err := s.webhookExecutionStore.Find(ctx, webhookExecutionID)
webhookExecution, err := w.webhookExecutorStore.Find(ctx, webhookExecutionID)
if err != nil {
return nil, fmt.Errorf("failed to find webhook execution with id %d: %w", webhookExecutionID, err)
}
@ -153,7 +164,7 @@ func (s *Service) RetriggerWebhookExecution(ctx context.Context, webhookExecutio
}
// find webhook
webhook, err := s.webhookStore.Find(ctx, webhookExecution.WebhookID)
webhook, err := w.webhookExecutorStore.FindWebhook(ctx, webhookExecution.WebhookID)
if err != nil {
return nil, fmt.Errorf("failed to find webhook with id %d: %w", webhookExecution.WebhookID, err)
}
@ -167,7 +178,7 @@ func (s *Service) RetriggerWebhookExecution(ctx context.Context, webhookExecutio
// NOTE: bBuff.Write(v) will always return (len(v), nil) - no need to error handle
body.WriteString(webhookExecution.Request.Body)
newExecution, err := s.executeWebhook(ctx, webhook, triggerID, triggerType, body, &webhookExecution.ID)
newExecution, err := w.executeWebhook(ctx, webhook, triggerID, triggerType, body, &webhookExecution.ID)
return &TriggerResult{
TriggerID: triggerID,
TriggerType: triggerType,
@ -178,10 +189,12 @@ func (s *Service) RetriggerWebhookExecution(ctx context.Context, webhookExecutio
}
//nolint:gocognit // refactor into smaller chunks if necessary.
func (s *Service) executeWebhook(ctx context.Context, webhook *types.Webhook, triggerID string,
triggerType enum.WebhookTrigger, body any, rerunOfID *int64) (*types.WebhookExecution, error) {
func (w *WebhookExecutor) executeWebhook(
ctx context.Context, webhook *types.WebhookCore, triggerID string,
triggerType enum.WebhookTrigger, body any, rerunOfID *int64,
) (*types.WebhookExecutionCore, error) {
// build execution entry on the fly (save no matter what)
execution := types.WebhookExecution{
execution := types.WebhookExecutionCore{
RetriggerOf: rerunOfID,
WebhookID: webhook.ID,
TriggerID: triggerID,
@ -196,7 +209,7 @@ func (s *Service) executeWebhook(ctx context.Context, webhook *types.Webhook, tr
execution.Created = time.Now().UnixMilli()
// TODO: what if saving execution failed? For now we will rerun it in case of error or not show it in history
err := s.webhookExecutionStore.Create(oCtx, &execution)
err := w.webhookExecutorStore.CreateWebhookExecution(oCtx, &execution)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msgf(
"failed to store webhook execution that ended with Result: %s, Response.Status: '%s', Error: '%s'",
@ -205,10 +218,7 @@ func (s *Service) executeWebhook(ctx context.Context, webhook *types.Webhook, tr
// update latest execution result of webhook IFF it's different from before (best effort)
if webhook.LatestExecutionResult == nil || *webhook.LatestExecutionResult != execution.Result {
_, err = s.webhookStore.UpdateOptLock(oCtx, webhook, func(hook *types.Webhook) error {
hook.LatestExecutionResult = &execution.Result
return nil
})
_, err = w.webhookExecutorStore.UpdateOptLock(oCtx, webhook, &execution)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msgf(
"failed to update latest execution result to %s for webhook %d",
@ -222,7 +232,7 @@ func (s *Service) executeWebhook(ctx context.Context, webhook *types.Webhook, tr
defer cancel()
// create request from webhook and body
req, err := s.prepareHTTPRequest(ctx, &execution, triggerType, webhook, body)
req, err := w.prepareHTTPRequest(ctx, &execution, triggerType, webhook, body)
if err != nil {
return &execution, err
}
@ -231,13 +241,13 @@ func (s *Service) executeWebhook(ctx context.Context, webhook *types.Webhook, tr
var resp *http.Response
switch {
case webhook.Type == enum.WebhookTypeInternal && webhook.Insecure:
resp, err = s.insecureHTTPClientInternal.Do(req)
resp, err = w.insecureHTTPClientInternal.Do(req)
case webhook.Type == enum.WebhookTypeInternal:
resp, err = s.secureHTTPClientInternal.Do(req)
resp, err = w.secureHTTPClientInternal.Do(req)
case webhook.Insecure:
resp, err = s.insecureHTTPClient.Do(req)
resp, err = w.insecureHTTPClient.Do(req)
default:
resp, err = s.secureHTTPClient.Do(req)
resp, err = w.secureHTTPClient.Do(req)
}
// always close the body!
@ -283,9 +293,11 @@ func (s *Service) executeWebhook(ctx context.Context, webhook *types.Webhook, tr
// prepareHTTPRequest prepares a new http.Request object for the webhook using the provided body as request body.
// All execution.Request.XXX values are set accordingly.
// NOTE: if the body is an io.Reader, the value is used as response body as is, otherwise it'll be JSON serialized.
func (s *Service) prepareHTTPRequest(ctx context.Context, execution *types.WebhookExecution,
triggerType enum.WebhookTrigger, webhook *types.Webhook, body any) (*http.Request, error) {
url, err := s.webhookURLProvider.GetWebhookURL(ctx, webhook)
func (w *WebhookExecutor) prepareHTTPRequest(
ctx context.Context, execution *types.WebhookExecutionCore,
triggerType enum.WebhookTrigger, webhook *types.WebhookCore, body any,
) (*http.Request, error) {
url, err := w.webhookURLProvider.GetWebhookURL(ctx, webhook)
if err != nil {
return nil, fmt.Errorf("webhook url is not resolvable: %w", err)
}
@ -336,27 +348,46 @@ func (s *Service) prepareHTTPRequest(ctx context.Context, execution *types.Webho
}
// setup headers
req.Header.Add("User-Agent", fmt.Sprintf("%s/%s", s.config.UserAgentIdentity, version.Version))
req.Header.Add("User-Agent", fmt.Sprintf("%s/%s", w.config.UserAgentIdentity, version.Version))
req.Header.Add("Content-Type", "application/json")
req.Header.Add(s.toXHeader("Trigger"), string(triggerType))
req.Header.Add(s.toXHeader("Webhook-Parent-Type"), string(webhook.ParentType))
req.Header.Add(s.toXHeader("Webhook-Parent-Id"), fmt.Sprint(webhook.ParentID))
// TODO [CODE-1363]: remove after identifier migration.
req.Header.Add(s.toXHeader("Webhook-Uid"), fmt.Sprint(webhook.Identifier))
req.Header.Add(s.toXHeader("Webhook-Identifier"), fmt.Sprint(webhook.Identifier))
// add HMAC only if a secret was provided
req.Header.Add(w.toXHeader("Webhook-Parent-Type"), string(webhook.ParentType))
req.Header.Add(w.toXHeader("Webhook-Parent-Id"), fmt.Sprint(webhook.ParentID))
// TODO [CODE-1363]: remove after identifier migration.
req.Header.Add(w.toXHeader("Webhook-Uid"), fmt.Sprint(webhook.Identifier))
req.Header.Add(w.toXHeader("Webhook-Identifier"), fmt.Sprint(webhook.Identifier))
req.Header.Add(w.toXHeader(w.source), string(triggerType))
if webhook.ExtraHeaders != nil {
for _, h := range webhook.ExtraHeaders {
req.Header.Add(h.Key, h.Value)
}
}
var secretValue string
if webhook.Secret != "" {
decryptedSecret, err := s.encrypter.Decrypt([]byte(webhook.Secret))
decryptedSecret, err := w.encrypter.Decrypt([]byte(webhook.Secret))
if err != nil {
return nil, fmt.Errorf("failed to decrypt webhook secret: %w", err)
}
secretValue = decryptedSecret
} else if webhook.SecretIdentifier != "" {
decryptedSecret, err := getSecretValue(ctx, w.spacePathStore, w.secretService,
int64(webhook.SecretSpaceID), webhook.SecretIdentifier)
if err != nil {
return nil, fmt.Errorf("failed get secret secret value: %w", err)
}
secretValue = decryptedSecret
}
// add HMAC only if a secret was provided
if secretValue != "" {
var hmac string
hmac, err = crypto.GenerateHMACSHA256(bBuff.Bytes(), []byte(decryptedSecret))
hmac, err = crypto.GenerateHMACSHA256(bBuff.Bytes(), []byte(secretValue))
if err != nil {
return nil, fmt.Errorf("failed to generate SHA256 based HMAC: %w", err)
}
req.Header.Add(s.toXHeader("Signature"), hmac)
req.Header.Add(w.toXHeader("Signature"), hmac)
}
hBuffer := &bytes.Buffer{}
@ -372,12 +403,12 @@ func (s *Service) prepareHTTPRequest(ctx context.Context, execution *types.Webho
return req, nil
}
func (s *Service) toXHeader(name string) string {
return fmt.Sprintf("X-%s-%s", s.config.HeaderIdentity, name)
func (w *WebhookExecutor) toXHeader(name string) string {
return fmt.Sprintf("X-%s-%s", w.config.HeaderIdentity, name)
}
//nolint:funlen // refactor if needed
func handleWebhookResponse(execution *types.WebhookExecution, resp *http.Response) error {
func handleWebhookResponse(execution *types.WebhookExecutionCore, resp *http.Response) error {
// store status (handle status later - want to first read body)
execution.Response.StatusCode = resp.StatusCode
execution.Response.Status = resp.Status
@ -473,3 +504,111 @@ func handleWebhookResponse(execution *types.WebhookExecution, resp *http.Respons
return fmt.Errorf("received response with unsupported status code %d", code)
}
}
func getSecretValue(
ctx context.Context, spacePathStore gitnessstore.SpacePathStore, secretService secret.Service,
secretSpaceID int64, secretSpacePath string,
) (string, error) {
spacePath, err := spacePathStore.FindPrimaryBySpaceID(ctx, secretSpaceID)
if err != nil {
err = fmt.Errorf("failed to find space path: %w", err)
log.Error().Msg(err.Error())
return "", err
}
decryptedSecret, err := secretService.DecryptSecret(ctx, spacePath.Value, secretSpacePath)
if err != nil {
err = fmt.Errorf("failed to decrypt secret: %w", err)
log.Error().Msg(err.Error())
return "", err
}
return decryptedSecret, nil
}
func CoreWebhookExecutionToGitnessWebhookExecution(execution *types.WebhookExecutionCore) *types.WebhookExecution {
return &types.WebhookExecution{
ID: execution.ID,
WebhookID: execution.WebhookID,
TriggerID: execution.TriggerID,
TriggerType: execution.TriggerType,
Result: execution.Result,
Error: execution.Error,
Request: execution.Request,
Response: execution.Response,
RetriggerOf: execution.RetriggerOf,
Retriggerable: execution.Retriggerable,
Duration: execution.Duration,
Created: execution.Created,
}
}
func GitnessWebhookExecutionToWebhookExecutionCore(execution *types.WebhookExecution) *types.WebhookExecutionCore {
return &types.WebhookExecutionCore{
ID: execution.ID,
WebhookID: execution.WebhookID,
TriggerID: execution.TriggerID,
TriggerType: execution.TriggerType,
Result: execution.Result,
Error: execution.Error,
Request: execution.Request,
Response: execution.Response,
RetriggerOf: execution.RetriggerOf,
Retriggerable: execution.Retriggerable,
Duration: execution.Duration,
Created: execution.Created,
}
}
func GitnessWebhookToWebhookCore(webhook *types.Webhook) *types.WebhookCore {
return &types.WebhookCore{
ID: webhook.ID,
Version: webhook.Version,
ParentID: webhook.ParentID,
ParentType: webhook.ParentType,
CreatedBy: webhook.CreatedBy,
Created: webhook.Created,
Updated: webhook.Updated,
Type: webhook.Type,
Scope: webhook.Scope,
Identifier: webhook.Identifier,
DisplayName: webhook.DisplayName,
Description: webhook.Description,
URL: webhook.URL,
Secret: webhook.Secret,
Enabled: webhook.Enabled,
Insecure: webhook.Insecure,
Triggers: webhook.Triggers,
LatestExecutionResult: webhook.LatestExecutionResult,
}
}
func CoreWebhookToGitnessWebhook(webhook *types.WebhookCore) *types.Webhook {
return &types.Webhook{
ID: webhook.ID,
Version: webhook.Version,
ParentID: webhook.ParentID,
ParentType: webhook.ParentType,
CreatedBy: webhook.CreatedBy,
Created: webhook.Created,
Updated: webhook.Updated,
Type: webhook.Type,
Scope: webhook.Scope,
Identifier: webhook.Identifier,
DisplayName: webhook.DisplayName,
Description: webhook.Description,
URL: webhook.URL,
Secret: webhook.Secret,
Enabled: webhook.Enabled,
Insecure: webhook.Insecure,
Triggers: webhook.Triggers,
LatestExecutionResult: webhook.LatestExecutionResult,
}
}
func GitnessWebhooksToWebhooksCore(webhooks []*types.Webhook) []*types.WebhookCore {
webhooksCore := make([]*types.WebhookCore, 0)
for _, webhook := range webhooks {
webhookCore := GitnessWebhookToWebhookCore(webhook)
webhooksCore = append(webhooksCore, webhookCore)
}
return webhooksCore
}

View File

@ -28,7 +28,7 @@ func NewURLProvider(_ context.Context) *GitnessURLProvider {
return &GitnessURLProvider{}
}
func (u *GitnessURLProvider) GetWebhookURL(_ context.Context, webhook *types.Webhook) (string, error) {
func (u *GitnessURLProvider) GetWebhookURL(_ context.Context, webhook *types.WebhookCore) (string, error) {
// set URL as is (already has been validated, any other error will be caught in request creation)
return webhook.URL, nil
}

View File

@ -21,5 +21,5 @@ import (
)
type URLProvider interface {
GetWebhookURL(ctx context.Context, webhook *types.Webhook) (string, error)
GetWebhookURL(ctx context.Context, webhook *types.WebhookCore) (string, error)
}

View File

@ -25,6 +25,7 @@ import (
"github.com/harness/gitness/encrypt"
"github.com/harness/gitness/events"
"github.com/harness/gitness/git"
"github.com/harness/gitness/secret"
"github.com/harness/gitness/store/database/dbtx"
"github.com/google/wire"
@ -56,6 +57,8 @@ func ProvideService(
webhookURLProvider URLProvider,
labelValueStore store.LabelValueStore,
sseStreamer sse.Streamer,
secretService secret.Service,
spacePathStore store.SpacePathStore,
) (*Service, error) {
return NewService(
ctx,
@ -76,6 +79,8 @@ func ProvideService(
webhookURLProvider,
labelValueStore,
sseStreamer,
secretService,
spacePathStore,
)
}

View File

@ -29,6 +29,7 @@ import (
"github.com/harness/gitness/app/services/trigger"
"github.com/harness/gitness/app/services/webhook"
"github.com/harness/gitness/job"
registrywebhooks "github.com/harness/gitness/registry/services/webhook"
"github.com/google/wire"
)
@ -38,20 +39,21 @@ var WireSet = wire.NewSet(
)
type Services struct {
Webhook *webhook.Service
PullReq *pullreq.Service
Trigger *trigger.Service
JobScheduler *job.Scheduler
MetricCollector *metric.Collector
RepoSizeCalculator *repo.SizeCalculator
Repo *repo.Service
Cleanup *cleanup.Service
Notification *notification.Service
Keywordsearch *keywordsearch.Service
GitspaceService *GitspaceServices
Instrumentation instrument.Service
instrumentConsumer instrument.Consumer
instrumentRepoCounter *instrument.RepositoryCount
Webhook *webhook.Service
PullReq *pullreq.Service
Trigger *trigger.Service
JobScheduler *job.Scheduler
MetricCollector *metric.Collector
RepoSizeCalculator *repo.SizeCalculator
Repo *repo.Service
Cleanup *cleanup.Service
Notification *notification.Service
Keywordsearch *keywordsearch.Service
GitspaceService *GitspaceServices
Instrumentation instrument.Service
instrumentConsumer instrument.Consumer
instrumentRepoCounter *instrument.RepositoryCount
registryWebhooksService *registrywebhooks.Service
}
type GitspaceServices struct {
@ -90,21 +92,23 @@ func ProvideServices(
instrumentation instrument.Service,
instrumentConsumer instrument.Consumer,
instrumentRepoCounter *instrument.RepositoryCount,
registryWebhooksService *registrywebhooks.Service,
) Services {
return Services{
Webhook: webhooksSvc,
PullReq: pullReqSvc,
Trigger: triggerSvc,
JobScheduler: jobScheduler,
MetricCollector: metricCollector,
RepoSizeCalculator: repoSizeCalculator,
Repo: repo,
Cleanup: cleanupSvc,
Notification: notificationSvc,
Keywordsearch: keywordsearchSvc,
GitspaceService: gitspaceSvc,
Instrumentation: instrumentation,
instrumentConsumer: instrumentConsumer,
instrumentRepoCounter: instrumentRepoCounter,
Webhook: webhooksSvc,
PullReq: pullReqSvc,
Trigger: triggerSvc,
JobScheduler: jobScheduler,
MetricCollector: metricCollector,
RepoSizeCalculator: repoSizeCalculator,
Repo: repo,
Cleanup: cleanupSvc,
Notification: notificationSvc,
Keywordsearch: keywordsearchSvc,
GitspaceService: gitspaceSvc,
Instrumentation: instrumentation,
instrumentConsumer: instrumentConsumer,
instrumentRepoCounter: instrumentRepoCounter,
registryWebhooksService: registryWebhooksService,
}
}

View File

@ -259,7 +259,12 @@ type (
FindActiveByUID(ctx context.Context, parentSpaceID int64, uid string) (*types.Repository, error)
// FindDeletedByUID finds a deleted repo by UID.
FindDeletedByUID(ctx context.Context, parentSpaceID int64, uid string, deletedAt int64) (*types.Repository, error)
FindDeletedByUID(
ctx context.Context,
parentSpaceID int64,
uid string,
deletedAt int64,
) (*types.Repository, error)
// Create a new repo.
Create(ctx context.Context, repo *types.Repository) error
@ -429,7 +434,11 @@ type (
Stream(ctx context.Context, opts *types.PullReqFilter) (<-chan *types.PullReq, <-chan error)
// ListOpenByBranchName returns open pull requests for each branch.
ListOpenByBranchName(ctx context.Context, repoID int64, branchNames []string) (map[string][]*types.PullReq, error)
ListOpenByBranchName(
ctx context.Context,
repoID int64,
branchNames []string,
) (map[string][]*types.PullReq, error)
}
PullReqActivityStore interface {

View File

@ -271,8 +271,10 @@ func (s *WebhookStore) Update(ctx context.Context, hook *types.Webhook) error {
}
// UpdateOptLock updates the webhook using the optimistic locking mechanism.
func (s *WebhookStore) UpdateOptLock(ctx context.Context, hook *types.Webhook,
mutateFn func(hook *types.Webhook) error) (*types.Webhook, error) {
func (s *WebhookStore) UpdateOptLock(
ctx context.Context, hook *types.Webhook,
mutateFn func(hook *types.Webhook) error,
) (*types.Webhook, error) {
for {
dup := *hook

View File

@ -23,7 +23,7 @@ import (
"strconv"
"strings"
"github.com/rs/zerolog/log"
"github.com/harness/gitness/app/paths"
)
const (
@ -80,6 +80,9 @@ type Provider interface {
RegistryURL(ctx context.Context, params ...string) string
GetUIBaseURL(ctx context.Context, params ...string) string
// GenerateUIRegistryURL returns the url for the UI screen of a registry.
GenerateUIRegistryURL(ctx context.Context, parentSpacePath string, registryName string) string
}
// Provider provides the URLs of the Harness system.
@ -239,12 +242,7 @@ func (p *provider) GetAPIProto(context.Context) string {
}
func (p *provider) RegistryURL(_ context.Context, params ...string) string {
u, err := url.Parse(p.registryURL.String())
if err != nil {
log.Warn().Msgf("failed to parse registry url: %v", err)
return p.registryURL.String()
}
u := *p.registryURL
segments := []string{u.Path}
if len(params) > 0 {
if len(params) > 1 && (params[1] == "generic" || params[1] == "maven") {
@ -263,6 +261,15 @@ func (p *provider) GetUIBaseURL(_ context.Context, _ ...string) string {
return p.uiURL.String()
}
func (p *provider) GenerateUIRegistryURL(_ context.Context, parentSpacePath string, registryName string) string {
segments := paths.Segments(parentSpacePath)
if len(segments) < 1 {
return ""
}
space := segments[0]
return p.uiURL.String() + "/spaces/" + space + "/registries/" + registryName
}
func BuildGITCloneSSHURL(user string, sshURL *url.URL, repoPath string) string {
repoPath = path.Clean(repoPath)
if !strings.HasSuffix(repoPath, GITSuffix) {

View File

@ -126,7 +126,9 @@ import (
"github.com/harness/gitness/livelog"
"github.com/harness/gitness/lock"
"github.com/harness/gitness/pubsub"
registryevents "github.com/harness/gitness/registry/app/events"
"github.com/harness/gitness/registry/app/pkg/docker"
registrywebhooks "github.com/harness/gitness/registry/services/webhook"
"github.com/harness/gitness/ssh"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
@ -273,6 +275,8 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
secretservice.WireSet,
runarg.WireSet,
usage.WireSet,
registryevents.WireSet,
registrywebhooks.WireSet,
)
return &cliserver.System{}, nil
}

View File

@ -119,6 +119,7 @@ import (
"github.com/harness/gitness/pubsub"
api2 "github.com/harness/gitness/registry/app/api"
"github.com/harness/gitness/registry/app/api/router"
events8 "github.com/harness/gitness/registry/app/events"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/docker"
"github.com/harness/gitness/registry/app/pkg/filemanager"
@ -127,6 +128,7 @@ import (
"github.com/harness/gitness/registry/app/pkg/pypi"
database2 "github.com/harness/gitness/registry/app/store/database"
"github.com/harness/gitness/registry/gc"
webhook3 "github.com/harness/gitness/registry/services/webhook"
"github.com/harness/gitness/ssh"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
@ -384,7 +386,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
webhookStore := database.ProvideWebhookStore(db)
webhookExecutionStore := database.ProvideWebhookExecutionStore(db)
urlProvider := webhook.ProvideURLProvider(ctx)
webhookService, err := webhook.ProvideService(ctx, webhookConfig, transactor, readerFactory, eventsReaderFactory, webhookStore, webhookExecutionStore, spaceStore, repoStore, pullReqStore, pullReqActivityStore, provider, principalStore, gitInterface, encrypter, labelStore, urlProvider, labelValueStore, streamer)
secretService := secret3.ProvideSecretService(secretStore, encrypter, spaceFinder)
webhookService, err := webhook.ProvideService(ctx, webhookConfig, transactor, readerFactory, eventsReaderFactory, webhookStore, webhookExecutionStore, spaceStore, repoStore, pullReqStore, pullReqActivityStore, provider, principalStore, gitInterface, encrypter, labelStore, urlProvider, labelValueStore, streamer, secretService, spacePathStore)
if err != nil {
return nil, err
}
@ -451,13 +454,16 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
layerRepository := database2.ProvideLayerDao(db, mediaTypesRepository)
eventReporter := docker.ProvideReporter()
ociImageIndexMappingRepository := database2.ProvideOCIImageIndexMappingDao(db)
manifestService := docker.ManifestServiceProvider(registryRepository, manifestRepository, blobRepository, mediaTypesRepository, manifestReferenceRepository, tagRepository, imageRepository, artifactRepository, layerRepository, gcService, transactor, eventReporter, spaceFinder, ociImageIndexMappingRepository)
reporter6, err := events8.ProvideArtifactReporter(eventsSystem)
if err != nil {
return nil, err
}
manifestService := docker.ManifestServiceProvider(registryRepository, manifestRepository, blobRepository, mediaTypesRepository, manifestReferenceRepository, tagRepository, imageRepository, artifactRepository, layerRepository, gcService, transactor, eventReporter, spaceFinder, ociImageIndexMappingRepository, reporter6, provider)
registryBlobRepository := database2.ProvideRegistryBlobDao(db)
bandwidthStatRepository := database2.ProvideBandwidthStatDao(db)
downloadStatRepository := database2.ProvideDownloadStatDao(db)
localRegistry := docker.LocalRegistryProvider(app, manifestService, blobRepository, registryRepository, manifestRepository, registryBlobRepository, mediaTypesRepository, tagRepository, imageRepository, artifactRepository, bandwidthStatRepository, downloadStatRepository, gcService, transactor)
upstreamProxyConfigRepository := database2.ProvideUpstreamDao(db, registryRepository, spaceFinder)
secretService := secret3.ProvideSecretService(secretStore, encrypter, spaceFinder)
proxyController := docker.ProvideProxyController(localRegistry, manifestService, secretService, spaceFinder)
remoteRegistry := docker.RemoteRegistryProvider(localRegistry, app, upstreamProxyConfigRepository, spaceFinder, secretService, proxyController)
coreController := pkg.CoreControllerProvider(registryRepository)
@ -471,7 +477,16 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
fileManager := filemanager.Provider(filemanagerApp, registryRepository, genericBlobRepository, nodesRepository, transactor)
cleanupPolicyRepository := database2.ProvideCleanupPolicyDao(db, transactor)
webhooksRepository := database2.ProvideWebhookDao(db)
apiHandler := router.APIHandlerProvider(registryRepository, upstreamProxyConfigRepository, fileManager, tagRepository, manifestRepository, cleanupPolicyRepository, imageRepository, storageDriver, spaceFinder, transactor, authenticator, provider, authorizer, auditService, artifactRepository, webhooksRepository)
webhooksExecutionRepository := database2.ProvideWebhookExecutionDao(db)
readerFactory2, err := events8.ProvideReaderFactory(eventsSystem)
if err != nil {
return nil, err
}
service2, err := webhook3.ProvideService(ctx, webhookConfig, transactor, readerFactory2, webhooksRepository, webhooksExecutionRepository, spaceStore, provider, principalStore, urlProvider, spacePathStore, secretService, registryRepository, encrypter)
if err != nil {
return nil, err
}
apiHandler := router.APIHandlerProvider(registryRepository, upstreamProxyConfigRepository, fileManager, tagRepository, manifestRepository, cleanupPolicyRepository, imageRepository, storageDriver, spaceFinder, transactor, authenticator, provider, authorizer, auditService, artifactRepository, webhooksRepository, webhooksExecutionRepository, service2, spacePathStore)
mavenDBStore := maven.DBStoreProvider(registryRepository, imageRepository, artifactRepository, spaceStore, bandwidthStatRepository, downloadStatRepository, nodesRepository, upstreamProxyConfigRepository)
mavenLocalRegistry := maven.LocalRegistryProvider(mavenDBStore, transactor, fileManager)
mavenController := maven.ProvideProxyController(mavenLocalRegistry, secretService, spaceFinder)
@ -514,11 +529,11 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
if err != nil {
return nil, err
}
readerFactory2, err := events2.ProvideReaderFactory(eventsSystem)
readerFactory3, err := events2.ProvideReaderFactory(eventsSystem)
if err != nil {
return nil, err
}
repoService, err := repo2.ProvideService(ctx, config, reporter, readerFactory2, repoStore, provider, gitInterface, lockerLocker)
repoService, err := repo2.ProvideService(ctx, config, reporter, readerFactory3, repoStore, provider, gitInterface, lockerLocker)
if err != nil {
return nil, err
}
@ -535,24 +550,24 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
return nil, err
}
keywordsearchConfig := server.ProvideKeywordSearchConfig(config)
keywordsearchService, err := keywordsearch.ProvideService(ctx, keywordsearchConfig, readerFactory, readerFactory2, repoStore, indexer)
keywordsearchService, err := keywordsearch.ProvideService(ctx, keywordsearchConfig, readerFactory, readerFactory3, repoStore, indexer)
if err != nil {
return nil, err
}
gitspaceeventConfig := server.ProvideGitspaceEventConfig(config)
readerFactory3, err := events3.ProvideReaderFactory(eventsSystem)
readerFactory4, err := events3.ProvideReaderFactory(eventsSystem)
if err != nil {
return nil, err
}
gitspaceeventService, err := gitspaceevent.ProvideService(ctx, gitspaceeventConfig, readerFactory3, gitspaceEventStore)
gitspaceeventService, err := gitspaceevent.ProvideService(ctx, gitspaceeventConfig, readerFactory4, gitspaceEventStore)
if err != nil {
return nil, err
}
readerFactory4, err := events4.ProvideReaderFactory(eventsSystem)
readerFactory5, err := events4.ProvideReaderFactory(eventsSystem)
if err != nil {
return nil, err
}
gitspaceinfraeventService, err := gitspaceinfraevent.ProvideService(ctx, gitspaceeventConfig, readerFactory4, orchestratorOrchestrator, gitspaceService, eventsReporter)
gitspaceinfraeventService, err := gitspaceinfraevent.ProvideService(ctx, gitspaceeventConfig, readerFactory5, orchestratorOrchestrator, gitspaceService, eventsReporter)
if err != nil {
return nil, err
}
@ -565,7 +580,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
if err != nil {
return nil, err
}
servicesServices := services.ProvideServices(webhookService, pullreqService, triggerService, jobScheduler, collector, sizeCalculator, repoService, cleanupService, notificationService, keywordsearchService, gitspaceServices, instrumentService, consumer, repositoryCount)
servicesServices := services.ProvideServices(webhookService, pullreqService, triggerService, jobScheduler, collector, sizeCalculator, repoService, cleanupService, notificationService, keywordsearchService, gitspaceServices, instrumentService, consumer, repositoryCount, service2)
serverSystem := server.NewSystem(bootstrapBootstrap, serverServer, sshServer, poller, resolverManager, servicesServices)
return serverSystem, nil
}

View File

@ -22,13 +22,12 @@ import (
"strconv"
"strings"
"github.com/harness/gitness/app/paths"
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
storagedriver "github.com/harness/gitness/registry/app/driver"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/storage"
"github.com/harness/gitness/registry/types"
registryenum "github.com/harness/gitness/registry/types/enum"
gitnessenum "github.com/harness/gitness/types/enum"
digest "github.com/opencontainers/go-digest"
"github.com/rs/zerolog/log"
@ -91,63 +90,6 @@ type ArtifactFilesRequestInfo struct {
searchTerm string
}
// GetRegistryRequestBaseInfo returns the base info for the registry request
// One of the regRefParam or (parentRefParam + regIdentifierParam) should be provided.
func (c *APIController) GetRegistryRequestBaseInfo(
ctx context.Context,
parentRef string,
regRef string,
) (*RegistryRequestBaseInfo, error) {
// ---------- CHECKS ------------
if commons.IsEmpty(parentRef) && !commons.IsEmpty(regRef) {
parentRef, _, _ = paths.DisectLeaf(regRef)
}
// ---------- PARENT ------------
if commons.IsEmpty(parentRef) {
return nil, fmt.Errorf("parent reference is required")
}
rootIdentifier, _, err := paths.DisectRoot(parentRef)
if err != nil {
return nil, fmt.Errorf("invalid parent reference: %w", err)
}
rootSpace, err := c.SpaceFinder.FindByRef(ctx, rootIdentifier)
if err != nil {
return nil, fmt.Errorf("root space not found: %w", err)
}
parentSpace, err := c.SpaceFinder.FindByRef(ctx, parentRef)
if err != nil {
return nil, fmt.Errorf("parent space not found: %w", err)
}
rootIdentifierID := rootSpace.ID
parentID := parentSpace.ID
baseInfo := &RegistryRequestBaseInfo{
ParentRef: parentRef,
parentID: parentID,
RootIdentifier: rootIdentifier,
rootIdentifierID: rootIdentifierID,
}
// ---------- REGISTRY ------------
if !commons.IsEmpty(regRef) {
_, regIdentifier, _ := paths.DisectLeaf(regRef)
reg, getRegistryErr := c.RegistryRepository.GetByParentIDAndName(ctx, parentID, regIdentifier)
if getRegistryErr != nil {
return nil, fmt.Errorf("registry not found: %w", err)
}
baseInfo.RegistryRef = regRef
baseInfo.RegistryIdentifier = regIdentifier
baseInfo.RegistryID = reg.ID
baseInfo.RegistryType = reg.Type
}
return baseInfo, nil
}
func (c *APIController) GetRegistryRequestInfo(
ctx context.Context,
registryRequestParams RegistryRequestParams,
@ -188,7 +130,8 @@ func (c *APIController) GetRegistryRequestInfo(
searchTerm = string(*registryRequestParams.search)
}
baseInfo, err := c.GetRegistryRequestBaseInfo(ctx, registryRequestParams.ParentRef, registryRequestParams.RegRef)
baseInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, registryRequestParams.ParentRef,
registryRequestParams.RegRef)
if err != nil {
return nil, err
}
@ -419,97 +362,14 @@ func CreateUpstreamProxyResponseJSONResponse(upstreamproxy *types.UpstreamProxy)
return response
}
func (c *APIController) mapToWebhookResponseEntity(
ctx context.Context,
createdWebhook types.Webhook,
) (*api.Webhook, error) {
createdAt := GetTimeInMs(createdWebhook.CreatedAt)
modifiedAt := GetTimeInMs(createdWebhook.UpdatedAt)
webhookResponseEntity := &api.Webhook{
Identifier: createdWebhook.Identifier,
Name: createdWebhook.Name,
Description: &createdWebhook.Description,
Url: createdWebhook.URL,
Version: &createdWebhook.Version,
Enabled: createdWebhook.Enabled,
Internal: &createdWebhook.Internal,
Insecure: createdWebhook.Insecure,
Triggers: &createdWebhook.Triggers,
CreatedBy: &createdWebhook.CreatedBy,
CreatedAt: &createdAt,
ModifiedAt: &modifiedAt,
LatestExecutionResult: createdWebhook.LatestExecutionResult,
}
if createdWebhook.ExtraHeaders != nil {
webhookResponseEntity.ExtraHeaders = &createdWebhook.ExtraHeaders
}
secretSpacePath := ""
if createdWebhook.SecretSpaceID > 0 {
primary, err := c.SpaceFinder.FindByID(ctx, int64(createdWebhook.SecretSpaceID))
if err != nil {
return nil, fmt.Errorf("failed to get secret space path: %w", err)
}
secretSpacePath = primary.Path
}
if createdWebhook.SecretIdentifier != "" {
webhookResponseEntity.SecretIdentifier = &createdWebhook.SecretIdentifier
}
if secretSpacePath != "" {
webhookResponseEntity.SecretSpacePath = &secretSpacePath
}
if createdWebhook.SecretSpaceID > 0 {
webhookResponseEntity.SecretSpaceId = &createdWebhook.SecretSpaceID
}
return webhookResponseEntity, nil
}
func (c *APIController) mapToWebhook(
ctx context.Context,
webhookRequest api.WebhookRequest,
regInfo *RegistryRequestBaseInfo,
) (*types.Webhook, error) {
webhook := &types.Webhook{
Name: webhookRequest.Identifier,
ParentType: registryenum.WebhookParentRegistry,
ParentID: regInfo.RegistryID,
Scope: webhookScopeRegistry,
Identifier: webhookRequest.Identifier,
URL: webhookRequest.Url,
Enabled: webhookRequest.Enabled,
Insecure: webhookRequest.Insecure,
Triggers: deduplicateTriggers(*webhookRequest.Triggers),
}
if webhookRequest.Description != nil {
webhook.Description = *webhookRequest.Description
}
if webhookRequest.SecretIdentifier != nil {
webhook.SecretIdentifier = *webhookRequest.SecretIdentifier
}
if webhookRequest.ExtraHeaders != nil {
webhook.ExtraHeaders = *webhookRequest.ExtraHeaders
}
if webhookRequest.SecretSpacePath != nil && len(*webhookRequest.SecretSpacePath) > 0 {
secretSpaceID, err := c.getSecretSpaceID(ctx, webhookRequest.SecretSpacePath)
if err != nil {
return nil, err
}
webhook.SecretSpaceID = secretSpaceID
} else if webhookRequest.SecretSpaceId != nil {
webhook.SecretSpaceID = *webhookRequest.SecretSpaceId
}
return webhook, nil
}
// deduplicateTriggers de-duplicates the triggers provided by the user.
func deduplicateTriggers(in []api.Trigger) []api.Trigger {
func deduplicateTriggers(in []gitnessenum.WebhookTrigger) []gitnessenum.WebhookTrigger {
if len(in) == 0 {
return []api.Trigger{}
return []gitnessenum.WebhookTrigger{}
}
triggerSet := make(map[api.Trigger]bool, len(in))
out := make([]api.Trigger, 0, len(in))
triggerSet := make(map[gitnessenum.WebhookTrigger]bool, len(in))
out := make([]gitnessenum.WebhookTrigger, 0, len(in))
for _, trigger := range in {
if triggerSet[trigger] {
continue
@ -547,7 +407,7 @@ func (c *APIController) GetArtifactFilesRequestInfo(
searchTerm = string(*r.Params.SearchTerm)
}
baseInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
baseInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return nil, err

View File

@ -22,28 +22,32 @@ import (
storagedriver "github.com/harness/gitness/registry/app/driver"
"github.com/harness/gitness/registry/app/pkg/filemanager"
"github.com/harness/gitness/registry/app/store"
registrywebhook "github.com/harness/gitness/registry/services/webhook"
"github.com/harness/gitness/store/database/dbtx"
)
// APIController simple struct.
type APIController struct {
ImageStore store.ImageRepository
fileManager filemanager.FileManager
BlobStore store.BlobRepository
GenericBlobStore store.GenericBlobRepository
RegistryRepository store.RegistryRepository
UpstreamProxyStore store.UpstreamProxyConfigRepository
TagStore store.TagRepository
ManifestStore store.ManifestRepository
CleanupPolicyStore store.CleanupPolicyRepository
SpaceFinder refcache.SpaceFinder
tx dbtx.Transactor
StorageDriver storagedriver.StorageDriver
URLProvider urlprovider.Provider
Authorizer authz.Authorizer
AuditService audit.Service
ArtifactStore store.ArtifactRepository
WebhooksRepository store.WebhooksRepository
ImageStore store.ImageRepository
fileManager filemanager.FileManager
BlobStore store.BlobRepository
GenericBlobStore store.GenericBlobRepository
RegistryRepository store.RegistryRepository
UpstreamProxyStore store.UpstreamProxyConfigRepository
TagStore store.TagRepository
ManifestStore store.ManifestRepository
CleanupPolicyStore store.CleanupPolicyRepository
SpaceFinder refcache.SpaceFinder
tx dbtx.Transactor
StorageDriver storagedriver.StorageDriver
URLProvider urlprovider.Provider
Authorizer authz.Authorizer
AuditService audit.Service
ArtifactStore store.ArtifactRepository
WebhooksRepository store.WebhooksRepository
WebhooksExecutionRepository store.WebhooksExecutionRepository
RegistryMetadataHelper RegistryMetadataHelper
WebhookService registrywebhook.Service
}
func NewAPIController(
@ -64,24 +68,30 @@ func NewAPIController(
auditService audit.Service,
artifactStore store.ArtifactRepository,
webhooksRepository store.WebhooksRepository,
webhooksExecutionRepository store.WebhooksExecutionRepository,
registryMetadataHelper RegistryMetadataHelper,
webhookService registrywebhook.Service,
) *APIController {
return &APIController{
fileManager: fileManager,
GenericBlobStore: genericBlobStore,
BlobStore: blobStore,
RegistryRepository: repositoryStore,
UpstreamProxyStore: upstreamProxyStore,
TagStore: tagStore,
ManifestStore: manifestStore,
CleanupPolicyStore: cleanupPolicyStore,
ImageStore: imageStore,
SpaceFinder: spaceFinder,
StorageDriver: driver,
tx: tx,
URLProvider: urlProvider,
Authorizer: authorizer,
AuditService: auditService,
ArtifactStore: artifactStore,
WebhooksRepository: webhooksRepository,
fileManager: fileManager,
GenericBlobStore: genericBlobStore,
BlobStore: blobStore,
RegistryRepository: repositoryStore,
UpstreamProxyStore: upstreamProxyStore,
TagStore: tagStore,
ManifestStore: manifestStore,
CleanupPolicyStore: cleanupPolicyStore,
ImageStore: imageStore,
SpaceFinder: spaceFinder,
StorageDriver: driver,
tx: tx,
URLProvider: urlProvider,
Authorizer: authorizer,
AuditService: auditService,
ArtifactStore: artifactStore,
WebhooksRepository: webhooksRepository,
WebhooksExecutionRepository: webhooksExecutionRepository,
RegistryMetadataHelper: registryMetadataHelper,
WebhookService: webhookService,
}
}

View File

@ -39,7 +39,7 @@ func (c *APIController) CreateRegistry(
registryRequest := artifact.RegistryRequest(*r.Body)
parentRef := artifact.SpaceRefPathParam(*registryRequest.ParentRef)
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, string(parentRef), "")
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, string(parentRef), "")
if err != nil {
return artifact.CreateRegistry400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -332,7 +332,7 @@ func (c *APIController) CreateUpstreamProxyEntity(
}
if res.SecretSpacePath != nil && len(*res.SecretSpacePath) > 0 {
upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretSpaceID(ctx, res.SecretSpacePath)
upstreamProxyConfigEntity.SecretSpaceID, err = c.RegistryMetadataHelper.getSecretSpaceID(ctx, res.SecretSpacePath)
if err != nil {
return nil, nil, err
}
@ -353,7 +353,8 @@ func (c *APIController) CreateUpstreamProxyEntity(
return nil, nil, fmt.Errorf("failed to create upstream proxy: access_key_secret_identifier missing")
default:
if res.AccessKeySecretSpacePath != nil && len(*res.AccessKeySecretSpacePath) > 0 {
upstreamProxyConfigEntity.UserNameSecretSpaceID, err = c.getSecretSpaceID(ctx, res.AccessKeySecretSpacePath)
upstreamProxyConfigEntity.UserNameSecretSpaceID, err =
c.RegistryMetadataHelper.getSecretSpaceID(ctx, res.AccessKeySecretSpacePath)
if err != nil {
return nil, nil, err
}
@ -364,7 +365,7 @@ func (c *APIController) CreateUpstreamProxyEntity(
}
if res.SecretKeySpacePath != nil && len(*res.SecretKeySpacePath) > 0 {
upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretSpaceID(ctx, res.SecretKeySpacePath)
upstreamProxyConfigEntity.SecretSpaceID, err = c.RegistryMetadataHelper.getSecretSpaceID(ctx, res.SecretKeySpacePath)
if err != nil {
return nil, nil, err
}

View File

@ -34,7 +34,10 @@ func (c *APIController) CreateWebhook(
r api.CreateWebhookRequestObject,
) (api.CreateWebhookResponseObject, error) {
webhookRequest := api.WebhookRequest(*r.Body)
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if webhookRequest.Identifier == internalWebhookIdentifier {
return createWebhookBadRequestErrorResponse(fmt.Errorf("webhook identifier %s is reserved", internalWebhookIdentifier))
}
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return createWebhookBadRequestErrorResponse(err)
}
@ -49,7 +52,8 @@ func (c *APIController) CreateWebhook(
return createWebhookBadRequestErrorResponse(err)
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryEdit)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryEdit)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
@ -65,8 +69,8 @@ func (c *APIController) CreateWebhook(
}, err
}
webhook, err := c.mapToWebhook(ctx, webhookRequest, regInfo)
webhook.Internal = false
webhook, err := c.RegistryMetadataHelper.MapToWebhookCore(ctx, webhookRequest, regInfo)
webhook.Type = enum.WebhookTypeExternal
webhook.CreatedBy = session.Principal.ID
if err != nil {
log.Ctx(ctx).Error().Msgf("failed to store webhook: %s with error: %v", webhookRequest.Identifier, err)
@ -93,7 +97,7 @@ func (c *APIController) CreateWebhook(
return createWebhookInternalErrorResponse(fmt.Errorf("failed to stored webhook: %w", err))
}
webhookResponseEntity, err := c.mapToWebhookResponseEntity(ctx, *createdWebhook)
webhookResponseEntity, err := c.RegistryMetadataHelper.MapToWebhookResponseEntity(ctx, createdWebhook)
if err != nil {
log.Ctx(ctx).Error().Msgf("failed to stored webhook: %s with error: %v",
webhookRequest.Identifier, err)

View File

@ -31,7 +31,7 @@ import (
func (c *APIController) DeleteArtifact(ctx context.Context, r artifact.DeleteArtifactRequestObject) (
artifact.DeleteArtifactResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.DeleteArtifact400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(

View File

@ -30,7 +30,7 @@ import (
func (c *APIController) DeleteArtifactVersion(ctx context.Context, r artifact.DeleteArtifactVersionRequestObject) (
artifact.DeleteArtifactVersionResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.DeleteArtifactVersion400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -88,7 +88,8 @@ func (c *APIController) DeleteArtifactVersion(ctx context.Context, r artifact.De
func (c *APIController) deleteTagWithAudit(
ctx context.Context, regInfo *RegistryRequestBaseInfo,
registryName string, principal types.Principal, artifactName string, versionName string) error {
registryName string, principal types.Principal, artifactName string, versionName string,
) error {
err := c.TagStore.DeleteTag(ctx, regInfo.RegistryID, artifactName, versionName)
if err != nil {
return err

View File

@ -36,7 +36,7 @@ func (c *APIController) DeleteRegistry(
ctx context.Context,
r artifact.DeleteRegistryRequestObject,
) (artifact.DeleteRegistryResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.DeleteRegistry400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -54,7 +54,8 @@ func (c *APIController) DeleteRegistry(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryDelete)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryDelete)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -16,19 +16,22 @@ package metadata
import (
"context"
"fmt"
"net/http"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/request"
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
func (c *APIController) DeleteWebhook(
ctx context.Context,
r api.DeleteWebhookRequestObject,
) (api.DeleteWebhookResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return deleteWebhookInternalErrorResponse(err)
}
@ -39,7 +42,8 @@ func (c *APIController) DeleteWebhook(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryEdit)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryEdit)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
@ -54,6 +58,16 @@ func (c *APIController) DeleteWebhook(
}
webhookIdentifier := string(r.WebhookIdentifier)
existingWebhook, err := c.WebhooksRepository.GetByRegistryAndIdentifier(ctx, regInfo.RegistryID, webhookIdentifier)
if err != nil {
log.Ctx(ctx).Error().Msgf("failed to get existing webhook: %s with error: %v",
webhookIdentifier, err)
return deleteWebhookInternalErrorResponse(fmt.Errorf("failed to get existing webhook"))
}
if existingWebhook.Type == enum.WebhookTypeInternal {
return deleteWebhookBadRequestErrorResponse(fmt.Errorf("cannot delete internal webhook: %s", webhookIdentifier))
}
err = c.WebhooksRepository.DeleteByRegistryAndIdentifier(ctx, regInfo.RegistryID, webhookIdentifier)
if err != nil {
return deleteWebhookInternalErrorResponse(err)
@ -70,3 +84,11 @@ func deleteWebhookInternalErrorResponse(err error) (api.DeleteWebhookResponseObj
),
}, err
}
func deleteWebhookBadRequestErrorResponse(err error) (api.DeleteWebhookResponseObject, error) {
return api.DeleteWebhook400JSONResponse{
BadRequestJSONResponse: api.BadRequestJSONResponse(
*GetErrorResponse(http.StatusBadRequest, err.Error()),
),
}, err
}

View File

@ -30,7 +30,7 @@ func (c *APIController) GetArtifactDetails(
ctx context.Context,
r artifact.GetArtifactDetailsRequestObject,
) (artifact.GetArtifactDetailsResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.GetArtifactDetails400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -49,7 +49,8 @@ func (c *APIController) GetArtifactDetails(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -51,7 +51,8 @@ func (c *APIController) GetArtifactFiles(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, reqInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
reqInfo.RegistryIdentifier, enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
@ -109,7 +110,7 @@ func (c *APIController) GetArtifactFiles(
reqInfo.sortByField, reqInfo.sortByOrder, reqInfo.limit, reqInfo.offset, reqInfo.searchTerm)
if err != nil {
log.Error().Msgf(err.Error())
log.Error().Msgf("Failed to fetch files for artifact, err: %v", err.Error())
return artifact.GetArtifactFiles500JSONResponse{
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
*GetErrorResponse(http.StatusInternalServerError,
@ -121,7 +122,7 @@ func (c *APIController) GetArtifactFiles(
count, err := c.fileManager.CountFilesByPath(ctx, filePathPrefix, img.RegistryID)
if err != nil {
log.Error().Msgf(err.Error())
log.Error().Msgf("Failed to count files for artifact, err: %v", err.Error())
return artifact.GetArtifactFiles500JSONResponse{
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
*GetErrorResponse(http.StatusInternalServerError,

View File

@ -24,6 +24,7 @@ import (
"github.com/harness/gitness/types/enum"
)
//nolint:nilnil
func (c *APIController) GetArtifactStats(
_ context.Context,
_ artifact.GetArtifactStatsRequestObject,
@ -31,12 +32,13 @@ func (c *APIController) GetArtifactStats(
return nil, nil
}
//nolint:nilnil
func (c *APIController) GetArtifactStatsForSpace(
ctx context.Context,
r artifact.GetArtifactStatsForSpaceRequestObject,
) (artifact.GetArtifactStatsForSpaceResponseObject, error) {
parentRef := r.SpaceRef
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, string(parentRef), "")
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, string(parentRef), "")
if err != nil {
return artifact.GetArtifactStatsForSpace400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -55,7 +57,8 @@ func (c *APIController) GetArtifactStatsForSpace(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
@ -71,11 +74,12 @@ func (c *APIController) GetArtifactStatsForSpace(
return nil, nil
}
//nolint:nilnil
func (c *APIController) GetArtifactStatsForRegistry(
ctx context.Context,
r artifact.GetArtifactStatsForRegistryRequestObject,
) (artifact.GetArtifactStatsForRegistryResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.GetArtifactStatsForRegistry400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(

View File

@ -57,7 +57,8 @@ func (c *APIController) GetAllArtifacts(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -34,7 +34,7 @@ func (c *APIController) GetDockerArtifactDetails(
ctx context.Context,
r artifact.GetDockerArtifactDetailsRequestObject,
) (artifact.GetDockerArtifactDetailsResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.GetDockerArtifactDetails400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -53,7 +53,8 @@ func (c *APIController) GetDockerArtifactDetails(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -47,7 +47,7 @@ func (c *APIController) GetDockerArtifactLayers(
ctx context.Context,
r artifact.GetDockerArtifactLayersRequestObject,
) (artifact.GetDockerArtifactLayersResponseObject, error) {
regInfo, _ := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, _ := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
space, err := c.SpaceFinder.FindByRef(ctx, regInfo.ParentRef)
if err != nil {
@ -59,7 +59,8 @@ func (c *APIController) GetDockerArtifactLayers(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -31,7 +31,7 @@ func (c *APIController) GetDockerArtifactManifest(
ctx context.Context,
r artifact.GetDockerArtifactManifestRequestObject,
) (artifact.GetDockerArtifactManifestResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.GetDockerArtifactManifest400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -50,7 +50,8 @@ func (c *APIController) GetDockerArtifactManifest(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -39,7 +39,7 @@ func (c *APIController) GetDockerArtifactManifests(
ctx context.Context,
r artifact.GetDockerArtifactManifestsRequestObject,
) (artifact.GetDockerArtifactManifestsResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.GetDockerArtifactManifests400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -58,7 +58,8 @@ func (c *APIController) GetDockerArtifactManifests(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
@ -136,7 +137,8 @@ func artifactManifestsErrorRs(err error) artifact.GetDockerArtifactManifestsResp
}
func getManifestDetails(
m *types.Manifest, mConfig *manifestConfig, downloadsCount int64) artifact.DockerManifestDetails {
m *types.Manifest, mConfig *manifestConfig, downloadsCount int64,
) artifact.DockerManifestDetails {
createdAt := GetTimeInMs(m.CreatedAt)
size := GetSize(m.TotalSize)

View File

@ -31,7 +31,7 @@ func (c *APIController) GetHelmArtifactDetails(
ctx context.Context,
r artifact.GetHelmArtifactDetailsRequestObject,
) (artifact.GetHelmArtifactDetailsResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.GetHelmArtifactDetails400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -49,7 +49,8 @@ func (c *APIController) GetHelmArtifactDetails(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -30,7 +30,7 @@ func (c *APIController) GetHelmArtifactManifest(
ctx context.Context,
r artifact.GetHelmArtifactManifestRequestObject,
) (artifact.GetHelmArtifactManifestResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return c.get400Error(err)
}
@ -45,7 +45,8 @@ func (c *APIController) GetHelmArtifactManifest(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -61,7 +61,8 @@ func (c *APIController) ListArtifactLabels(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -29,7 +29,7 @@ func (c *APIController) GetArtifactSummary(
ctx context.Context,
r artifact.GetArtifactSummaryRequestObject,
) (artifact.GetArtifactSummaryResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.GetArtifactSummary400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -48,7 +48,8 @@ func (c *APIController) GetArtifactSummary(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -48,7 +48,7 @@ func (c *APIController) FetchArtifactSummary(
ctx context.Context,
r artifact.GetArtifactVersionSummaryRequestObject,
) (string, string, artifact.PackageType, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return "", "", "", fmt.Errorf("failed to get registry request base info: %w", err)
@ -60,7 +60,8 @@ func (c *APIController) FetchArtifactSummary(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -61,7 +61,8 @@ func (c *APIController) GetAllArtifactVersions(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -35,7 +35,7 @@ func (c *APIController) GetClientSetupDetails(
imageParam := r.Params.Artifact
tagParam := r.Params.Version
regInfo, _ := c.GetRegistryRequestBaseInfo(ctx, "", string(regRefParam))
regInfo, _ := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(regRefParam))
space, err := c.SpaceFinder.FindByRef(ctx, regInfo.ParentRef)
if err != nil {
@ -47,7 +47,8 @@ func (c *APIController) GetClientSetupDetails(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -28,7 +28,7 @@ func (c *APIController) GetRegistry(
ctx context.Context,
r artifact.GetRegistryRequestObject,
) (artifact.GetRegistryResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.GetRegistry400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -46,7 +46,8 @@ func (c *APIController) GetRegistry(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -57,7 +57,8 @@ func (c *APIController) GetAllArtifactsByRegistry(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -31,7 +31,7 @@ func (c *APIController) GetWebhook(
ctx context.Context,
r api.GetWebhookRequestObject,
) (api.GetWebhookResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
log.Ctx(ctx).Error().Msgf("failed to get registry details: %v", err)
return getWebhookInternalErrorResponse(err)
@ -44,7 +44,8 @@ func (c *APIController) GetWebhook(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
@ -67,7 +68,7 @@ func (c *APIController) GetWebhook(
return getWebhookInternalErrorResponse(fmt.Errorf("failed to get webhook"))
}
webhookResponseEntity, err := c.mapToWebhookResponseEntity(ctx, *webhook)
webhookResponseEntity, err := c.RegistryMetadataHelper.MapToWebhookResponseEntity(ctx, webhook)
if err != nil {
log.Ctx(ctx).Error().Msgf("failed to get webhook: %s with error: %v", webhookIdentifier, err)
return getWebhookInternalErrorResponse(fmt.Errorf("failed to get webhook"))

View File

@ -16,18 +16,82 @@ package metadata
import (
"context"
"net/http"
"strconv"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/request"
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
const getWebhookErrMsg = "failed to get webhook execution for registry: %s, webhook: %s with error: %v"
func (c *APIController) GetWebhookExecution(
_ context.Context,
_ api.GetWebhookExecutionRequestObject,
ctx context.Context,
r api.GetWebhookExecutionRequestObject,
) (api.GetWebhookExecutionResponseObject, error) {
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return getWebhooksExecutionsInternalErrorResponse(err)
}
space, err := c.SpaceFinder.FindByRef(ctx, regInfo.ParentRef)
if err != nil {
return getWebhooksExecutionsInternalErrorResponse(err)
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
session,
permissionChecks...,
); err != nil {
log.Ctx(ctx).Error().Msgf("permission check failed while get webhook execution for registry: %s, error: %v",
regInfo.RegistryIdentifier, err)
return api.GetWebhookExecution403JSONResponse{
UnauthorizedJSONResponse: api.UnauthorizedJSONResponse(
*GetErrorResponse(http.StatusForbidden, err.Error()),
),
}, err
}
webhookExecutionID, err := strconv.ParseInt(string(r.WebhookExecutionId), 10, 64)
if err != nil || webhookExecutionID <= 0 {
log.Ctx(ctx).Error().Msgf("invalid webhook execution identifier: %s, err: %v", string(r.WebhookExecutionId), err)
return api.GetWebhookExecution400JSONResponse{
BadRequestJSONResponse: api.BadRequestJSONResponse(
*GetErrorResponse(http.StatusBadRequest, err.Error()),
),
}, err
}
w, err := c.WebhooksExecutionRepository.Find(ctx, webhookExecutionID)
if err != nil {
log.Ctx(ctx).Error().Msgf(getWebhookErrMsg, regInfo.RegistryRef, r.WebhookIdentifier, err)
return getWebhooksExecutionsInternalErrorResponse(err)
}
webhookExecution, err := MapToWebhookExecutionResponseEntity(*w)
if err != nil {
log.Ctx(ctx).Error().Msgf(getWebhookErrMsg, regInfo.RegistryRef, r.WebhookIdentifier, err)
return getWebhooksExecutionsInternalErrorResponse(err)
}
return api.GetWebhookExecution200JSONResponse{
WebhookExecutionResponseJSONResponse: api.WebhookExecutionResponseJSONResponse{
Data: api.WebhookExecution{},
Data: *webhookExecution,
Status: api.StatusSUCCESS,
},
}, nil
}
func getWebhooksExecutionsInternalErrorResponse(err error) (api.GetWebhookExecution500JSONResponse, error) {
return api.GetWebhookExecution500JSONResponse{
InternalServerErrorJSONResponse: api.InternalServerErrorJSONResponse(
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
),
}, err
}

View File

@ -16,18 +16,168 @@ package metadata
import (
"context"
"net/http"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/request"
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
gitnesstypes "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
const listWebhooksErrMsg = "failed to list webhooks executions for registry: %s, webhook: %s with error: %v"
func (c *APIController) ListWebhookExecutions(
_ context.Context,
_ api.ListWebhookExecutionsRequestObject,
ctx context.Context,
r api.ListWebhookExecutionsRequestObject,
) (api.ListWebhookExecutionsResponseObject, error) {
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return listWebhooksExecutionsInternalErrorResponse(err)
}
space, err := c.SpaceFinder.FindByRef(ctx, regInfo.ParentRef)
if err != nil {
return listWebhooksExecutionsInternalErrorResponse(err)
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
session,
permissionChecks...,
); err != nil {
log.Ctx(ctx).Error().Msgf("permission check failed while listing webhooks for registry: %s, error: %v",
regInfo.RegistryIdentifier, err)
return api.ListWebhookExecutions403JSONResponse{
UnauthorizedJSONResponse: api.UnauthorizedJSONResponse(
*GetErrorResponse(http.StatusForbidden, err.Error()),
),
}, err
}
size := GetOffset(r.Params.Size, r.Params.Page)
limit := GetPageLimit(r.Params.Size)
pageNumber := GetPageNumber(r.Params.Page)
reg, err := c.RegistryRepository.GetByParentIDAndName(ctx, space.ID, regInfo.RegistryIdentifier)
if err != nil {
log.Ctx(ctx).Error().Msgf(listWebhooksErrMsg, regInfo.RegistryRef, r.WebhookIdentifier, err)
return listWebhooksExecutionsInternalErrorResponse(err)
}
webhook, err := c.WebhooksRepository.GetByRegistryAndIdentifier(ctx, reg.ID, string(r.WebhookIdentifier))
if err != nil {
log.Ctx(ctx).Error().Msgf(listWebhooksErrMsg, regInfo.RegistryRef, r.WebhookIdentifier, err)
return listWebhooksExecutionsInternalErrorResponse(err)
}
we, err := c.WebhooksExecutionRepository.ListForWebhook(ctx, webhook.ID, limit, int(pageNumber), size)
if err != nil {
log.Ctx(ctx).Error().Msgf(listWebhooksErrMsg, regInfo.RegistryRef, r.WebhookIdentifier, err)
return listWebhooksExecutionsInternalErrorResponse(err)
}
webhookExecutions, err := mapToAPIListWebhooksExecutions(we)
if err != nil {
log.Ctx(ctx).Error().Msgf(listWebhooksErrMsg, regInfo.RegistryRef, r.WebhookIdentifier, err)
return listWebhooksExecutionsInternalErrorResponse(err)
}
count, err := c.WebhooksExecutionRepository.CountForWebhook(ctx, webhook.ID)
if err != nil {
log.Ctx(ctx).Error().Msgf(listWebhooksErrMsg, regInfo.RegistryRef, r.WebhookIdentifier, err)
return listWebhooksExecutionsInternalErrorResponse(err)
}
pageCount := GetPageCount(count, limit)
currentPageSize := len(webhookExecutions)
return api.ListWebhookExecutions200JSONResponse{
ListWebhooksExecutionResponseJSONResponse: api.ListWebhooksExecutionResponseJSONResponse{
Data: api.ListWebhooksExecutions{},
Data: api.ListWebhooksExecutions{
Executions: webhookExecutions,
ItemCount: &count,
PageCount: &pageCount,
PageIndex: &pageNumber,
PageSize: &currentPageSize,
},
Status: api.StatusSUCCESS,
},
}, nil
}
func listWebhooksExecutionsInternalErrorResponse(err error) (api.ListWebhookExecutionsResponseObject, error) {
return api.ListWebhookExecutions500JSONResponse{
InternalServerErrorJSONResponse: api.InternalServerErrorJSONResponse(
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
),
}, err
}
func mapToAPIListWebhooksExecutions(executions []*gitnesstypes.WebhookExecutionCore) ([]api.WebhookExecution, error) {
webhooksExecutionEntities := make([]api.WebhookExecution, 0, len(executions))
for _, e := range executions {
webhookExecution, err := MapToWebhookExecutionResponseEntity(*e)
if err != nil {
return nil, err
}
webhooksExecutionEntities = append(webhooksExecutionEntities, *webhookExecution)
}
return webhooksExecutionEntities, nil
}
func MapToWebhookExecutionResponseEntity(
execution gitnesstypes.WebhookExecutionCore,
) (*api.WebhookExecution, error) {
webhookResponseEntity := api.WebhookExecution{
Created: &execution.Created,
Duration: &execution.Duration,
Id: &execution.ID,
Error: &execution.Error,
Request: &api.WebhookExecRequest{
Body: &execution.Request.Body,
Headers: &execution.Request.Headers,
Url: &execution.Request.URL,
},
Response: &api.WebhookExecResponse{
Body: &execution.Response.Body,
Headers: &execution.Response.Headers,
Status: &execution.Response.Status,
StatusCode: &execution.Response.StatusCode,
},
RetriggerOf: execution.RetriggerOf,
Retriggerable: &execution.Retriggerable,
WebhookId: &execution.WebhookID,
}
webhookExecResult := mapTpAPIExecutionResult(execution.Result)
if webhookExecResult != "" {
webhookResponseEntity.Result = &webhookExecResult
}
triggerType := mapTpAPITriggerType(execution.TriggerType)
if triggerType != "" {
webhookResponseEntity.TriggerType = &triggerType
}
return &webhookResponseEntity, nil
}
func mapTpAPIExecutionResult(result enum.WebhookExecutionResult) api.WebhookExecResult {
switch result {
case enum.WebhookExecutionResultSuccess:
return api.WebhookExecResultSUCCESS
case enum.WebhookExecutionResultFatalError:
return api.WebhookExecResultFATALERROR
case enum.WebhookExecutionResultRetriableError:
return api.WebhookExecResultRETRIABLEERROR
}
return ""
}
func mapTpAPITriggerType(trigger enum.WebhookTrigger) api.Trigger {
switch trigger {
case enum.WebhookTriggerArtifactCreated:
return api.TriggerARTIFACTCREATION
case enum.WebhookTriggerArtifactUpdated:
return api.TriggerARTIFACTMODIFICATION
case enum.WebhookTriggerArtifactDeleted:
return api.TriggerARTIFACTDELETION
}
return ""
}

View File

@ -22,7 +22,7 @@ import (
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/request"
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/registry/types"
gitnesstypes "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
@ -32,7 +32,7 @@ func (c *APIController) ListWebhooks(
ctx context.Context,
r api.ListWebhooksRequestObject,
) (api.ListWebhooksResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return listWebhookInternalErrorResponse(err)
}
@ -43,7 +43,8 @@ func (c *APIController) ListWebhooks(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryView)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
@ -126,11 +127,11 @@ func listWebhookInternalErrorResponse(err error) (api.ListWebhooksResponseObject
func (c *APIController) mapToListWebhookResponseEntity(
ctx context.Context,
webhooks *[]types.Webhook,
webhooks []*gitnesstypes.WebhookCore,
) ([]api.Webhook, error) {
webhooksEntities := make([]api.Webhook, 0, len(*webhooks))
for _, d := range *webhooks {
webhook, err := c.mapToWebhookResponseEntity(ctx, d)
webhooksEntities := make([]api.Webhook, 0, len(webhooks))
for _, d := range webhooks {
webhook, err := c.RegistryMetadataHelper.MapToWebhookResponseEntity(ctx, d)
if err != nil {
return nil, err
}

View File

@ -0,0 +1,296 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metadata
import (
"context"
"fmt"
"strconv"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/app/services/refcache"
corestore "github.com/harness/gitness/app/store"
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
type RegistryMetadataHelper struct {
spacePathStore corestore.SpacePathStore
spaceFinder refcache.SpaceFinder
registryRepository store.RegistryRepository
}
func NewRegistryMetadataHelper(
spacePathStore corestore.SpacePathStore,
spaceFinder refcache.SpaceFinder,
registryRepository store.RegistryRepository,
) *RegistryMetadataHelper {
return &RegistryMetadataHelper{
spacePathStore: spacePathStore,
spaceFinder: spaceFinder,
registryRepository: registryRepository,
}
}
func (r *RegistryMetadataHelper) getSecretSpaceID(ctx context.Context, secretSpacePath *string) (int, error) {
if secretSpacePath == nil {
return -1, fmt.Errorf("secret space path is missing")
}
path, err := r.spacePathStore.FindByPath(ctx, *secretSpacePath)
if err != nil {
return -1, fmt.Errorf("failed to get Space Path: %w", err)
}
return int(path.SpaceID), nil
}
// GetRegistryRequestBaseInfo returns the base info for the registry request
// One of the regRefParam or (parentRefParam + regIdentifierParam) should be provided.
func (r *RegistryMetadataHelper) GetRegistryRequestBaseInfo(
ctx context.Context,
parentRef string,
regRef string,
) (*RegistryRequestBaseInfo, error) {
// ---------- CHECKS ------------
if commons.IsEmpty(parentRef) && !commons.IsEmpty(regRef) {
parentRef, _, _ = paths.DisectLeaf(regRef)
}
// ---------- PARENT ------------
if commons.IsEmpty(parentRef) {
return nil, fmt.Errorf("parent reference is required")
}
rootIdentifier, _, err := paths.DisectRoot(parentRef)
if err != nil {
return nil, fmt.Errorf("invalid parent reference: %w", err)
}
rootSpace, err := r.spaceFinder.FindByRef(ctx, rootIdentifier)
if err != nil {
return nil, fmt.Errorf("root space not found: %w", err)
}
parentSpace, err := r.spaceFinder.FindByRef(ctx, parentRef)
if err != nil {
return nil, fmt.Errorf("parent space not found: %w", err)
}
rootIdentifierID := rootSpace.ID
parentID := parentSpace.ID
baseInfo := &RegistryRequestBaseInfo{
ParentRef: parentRef,
parentID: parentID,
RootIdentifier: rootIdentifier,
rootIdentifierID: rootIdentifierID,
}
// ---------- REGISTRY ------------
if !commons.IsEmpty(regRef) {
_, regIdentifier, _ := paths.DisectLeaf(regRef)
reg, getRegistryErr := r.registryRepository.GetByParentIDAndName(ctx, parentID, regIdentifier)
if getRegistryErr != nil {
return nil, fmt.Errorf("registry not found: %w", err)
}
baseInfo.RegistryRef = regRef
baseInfo.RegistryIdentifier = regIdentifier
baseInfo.RegistryID = reg.ID
baseInfo.RegistryType = reg.Type
}
return baseInfo, nil
}
func (r *RegistryMetadataHelper) GetPermissionChecks(
space *types.SpaceCore,
registryIdentifier string,
permission enum.Permission,
) []types.PermissionCheck {
var permissionChecks []types.PermissionCheck
permissionCheck := &types.PermissionCheck{
Scope: types.Scope{SpacePath: space.Path},
Resource: types.Resource{Type: enum.ResourceTypeRegistry, Identifier: registryIdentifier},
Permission: permission,
}
permissionChecks = append(permissionChecks, *permissionCheck)
return permissionChecks
}
func (r *RegistryMetadataHelper) MapToWebhookCore(
ctx context.Context,
webhookRequest api.WebhookRequest,
regInfo *RegistryRequestBaseInfo,
) (*types.WebhookCore, error) {
webhook := &types.WebhookCore{
DisplayName: webhookRequest.Name,
ParentType: enum.WebhookParentRegistry,
ParentID: regInfo.RegistryID,
Scope: webhookScopeRegistry,
Identifier: webhookRequest.Identifier,
URL: webhookRequest.Url,
Enabled: webhookRequest.Enabled,
Insecure: webhookRequest.Insecure,
}
if webhookRequest.Triggers != nil {
triggers := r.MapToInternalWebhookTriggers(*webhookRequest.Triggers)
webhook.Triggers = deduplicateTriggers(triggers)
}
if webhookRequest.Description != nil {
webhook.Description = *webhookRequest.Description
}
if webhookRequest.SecretIdentifier != nil {
webhook.SecretIdentifier = *webhookRequest.SecretIdentifier
}
if webhookRequest.SecretSpacePath != nil && len(*webhookRequest.SecretSpacePath) > 0 {
secretSpaceID, err := r.getSecretSpaceID(ctx, webhookRequest.SecretSpacePath)
if err != nil {
return nil, err
}
webhook.SecretSpaceID = secretSpaceID
} else if webhookRequest.SecretSpaceId != nil {
webhook.SecretSpaceID = *webhookRequest.SecretSpaceId
}
if webhookRequest.ExtraHeaders != nil {
webhook.ExtraHeaders = mapToDTOHeaders(webhookRequest.ExtraHeaders)
}
return webhook, nil
}
func mapToDTOHeaders(extraHeaders *[]api.ExtraHeader) []types.ExtraHeader {
var headers []types.ExtraHeader
for _, h := range *extraHeaders {
headers = append(headers, types.ExtraHeader{Key: h.Key, Value: h.Value})
}
return headers
}
func (r *RegistryMetadataHelper) MapToWebhookResponseEntity(
ctx context.Context,
createdWebhook *types.WebhookCore,
) (*api.Webhook, error) {
createdAt := strconv.FormatInt(createdWebhook.Created, 10)
modifiedAt := strconv.FormatInt(createdWebhook.Updated, 10)
triggers := r.MapToAPIWebhookTriggers(createdWebhook.Triggers)
webhookResponseEntity := &api.Webhook{
Identifier: createdWebhook.Identifier,
Name: createdWebhook.DisplayName,
Description: &createdWebhook.Description,
Url: createdWebhook.URL,
Version: &createdWebhook.Version,
Enabled: createdWebhook.Enabled,
Insecure: createdWebhook.Insecure,
Triggers: &triggers,
CreatedBy: &createdWebhook.CreatedBy,
CreatedAt: &createdAt,
ModifiedAt: &modifiedAt,
}
isInternal := false
if createdWebhook.Type == enum.WebhookTypeInternal {
isInternal = true
} else {
isInternal = false
}
webhookResponseEntity.Internal = &isInternal
if createdWebhook.LatestExecutionResult != nil {
result := r.MapToAPIExecutionResult(*createdWebhook.LatestExecutionResult)
webhookResponseEntity.LatestExecutionResult = &result
}
if createdWebhook.ExtraHeaders != nil {
extraHeaders := r.MapToAPIExtraHeaders(createdWebhook.ExtraHeaders)
webhookResponseEntity.ExtraHeaders = &extraHeaders
}
secretSpacePath := ""
if createdWebhook.SecretSpaceID > 0 {
primary, err := r.spacePathStore.FindPrimaryBySpaceID(ctx, int64(createdWebhook.SecretSpaceID))
if err != nil {
return nil, fmt.Errorf("failed to get secret space path: %w", err)
}
secretSpacePath = primary.Value
}
if createdWebhook.SecretIdentifier != "" {
webhookResponseEntity.SecretIdentifier = &createdWebhook.SecretIdentifier
}
if secretSpacePath != "" {
webhookResponseEntity.SecretSpacePath = &secretSpacePath
}
if createdWebhook.SecretSpaceID > 0 {
webhookResponseEntity.SecretSpaceId = &createdWebhook.SecretSpaceID
}
return webhookResponseEntity, nil
}
func (r *RegistryMetadataHelper) MapToInternalWebhookTriggers(
triggers []api.Trigger,
) []enum.WebhookTrigger {
var webhookTriggers = make([]enum.WebhookTrigger, 0)
for _, trigger := range triggers {
switch trigger {
case api.TriggerARTIFACTCREATION:
webhookTriggers = append(webhookTriggers, enum.WebhookTriggerArtifactCreated)
case api.TriggerARTIFACTMODIFICATION:
webhookTriggers = append(webhookTriggers, enum.WebhookTriggerArtifactUpdated)
case api.TriggerARTIFACTDELETION:
webhookTriggers = append(webhookTriggers, enum.WebhookTriggerArtifactDeleted)
}
}
return webhookTriggers
}
func (r *RegistryMetadataHelper) MapToAPIExecutionResult(result enum.WebhookExecutionResult) api.WebhookExecResult {
switch result {
case enum.WebhookExecutionResultSuccess:
return api.WebhookExecResultSUCCESS
case enum.WebhookExecutionResultRetriableError:
return api.WebhookExecResultRETRIABLEERROR
case enum.WebhookExecutionResultFatalError:
return api.WebhookExecResultFATALERROR
}
return ""
}
func (r *RegistryMetadataHelper) MapToAPIWebhookTriggers(triggers []enum.WebhookTrigger) []api.Trigger {
var webhookTriggers = make([]api.Trigger, 0)
for _, trigger := range triggers {
//nolint:exhaustive
switch trigger {
case enum.WebhookTriggerArtifactCreated:
webhookTriggers = append(webhookTriggers, api.TriggerARTIFACTCREATION)
case enum.WebhookTriggerArtifactUpdated:
webhookTriggers = append(webhookTriggers, api.TriggerARTIFACTMODIFICATION)
case enum.WebhookTriggerArtifactDeleted:
webhookTriggers = append(webhookTriggers, api.TriggerARTIFACTDELETION)
}
}
return webhookTriggers
}
func (r *RegistryMetadataHelper) MapToAPIExtraHeaders(headers []types.ExtraHeader) []api.ExtraHeader {
apiHeaders := make([]api.ExtraHeader, 0)
for _, h := range headers {
apiHeaders = append(apiHeaders, api.ExtraHeader{Key: h.Key, Value: h.Value})
}
return apiHeaders
}

View File

@ -16,18 +16,80 @@ package metadata
import (
"context"
"net/http"
"strconv"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/request"
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
func (c *APIController) ReTriggerWebhookExecution(
_ context.Context,
_ api.ReTriggerWebhookExecutionRequestObject,
ctx context.Context,
r api.ReTriggerWebhookExecutionRequestObject,
) (api.ReTriggerWebhookExecutionResponseObject, error) {
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return getReTriggerWebhooksExecutionsInternalErrorResponse(err)
}
space, err := c.SpaceFinder.FindByRef(ctx, regInfo.ParentRef)
if err != nil {
return getReTriggerWebhooksExecutionsInternalErrorResponse(err)
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryEdit)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
session,
permissionChecks...,
); err != nil {
log.Ctx(ctx).Error().Msgf("permission check failed while retrigger webhook execution for registry: %s, error: %v",
regInfo.RegistryIdentifier, err)
return api.ReTriggerWebhookExecution403JSONResponse{
UnauthorizedJSONResponse: api.UnauthorizedJSONResponse(
*GetErrorResponse(http.StatusForbidden, err.Error()),
),
}, err
}
webhookExecutionID, err := strconv.ParseInt(string(r.WebhookExecutionId), 10, 64)
if err != nil || webhookExecutionID <= 0 {
log.Ctx(ctx).Error().Msgf("invalid webhook execution identifier: %s, err: %v", string(r.WebhookExecutionId), err)
return api.ReTriggerWebhookExecution400JSONResponse{
BadRequestJSONResponse: api.BadRequestJSONResponse(
*GetErrorResponse(http.StatusBadRequest, err.Error()),
),
}, err
}
result, err := c.WebhookService.WebhookExecutor.RetriggerWebhookExecution(ctx, webhookExecutionID)
if err != nil {
return getReTriggerWebhooksExecutionsInternalErrorResponse(err)
}
webhookExecution, err := MapToWebhookExecutionResponseEntity(*result.Execution)
if err != nil {
log.Ctx(ctx).Error().Msgf(getWebhookErrMsg, regInfo.RegistryRef, r.WebhookIdentifier, err)
return getReTriggerWebhooksExecutionsInternalErrorResponse(err)
}
return api.ReTriggerWebhookExecution200JSONResponse{
WebhookExecutionResponseJSONResponse: api.WebhookExecutionResponseJSONResponse{
Data: api.WebhookExecution{},
Data: *webhookExecution,
Status: api.StatusSUCCESS,
},
}, nil
}
func getReTriggerWebhooksExecutionsInternalErrorResponse(
err error,
) (api.ReTriggerWebhookExecution500JSONResponse, error) {
return api.ReTriggerWebhookExecution500JSONResponse{
InternalServerErrorJSONResponse: api.InternalServerErrorJSONResponse(
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
),
}, err
}

View File

@ -54,7 +54,8 @@ func (c *APIController) UpdateArtifactLabels(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryEdit)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryEdit)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,

View File

@ -35,7 +35,7 @@ func (c *APIController) ModifyRegistry(
ctx context.Context,
r artifact.ModifyRegistryRequestObject,
) (artifact.ModifyRegistryResponseObject, error) {
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return artifact.ModifyRegistry400JSONResponse{
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
@ -53,7 +53,8 @@ func (c *APIController) ModifyRegistry(
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, gitnessenum.PermissionRegistryEdit)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space,
regInfo.RegistryIdentifier, gitnessenum.PermissionRegistryEdit)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
@ -392,7 +393,7 @@ func (c *APIController) UpdateUpstreamProxyEntity(
}
if res.SecretSpacePath != nil && len(*res.SecretSpacePath) > 0 {
upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretSpaceID(ctx, res.SecretSpacePath)
upstreamProxyConfigEntity.SecretSpaceID, err = c.RegistryMetadataHelper.getSecretSpaceID(ctx, res.SecretSpacePath)
if err != nil {
return nil, nil, err
}
@ -412,7 +413,8 @@ func (c *APIController) UpdateUpstreamProxyEntity(
return nil, nil, fmt.Errorf("failed to create upstream proxy: access_key_secret_identifier missing")
default:
if res.AccessKeySecretSpacePath != nil && len(*res.AccessKeySecretSpacePath) > 0 {
upstreamProxyConfigEntity.UserNameSecretSpaceID, err = c.getSecretSpaceID(ctx, res.AccessKeySecretSpacePath)
upstreamProxyConfigEntity.UserNameSecretSpaceID, err =
c.RegistryMetadataHelper.getSecretSpaceID(ctx, res.AccessKeySecretSpacePath)
if err != nil {
return nil, nil, err
}
@ -423,7 +425,8 @@ func (c *APIController) UpdateUpstreamProxyEntity(
}
if res.SecretKeySpacePath != nil && len(*res.SecretKeySpacePath) > 0 {
upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretSpaceID(ctx, res.SecretKeySpacePath)
upstreamProxyConfigEntity.SecretSpaceID, err =
c.RegistryMetadataHelper.getSecretSpaceID(ctx, res.SecretKeySpacePath)
if err != nil {
return nil, nil, err
}

View File

@ -32,7 +32,7 @@ func (c *APIController) UpdateWebhook(
r api.UpdateWebhookRequestObject,
) (api.UpdateWebhookResponseObject, error) {
webhookRequest := api.WebhookRequest(*r.Body)
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
regInfo, err := c.RegistryMetadataHelper.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
if err != nil {
return updateWebhookInternalErrorResponse(err)
}
@ -41,7 +41,8 @@ func (c *APIController) UpdateWebhook(
return updateWebhookInternalErrorResponse(err)
}
session, _ := request.AuthSessionFrom(ctx)
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryEdit)
permissionChecks := c.RegistryMetadataHelper.GetPermissionChecks(space, regInfo.RegistryIdentifier,
enum.PermissionRegistryEdit)
if err = apiauth.CheckRegistry(
ctx,
c.Authorizer,
@ -56,8 +57,19 @@ func (c *APIController) UpdateWebhook(
),
}, err
}
existingWebhook, err := c.WebhooksRepository.GetByRegistryAndIdentifier(ctx,
regInfo.RegistryID, webhookRequest.Identifier)
if err != nil {
log.Ctx(ctx).Error().Msgf("failed to get existing webhook: %s with error: %v",
webhookRequest.Identifier, err)
return updateWebhookInternalErrorResponse(fmt.Errorf("failed to get existing webhook"))
}
if existingWebhook.Type == enum.WebhookTypeInternal {
return updateWebhookBadRequestErrorResponse(fmt.Errorf("cannot edit internal webhook: %s",
webhookRequest.Identifier))
}
webhook, err := c.mapToWebhook(ctx, webhookRequest, regInfo)
webhook, err := c.RegistryMetadataHelper.MapToWebhookCore(ctx, webhookRequest, regInfo)
if err != nil {
log.Ctx(ctx).Error().Msgf("failed to update webhook: %s with error: %v", webhookRequest.Identifier, err)
return updateWebhookBadRequestErrorResponse(fmt.Errorf("failed to update webhook"))
@ -80,7 +92,7 @@ func (c *APIController) UpdateWebhook(
return updateWebhookInternalErrorResponse(fmt.Errorf("failed to get updated webhook"))
}
webhookResponseEntity, err := c.mapToWebhookResponseEntity(ctx, *updatedWebhook)
webhookResponseEntity, err := c.RegistryMetadataHelper.MapToWebhookResponseEntity(ctx, updatedWebhook)
if err != nil {
log.Ctx(ctx).Error().Msgf("failed to get updated webhook: %s with error: %v",
webhookRequest.Identifier, err)

View File

@ -26,8 +26,6 @@ import (
a "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/inhies/go-bytesize"
"github.com/rs/zerolog/log"
@ -49,7 +47,8 @@ const (
ArtifactFilesResource = "artifactFiles"
RegistryIdentifierErrorMsg = "registry name should be 1~255 characters long with lower case characters, numbers " +
"and ._- and must be start with numbers or characters"
RegexIdentifierPattern = "^[a-z0-9]+(?:[._-][a-z0-9]+)*$"
RegexIdentifierPattern = "^[a-z0-9]+(?:[._-][a-z0-9]+)*$"
internalWebhookIdentifier = "harnesstriggerwebhok"
)
var RegistrySortMap = map[string]string{
@ -443,18 +442,3 @@ func CleanURLPath(input *string) {
// Update the input string with the cleaned URL string representation
*input = u.String()
}
func GetPermissionChecks(
space *types.SpaceCore,
registryIdentifier string,
permission enum.Permission,
) []types.PermissionCheck {
var permissionChecks []types.PermissionCheck
permissionCheck := &types.PermissionCheck{
Scope: types.Scope{SpacePath: space.Path},
Resource: types.Resource{Type: enum.ResourceTypeRegistry, Identifier: registryIdentifier},
Permission: permission,
}
permissionChecks = append(permissionChecks, *permissionCheck)
return permissionChecks
}

View File

@ -229,11 +229,12 @@ func (h *Handler) GetRegistryInfo(r *http.Request, remoteSupport bool) (pkg.Regi
RegIdentifier: registryIdentifier,
Image: image,
},
Reference: ref,
Digest: dgst,
Tag: tag,
URLBuilder: v2.NewURLBuilderFromRequest(r, h.OCIRelativeURL),
Path: r.URL.Path,
Reference: ref,
Digest: dgst,
Tag: tag,
URLBuilder: v2.NewURLBuilderFromRequest(r, h.OCIRelativeURL),
Path: r.URL.Path,
PackageType: registry.PackageType,
}
log.Ctx(ctx).Info().Msgf("Dispatch: URI: %s", path)

View File

@ -22,7 +22,7 @@ import (
"github.com/rs/zerolog/log"
)
// PutManifest validates and stores a manifest in the registry.
// DeleteManifest a manifest from the registry.
func (h *Handler) DeleteManifest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
info, err := h.GetRegistryInfo(r, false)

View File

@ -2172,6 +2172,9 @@ components:
type: string
value:
type: string
required:
- key
- value
WebhookExecResult:
type: string
description: refers to webhook execution

View File

@ -1,6 +1,6 @@
// Package artifact provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT.
package artifact
import (
@ -346,6 +346,7 @@ type MiddlewareFunc func(http.Handler) http.Handler
// CreateRegistry operation middleware
func (siw *ServerInterfaceWrapper) CreateRegistry(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -368,11 +369,12 @@ func (siw *ServerInterfaceWrapper) CreateRegistry(w http.ResponseWriter, r *http
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// DeleteRegistry operation middleware
func (siw *ServerInterfaceWrapper) DeleteRegistry(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -393,11 +395,12 @@ func (siw *ServerInterfaceWrapper) DeleteRegistry(w http.ResponseWriter, r *http
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetRegistry operation middleware
func (siw *ServerInterfaceWrapper) GetRegistry(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -418,11 +421,12 @@ func (siw *ServerInterfaceWrapper) GetRegistry(w http.ResponseWriter, r *http.Re
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// ModifyRegistry operation middleware
func (siw *ServerInterfaceWrapper) ModifyRegistry(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -443,11 +447,12 @@ func (siw *ServerInterfaceWrapper) ModifyRegistry(w http.ResponseWriter, r *http
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// ListArtifactLabels operation middleware
func (siw *ServerInterfaceWrapper) ListArtifactLabels(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -495,11 +500,12 @@ func (siw *ServerInterfaceWrapper) ListArtifactLabels(w http.ResponseWriter, r *
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetArtifactStatsForRegistry operation middleware
func (siw *ServerInterfaceWrapper) GetArtifactStatsForRegistry(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -539,11 +545,12 @@ func (siw *ServerInterfaceWrapper) GetArtifactStatsForRegistry(w http.ResponseWr
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// DeleteArtifact operation middleware
func (siw *ServerInterfaceWrapper) DeleteArtifact(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -573,11 +580,12 @@ func (siw *ServerInterfaceWrapper) DeleteArtifact(w http.ResponseWriter, r *http
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// UpdateArtifactLabels operation middleware
func (siw *ServerInterfaceWrapper) UpdateArtifactLabels(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -607,11 +615,12 @@ func (siw *ServerInterfaceWrapper) UpdateArtifactLabels(w http.ResponseWriter, r
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetArtifactStats operation middleware
func (siw *ServerInterfaceWrapper) GetArtifactStats(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -660,11 +669,12 @@ func (siw *ServerInterfaceWrapper) GetArtifactStats(w http.ResponseWriter, r *ht
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetArtifactSummary operation middleware
func (siw *ServerInterfaceWrapper) GetArtifactSummary(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -694,11 +704,12 @@ func (siw *ServerInterfaceWrapper) GetArtifactSummary(w http.ResponseWriter, r *
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// DeleteArtifactVersion operation middleware
func (siw *ServerInterfaceWrapper) DeleteArtifactVersion(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -737,11 +748,12 @@ func (siw *ServerInterfaceWrapper) DeleteArtifactVersion(w http.ResponseWriter,
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetArtifactDetails operation middleware
func (siw *ServerInterfaceWrapper) GetArtifactDetails(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -791,11 +803,12 @@ func (siw *ServerInterfaceWrapper) GetArtifactDetails(w http.ResponseWriter, r *
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetDockerArtifactDetails operation middleware
func (siw *ServerInterfaceWrapper) GetDockerArtifactDetails(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -852,11 +865,12 @@ func (siw *ServerInterfaceWrapper) GetDockerArtifactDetails(w http.ResponseWrite
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetDockerArtifactLayers operation middleware
func (siw *ServerInterfaceWrapper) GetDockerArtifactLayers(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -913,11 +927,12 @@ func (siw *ServerInterfaceWrapper) GetDockerArtifactLayers(w http.ResponseWriter
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetDockerArtifactManifest operation middleware
func (siw *ServerInterfaceWrapper) GetDockerArtifactManifest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -974,11 +989,12 @@ func (siw *ServerInterfaceWrapper) GetDockerArtifactManifest(w http.ResponseWrit
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetDockerArtifactManifests operation middleware
func (siw *ServerInterfaceWrapper) GetDockerArtifactManifests(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1017,11 +1033,12 @@ func (siw *ServerInterfaceWrapper) GetDockerArtifactManifests(w http.ResponseWri
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetArtifactFiles operation middleware
func (siw *ServerInterfaceWrapper) GetArtifactFiles(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1103,11 +1120,12 @@ func (siw *ServerInterfaceWrapper) GetArtifactFiles(w http.ResponseWriter, r *ht
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetHelmArtifactDetails operation middleware
func (siw *ServerInterfaceWrapper) GetHelmArtifactDetails(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1146,11 +1164,12 @@ func (siw *ServerInterfaceWrapper) GetHelmArtifactDetails(w http.ResponseWriter,
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetHelmArtifactManifest operation middleware
func (siw *ServerInterfaceWrapper) GetHelmArtifactManifest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1189,11 +1208,12 @@ func (siw *ServerInterfaceWrapper) GetHelmArtifactManifest(w http.ResponseWriter
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetArtifactVersionSummary operation middleware
func (siw *ServerInterfaceWrapper) GetArtifactVersionSummary(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1232,11 +1252,12 @@ func (siw *ServerInterfaceWrapper) GetArtifactVersionSummary(w http.ResponseWrit
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetAllArtifactVersions operation middleware
func (siw *ServerInterfaceWrapper) GetAllArtifactVersions(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1309,11 +1330,12 @@ func (siw *ServerInterfaceWrapper) GetAllArtifactVersions(w http.ResponseWriter,
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetAllArtifactsByRegistry operation middleware
func (siw *ServerInterfaceWrapper) GetAllArtifactsByRegistry(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1385,11 +1407,12 @@ func (siw *ServerInterfaceWrapper) GetAllArtifactsByRegistry(w http.ResponseWrit
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetClientSetupDetails operation middleware
func (siw *ServerInterfaceWrapper) GetClientSetupDetails(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1429,11 +1452,12 @@ func (siw *ServerInterfaceWrapper) GetClientSetupDetails(w http.ResponseWriter,
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// ListWebhooks operation middleware
func (siw *ServerInterfaceWrapper) ListWebhooks(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1497,11 +1521,12 @@ func (siw *ServerInterfaceWrapper) ListWebhooks(w http.ResponseWriter, r *http.R
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// CreateWebhook operation middleware
func (siw *ServerInterfaceWrapper) CreateWebhook(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1522,11 +1547,12 @@ func (siw *ServerInterfaceWrapper) CreateWebhook(w http.ResponseWriter, r *http.
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// DeleteWebhook operation middleware
func (siw *ServerInterfaceWrapper) DeleteWebhook(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1556,11 +1582,12 @@ func (siw *ServerInterfaceWrapper) DeleteWebhook(w http.ResponseWriter, r *http.
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetWebhook operation middleware
func (siw *ServerInterfaceWrapper) GetWebhook(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1590,11 +1617,12 @@ func (siw *ServerInterfaceWrapper) GetWebhook(w http.ResponseWriter, r *http.Req
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// UpdateWebhook operation middleware
func (siw *ServerInterfaceWrapper) UpdateWebhook(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1624,11 +1652,12 @@ func (siw *ServerInterfaceWrapper) UpdateWebhook(w http.ResponseWriter, r *http.
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// ListWebhookExecutions operation middleware
func (siw *ServerInterfaceWrapper) ListWebhookExecutions(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1677,11 +1706,12 @@ func (siw *ServerInterfaceWrapper) ListWebhookExecutions(w http.ResponseWriter,
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetWebhookExecution operation middleware
func (siw *ServerInterfaceWrapper) GetWebhookExecution(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1720,11 +1750,12 @@ func (siw *ServerInterfaceWrapper) GetWebhookExecution(w http.ResponseWriter, r
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// ReTriggerWebhookExecution operation middleware
func (siw *ServerInterfaceWrapper) ReTriggerWebhookExecution(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1763,11 +1794,12 @@ func (siw *ServerInterfaceWrapper) ReTriggerWebhookExecution(w http.ResponseWrit
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetArtifactStatsForSpace operation middleware
func (siw *ServerInterfaceWrapper) GetArtifactStatsForSpace(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1807,11 +1839,12 @@ func (siw *ServerInterfaceWrapper) GetArtifactStatsForSpace(w http.ResponseWrite
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetAllArtifacts operation middleware
func (siw *ServerInterfaceWrapper) GetAllArtifacts(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1899,11 +1932,12 @@ func (siw *ServerInterfaceWrapper) GetAllArtifacts(w http.ResponseWriter, r *htt
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetAllRegistries operation middleware
func (siw *ServerInterfaceWrapper) GetAllRegistries(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
@ -1991,7 +2025,7 @@ func (siw *ServerInterfaceWrapper) GetAllRegistries(w http.ResponseWriter, r *ht
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
handler.ServeHTTP(w, r.WithContext(ctx))
}
type UnescapedCookieParamError struct {
@ -5777,53 +5811,54 @@ var swaggerSpec = []string{
"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",
"o/vY4wAA",
}
// GetSwagger returns the content of the embedded swagger specification file

View File

@ -1,6 +1,6 @@
// Package artifact provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT.
package artifact
import (
@ -301,8 +301,8 @@ type Error struct {
// ExtraHeader Webhook Extra Header
type ExtraHeader struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
Key string `json:"key"`
Value string `json:"value"`
}
// FileDetail File Detail

View File

@ -22,6 +22,7 @@ import (
"github.com/harness/gitness/app/auth/authn"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/refcache"
corestore "github.com/harness/gitness/app/store"
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/registry/app/api/controller/metadata"
@ -30,6 +31,7 @@ import (
storagedriver "github.com/harness/gitness/registry/app/driver"
"github.com/harness/gitness/registry/app/pkg/filemanager"
"github.com/harness/gitness/registry/app/store"
registrywebhook "github.com/harness/gitness/registry/services/webhook"
"github.com/harness/gitness/store/database/dbtx"
"github.com/go-chi/chi/v5"
@ -69,11 +71,15 @@ func NewAPIHandler(
auditService audit.Service,
artifactStore store.ArtifactRepository,
webhooksRepository store.WebhooksRepository,
webhooksExecutionRepository store.WebhooksExecutionRepository,
webhookService registrywebhook.Service,
spacePathStore corestore.SpacePathStore,
) APIHandler {
r := chi.NewRouter()
r.Use(audit.Middleware())
r.Use(middlewareauthn.Attempt(authenticator))
r.Use(middleware.CheckAuth())
registryMetadataHelper := metadata.NewRegistryMetadataHelper(spacePathStore, spaceFinder, repoDao)
apiController := metadata.NewAPIController(
repoDao,
fileManager,
@ -92,7 +98,11 @@ func NewAPIHandler(
auditService,
artifactStore,
webhooksRepository,
webhooksExecutionRepository,
*registryMetadataHelper,
webhookService,
)
handler := artifact.NewStrictHandler(apiController, []artifact.StrictMiddlewareFunc{})
muxHandler := artifact.HandlerFromMuxWithBaseURL(handler, r, baseURL)
return encode.TerminatedPathBefore(

View File

@ -19,6 +19,7 @@ import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/config"
"github.com/harness/gitness/app/services/refcache"
corestore "github.com/harness/gitness/app/store"
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/registry/app/api/handler/generic"
@ -34,6 +35,7 @@ import (
storagedriver "github.com/harness/gitness/registry/app/driver"
"github.com/harness/gitness/registry/app/pkg/filemanager"
"github.com/harness/gitness/registry/app/store"
registrywebhook "github.com/harness/gitness/registry/services/webhook"
"github.com/harness/gitness/store/database/dbtx"
"github.com/google/wire"
@ -66,6 +68,9 @@ func APIHandlerProvider(
auditService audit.Service,
artifactStore store.ArtifactRepository,
webhooksRepository store.WebhooksRepository,
webhooksExecutionRepository store.WebhooksExecutionRepository,
webhookService *registrywebhook.Service,
spacePathStore corestore.SpacePathStore,
) harness.APIHandler {
return harness.NewAPIHandler(
repoDao,
@ -85,6 +90,9 @@ func APIHandlerProvider(
auditService,
artifactStore,
webhooksRepository,
webhooksExecutionRepository,
*webhookService,
spacePathStore,
)
}

View File

@ -0,0 +1,161 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package events
import (
"context"
"github.com/harness/gitness/events"
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/rs/zerolog/log"
)
const ArtifactsCategory = "artifacts"
const ArtifactCreatedEvent events.EventType = "artifact-created"
const ArtifactUpdatedEvent events.EventType = "artifact-updated"
const ArtifactDeletedEvent events.EventType = "artifact-deleted"
type ArtifactCreatedPayload struct {
RegistryID int64 `json:"registry_id"`
PrincipalID int64 `json:"principal_id"`
ArtifactType artifact.PackageType `json:"artifact_type"`
Artifact Artifact `json:"artifact"`
}
type Artifact interface {
GetInfo() string
}
type BaseArtifact struct {
Name string `json:"name"`
Ref string `json:"ref"`
}
type DockerArtifact struct {
BaseArtifact
URL string `json:"url"`
Tag string `json:"tag"`
Digest string `json:"digest"`
}
func (a *DockerArtifact) GetInfo() string {
return a.Ref
}
type HelmArtifact struct {
BaseArtifact
URL string `json:"url"`
Tag string `json:"tag"`
Digest string `json:"digest"`
}
func (a *HelmArtifact) GetInfo() string {
return a.Ref
}
type ArtifactInfo struct {
Type artifact.PackageType `json:"type"`
Name string `json:"name"`
Version string `json:"version"`
Artifact interface{} `json:"artifact"`
}
type ArtifactChangeInfo struct {
Type artifact.PackageType `json:"type"`
Name string `json:"name"`
ArtifactChange interface{} `json:"artifact_change"`
}
func (r *Reporter) ArtifactCreated(ctx context.Context, payload *ArtifactCreatedPayload) {
eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, ArtifactCreatedEvent, payload)
if err != nil {
log.Ctx(ctx).Err(err).Msgf("failed to send artifact-created created event")
return
}
log.Ctx(ctx).Debug().Msgf("reported artifact-created event with id '%s'", eventID)
}
func (r *Reader) RegisterArtifactCreated(
fn events.HandlerFunc[*ArtifactCreatedPayload],
opts ...events.HandlerOption,
) error {
return events.ReaderRegisterEvent(r.innerReader, ArtifactCreatedEvent, fn, opts...)
}
type ArtifactUpdatedPayload struct {
RegistryID int64 `json:"registry_id"`
PrincipalID int64 `json:"principal_id"`
ArtifactType artifact.PackageType `json:"artifact_type"`
ArtifactChange ArtifactChange `json:"artifact_change"`
}
type ArtifactChange struct {
Old Artifact
New Artifact
}
type DockerArtifactChange struct {
Old DockerArtifact
New DockerArtifact
}
type HelmArtifactChange struct {
Old HelmArtifact
New HelmArtifact
}
func (r *Reporter) ArtifactUpdated(ctx context.Context, payload *ArtifactUpdatedPayload) {
eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, ArtifactUpdatedEvent, payload)
if err != nil {
log.Ctx(ctx).Err(err).Msgf("failed to send artifact updated event")
return
}
log.Ctx(ctx).Debug().Msgf("reported artifact updated event with id '%s'", eventID)
}
func (r *Reader) RegisterArtifactUpdated(
fn events.HandlerFunc[*ArtifactUpdatedPayload],
opts ...events.HandlerOption,
) error {
return events.ReaderRegisterEvent(r.innerReader, ArtifactUpdatedEvent, fn, opts...)
}
type ArtifactDeletedPayload struct {
RegistryID int64 `json:"registry_id"`
PrincipalID int64 `json:"principal_id"`
ArtifactType artifact.PackageType `json:"artifact_type"`
Artifact Artifact `json:"artifact"`
}
func (r *Reporter) ArtifactDeleted(ctx context.Context, payload *ArtifactDeletedPayload) {
eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, ArtifactDeletedEvent, payload)
if err != nil {
log.Ctx(ctx).Err(err).Msgf("failed to send artifact deleted event")
return
}
log.Ctx(ctx).Debug().Msgf("reported artifact deleted event with id '%s'", eventID)
}
func (r *Reader) RegisterArtifactDeleted(
fn events.HandlerFunc[*ArtifactDeletedPayload],
opts ...events.HandlerOption,
) error {
return events.ReaderRegisterEvent(r.innerReader, ArtifactDeletedEvent, fn, opts...)
}

View File

@ -0,0 +1,40 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package events
import (
"github.com/harness/gitness/events"
)
func NewReaderFactory(eventsSystem *events.System) (*events.ReaderFactory[*Reader], error) {
readerFactoryFunc := func(innerReader *events.GenericReader) (*Reader, error) {
return &Reader{
innerReader: innerReader,
}, nil
}
return events.NewReaderFactory(eventsSystem, ArtifactsCategory, readerFactoryFunc)
}
// Reader is the event reader for this package.
// It exposes typesafe event registration methods for all events by this package.
// NOTE: Event registration methods are in the event's dedicated file.
type Reader struct {
innerReader *events.GenericReader
}
func (r *Reader) Configure(opts ...events.ReaderOption) {
r.innerReader.Configure(opts...)
}

View File

@ -0,0 +1,39 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package events
import (
"errors"
"github.com/harness/gitness/events"
)
// Reporter is the event reporter for this package.
// It exposes typesafe send methods for all events of this package.
// NOTE: Event send methods are in the event's dedicated file.
type Reporter struct {
innerReporter *events.GenericReporter
}
func NewReporter(eventsSystem *events.System) (*Reporter, error) {
innerReporter, err := events.NewReporter(eventsSystem, ArtifactsCategory)
if err != nil {
return nil, errors.New("failed to create new GenericReporter from event system")
}
return &Reporter{
innerReporter: innerReporter,
}, nil
}

View File

@ -0,0 +1,42 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package events
import (
"encoding/gob"
"github.com/harness/gitness/events"
"github.com/google/wire"
)
func ProvideReaderFactory(eventsSystem *events.System) (*events.ReaderFactory[*Reader], error) {
return NewReaderFactory(eventsSystem)
}
func ProvideArtifactReporter(eventsSystem *events.System) (*Reporter, error) {
reporter, err := NewReporter(eventsSystem)
if err != nil {
return nil, err
}
gob.Register(&DockerArtifact{})
gob.Register(&HelmArtifact{})
return reporter, nil
}
var WireSet = wire.NewSet(
ProvideReaderFactory,
ProvideArtifactReporter,
)

View File

@ -38,11 +38,12 @@ type ArtifactInfo struct {
type RegistryInfo struct {
*ArtifactInfo
Reference string
Digest string
Tag string
URLBuilder *v2.URLBuilder
Path string
Reference string
Digest string
Tag string
URLBuilder *v2.URLBuilder
Path string
PackageType artifact.PackageType
}
type FileInfo struct {

View File

@ -21,11 +21,15 @@ import (
"database/sql"
"errors"
"fmt"
"net/url"
"time"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/services/refcache"
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/registry/app/event"
registryevents "github.com/harness/gitness/registry/app/events"
"github.com/harness/gitness/registry/app/manifest"
"github.com/harness/gitness/registry/app/manifest/manifestlist"
"github.com/harness/gitness/registry/app/manifest/ocischema"
@ -47,6 +51,8 @@ import (
"github.com/rs/zerolog/log"
)
const ociPrefix = "oci://"
type manifestService struct {
registryDao store.RegistryRepository
manifestDao store.ManifestRepository
@ -62,6 +68,8 @@ type manifestService struct {
gcService gc.Service
tx dbtx.Transactor
reporter event.Reporter
artifactEventReporter registryevents.Reporter
urlProvider urlprovider.Provider
}
func NewManifestService(
@ -70,7 +78,8 @@ func NewManifestService(
imageDao store.ImageRepository, artifactDao store.ArtifactRepository,
layerDao store.LayerRepository, manifestRefDao store.ManifestReferenceRepository,
tx dbtx.Transactor, gcService gc.Service, reporter event.Reporter, spaceFinder refcache.SpaceFinder,
ociImageIndexMappingDao store.OCIImageIndexMappingRepository,
ociImageIndexMappingDao store.OCIImageIndexMappingRepository, artifactEventReporter registryevents.Reporter,
urlProvider urlprovider.Provider,
) ManifestService {
return &manifestService{
registryDao: registryDao,
@ -87,6 +96,8 @@ func NewManifestService(
reporter: reporter,
spaceFinder: spaceFinder,
ociImageIndexMappingDao: ociImageIndexMappingDao,
artifactEventReporter: artifactEventReporter,
urlProvider: urlProvider,
}
}
@ -193,6 +204,7 @@ func (l *manifestService) dbTagManifest(
if err != nil {
return formatFailedToTagErr(err)
}
existingDigest := l.getTagDigest(ctx, dbRegistry.ID, imageName, tagName)
err = l.tx.WithTx(ctx, func(ctx context.Context) error {
// Prevent long running transactions by setting an upper limit of manifestTagGCLockTimeout. If the GC is holding
@ -228,6 +240,16 @@ func (l *manifestService) dbTagManifest(
return err
}
l.reportEventAsync(ctx, reg.ID, info.RegIdentifier, imageName, tagName, packageType, spacePath)
session, _ := request.AuthSessionFrom(ctx)
createPayload := l.getArtifactCreatedPayload(ctx, info, session.Principal.ID,
reg.ID, reg.Name, tagName, dgst.String())
l.artifactEventReporter.ArtifactCreated(ctx, &createPayload)
if existingDigest != "" && existingDigest != dgst {
updatePayload := l.getArtifactUpdatedPayload(ctx, info, session.Principal.ID,
reg.ID, reg.Name, tagName, existingDigest.String(), dgst.String())
l.artifactEventReporter.ArtifactUpdated(ctx, &updatePayload)
}
} else {
log.Ctx(ctx).Err(err).Msg("Failed to find spacePath, not publishing event")
}
@ -235,6 +257,139 @@ func (l *manifestService) dbTagManifest(
return nil
}
func (l *manifestService) getArtifactCreatedPayload(
ctx context.Context,
info pkg.RegistryInfo,
principalID int64,
registryID int64,
regIdentifier string,
tag string,
digest string,
) registryevents.ArtifactCreatedPayload {
payload := registryevents.ArtifactCreatedPayload{
RegistryID: registryID,
PrincipalID: principalID,
ArtifactType: info.PackageType,
}
artifactURL := l.urlProvider.RegistryURL(ctx, info.RootIdentifier, regIdentifier) + "/" + info.Image + ":" + tag
urlWithoutProtocol := GetRepoURLWithoutProtocol(artifactURL)
baseArtifact := registryevents.BaseArtifact{
Name: info.Image,
Ref: fmt.Sprintf("%s:%s", info.Image, tag),
}
if info.PackageType == artifact.PackageTypeDOCKER {
payload.Artifact = &registryevents.DockerArtifact{
BaseArtifact: baseArtifact,
Tag: tag,
URL: urlWithoutProtocol,
Digest: digest,
}
} else if info.PackageType == artifact.PackageTypeHELM {
payload.Artifact = &registryevents.HelmArtifact{
BaseArtifact: baseArtifact,
Tag: tag,
URL: ociPrefix + urlWithoutProtocol,
Digest: digest,
}
}
return payload
}
func (l *manifestService) getArtifactDeletedPayload(
ctx context.Context,
info pkg.RegistryInfo,
principalID int64,
registryID int64,
regIdentifier string,
tag string,
digest string,
) registryevents.ArtifactDeletedPayload {
payload := registryevents.ArtifactDeletedPayload{
RegistryID: registryID,
PrincipalID: principalID,
ArtifactType: info.PackageType,
}
artifactURL := l.urlProvider.RegistryURL(ctx, info.RootIdentifier, regIdentifier) + "/" + info.Image + ":" + tag
urlWithoutProtocol := GetRepoURLWithoutProtocol(artifactURL)
baseArtifact := registryevents.BaseArtifact{
Name: info.Image,
Ref: fmt.Sprintf("%s:%s", info.Image, tag),
}
if info.PackageType == artifact.PackageTypeDOCKER {
payload.Artifact = &registryevents.DockerArtifact{
BaseArtifact: baseArtifact,
Tag: tag,
Digest: digest,
URL: urlWithoutProtocol,
}
} else if info.PackageType == artifact.PackageTypeHELM {
payload.Artifact = &registryevents.HelmArtifact{
BaseArtifact: baseArtifact,
Tag: tag,
Digest: digest,
URL: ociPrefix + urlWithoutProtocol,
}
}
return payload
}
func (l *manifestService) getArtifactUpdatedPayload(
ctx context.Context,
info pkg.RegistryInfo,
principalID int64,
registryID int64,
regIdentifier string,
tag string,
oldDigest string,
newDigest string,
) registryevents.ArtifactUpdatedPayload {
payload := registryevents.ArtifactUpdatedPayload{
RegistryID: registryID,
PrincipalID: principalID,
ArtifactType: info.PackageType,
}
artifactURL := l.urlProvider.RegistryURL(ctx, info.RootIdentifier, regIdentifier) + "/" + info.Image + ":" + tag
urlWithoutProtocol := GetRepoURLWithoutProtocol(artifactURL)
baseArtifact := registryevents.BaseArtifact{
Name: info.Image,
Ref: fmt.Sprintf("%s:%s", info.Image, tag),
}
if info.PackageType == artifact.PackageTypeDOCKER {
payload.ArtifactChange = registryevents.ArtifactChange{
Old: &registryevents.DockerArtifact{
BaseArtifact: baseArtifact,
Tag: tag,
URL: urlWithoutProtocol,
Digest: oldDigest,
},
New: &registryevents.DockerArtifact{
BaseArtifact: baseArtifact,
Tag: tag,
URL: urlWithoutProtocol,
Digest: newDigest,
},
}
} else if info.PackageType == artifact.PackageTypeHELM {
payload.ArtifactChange = registryevents.ArtifactChange{
Old: &registryevents.HelmArtifact{
BaseArtifact: baseArtifact,
Tag: tag,
URL: ociPrefix + urlWithoutProtocol,
Digest: oldDigest,
},
New: &registryevents.HelmArtifact{
BaseArtifact: baseArtifact,
Tag: tag,
URL: ociPrefix + urlWithoutProtocol,
Digest: newDigest,
},
}
}
return payload
}
func formatFailedToTagErr(err error) error {
return fmt.Errorf("failed to tag manifest: %w", err)
}
@ -366,7 +521,8 @@ func (l *manifestService) upsertImageAndArtifact(
ctx context.Context,
d digest.Digest,
repoKey string,
info pkg.RegistryInfo) error {
info pkg.RegistryInfo,
) error {
dbRepo, err := l.registryDao.GetByParentIDAndName(ctx, info.ParentID, repoKey)
if err != nil {
return err
@ -399,7 +555,8 @@ func (l *manifestService) upsertImageAndArtifact(
func (l *manifestService) UpsertImage(
ctx context.Context,
repoKey string,
info pkg.RegistryInfo) error {
info pkg.RegistryInfo,
) error {
dbRepo, err := l.registryDao.GetByParentIDAndName(ctx, info.ParentID, repoKey)
if err != nil {
return err
@ -1068,17 +1225,39 @@ func (l *manifestService) DeleteTag(
return false, err
}
found, err := l.tagDao.DeleteTagByName(ctx, registry.ID, tag)
existingDigest := l.getTagDigest(ctx, registry.ID, info.Image, tag)
deleted, err := l.tagDao.DeleteTagByName(ctx, registry.ID, tag)
if err != nil {
return false, fmt.Errorf("failed to delete tag in database: %w", err)
}
if !found {
if !deleted {
return false, distribution.ErrTagUnknown{Tag: tag}
}
session, _ := request.AuthSessionFrom(ctx)
payload := l.getArtifactDeletedPayload(ctx, info, session.Principal.ID, registry.ID,
registry.Name, tag, existingDigest.String())
l.artifactEventReporter.ArtifactDeleted(ctx, &payload)
return true, nil
}
func (l *manifestService) getTagDigest(
ctx context.Context,
registryID int64,
imageName string,
tag string,
) digest.Digest {
existingTag, findTagErr := l.tagDao.FindTag(ctx, registryID, imageName, tag)
if findTagErr == nil && existingTag != nil {
existingTaggedManifest, getManifestErr := l.manifestDao.Get(ctx, existingTag.ManifestID)
if getManifestErr == nil {
return existingTaggedManifest.Digest
}
}
return ""
}
func (l *manifestService) DeleteTagsByManifestID(
ctx context.Context,
repoKey string,
@ -1179,3 +1358,14 @@ func (l *manifestService) DeleteManifest(
},
)
}
func GetRepoURLWithoutProtocol(registryURL string) string {
repoURL := registryURL
parsedURL, err := url.Parse(repoURL)
if err != nil {
log.Error().Stack().Err(err).Msg("Error parsing URL: ")
return ""
}
return parsedURL.Host + parsedURL.Path
}

View File

@ -18,8 +18,10 @@ import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/refcache"
gitnessstore "github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
storagedriver "github.com/harness/gitness/registry/app/driver"
"github.com/harness/gitness/registry/app/event"
registryevents "github.com/harness/gitness/registry/app/events"
"github.com/harness/gitness/registry/app/manifest/manifestlist"
"github.com/harness/gitness/registry/app/manifest/schema2"
"github.com/harness/gitness/registry/app/pkg"
@ -61,11 +63,13 @@ func ManifestServiceProvider(
artifactDao store.ArtifactRepository, layerDao store.LayerRepository,
gcService gc.Service, tx dbtx.Transactor, reporter event.Reporter, spaceFinder refcache.SpaceFinder,
ociImageIndexMappingDao store.OCIImageIndexMappingRepository,
artifactEventReporter *registryevents.Reporter,
urlProvider url.Provider,
) ManifestService {
return NewManifestService(
registryDao, manifestDao, blobRepo, mtRepository, tagDao, imageDao,
artifactDao, layerDao, manifestRefDao, tx, gcService, reporter, spaceFinder,
ociImageIndexMappingDao,
ociImageIndexMappingDao, *artifactEventReporter, urlProvider,
)
}

View File

@ -20,6 +20,7 @@ import (
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/registry/types"
gitnesstypes "github.com/harness/gitness/types"
"github.com/lib/pq"
"github.com/opencontainers/go-digest"
@ -579,8 +580,13 @@ type GenericBlobRepository interface {
}
type WebhooksRepository interface {
Create(ctx context.Context, webhook *types.Webhook) error
GetByRegistryAndIdentifier(ctx context.Context, registryID int64, webhookIdentifier string) (*types.Webhook, error)
Create(ctx context.Context, webhook *gitnesstypes.WebhookCore) error
GetByRegistryAndIdentifier(
ctx context.Context,
registryID int64,
webhookIdentifier string,
) (*gitnesstypes.WebhookCore, error)
Find(ctx context.Context, webhookID int64) (*gitnesstypes.WebhookCore, error)
ListByRegistry(
ctx context.Context,
sortByField string,
@ -589,13 +595,42 @@ type WebhooksRepository interface {
offset int,
search string,
registryID int64,
) (*[]types.Webhook, error)
) ([]*gitnesstypes.WebhookCore, error)
ListAllByRegistry(
ctx context.Context,
parents []gitnesstypes.WebhookParentInfo,
) ([]*gitnesstypes.WebhookCore, error)
CountAllByRegistry(
ctx context.Context,
registryID int64,
search string,
) (int64, error)
Update(ctx context.Context, webhook *types.Webhook) error
Update(ctx context.Context, webhook *gitnesstypes.WebhookCore) error
DeleteByRegistryAndIdentifier(ctx context.Context, registryID int64, webhookIdentifier string) error
UpdateOptLock(
ctx context.Context, hook *gitnesstypes.WebhookCore,
mutateFn func(hook *gitnesstypes.WebhookCore) error,
) (*gitnesstypes.WebhookCore, error)
}
type WebhooksExecutionRepository interface {
Find(ctx context.Context, id int64) (*gitnesstypes.WebhookExecutionCore, error)
// Create creates a new webhook execution entry.
Create(ctx context.Context, hook *gitnesstypes.WebhookExecutionCore) error
// ListForWebhook lists the webhook executions for a given webhook id.
ListForWebhook(
ctx context.Context,
webhookID int64,
limit int,
page int,
size int,
) ([]*gitnesstypes.WebhookExecutionCore, error)
CountForWebhook(ctx context.Context, webhookID int64) (int64, error)
// ListForTrigger lists the webhook executions for a given trigger id.
ListForTrigger(ctx context.Context, triggerID string) ([]*gitnesstypes.WebhookExecutionCore, error)
}

View File

@ -261,6 +261,9 @@ func (r registryDao) GetAll(
repoType string,
recursive bool,
) (repos *[]store.RegistryMetadata, err error) {
if limit < 0 || offset < 0 {
return nil, fmt.Errorf("limit and offset must be non-negative")
}
selectFields := `
r.registry_id AS registry_id,
r.registry_name AS reg_identifier,

View File

@ -22,16 +22,16 @@ import (
"strings"
"time"
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/registry/app/store/database/util"
"github.com/harness/gitness/registry/types"
"github.com/harness/gitness/registry/types/enum"
gitnessstore "github.com/harness/gitness/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
gitnesstypes "github.com/harness/gitness/types"
gitnessenum "github.com/harness/gitness/types/enum"
"github.com/Masterminds/squirrel"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
@ -95,7 +95,7 @@ type WebhookDao struct {
db *sqlx.DB
}
func (w WebhookDao) Create(ctx context.Context, webhook *types.Webhook) error {
func (w WebhookDao) Create(ctx context.Context, webhook *gitnesstypes.WebhookCore) error {
const sqlQuery = `
INSERT INTO registry_webhooks (
registry_webhook_registry_id
@ -140,8 +140,8 @@ func (w WebhookDao) Create(ctx context.Context, webhook *types.Webhook) error {
db := dbtx.GetAccessor(ctx, w.db)
dbwebhook, err := mapToWebhookDB(webhook)
dbwebhook.Created = webhook.CreatedAt.UnixMilli()
dbwebhook.Updated = webhook.UpdatedAt.UnixMilli()
dbwebhook.Created = webhook.Created
dbwebhook.Updated = webhook.Updated
if err != nil {
return fmt.Errorf("failed to map registry webhook to internal db type: %w", err)
}
@ -162,7 +162,7 @@ func (w WebhookDao) GetByRegistryAndIdentifier(
ctx context.Context,
registryID int64,
webhookIdentifier string,
) (*types.Webhook, error) {
) (*gitnesstypes.WebhookCore, error) {
query := database.Builder.Select(registryWebhooksFields...).
From("registry_webhooks").
Where("registry_webhook_registry_id = ? AND registry_webhook_identifier = ?", registryID, webhookIdentifier)
@ -182,6 +182,28 @@ func (w WebhookDao) GetByRegistryAndIdentifier(
return mapToWebhook(dst)
}
func (w WebhookDao) Find(
ctx context.Context, id int64,
) (*gitnesstypes.WebhookCore, error) {
query := database.Builder.Select(registryWebhooksFields...).
From("registry_webhooks").
Where("registry_webhook_id = ?", id)
sqlQuery, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, w.db)
dst := new(webhookDB)
if err = db.GetContext(ctx, dst, sqlQuery, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to get webhook detail")
}
return mapToWebhook(dst)
}
func (w WebhookDao) ListByRegistry(
ctx context.Context,
sortByField string,
@ -190,7 +212,7 @@ func (w WebhookDao) ListByRegistry(
offset int,
search string,
registryID int64,
) (*[]types.Webhook, error) {
) ([]*gitnesstypes.WebhookCore, error) {
query := database.Builder.Select(registryWebhooksFields...).
From("registry_webhooks").
Where("registry_webhook_registry_id = ?", registryID)
@ -223,6 +245,31 @@ func (w WebhookDao) ListByRegistry(
return mapToWebhooksList(dst)
}
func (w WebhookDao) ListAllByRegistry(
ctx context.Context,
parents []gitnesstypes.WebhookParentInfo,
) ([]*gitnesstypes.WebhookCore, error) {
query := database.Builder.Select(registryWebhooksFields...).
From("registry_webhooks")
err := selectWebhookParents(parents, &query)
if err != nil {
return nil, fmt.Errorf("failed to select webhook parents: %w", err)
}
sqlQuery, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, w.db)
var dst []*webhookDB
if err = db.SelectContext(ctx, &dst, sqlQuery, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list webhooks details")
}
return mapToWebhooksList(dst)
}
func (w WebhookDao) CountAllByRegistry(
ctx context.Context,
registryID int64,
@ -251,9 +298,10 @@ func (w WebhookDao) CountAllByRegistry(
return count, nil
}
func (w WebhookDao) Update(ctx context.Context, webhook *types.Webhook) error {
func (w WebhookDao) Update(ctx context.Context, webhook *gitnesstypes.WebhookCore) error {
var sqlQuery = " UPDATE registry_webhooks SET " +
util.GetSetDBKeys(webhookDB{},
"registry_webhook_id",
"registry_webhook_identifier",
"registry_webhook_registry_id",
"registry_webhook_created",
@ -265,7 +313,7 @@ func (w WebhookDao) Update(ctx context.Context, webhook *types.Webhook) error {
" AND registry_webhook_registry_id = :registry_webhook_registry_id"
dbWebhook, err := mapToWebhookDB(webhook)
dbWebhook.Updated = webhook.UpdatedAt.UnixMilli()
dbWebhook.Updated = webhook.Updated
if err != nil {
return err
}
@ -318,59 +366,89 @@ func (w WebhookDao) DeleteByRegistryAndIdentifier(
return nil
}
func mapToWebhookDB(webhook *types.Webhook) (*webhookDB, error) {
if webhook.CreatedAt.IsZero() {
webhook.CreatedAt = time.Now()
// UpdateOptLock updates the webhook using the optimistic locking mechanism.
func (w *WebhookDao) UpdateOptLock(
ctx context.Context, hook *gitnesstypes.WebhookCore,
mutateFn func(hook *gitnesstypes.WebhookCore) error,
) (*gitnesstypes.WebhookCore, error) {
for {
dup := *hook
err := mutateFn(&dup)
if err != nil {
return nil, fmt.Errorf("failed to mutate the webhook: %w", err)
}
err = w.Update(ctx, &dup)
if err == nil {
return &dup, nil
}
if !errors.Is(err, gitnessstore.ErrVersionConflict) {
return nil, fmt.Errorf("failed to update the webhook: %w", err)
}
hook, err = w.Find(ctx, hook.ID)
if err != nil {
return nil, fmt.Errorf("failed to find the latst version of the webhook: %w", err)
}
}
webhook.UpdatedAt = time.Now()
dBwebhook := &webhookDB{
}
func mapToWebhookDB(webhook *gitnesstypes.WebhookCore) (*webhookDB, error) {
if webhook.Created < 0 {
webhook.Created = time.Now().UnixMilli()
}
webhook.Updated = time.Now().UnixMilli()
dBWebhook := &webhookDB{
ID: webhook.ID,
Version: webhook.Version,
CreatedBy: webhook.CreatedBy,
Identifier: webhook.Identifier,
Scope: webhook.Scope,
Name: webhook.Name,
Name: webhook.DisplayName,
Description: webhook.Description,
URL: webhook.URL,
SecretIdentifier: util.GetEmptySQLString(webhook.SecretIdentifier),
SecretSpaceID: util.GetEmptySQLInt64(webhook.SecretSpaceID),
Enabled: webhook.Enabled,
Insecure: webhook.Insecure,
Internal: webhook.Internal,
Triggers: triggersToString(webhook.Triggers),
ExtraHeaders: null.StringFrom(structListToString(webhook.ExtraHeaders)),
LatestExecutionResult: null.StringFromPtr((*string)(webhook.LatestExecutionResult)),
}
if webhook.Type == gitnessenum.WebhookTypeInternal {
dBWebhook.Internal = true
}
switch webhook.ParentType {
case enum.WebhookParentRegistry:
dBwebhook.RegistryID = null.IntFrom(webhook.ParentID)
case enum.WebhookParentSpace:
dBwebhook.SpaceID = null.IntFrom(webhook.ParentID)
case gitnessenum.WebhookParentRegistry:
dBWebhook.RegistryID = null.IntFrom(webhook.ParentID)
case gitnessenum.WebhookParentSpace:
dBWebhook.SpaceID = null.IntFrom(webhook.ParentID)
default:
return nil, fmt.Errorf("webhook parent type %q is not supported", webhook.ParentType)
}
return dBwebhook, nil
return dBWebhook, nil
}
func mapToWebhook(webhookDB *webhookDB) (*types.Webhook, error) {
webhook := &types.Webhook{
func mapToWebhook(webhookDB *webhookDB) (*gitnesstypes.WebhookCore, error) {
webhook := &gitnesstypes.WebhookCore{
ID: webhookDB.ID,
Version: webhookDB.Version,
CreatedBy: webhookDB.CreatedBy,
CreatedAt: time.UnixMilli(webhookDB.Created),
UpdatedAt: time.UnixMilli(webhookDB.Updated),
Created: webhookDB.Created,
Updated: webhookDB.Updated,
Scope: webhookDB.Scope,
Identifier: webhookDB.Identifier,
Name: webhookDB.Name,
DisplayName: webhookDB.Name,
Description: webhookDB.Description,
URL: webhookDB.URL,
Enabled: webhookDB.Enabled,
Internal: webhookDB.Internal,
Insecure: webhookDB.Insecure,
Triggers: triggersFromString(webhookDB.Triggers),
ExtraHeaders: stringToStructList(webhookDB.ExtraHeaders.String),
LatestExecutionResult: (*artifact.WebhookExecResult)(webhookDB.LatestExecutionResult.Ptr()),
LatestExecutionResult: (*gitnessenum.WebhookExecutionResult)(webhookDB.LatestExecutionResult.Ptr()),
}
if webhookDB.SecretIdentifier.Valid {
@ -380,14 +458,20 @@ func mapToWebhook(webhookDB *webhookDB) (*types.Webhook, error) {
webhook.SecretSpaceID = int(webhookDB.SecretSpaceID.Int64)
}
if webhookDB.Internal {
webhook.Type = gitnessenum.WebhookTypeInternal
} else {
webhook.Type = gitnessenum.WebhookTypeExternal
}
switch {
case webhookDB.RegistryID.Valid && webhookDB.SpaceID.Valid:
return nil, fmt.Errorf("both registryID and spaceID are set for hook %d", webhookDB.ID)
case webhookDB.RegistryID.Valid:
webhook.ParentType = enum.WebhookParentRegistry
webhook.ParentType = gitnessenum.WebhookParentRegistry
webhook.ParentID = webhookDB.RegistryID.Int64
case webhookDB.SpaceID.Valid:
webhook.ParentType = enum.WebhookParentSpace
webhook.ParentType = gitnessenum.WebhookParentSpace
webhook.ParentID = webhookDB.SpaceID.Int64
default:
return nil, fmt.Errorf("neither registryID nor spaceID are set for hook %d", webhookDB.ID)
@ -396,7 +480,32 @@ func mapToWebhook(webhookDB *webhookDB) (*types.Webhook, error) {
return webhook, nil
}
func triggersToString(triggers []artifact.Trigger) string {
func selectWebhookParents(
parents []gitnesstypes.WebhookParentInfo,
stmt *squirrel.SelectBuilder,
) error {
var parentSelector squirrel.Or
for _, parent := range parents {
switch parent.Type {
case gitnessenum.WebhookParentRegistry:
parentSelector = append(parentSelector, squirrel.Eq{
"registry_webhook_registry_id": parent.ID,
})
case gitnessenum.WebhookParentSpace:
parentSelector = append(parentSelector, squirrel.Eq{
"registry_webhook_space_id": parent.ID,
})
default:
return fmt.Errorf("webhook parent type '%s' is not supported", parent.Type)
}
}
*stmt = stmt.Where(parentSelector)
return nil
}
func triggersToString(triggers []gitnessenum.WebhookTrigger) string {
rawTriggers := make([]string, len(triggers))
for i := range triggers {
rawTriggers[i] = string(triggers[i])
@ -405,22 +514,22 @@ func triggersToString(triggers []artifact.Trigger) string {
return strings.Join(rawTriggers, triggersSeparator)
}
func triggersFromString(triggersString string) []artifact.Trigger {
func triggersFromString(triggersString string) []gitnessenum.WebhookTrigger {
if triggersString == "" {
return []artifact.Trigger{}
return []gitnessenum.WebhookTrigger{}
}
rawTriggers := strings.Split(triggersString, triggersSeparator)
triggers := make([]artifact.Trigger, len(rawTriggers))
triggers := make([]gitnessenum.WebhookTrigger, len(rawTriggers))
for i, rawTrigger := range rawTriggers {
triggers[i] = artifact.Trigger(rawTrigger)
triggers[i] = gitnessenum.WebhookTrigger(rawTrigger)
}
return triggers
}
// Convert a list of ExtraHeaders structs to a JSON string.
func structListToString(headers []artifact.ExtraHeader) string {
func structListToString(headers []gitnesstypes.ExtraHeader) string {
jsonData, err := json.Marshal(headers)
if err != nil {
return ""
@ -429,8 +538,8 @@ func structListToString(headers []artifact.ExtraHeader) string {
}
// Convert a JSON string back to a list of ExtraHeaders structs.
func stringToStructList(jsonStr string) []artifact.ExtraHeader {
var headers []artifact.ExtraHeader
func stringToStructList(jsonStr string) []gitnesstypes.ExtraHeader {
var headers []gitnesstypes.ExtraHeader
err := json.Unmarshal([]byte(jsonStr), &headers)
if err != nil {
return nil
@ -440,14 +549,14 @@ func stringToStructList(jsonStr string) []artifact.ExtraHeader {
func mapToWebhooksList(
dst []*webhookDB,
) (*[]types.Webhook, error) {
webhooks := make([]types.Webhook, 0, len(dst))
) ([]*gitnesstypes.WebhookCore, error) {
webhooks := make([]*gitnesstypes.WebhookCore, 0, len(dst))
for _, d := range dst {
webhook, err := mapToWebhook(d)
if err != nil {
return nil, err
}
webhooks = append(webhooks, *webhook)
webhooks = append(webhooks, webhook)
}
return &webhooks, nil
return webhooks, nil
}

View File

@ -0,0 +1,281 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
gitnesstypes "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
)
type WebhookExecutionDao struct {
db *sqlx.DB
}
const (
webhookExecutionColumns = `
registry_webhook_execution_id
,registry_webhook_execution_retrigger_of
,registry_webhook_execution_retriggerable
,registry_webhook_execution_webhook_id
,registry_webhook_execution_trigger_type
,registry_webhook_execution_trigger_id
,registry_webhook_execution_result
,registry_webhook_execution_created
,registry_webhook_execution_duration
,registry_webhook_execution_error
,registry_webhook_execution_request_url
,registry_webhook_execution_request_headers
,registry_webhook_execution_request_body
,registry_webhook_execution_response_status_code
,registry_webhook_execution_response_status
,registry_webhook_execution_response_headers
,registry_webhook_execution_response_body`
webhookExecutionSelectBase = `
SELECT` + webhookExecutionColumns + `
FROM registry_webhook_executions`
)
func (w WebhookExecutionDao) Find(ctx context.Context, id int64) (*gitnesstypes.WebhookExecutionCore, error) {
const sqlQuery = webhookExecutionSelectBase + `
WHERE registry_webhook_execution_id = $1`
db := dbtx.GetAccessor(ctx, w.db)
dst := &webhookExecutionDB{}
if err := db.GetContext(ctx, dst, sqlQuery, id); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Select query failed")
}
return mapToWebhookExecution(dst), nil
}
func (w WebhookExecutionDao) Create(ctx context.Context, webhookExecution *gitnesstypes.WebhookExecutionCore) error {
const sqlQuery = `
INSERT INTO registry_webhook_executions (
registry_webhook_execution_retrigger_of
,registry_webhook_execution_retriggerable
,registry_webhook_execution_webhook_id
,registry_webhook_execution_trigger_type
,registry_webhook_execution_trigger_id
,registry_webhook_execution_result
,registry_webhook_execution_created
,registry_webhook_execution_duration
,registry_webhook_execution_error
,registry_webhook_execution_request_url
,registry_webhook_execution_request_headers
,registry_webhook_execution_request_body
,registry_webhook_execution_response_status_code
,registry_webhook_execution_response_status
,registry_webhook_execution_response_headers
,registry_webhook_execution_response_body
) values (
:registry_webhook_execution_retrigger_of
,:registry_webhook_execution_retriggerable
,:registry_webhook_execution_webhook_id
,:registry_webhook_execution_trigger_type
,:registry_webhook_execution_trigger_id
,:registry_webhook_execution_result
,:registry_webhook_execution_created
,:registry_webhook_execution_duration
,:registry_webhook_execution_error
,:registry_webhook_execution_request_url
,:registry_webhook_execution_request_headers
,:registry_webhook_execution_request_body
,:registry_webhook_execution_response_status_code
,:registry_webhook_execution_response_status
,:registry_webhook_execution_response_headers
,:registry_webhook_execution_response_body
) RETURNING registry_webhook_execution_id`
db := dbtx.GetAccessor(ctx, w.db)
dbwebhookExecution := mapToWebhookExecutionDB(webhookExecution)
query, arg, err := db.BindNamed(sqlQuery, dbwebhookExecution)
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to registry bind webhook object")
}
if err = db.QueryRowContext(ctx, query, arg...).Scan(&dbwebhookExecution.ID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Insert query failed")
}
return nil
}
func (w WebhookExecutionDao) ListForWebhook(
ctx context.Context,
webhookID int64,
limit int,
page int,
size int,
) ([]*gitnesstypes.WebhookExecutionCore, error) {
stmt := database.Builder.
Select(webhookExecutionColumns).
From("registry_webhook_executions").
Where("registry_webhook_execution_webhook_id = ?", webhookID)
stmt = stmt.Limit(database.Limit(limit))
stmt = stmt.Offset(database.Offset(page, size))
// fixed ordering by desc id (new ones first) - add customized ordering if deemed necessary.
stmt = stmt.OrderBy("registry_webhook_execution_id DESC")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, w.db)
dst := []*webhookExecutionDB{}
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Select query failed")
}
return mapToWebhookExecutions(dst), nil
}
func (w WebhookExecutionDao) CountForWebhook(ctx context.Context, webhookID int64) (int64, error) {
stmt := database.Builder.
Select("COUNT(*)").
From("registry_webhook_executions").
Where("registry_webhook_execution_webhook_id = ?", webhookID)
sql, args, err := stmt.ToSql()
if err != nil {
return 0, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, w.db)
var count int64
if err = db.GetContext(ctx, &count, sql, args...); err != nil {
return 0, database.ProcessSQLErrorf(ctx, err, "Count query failed")
}
return count, nil
}
func (w WebhookExecutionDao) ListForTrigger(
ctx context.Context,
triggerID string,
) ([]*gitnesstypes.WebhookExecutionCore, error) {
const sqlQuery = webhookExecutionSelectBase + `
WHERE registry_webhook_execution_trigger_id = $1`
db := dbtx.GetAccessor(ctx, w.db)
dst := []*webhookExecutionDB{}
if err := db.SelectContext(ctx, &dst, sqlQuery, triggerID); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Select query failed")
}
return mapToWebhookExecutions(dst), nil
}
func NewWebhookExecutionDao(db *sqlx.DB) store.WebhooksExecutionRepository {
return &WebhookExecutionDao{
db: db,
}
}
type webhookExecutionDB struct {
ID int64 `db:"registry_webhook_execution_id"`
RetriggerOf null.Int `db:"registry_webhook_execution_retrigger_of"`
Retriggerable bool `db:"registry_webhook_execution_retriggerable"`
WebhookID int64 `db:"registry_webhook_execution_webhook_id"`
TriggerType enum.WebhookTrigger `db:"registry_webhook_execution_trigger_type"`
TriggerID string `db:"registry_webhook_execution_trigger_id"`
Result enum.WebhookExecutionResult `db:"registry_webhook_execution_result"`
Created int64 `db:"registry_webhook_execution_created"`
Duration int64 `db:"registry_webhook_execution_duration"`
Error string `db:"registry_webhook_execution_error"`
RequestURL string `db:"registry_webhook_execution_request_url"`
RequestHeaders string `db:"registry_webhook_execution_request_headers"`
RequestBody string `db:"registry_webhook_execution_request_body"`
ResponseStatusCode int `db:"registry_webhook_execution_response_status_code"`
ResponseStatus string `db:"registry_webhook_execution_response_status"`
ResponseHeaders string `db:"registry_webhook_execution_response_headers"`
ResponseBody string `db:"registry_webhook_execution_response_body"`
}
func mapToWebhookExecution(webhookExecutionDB *webhookExecutionDB) *gitnesstypes.WebhookExecutionCore {
webhookExecution := &gitnesstypes.WebhookExecutionCore{
ID: webhookExecutionDB.ID,
RetriggerOf: webhookExecutionDB.RetriggerOf.Ptr(),
Retriggerable: webhookExecutionDB.Retriggerable,
WebhookID: webhookExecutionDB.WebhookID,
TriggerType: webhookExecutionDB.TriggerType,
TriggerID: webhookExecutionDB.TriggerID,
Result: webhookExecutionDB.Result,
Created: webhookExecutionDB.Created,
Duration: webhookExecutionDB.Duration,
Error: webhookExecutionDB.Error,
Request: gitnesstypes.WebhookExecutionRequest{
URL: webhookExecutionDB.RequestURL,
Headers: webhookExecutionDB.RequestHeaders,
Body: webhookExecutionDB.RequestBody,
},
Response: gitnesstypes.WebhookExecutionResponse{
StatusCode: webhookExecutionDB.ResponseStatusCode,
Status: webhookExecutionDB.ResponseStatus,
Headers: webhookExecutionDB.ResponseHeaders,
Body: webhookExecutionDB.ResponseBody,
},
}
return webhookExecution
}
func mapToWebhookExecutionDB(webhookExecution *gitnesstypes.WebhookExecutionCore) *webhookExecutionDB {
webhookExecutionDD := &webhookExecutionDB{
ID: webhookExecution.ID,
RetriggerOf: null.IntFromPtr(webhookExecution.RetriggerOf),
Retriggerable: webhookExecution.Retriggerable,
WebhookID: webhookExecution.WebhookID,
TriggerType: webhookExecution.TriggerType,
TriggerID: webhookExecution.TriggerID,
Result: webhookExecution.Result,
Created: webhookExecution.Created,
Duration: webhookExecution.Duration,
Error: webhookExecution.Error,
RequestURL: webhookExecution.Request.URL,
RequestHeaders: webhookExecution.Request.Headers,
RequestBody: webhookExecution.Request.Body,
ResponseStatusCode: webhookExecution.Response.StatusCode,
ResponseStatus: webhookExecution.Response.Status,
ResponseHeaders: webhookExecution.Response.Headers,
ResponseBody: webhookExecution.Response.Body,
}
return webhookExecutionDD
}
func mapToWebhookExecutions(executions []*webhookExecutionDB) []*gitnesstypes.WebhookExecutionCore {
m := make([]*gitnesstypes.WebhookExecutionCore, len(executions))
for i, hook := range executions {
m[i] = mapToWebhookExecution(hook)
}
return m
}

View File

@ -75,6 +75,10 @@ func ProvideWebhookDao(sqlDB *sqlx.DB) store.WebhooksRepository {
return NewWebhookDao(sqlDB)
}
func ProvideWebhookExecutionDao(sqlDB *sqlx.DB) store.WebhooksExecutionRepository {
return NewWebhookExecutionDao(sqlDB)
}
func ProvideManifestRefDao(db *sqlx.DB) store.ManifestReferenceRepository {
return NewManifestReferenceDao(db)
}
@ -118,4 +122,5 @@ var WireSet = wire.NewSet(
ProvideNodeDao,
ProvideGenericBlobDao,
ProvideWebhookDao,
ProvideWebhookExecutionDao,
)

View File

@ -0,0 +1,257 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package webhook
import (
"context"
"fmt"
gitnesswebhook "github.com/harness/gitness/app/services/webhook"
"github.com/harness/gitness/events"
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
registryevents "github.com/harness/gitness/registry/app/events"
registrytypes "github.com/harness/gitness/registry/types"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ArtifactEventPayload describes the payload of Artifact related webhook triggers.
type ArtifactEventPayload struct {
Trigger enum.WebhookTrigger `json:"trigger"`
Registry RegistryInfo `json:"registry"`
Principal gitnesswebhook.PrincipalInfo `json:"principal"`
ArtifactInfo *registryevents.ArtifactInfo `json:"artifact_info"`
ArtifactChangeInfo *registryevents.ArtifactChangeInfo `json:"artifact_change_info"`
}
type RegistryInfo struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
URL string `json:"url"`
}
// handleEventArtifactCreated handles branch created events
// and triggers branch created webhooks for the source repo.
func (s *Service) handleEventArtifactCreated(
ctx context.Context,
event *events.Event[*registryevents.ArtifactCreatedPayload],
) error {
return s.triggerForEventWithArtifact(ctx, enum.WebhookTriggerArtifactCreated,
event.ID, event.Payload.PrincipalID, event.Payload.RegistryID,
func(
principal *types.Principal,
registry *registrytypes.Registry,
) (any, error) {
space, err := s.spaceStore.Find(ctx, registry.ParentID)
if err != nil {
return nil, err
}
return &ArtifactEventPayload{
Trigger: enum.WebhookTriggerArtifactCreated,
Registry: RegistryInfo{
ID: registry.ID,
Name: registry.Name,
Description: registry.Description,
URL: s.urlProvider.GenerateUIRegistryURL(ctx, space.Path, registry.Name),
},
Principal: gitnesswebhook.PrincipalInfo{
ID: principal.ID,
UID: principal.UID,
DisplayName: principal.DisplayName,
Email: principal.Email,
Type: principal.Type,
Created: principal.Created,
Updated: principal.Updated,
},
ArtifactInfo: getArtifactInfo(event.Payload.Artifact),
}, nil
})
}
// handleEventArtifactUpdated handles branch updated events
// and triggers branch updated webhooks for the source repo.
func (s *Service) handleEventArtifactUpdated(
ctx context.Context,
event *events.Event[*registryevents.ArtifactUpdatedPayload],
) error {
return s.triggerForEventWithArtifact(ctx, enum.WebhookTriggerArtifactUpdated,
event.ID, event.Payload.PrincipalID, event.Payload.RegistryID,
func(
principal *types.Principal,
registry *registrytypes.Registry,
) (any, error) {
space, err := s.spaceStore.Find(ctx, registry.ParentID)
if err != nil {
return nil, err
}
return &ArtifactEventPayload{
Trigger: enum.WebhookTriggerArtifactUpdated,
Registry: RegistryInfo{
ID: registry.ID,
Name: registry.Name,
Description: registry.Description,
URL: s.urlProvider.GenerateUIRegistryURL(ctx, space.Path, registry.Name),
},
Principal: gitnesswebhook.PrincipalInfo{
ID: principal.ID,
UID: principal.UID,
DisplayName: principal.DisplayName,
Email: principal.Email,
Type: principal.Type,
Created: principal.Created,
Updated: principal.Updated,
},
ArtifactInfo: getArtifactInfo(event.Payload.ArtifactChange.New),
ArtifactChangeInfo: getArtifactInfoForArtifactUpdated(*event.Payload),
}, nil
})
}
// handleEventArtifactDeleted handles branch deleted events
// and triggers branch deleted webhooks for the source repo.
func (s *Service) handleEventArtifactDeleted(
ctx context.Context,
event *events.Event[*registryevents.ArtifactDeletedPayload],
) error {
return s.triggerForEventWithArtifact(ctx, enum.WebhookTriggerArtifactDeleted,
event.ID, event.Payload.PrincipalID, event.Payload.RegistryID,
func(
principal *types.Principal,
registry *registrytypes.Registry,
) (any, error) {
space, err := s.spaceStore.Find(ctx, registry.ParentID)
if err != nil {
return nil, err
}
return &ArtifactEventPayload{
Trigger: enum.WebhookTriggerArtifactDeleted,
Registry: RegistryInfo{
ID: registry.ID,
Name: registry.Name,
Description: registry.Description,
URL: s.urlProvider.GenerateUIRegistryURL(ctx, space.Path, registry.Name),
},
Principal: gitnesswebhook.PrincipalInfo{
ID: principal.ID,
UID: principal.UID,
DisplayName: principal.DisplayName,
Email: principal.Email,
Type: principal.Type,
Created: principal.Created,
Updated: principal.Updated,
},
ArtifactInfo: getArtifactInfo(event.Payload.Artifact),
}, nil
})
}
func getArtifactInfo(eventArtifact registryevents.Artifact) *registryevents.ArtifactInfo {
artifactInfo := registryevents.ArtifactInfo{}
if dockerArtifact, ok := eventArtifact.(*registryevents.DockerArtifact); ok {
artifactInfo.Type = artifact.PackageTypeDOCKER
artifactInfo.Name = dockerArtifact.Name
artifactInfo.Version = dockerArtifact.Tag
artifactInfo.Artifact = &dockerArtifact
} else if helmArtifact, ok := eventArtifact.(*registryevents.HelmArtifact); ok {
artifactInfo.Type = artifact.PackageTypeHELM
artifactInfo.Name = helmArtifact.Name
artifactInfo.Version = helmArtifact.Tag
artifactInfo.Artifact = &helmArtifact
}
return &artifactInfo
}
func getArtifactInfoForArtifactUpdated(
payload registryevents.ArtifactUpdatedPayload,
) *registryevents.ArtifactChangeInfo {
artifactInfo := registryevents.ArtifactChangeInfo{
Type: payload.ArtifactType,
}
if dockerArtifact, ok := payload.ArtifactChange.New.(*registryevents.DockerArtifact); ok {
artifactInfo.Name = dockerArtifact.Name
} else if helmArtifact, ok := payload.ArtifactChange.New.(*registryevents.HelmArtifact); ok {
artifactInfo.Name = helmArtifact.Name
}
artifactInfo.ArtifactChange = &payload.ArtifactChange
return &artifactInfo
}
// triggerForEventWithArtifact triggers all webhooks for the given registry and triggerType
// using the eventID to generate a deterministic triggerID and using the output of bodyFn as payload.
// The method tries to find the registry and principal and provides both to the bodyFn to generate the body.
// NOTE: technically we could avoid this call if we send the data via the event (though then events will get big).
func (s *Service) triggerForEventWithArtifact(
ctx context.Context,
triggerType enum.WebhookTrigger,
eventID string,
principalID int64,
registryID int64,
createBodyFn func(*types.Principal, *registrytypes.Registry) (any, error),
) error {
principal, err := s.WebhookExecutor.FindPrincipalForEvent(ctx, principalID)
if err != nil {
return err
}
registry, err := s.registryRepository.Get(ctx, registryID)
if err != nil {
return err
}
body, err := createBodyFn(principal, registry)
if err != nil {
return fmt.Errorf("body creation function failed: %w", err)
}
parents, err := s.getParentInfoRegistry(ctx, registry.ID, true)
if err != nil {
return fmt.Errorf("failed to get webhook parent info: %w", err)
}
return s.WebhookExecutor.TriggerForEvent(ctx, eventID, parents, triggerType, body)
}
func (s *Service) getParentInfoRegistry(
ctx context.Context,
registryId int64,
inherited bool,
) ([]types.WebhookParentInfo, error) {
var parents []types.WebhookParentInfo
parents = append(parents, types.WebhookParentInfo{
ID: registryId,
Type: enum.WebhookParentRegistry,
})
if inherited {
registry, err := s.registryRepository.Get(ctx, registryId)
if err != nil {
return nil, fmt.Errorf("failed to get registry: %w", err)
}
ids, err := s.spaceStore.GetAncestorIDs(ctx, registry.ParentID)
if err != nil {
return nil, fmt.Errorf("failed to get parent space ids: %w", err)
}
for _, id := range ids {
parents = append(parents, types.WebhookParentInfo{
Type: enum.WebhookParentSpace,
ID: id,
})
}
}
return parents, nil
}

View File

@ -0,0 +1,56 @@
package webhook
import (
"context"
registrystore "github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/types"
)
type RegistryWebhookExecutorStore struct {
webhookStore registrystore.WebhooksRepository
webhookExecutionStore registrystore.WebhooksExecutionRepository
}
func (s *RegistryWebhookExecutorStore) Find(ctx context.Context, id int64) (*types.WebhookExecutionCore, error) {
return s.webhookExecutionStore.Find(ctx, id)
}
func (s *RegistryWebhookExecutorStore) ListWebhooks(
ctx context.Context,
parents []types.WebhookParentInfo,
) ([]*types.WebhookCore, error) {
return s.webhookStore.ListAllByRegistry(ctx, parents)
}
func (s *RegistryWebhookExecutorStore) ListForTrigger(
ctx context.Context,
triggerID string,
) ([]*types.WebhookExecutionCore, error) {
return s.webhookExecutionStore.ListForTrigger(ctx, triggerID)
}
func (s *RegistryWebhookExecutorStore) CreateWebhookExecution(
ctx context.Context,
hook *types.WebhookExecutionCore,
) error {
return s.webhookExecutionStore.Create(ctx, hook)
}
func (s *RegistryWebhookExecutorStore) UpdateOptLock(
ctx context.Context, hook *types.WebhookCore,
execution *types.WebhookExecutionCore,
) (*types.WebhookCore, error) {
fn := func(hook *types.WebhookCore) error {
hook.LatestExecutionResult = &execution.Result
return nil
}
return s.webhookStore.UpdateOptLock(ctx, hook, fn)
}
func (s *RegistryWebhookExecutorStore) FindWebhook(
ctx context.Context,
id int64,
) (*types.WebhookCore, error) {
return s.webhookStore.Find(ctx, id)
}

View File

@ -0,0 +1,109 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package webhook
import (
"context"
"fmt"
"time"
gitnesswebhook "github.com/harness/gitness/app/services/webhook"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/encrypt"
"github.com/harness/gitness/events"
events2 "github.com/harness/gitness/registry/app/events"
registrystore "github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/secret"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/stream"
)
const (
eventsReaderGroupName = "gitness:webhook"
)
// Service is responsible for processing webhook events.
type Service struct {
WebhookExecutor *gitnesswebhook.WebhookExecutor
tx dbtx.Transactor
urlProvider url.Provider
spaceStore store.SpaceStore
principalStore store.PrincipalStore
config gitnesswebhook.Config
spacePathStore store.SpacePathStore
registryRepository registrystore.RegistryRepository
}
func NewService(
ctx context.Context,
config gitnesswebhook.Config,
tx dbtx.Transactor,
artifactsReaderFactory *events.ReaderFactory[*events2.Reader],
webhookStore registrystore.WebhooksRepository,
webhookExecutionStore registrystore.WebhooksExecutionRepository,
spaceStore store.SpaceStore,
urlProvider url.Provider,
principalStore store.PrincipalStore,
webhookURLProvider gitnesswebhook.URLProvider,
spacePathStore store.SpacePathStore,
secretService secret.Service,
registryRepository registrystore.RegistryRepository,
encrypter encrypt.Encrypter,
) (*Service, error) {
if err := config.Prepare(); err != nil {
return nil, fmt.Errorf("provided webhook service config is invalid: %w", err)
}
webhookExecutorStore := &RegistryWebhookExecutorStore{
webhookStore: webhookStore,
webhookExecutionStore: webhookExecutionStore,
}
executor := gitnesswebhook.NewWebhookExecutor(config, webhookURLProvider, encrypter, spacePathStore,
secretService, principalStore, webhookExecutorStore, gitnesswebhook.ArtifactRegistryTrigger)
service := &Service{
WebhookExecutor: executor,
tx: tx,
spaceStore: spaceStore,
urlProvider: urlProvider,
principalStore: principalStore,
config: config,
spacePathStore: spacePathStore,
registryRepository: registryRepository,
}
_, err := artifactsReaderFactory.Launch(ctx, eventsReaderGroupName, config.EventReaderName,
func(r *events2.Reader) error {
const idleTimeout = 1 * time.Minute
r.Configure(
stream.WithConcurrency(config.Concurrency),
stream.WithHandlerOptions(
stream.WithIdleTimeout(idleTimeout),
stream.WithMaxRetries(config.MaxRetries),
))
// register events
_ = r.RegisterArtifactCreated(service.handleEventArtifactCreated)
_ = r.RegisterArtifactUpdated(service.handleEventArtifactUpdated)
_ = r.RegisterArtifactDeleted(service.handleEventArtifactDeleted)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to launch git event reader for webhooks: %w", err)
}
return service, nil
}

View File

@ -0,0 +1,73 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package webhook
import (
"context"
"encoding/gob"
gitnesswebhook "github.com/harness/gitness/app/services/webhook"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/encrypt"
"github.com/harness/gitness/events"
registryevents "github.com/harness/gitness/registry/app/events"
registrystore "github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/secret"
"github.com/harness/gitness/store/database/dbtx"
"github.com/google/wire"
)
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
ProvideService,
)
func ProvideService(
ctx context.Context,
config gitnesswebhook.Config,
tx dbtx.Transactor,
artifactsReaderFactory *events.ReaderFactory[*registryevents.Reader],
webhookStore registrystore.WebhooksRepository,
webhookExecutionStore registrystore.WebhooksExecutionRepository,
spaceStore store.SpaceStore,
urlProvider url.Provider,
principalStore store.PrincipalStore,
webhookURLProvider gitnesswebhook.URLProvider,
spacePathStore store.SpacePathStore,
secretService secret.Service,
registryRepository registrystore.RegistryRepository,
encrypter encrypt.Encrypter,
) (*Service, error) {
gob.Register(&registryevents.DockerArtifact{})
gob.Register(&registryevents.HelmArtifact{})
return NewService(
ctx,
config,
tx,
artifactsReaderFactory,
webhookStore,
webhookExecutionStore,
spaceStore,
urlProvider,
principalStore,
webhookURLProvider,
spacePathStore,
secretService,
registryRepository,
encrypter,
)
}

View File

@ -31,3 +31,16 @@ func sortEnum[T constraints.Ordered](slice []T) []T {
slices.Sort(slice)
return slice
}
func Sanitize[E constraints.Ordered](element E, all func() ([]E, E)) (E, bool) {
allValues, defValue := all()
var empty E
if element == empty && defValue != empty {
return defValue, true
}
idx, exists := slices.BinarySearch(allValues, element)
if exists {
return allValues[idx], true
}
return defValue, false
}

View File

@ -1,33 +0,0 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package enum
// RegistryWebhookParent defines different types of parents of a webhook.
type RegistryWebhookParent string
func (RegistryWebhookParent) Enum() []interface{} { return toInterfaceSlice(webhookParents) }
const (
// WebhookParentRegistry describes a registry as webhook owner.
WebhookParentRegistry RegistryWebhookParent = "registry"
// WebhookParentSpace describes a space as webhook owner.
WebhookParentSpace RegistryWebhookParent = "space"
)
var webhookParents = sortEnum([]RegistryWebhookParent{
WebhookParentRegistry,
WebhookParentSpace,
})

View File

@ -1,46 +0,0 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"time"
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/registry/types/enum"
)
// Webhook DTO object.
type Webhook struct {
ID int64
Version int64
ParentType enum.RegistryWebhookParent
ParentID int64
CreatedBy int64
CreatedAt time.Time
UpdatedAt time.Time
Scope int64
Identifier string
Name string
Description string
URL string
SecretIdentifier string
SecretSpaceID int
Enabled bool
Insecure bool
Internal bool
ExtraHeaders []artifact.ExtraHeader
Triggers []artifact.Trigger
LatestExecutionResult *artifact.WebhookExecResult
}

View File

@ -92,11 +92,15 @@ const (
// WebhookParentSpace describes a space as webhook owner.
WebhookParentSpace WebhookParent = "space"
// WebhookParentRegistry describes a registry as webhook owner.
WebhookParentRegistry WebhookParent = "registry"
)
var webhookParents = sortEnum([]WebhookParent{
WebhookParentRepo,
WebhookParentSpace,
WebhookParentRegistry,
})
// WebhookExecutionResult defines the different results of a webhook execution.
@ -130,7 +134,7 @@ const (
// WebhookTypeExternal describes a webhook url pointing to external source.
WebhookTypeExternal WebhookType = iota
// WebhookTypeInternal describes a webhook url pointing to internal url.
// WebhookTypeInternal describes a repo webhook url pointing to internal url.
WebhookTypeInternal
// WebhookTypeJira describes a webhook url pointing to jira.
@ -190,6 +194,13 @@ const (
WebhookTriggerPullReqLabelAssigned WebhookTrigger = "pullreq_label_assigned"
// WebhookTriggerPullReqReviewSubmitted gets triggered when a pull request review is submitted.
WebhookTriggerPullReqReviewSubmitted = "pullreq_review_submitted"
// WebhookTriggerArtifactCreated gets triggered when an artifact gets created.
WebhookTriggerArtifactCreated WebhookTrigger = "artifact_created"
// WebhookTriggerArtifactUpdated gets triggered when an artifact gets updated.
WebhookTriggerArtifactUpdated WebhookTrigger = "artifact_updated"
// WebhookTriggerArtifactDeleted gets triggered when an artifact gets deleted.
WebhookTriggerArtifactDeleted WebhookTrigger = "artifact_deleted"
)
var webhookTriggers = sortEnum([]WebhookTrigger{
@ -210,4 +221,7 @@ var webhookTriggers = sortEnum([]WebhookTrigger{
WebhookTriggerPullReqMerged,
WebhookTriggerPullReqLabelAssigned,
WebhookTriggerPullReqReviewSubmitted,
WebhookTriggerArtifactCreated,
WebhookTriggerArtifactUpdated,
WebhookTriggerArtifactDeleted,
})

View File

@ -153,3 +153,49 @@ type WebhookParentInfo struct {
Type enum.WebhookParent
ID int64
}
// WebhookCore represents a webhook DTO object.
type WebhookCore struct {
ID int64
Version int64
ParentID int64
ParentType enum.WebhookParent
CreatedBy int64
Created int64
Updated int64
Type enum.WebhookType
Scope int64
Identifier string
DisplayName string
Description string
URL string
Secret string
Enabled bool
Insecure bool
Triggers []enum.WebhookTrigger
LatestExecutionResult *enum.WebhookExecutionResult
SecretIdentifier string
SecretSpaceID int
ExtraHeaders []ExtraHeader
}
// WebhookExecutionCore represents a webhook execution DTO object.
type WebhookExecutionCore struct {
ID int64
RetriggerOf *int64
Retriggerable bool
Created int64
WebhookID int64
TriggerType enum.WebhookTrigger
TriggerID string
Result enum.WebhookExecutionResult
Duration int64
Error string
Request WebhookExecutionRequest
Response WebhookExecutionResponse
}
type ExtraHeader struct {
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
}