feat: [CODE-2292]: Add API for space level webhook creation (#2730)

* Merge remote-tracking branch 'origin/main' into dd/webhooks-space
* Move webphookpreprocessor to webhook controller
* Merge remote-tracking branch 'origin/main' into dd/webhooks-space
* Merge branch 'main' into dd/webhooks-space
* Add webhookpreprocessor service
* Add WebhookParentInfo type and refactor webhook svc and store to use it
* Introduce webhookpreprocessor service
* Add space webhooks
* Refactor handlers to use custom fn params
* Move everything webhook service related to webhook service
* Remove webhook parent info type
* Add webhook execution count
* Add space webhooks
pull/3576/head
Darko Draskovic 2024-10-18 16:27:56 +00:00 committed by Harness
parent fd848b1e15
commit b002a60b02
60 changed files with 2158 additions and 684 deletions

View File

@ -19,54 +19,47 @@ import (
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/webhook"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/encrypt"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
type Controller struct {
allowLoopback bool
allowPrivateNetwork bool
authorizer authz.Authorizer
webhookStore store.WebhookStore
webhookExecutionStore store.WebhookExecutionStore
repoStore store.RepoStore
webhookService *webhook.Service
encrypter encrypt.Encrypter
authorizer authz.Authorizer
spaceStore store.SpaceStore
repoStore store.RepoStore
webhookService *webhook.Service
encrypter encrypt.Encrypter
preprocessor Preprocessor
}
func NewController(
allowLoopback bool,
allowPrivateNetwork bool,
authorizer authz.Authorizer,
webhookStore store.WebhookStore,
webhookExecutionStore store.WebhookExecutionStore,
spaceStore store.SpaceStore,
repoStore store.RepoStore,
webhookService *webhook.Service,
encrypter encrypt.Encrypter,
preprocessor Preprocessor,
) *Controller {
return &Controller{
allowLoopback: allowLoopback,
allowPrivateNetwork: allowPrivateNetwork,
authorizer: authorizer,
webhookStore: webhookStore,
webhookExecutionStore: webhookExecutionStore,
repoStore: repoStore,
webhookService: webhookService,
encrypter: encrypter,
authorizer: authorizer,
spaceStore: spaceStore,
repoStore: repoStore,
webhookService: webhookService,
encrypter: encrypter,
preprocessor: preprocessor,
}
}
func (c *Controller) getRepoCheckAccess(ctx context.Context,
session *auth.Session, repoRef string, reqPermission enum.Permission) (*types.Repository, error) {
if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
return nil, errors.InvalidArgument("A valid repository reference must be provided.")
}
repo, err := c.repoStore.FindByRef(ctx, repoRef)
@ -80,3 +73,24 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return repo, nil
}
func (c *Controller) getSpaceCheckAccess(
ctx context.Context,
session *auth.Session,
spaceRef string,
permission enum.Permission,
) (*types.Space, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("parent space not found: %w", err)
}
scope := &types.Scope{SpacePath: space.Path}
resource := &types.Resource{Type: enum.ResourceTypeRepo}
err = apiauth.Check(ctx, c.authorizer, session, scope, resource, permission)
if err != nil {
return nil, fmt.Errorf("auth check failed: %w", err)
}
return space, nil
}

View File

@ -1,71 +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 webhook
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// Find finds a webhook from the provided repository.
func (c *Controller) Find(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
) (*types.Webhook, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}
return c.getWebhookVerifyOwnership(ctx, repo.ID, webhookIdentifier)
}
func (c *Controller) getWebhookVerifyOwnership(
ctx context.Context,
repoID int64,
webhookIdentifier string,
) (*types.Webhook, error) {
// TODO: Remove once webhook identifier migration completed
webhookID, err := strconv.ParseInt(webhookIdentifier, 10, 64)
if (err == nil && webhookID <= 0) || len(strings.TrimSpace(webhookIdentifier)) == 0 {
return nil, usererror.BadRequest("A valid webhook identifier must be provided.")
}
var webhook *types.Webhook
if err == nil {
webhook, err = c.webhookStore.Find(ctx, webhookID)
} else {
webhook, err = c.webhookStore.FindByIdentifier(ctx, enum.WebhookParentRepo, repoID, webhookIdentifier)
}
if err != nil {
return nil, fmt.Errorf("failed to find webhook with identifier %q: %w", webhookIdentifier, err)
}
// ensure the webhook actually belongs to the repo
if webhook.ParentType != enum.WebhookParentRepo || webhook.ParentID != repoID {
return nil, fmt.Errorf("webhook doesn't belong to requested repo. Returning error %w", usererror.ErrNotFound)
}
return webhook, nil
}

View File

@ -1,72 +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 webhook
import (
"context"
"fmt"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// FindExecution finds a webhook execution.
func (c *Controller) FindExecution(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}
// get the webhook and ensure it belongs to us
webhook, err := c.getWebhookVerifyOwnership(ctx, repo.ID, webhookIdentifier)
if err != nil {
return nil, err
}
// get the webhook execution and ensure it belongs to us
webhookExecution, err := c.getWebhookExecutionVerifyOwnership(ctx, webhook.ID, webhookExecutionID)
if err != nil {
return nil, err
}
return webhookExecution, nil
}
func (c *Controller) getWebhookExecutionVerifyOwnership(ctx context.Context, webhookID int64,
webhookExecutionID int64) (*types.WebhookExecution, error) {
if webhookExecutionID <= 0 {
return nil, usererror.BadRequest("A valid webhook execution ID must be provided.")
}
webhookExecution, err := c.webhookExecutionStore.Find(ctx, webhookExecutionID)
if err != nil {
return nil, fmt.Errorf("failed to find webhook execution with id %d: %w", webhookExecutionID, err)
}
// ensure the webhook execution actually belongs to the webhook
if webhookID != webhookExecution.WebhookID {
return nil, fmt.Errorf("webhook execution doesn't belong to requested webhook. Returning error %w",
usererror.ErrNotFound)
}
return webhookExecution, nil
}

View File

@ -0,0 +1,59 @@
// 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 (
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
type Preprocessor interface {
PreprocessCreateInput(enum.PrincipalType, *types.WebhookCreateInput) (bool, error)
PreprocessUpdateInput(enum.PrincipalType, *types.WebhookUpdateInput) (bool, error)
PreprocessFilter(enum.PrincipalType, *types.WebhookFilter)
IsInternalCall(enum.PrincipalType) bool
}
type NoopPreprocessor struct {
}
// PreprocessCreateInput always return false for internal.
func (p NoopPreprocessor) PreprocessCreateInput(
enum.PrincipalType,
*types.WebhookCreateInput,
) (bool, error) {
return false, nil
}
// PreprocessUpdateInput always return false for internal.
func (p NoopPreprocessor) PreprocessUpdateInput(
enum.PrincipalType,
*types.WebhookUpdateInput,
) (bool, error) {
return false, nil
}
func (p NoopPreprocessor) PreprocessFilter(_ enum.PrincipalType, filter *types.WebhookFilter) {
if filter.Order == enum.OrderDefault {
filter.Order = enum.OrderAsc
}
// always skip internal for requests from handler
filter.SkipInternal = true
}
func (p NoopPreprocessor) IsInternalCall(enum.PrincipalType) bool {
return false
}

View File

@ -0,0 +1,51 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// CreateRepo creates a new repo webhook.
func (c *Controller) CreateRepo(
ctx context.Context,
session *auth.Session,
repoRef string,
in *types.WebhookCreateInput,
) (*types.Webhook, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to the repo: %w", err)
}
internal, err := c.preprocessor.PreprocessCreateInput(session.Principal.Type, in)
if err != nil {
return nil, fmt.Errorf("failed to preprocess create input: %w", err)
}
hook, err := c.webhookService.Create(
ctx, session.Principal.ID, repo.ID, enum.WebhookParentRepo, internal, in,
)
if err != nil {
return nil, fmt.Errorf("failed create webhook: %w", err)
}
return hook, nil
}

View File

@ -16,32 +16,26 @@ package webhook
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
// Delete deletes an existing webhook.
func (c *Controller) Delete(
// DeleteRepo deletes an existing webhook.
func (c *Controller) DeleteRepo(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
allowDeletingInternal bool,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return err
return fmt.Errorf("failed to acquire access to the repo: %w", err)
}
// get the webhook and ensure it belongs to us
webhook, err := c.getWebhookVerifyOwnership(ctx, repo.ID, webhookIdentifier)
if err != nil {
return err
}
if webhook.Internal && !allowDeletingInternal {
return ErrInternalWebhookOperationNotAllowed
}
// delete webhook
return c.webhookStore.Delete(ctx, webhook.ID)
return c.webhookService.Delete(
ctx, repo.ID, enum.WebhookParentRepo, webhookIdentifier,
c.preprocessor.IsInternalCall(session.Principal.Type),
)
}

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 webhook
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// FindRepo finds a webhook from the provided repository.
func (c *Controller) FindRepo(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
) (*types.Webhook, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to the repo: %w", err)
}
return c.webhookService.Find(ctx, repo.ID, enum.WebhookParentRepo, webhookIdentifier)
}

View File

@ -0,0 +1,41 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// FindExecutionRepo finds a webhook execution.
func (c *Controller) FindExecutionRepo(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to the repo: %w", err)
}
return c.webhookService.FindExecution(
ctx, repo.ID, enum.WebhookParentRepo, webhookIdentifier, webhookExecutionID)
}

View File

@ -23,27 +23,20 @@ import (
"github.com/harness/gitness/types/enum"
)
// List returns the webhooks from the provided repository.
func (c *Controller) List(
// ListRepo returns the webhooks from the provided repository.
func (c *Controller) ListRepo(
ctx context.Context,
session *auth.Session,
repoRef string,
inherited bool,
filter *types.WebhookFilter,
) ([]*types.Webhook, int64, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, 0, err
return nil, 0, fmt.Errorf("failed to acquire access to the repo: %w", err)
}
count, err := c.webhookStore.Count(ctx, enum.WebhookParentRepo, repo.ID, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to count webhooks for repo with id %d: %w", repo.ID, err)
}
c.preprocessor.PreprocessFilter(session.Principal.Type, filter)
webhooks, err := c.webhookStore.List(ctx, enum.WebhookParentRepo, repo.ID, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list webhooks for repo with id %d: %w", repo.ID, err)
}
return webhooks, count, nil
return c.webhookService.List(ctx, repo.ID, enum.WebhookParentRepo, inherited, filter)
}

View File

@ -23,30 +23,19 @@ import (
"github.com/harness/gitness/types/enum"
)
// ListExecutions returns the executions of the webhook.
func (c *Controller) ListExecutions(
// ListExecutionsRepo returns the executions of the webhook.
func (c *Controller) ListExecutionsRepo(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
filter *types.WebhookExecutionFilter,
) ([]*types.WebhookExecution, error) {
) ([]*types.WebhookExecution, int64, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
return nil, 0, fmt.Errorf("failed to acquire access to the repo: %w", err)
}
// get the webhook and ensure it belongs to us
webhook, err := c.getWebhookVerifyOwnership(ctx, repo.ID, webhookIdentifier)
if err != nil {
return nil, err
}
// get webhook executions
webhookExecutions, err := c.webhookExecutionStore.ListForWebhook(ctx, webhook.ID, filter)
if err != nil {
return nil, fmt.Errorf("failed to list webhook executions for webhook %d: %w", webhook.ID, err)
}
return webhookExecutions, nil
return c.webhookService.ListExecutions(
ctx, repo.ID, enum.WebhookParentRepo, webhookIdentifier, filter)
}

View File

@ -0,0 +1,41 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// RetriggerExecutionRepo retriggers an existing webhook execution.
func (c *Controller) RetriggerExecutionRepo(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to the repo: %w", err)
}
return c.webhookService.RetriggerExecution(
ctx, repo.ID, enum.WebhookParentRepo, webhookIdentifier, webhookExecutionID)
}

View File

@ -0,0 +1,47 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// UpdateRepo updates an existing webhook.
func (c *Controller) UpdateRepo(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
in *types.WebhookUpdateInput,
) (*types.Webhook, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to the repo: %w", err)
}
allowModifyingInternal, err := c.preprocessor.PreprocessUpdateInput(session.Principal.Type, in)
if err != nil {
return nil, fmt.Errorf("failed to preprocess update input: %w", err)
}
return c.webhookService.Update(
ctx, repo.ID, enum.WebhookParentRepo, webhookIdentifier, allowModifyingInternal, in,
)
}

View File

@ -1,67 +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 webhook
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
// RetriggerExecution retriggers an existing webhook execution.
func (c *Controller) RetriggerExecution(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to the repo: %w", err)
}
// get the webhook and ensure it belongs to us
webhook, err := c.getWebhookVerifyOwnership(ctx, repo.ID, webhookIdentifier)
if err != nil {
return nil, err
}
// get the webhookexecution and ensure it belongs to us
webhookExecution, err := c.getWebhookExecutionVerifyOwnership(ctx, webhook.ID, webhookExecutionID)
if err != nil {
return nil, err
}
// retrigger the execution ...
executionResult, err := c.webhookService.RetriggerWebhookExecution(ctx, webhookExecution.ID)
if err != nil {
return nil, fmt.Errorf("failed to retrigger webhook execution: %w", err)
}
// log execution error so we have the necessary debug information if needed
if executionResult.Err != nil {
log.Ctx(ctx).Warn().Err(executionResult.Err).Msgf(
"retrigger of webhhook %d execution %d (new id: %d) had an error",
webhook.ID, webhookExecution.ID, executionResult.Execution.ID)
}
return executionResult.Execution, nil
}

View File

@ -0,0 +1,51 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// CreateSpace creates a new webhook.
func (c *Controller) CreateSpace(
ctx context.Context,
session *auth.Session,
spaceRef string,
in *types.WebhookCreateInput,
) (*types.Webhook, error) {
space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
internal, err := c.preprocessor.PreprocessCreateInput(session.Principal.Type, in)
if err != nil {
return nil, fmt.Errorf("failed to preprocess create input: %w", err)
}
hook, err := c.webhookService.Create(
ctx, session.Principal.ID, space.ID, enum.WebhookParentSpace, internal, in,
)
if err != nil {
return nil, fmt.Errorf("failed to create webhook: %w", err)
}
return hook, nil
}

View File

@ -0,0 +1,41 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
// DeleteSpace deletes an existing webhook.
func (c *Controller) DeleteSpace(
ctx context.Context,
session *auth.Session,
spaceRef string,
webhookIdentifier string,
) error {
space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return fmt.Errorf("failed to acquire access to space: %w", err)
}
return c.webhookService.Delete(
ctx, space.ID, enum.WebhookParentSpace, webhookIdentifier,
c.preprocessor.IsInternalCall(session.Principal.Type),
)
}

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 webhook
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// FindSpace finds a webhook from the provided repository.
func (c *Controller) FindSpace(
ctx context.Context,
session *auth.Session,
spaceRef string,
webhookIdentifier string,
) (*types.Webhook, error) {
space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
return c.webhookService.Find(ctx, space.ID, enum.WebhookParentSpace, webhookIdentifier)
}

View File

@ -0,0 +1,41 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// FindExecutionSpace finds a webhook execution.
func (c *Controller) FindExecutionSpace(
ctx context.Context,
session *auth.Session,
spaceRef string,
webhookIdentifier string,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
return c.webhookService.FindExecution(
ctx, space.ID, enum.WebhookParentSpace, webhookIdentifier, webhookExecutionID)
}

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 webhook
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListSpace returns the webhooks from the provided space.
func (c *Controller) ListSpace(
ctx context.Context,
session *auth.Session,
spaceRef string,
inherited bool,
filter *types.WebhookFilter,
) ([]*types.Webhook, int64, error) {
space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, 0, fmt.Errorf("failed to acquire access to space: %w", err)
}
c.preprocessor.PreprocessFilter(session.Principal.Type, filter)
return c.webhookService.List(ctx, space.ID, enum.WebhookParentSpace, inherited, filter)
}

View File

@ -0,0 +1,41 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListExecutionsSpace returns the executions of the webhook.
func (c *Controller) ListExecutionsSpace(
ctx context.Context,
session *auth.Session,
spaceRef string,
webhookIdentifier string,
filter *types.WebhookExecutionFilter,
) ([]*types.WebhookExecution, int64, error) {
space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, 0, fmt.Errorf("failed to acquire access to space: %w", err)
}
return c.webhookService.ListExecutions(
ctx, space.ID, enum.WebhookParentSpace, webhookIdentifier, filter)
}

View File

@ -0,0 +1,41 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// RetriggerExecutionSpace retriggers an existing webhook execution.
func (c *Controller) RetriggerExecutionSpace(
ctx context.Context,
session *auth.Session,
spaceRef string,
webhookIdentifier string,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
return c.webhookService.RetriggerExecution(
ctx, space.ID, enum.WebhookParentSpace, webhookIdentifier, webhookExecutionID)
}

View File

@ -0,0 +1,47 @@
// 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"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// UpdateSpace updates an existing webhook.
func (c *Controller) UpdateSpace(
ctx context.Context,
session *auth.Session,
spaceRef string,
webhookIdentifier string,
in *types.WebhookUpdateInput,
) (*types.Webhook, error) {
space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
allowModifyingInternal, err := c.preprocessor.PreprocessUpdateInput(session.Principal.Type, in)
if err != nil {
return nil, fmt.Errorf("failed to preprocess update input: %w", err)
}
return c.webhookService.Update(
ctx, space.ID, enum.WebhookParentSpace, webhookIdentifier, allowModifyingInternal, in,
)
}

View File

@ -28,12 +28,15 @@ var WireSet = wire.NewSet(
ProvideController,
)
func ProvideController(config webhook.Config, authorizer authz.Authorizer,
webhookStore store.WebhookStore, webhookExecutionStore store.WebhookExecutionStore,
repoStore store.RepoStore, webhookService *webhook.Service, encrypter encrypt.Encrypter,
func ProvideController(authorizer authz.Authorizer,
spaceStore store.SpaceStore, repoStore store.RepoStore,
webhookService *webhook.Service, encrypter encrypt.Encrypter,
preprocessor Preprocessor,
) *Controller {
return NewController(
config.AllowLoopback, config.AllowPrivateNetwork, authorizer,
webhookStore, webhookExecutionStore,
repoStore, webhookService, encrypter)
authorizer, spaceStore, repoStore, webhookService, encrypter, preprocessor)
}
func ProvidePreprocessor() Preprocessor {
return NoopPreprocessor{}
}

View File

@ -21,10 +21,11 @@ import (
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
// HandleCreate returns a http.HandlerFunc that creates a new webhook.
func HandleCreate(webhookCtrl *webhook.Controller) http.HandlerFunc {
// HandleCreateRepo returns a http.HandlerFunc that creates a new webhook.
func HandleCreateRepo(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
@ -35,14 +36,14 @@ func HandleCreate(webhookCtrl *webhook.Controller) http.HandlerFunc {
return
}
in := new(webhook.CreateInput)
in := new(types.WebhookCreateInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)
return
}
hook, err := webhookCtrl.Create(ctx, session, repoRef, in, false)
hook, err := webhookCtrl.CreateRepo(ctx, session, repoRef, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -22,8 +22,8 @@ import (
"github.com/harness/gitness/app/api/request"
)
// HandleDelete returns a http.HandlerFunc that deletes a webhook.
func HandleDelete(webhookCtrl *webhook.Controller) http.HandlerFunc {
// HandleDeleteRepo returns a http.HandlerFunc that deletes a webhook.
func HandleDeleteRepo(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
@ -40,7 +40,7 @@ func HandleDelete(webhookCtrl *webhook.Controller) http.HandlerFunc {
return
}
err = webhookCtrl.Delete(ctx, session, repoRef, webhookIdentifier, false)
err = webhookCtrl.DeleteRepo(ctx, session, repoRef, webhookIdentifier)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -22,8 +22,8 @@ import (
"github.com/harness/gitness/app/api/request"
)
// HandleFind returns a http.HandlerFunc that finds a webhook.
func HandleFind(webhookCtrl *webhook.Controller) http.HandlerFunc {
// HandleFindRepo returns a http.HandlerFunc that finds a webhook.
func HandleFindRepo(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
@ -40,7 +40,7 @@ func HandleFind(webhookCtrl *webhook.Controller) http.HandlerFunc {
return
}
webhook, err := webhookCtrl.Find(ctx, session, repoRef, webhookIdentifier)
webhook, err := webhookCtrl.FindRepo(ctx, session, repoRef, webhookIdentifier)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -22,8 +22,8 @@ import (
"github.com/harness/gitness/app/api/request"
)
// HandleFindExecution returns a http.HandlerFunc that finds a webhook execution.
func HandleFindExecution(webhookCtrl *webhook.Controller) http.HandlerFunc {
// HandleFindExecutionRepo returns a http.HandlerFunc that finds a webhook execution.
func HandleFindExecutionRepo(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
@ -46,7 +46,7 @@ func HandleFindExecution(webhookCtrl *webhook.Controller) http.HandlerFunc {
return
}
execution, err := webhookCtrl.FindExecution(ctx, session, repoRef, webhookIdentifier, webhookExecutionID)
execution, err := webhookCtrl.FindExecutionRepo(ctx, session, repoRef, webhookIdentifier, webhookExecutionID)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -20,11 +20,10 @@ import (
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types/enum"
)
// HandleList returns a http.HandlerFunc that lists webhooks.
func HandleList(webhookCtrl *webhook.Controller) http.HandlerFunc {
// HandleListRepo returns a http.HandlerFunc that lists webhooks.
func HandleListRepo(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
@ -35,15 +34,14 @@ func HandleList(webhookCtrl *webhook.Controller) http.HandlerFunc {
return
}
filter := request.ParseWebhookFilter(r)
if filter.Order == enum.OrderDefault {
filter.Order = enum.OrderAsc
inherited, err := request.ParseInheritedFromQuery(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
}
// always skip internal for requests from handler
filter.SkipInternal = true
filter := request.ParseWebhookFilter(r)
webhooks, totalCount, err := webhookCtrl.List(ctx, session, repoRef, filter)
webhooks, totalCount, err := webhookCtrl.ListRepo(ctx, session, repoRef, inherited, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -22,8 +22,8 @@ import (
"github.com/harness/gitness/app/api/request"
)
// HandleListExecutions returns a http.HandlerFunc that lists webhook executions.
func HandleListExecutions(webhookCtrl *webhook.Controller) http.HandlerFunc {
// HandleListExecutionsRepo returns a http.HandlerFunc that lists webhook executions.
func HandleListExecutionsRepo(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
@ -42,15 +42,13 @@ func HandleListExecutions(webhookCtrl *webhook.Controller) http.HandlerFunc {
filter := request.ParseWebhookExecutionFilter(r)
executions, err := webhookCtrl.ListExecutions(ctx, session, repoRef, webhookIdentifier, filter)
executions, total, err := webhookCtrl.ListExecutionsRepo(ctx, session, repoRef, webhookIdentifier, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
// TODO: get last page indicator explicitly - current check is wrong in case len % pageSize == 0
isLastPage := len(executions) < filter.Size
render.PaginationNoTotal(r, w, filter.Page, filter.Size, isLastPage)
render.Pagination(r, w, filter.Page, filter.Size, int(total))
render.JSON(w, http.StatusOK, executions)
}
}

View File

@ -22,8 +22,8 @@ import (
"github.com/harness/gitness/app/api/request"
)
// HandleRetriggerExecution returns a http.HandlerFunc that retriggers a webhook executions.
func HandleRetriggerExecution(webhookCtrl *webhook.Controller) http.HandlerFunc {
// HandleRetriggerExecutionRepo returns a http.HandlerFunc that retriggers a webhook executions.
func HandleRetriggerExecutionRepo(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
@ -46,7 +46,7 @@ func HandleRetriggerExecution(webhookCtrl *webhook.Controller) http.HandlerFunc
return
}
execution, err := webhookCtrl.RetriggerExecution(ctx, session, repoRef, webhookIdentifier, webhookExecutionID)
execution, err := webhookCtrl.RetriggerExecutionRepo(ctx, session, repoRef, webhookIdentifier, webhookExecutionID)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -21,10 +21,11 @@ import (
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
// HandleUpdate returns a http.HandlerFunc that updates an existing webhook.
func HandleUpdate(webhookCtrl *webhook.Controller) http.HandlerFunc {
// HandleUpdateRepo returns a http.HandlerFunc that updates an existing webhook.
func HandleUpdateRepo(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
@ -41,14 +42,14 @@ func HandleUpdate(webhookCtrl *webhook.Controller) http.HandlerFunc {
return
}
in := new(webhook.UpdateInput)
in := new(types.WebhookUpdateInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)
return
}
hook, err := webhookCtrl.Update(ctx, session, repoRef, webhookIdentifier, in, false)
hook, err := webhookCtrl.UpdateRepo(ctx, session, repoRef, webhookIdentifier, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -0,0 +1,54 @@
// 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 (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
// HandleCreateSpace returns a http.HandlerFunc that creates a new webhook.
func HandleCreateSpace(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.WebhookCreateInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)
return
}
hook, err := webhookCtrl.CreateSpace(ctx, session, spaceRef, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusCreated, hook)
}
}

View File

@ -0,0 +1,51 @@
// 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 (
"net/http"
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleDeleteSpace returns a http.HandlerFunc that deletes a webhook.
func HandleDeleteSpace(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
webhookIdentifier, err := request.GetWebhookIdentifierFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
err = webhookCtrl.DeleteSpace(ctx, session, spaceRef, webhookIdentifier)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,51 @@
// 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 (
"net/http"
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleFindSpace returns a http.HandlerFunc that finds a webhook.
func HandleFindSpace(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
webhookIdentifier, err := request.GetWebhookIdentifierFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
webhook, err := webhookCtrl.FindSpace(ctx, session, spaceRef, webhookIdentifier)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, webhook)
}
}

View File

@ -0,0 +1,57 @@
// 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 (
"net/http"
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleFindExecutionSpace returns a http.HandlerFunc that finds a webhook execution.
func HandleFindExecutionSpace(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
webhookIdentifier, err := request.GetWebhookIdentifierFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
webhookExecutionID, err := request.GetWebhookExecutionIDFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
execution, err := webhookCtrl.FindExecutionSpace(ctx, session, spaceRef, webhookIdentifier, webhookExecutionID)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, execution)
}
}

View File

@ -0,0 +1,53 @@
// 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 (
"net/http"
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleListSpace returns a http.HandlerFunc that lists webhooks.
func HandleListSpace(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
inherited, err := request.ParseInheritedFromQuery(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
}
filter := request.ParseWebhookFilter(r)
webhooks, totalCount, err := webhookCtrl.ListSpace(ctx, session, spaceRef, inherited, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.Pagination(r, w, filter.Page, filter.Size, int(totalCount))
render.JSON(w, http.StatusOK, webhooks)
}
}

View File

@ -0,0 +1,54 @@
// 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 (
"net/http"
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleListExecutionsSpace returns a http.HandlerFunc that lists webhook executions.
func HandleListExecutionsSpace(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
webhookIdentifier, err := request.GetWebhookIdentifierFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
filter := request.ParseWebhookExecutionFilter(r)
executions, total, err := webhookCtrl.ListExecutionsSpace(ctx, session, spaceRef, webhookIdentifier, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.Pagination(r, w, filter.Page, filter.Size, int(total))
render.JSON(w, http.StatusOK, executions)
}
}

View File

@ -0,0 +1,57 @@
// 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 (
"net/http"
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleRetriggerExecutionSpace returns a http.HandlerFunc that retriggers a webhook executions.
func HandleRetriggerExecutionSpace(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
webhookIdentifier, err := request.GetWebhookIdentifierFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
webhookExecutionID, err := request.GetWebhookExecutionIDFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
execution, err := webhookCtrl.RetriggerExecutionSpace(ctx, session, spaceRef, webhookIdentifier, webhookExecutionID)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, execution)
}
}

View File

@ -0,0 +1,60 @@
// 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 (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
// HandleUpdateSpace returns a http.HandlerFunc that updates an existing webhook.
func HandleUpdateSpace(webhookCtrl *webhook.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
webhookIdentifier, err := request.GetWebhookIdentifierFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.WebhookUpdateInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)
return
}
hook, err := webhookCtrl.UpdateSpace(ctx, session, spaceRef, webhookIdentifier, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, hook)
}
}

View File

@ -17,7 +17,6 @@ package openapi
import (
"net/http"
"github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/types"
@ -33,44 +32,84 @@ type webhookType struct {
HasSecret bool `json:"has_secret"`
}
type createWebhookRequest struct {
repoRequest
webhook.CreateInput
type createSpaceWebhookRequest struct {
spaceRequest
types.WebhookCreateInput
}
type listWebhooksRequest struct {
type createRepoWebhookRequest struct {
repoRequest
types.WebhookCreateInput
}
type listSpaceWebhooksRequest struct {
spaceRequest
}
type listRepoWebhooksRequest struct {
repoRequest
}
type webhookRequest struct {
type spaceWebhookRequest struct {
spaceRequest
ID int64 `path:"webhook_identifier"`
}
type repoWebhookRequest struct {
repoRequest
ID int64 `path:"webhook_identifier"`
}
type getWebhookRequest struct {
webhookRequest
type getSpaceWebhookRequest struct {
spaceWebhookRequest
}
type deleteWebhookRequest struct {
webhookRequest
type getRepoWebhookRequest struct {
repoWebhookRequest
}
type updateWebhookRequest struct {
webhookRequest
webhook.UpdateInput
type deleteSpaceWebhookRequest struct {
spaceWebhookRequest
}
type listWebhookExecutionsRequest struct {
webhookRequest
type deleteRepoWebhookRequest struct {
repoWebhookRequest
}
type webhookExecutionRequest struct {
webhookRequest
type updateSpaceWebhookRequest struct {
spaceWebhookRequest
types.WebhookUpdateInput
}
type updateRepoWebhookRequest struct {
repoWebhookRequest
types.WebhookUpdateInput
}
type listSpaceWebhookExecutionsRequest struct {
spaceWebhookRequest
}
type listRepoWebhookExecutionsRequest struct {
repoWebhookRequest
}
type spaceWebhookExecutionRequest struct {
spaceWebhookRequest
ID int64 `path:"webhook_execution_id"`
}
type getWebhookExecutionRequest struct {
webhookExecutionRequest
type repoWebhookExecutionRequest struct {
repoWebhookRequest
ID int64 `path:"webhook_execution_id"`
}
type getSpaceWebhookExecutionRequest struct {
spaceWebhookExecutionRequest
}
type getRepoWebhookExecutionRequest struct {
repoWebhookExecutionRequest
}
var queryParameterSortWebhook = openapi3.ParameterOrRef{
@ -113,99 +152,210 @@ var queryParameterQueryWebhook = openapi3.ParameterOrRef{
//nolint:funlen
func webhookOperations(reflector *openapi3.Reflector) {
createWebhook := openapi3.Operation{}
createWebhook.WithTags("webhook")
createWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "createWebhook"})
_ = reflector.SetRequest(&createWebhook, new(createWebhookRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&createWebhook, new(webhookType), http.StatusCreated)
_ = reflector.SetJSONResponse(&createWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&createWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&createWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&createWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos/{repo_ref}/webhooks", createWebhook)
// space
listWebhooks := openapi3.Operation{}
listWebhooks.WithTags("webhook")
listWebhooks.WithMapOfAnything(map[string]interface{}{"operationId": "listWebhooks"})
listWebhooks.WithParameters(queryParameterQueryWebhook, queryParameterSortWebhook, queryParameterOrder,
createSpaceWebhook := openapi3.Operation{}
createSpaceWebhook.WithTags("webhook")
createSpaceWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "createSpaceWebhook"})
_ = reflector.SetRequest(&createSpaceWebhook, new(createSpaceWebhookRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&createSpaceWebhook, new(webhookType), http.StatusCreated)
_ = reflector.SetJSONResponse(&createSpaceWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&createSpaceWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&createSpaceWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&createSpaceWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/spaces/{space_ref}/webhooks", createSpaceWebhook)
listSpaceWebhooks := openapi3.Operation{}
listSpaceWebhooks.WithTags("webhook")
listSpaceWebhooks.WithMapOfAnything(map[string]interface{}{"operationId": "listSpaceWebhooks"})
listSpaceWebhooks.WithParameters(queryParameterQueryWebhook, queryParameterSortWebhook, queryParameterOrder,
QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&listWebhooks, new(listWebhooksRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&listWebhooks, new([]webhookType), http.StatusOK)
_ = reflector.SetJSONResponse(&listWebhooks, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&listWebhooks, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&listWebhooks, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&listWebhooks, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/webhooks", listWebhooks)
_ = reflector.SetRequest(&listSpaceWebhooks, new(listSpaceWebhooksRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&listSpaceWebhooks, new([]webhookType), http.StatusOK)
_ = reflector.SetJSONResponse(&listSpaceWebhooks, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&listSpaceWebhooks, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&listSpaceWebhooks, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&listSpaceWebhooks, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/webhooks", listSpaceWebhooks)
getWebhook := openapi3.Operation{}
getWebhook.WithTags("webhook")
getWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "getWebhook"})
_ = reflector.SetRequest(&getWebhook, new(getWebhookRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&getWebhook, new(webhookType), http.StatusOK)
_ = reflector.SetJSONResponse(&getWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&getWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&getWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&getWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/webhooks/{webhook_identifier}", getWebhook)
getSpaceWebhook := openapi3.Operation{}
getSpaceWebhook.WithTags("webhook")
getSpaceWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "getSpaceWebhook"})
_ = reflector.SetRequest(&getSpaceWebhook, new(getSpaceWebhookRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&getSpaceWebhook, new(webhookType), http.StatusOK)
_ = reflector.SetJSONResponse(&getSpaceWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&getSpaceWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&getSpaceWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&getSpaceWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/webhooks/{webhook_identifier}", getSpaceWebhook)
updateWebhook := openapi3.Operation{}
updateWebhook.WithTags("webhook")
updateWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "updateWebhook"})
_ = reflector.SetRequest(&updateWebhook, new(updateWebhookRequest), http.MethodPatch)
_ = reflector.SetJSONResponse(&updateWebhook, new(webhookType), http.StatusOK)
_ = reflector.SetJSONResponse(&updateWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&updateWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&updateWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&updateWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPatch, "/repos/{repo_ref}/webhooks/{webhook_identifier}", updateWebhook)
updateSpaceWebhook := openapi3.Operation{}
updateSpaceWebhook.WithTags("webhook")
updateSpaceWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "updateWebhook"})
_ = reflector.SetRequest(&updateSpaceWebhook, new(updateSpaceWebhookRequest), http.MethodPatch)
_ = reflector.SetJSONResponse(&updateSpaceWebhook, new(webhookType), http.StatusOK)
_ = reflector.SetJSONResponse(&updateSpaceWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&updateSpaceWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&updateSpaceWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&updateSpaceWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(
http.MethodPatch, "/spaces/{space_ref}/webhooks/{webhook_identifier}", updateSpaceWebhook,
)
deleteWebhook := openapi3.Operation{}
deleteWebhook.WithTags("webhook")
deleteWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "deleteWebhook"})
_ = reflector.SetRequest(&deleteWebhook, new(deleteWebhookRequest), http.MethodDelete)
_ = reflector.SetJSONResponse(&deleteWebhook, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&deleteWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&deleteWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&deleteWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&deleteWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodDelete, "/repos/{repo_ref}/webhooks/{webhook_identifier}", deleteWebhook)
deleteSpaceWebhook := openapi3.Operation{}
deleteSpaceWebhook.WithTags("webhook")
deleteSpaceWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "deleteWebhook"})
_ = reflector.SetRequest(&deleteSpaceWebhook, new(deleteSpaceWebhookRequest), http.MethodDelete)
_ = reflector.SetJSONResponse(&deleteSpaceWebhook, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&deleteSpaceWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&deleteSpaceWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&deleteSpaceWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&deleteSpaceWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(
http.MethodDelete, "/spaces/{space_ref}/webhooks/{webhook_identifier}", deleteSpaceWebhook,
)
listWebhookExecutions := openapi3.Operation{}
listWebhookExecutions.WithTags("webhook")
listWebhookExecutions.WithMapOfAnything(map[string]interface{}{"operationId": "listWebhookExecutions"})
listWebhookExecutions.WithParameters(QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&listWebhookExecutions, new(listWebhookExecutionsRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&listWebhookExecutions, new([]types.WebhookExecution), http.StatusOK)
_ = reflector.SetJSONResponse(&listWebhookExecutions, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&listWebhookExecutions, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&listWebhookExecutions, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&listWebhookExecutions, new(usererror.Error), http.StatusForbidden)
listSpaceWebhookExecutions := openapi3.Operation{}
listSpaceWebhookExecutions.WithTags("webhook")
listSpaceWebhookExecutions.WithMapOfAnything(map[string]interface{}{"operationId": "listSpaceWebhookExecutions"})
listSpaceWebhookExecutions.WithParameters(QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&listSpaceWebhookExecutions, new(listSpaceWebhookExecutionsRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&listSpaceWebhookExecutions, new([]types.WebhookExecution), http.StatusOK)
_ = reflector.SetJSONResponse(&listSpaceWebhookExecutions, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&listSpaceWebhookExecutions, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&listSpaceWebhookExecutions, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&listSpaceWebhookExecutions, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet,
"/repos/{repo_ref}/webhooks/{webhook_identifier}/executions", listWebhookExecutions)
"/spaces/{space_ref}/webhooks/{webhook_identifier}/executions", listSpaceWebhookExecutions)
getWebhookExecution := openapi3.Operation{}
getWebhookExecution.WithTags("webhook")
getWebhookExecution.WithMapOfAnything(map[string]interface{}{"operationId": "getWebhookExecution"})
getWebhookExecution.WithParameters(QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&getWebhookExecution, new(getWebhookExecutionRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&getWebhookExecution, new(types.WebhookExecution), http.StatusOK)
_ = reflector.SetJSONResponse(&getWebhookExecution, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&getWebhookExecution, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&getWebhookExecution, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&getWebhookExecution, new(usererror.Error), http.StatusForbidden)
getSpaceWebhookExecution := openapi3.Operation{}
getSpaceWebhookExecution.WithTags("webhook")
getSpaceWebhookExecution.WithMapOfAnything(map[string]interface{}{"operationId": "getSpaceWebhookExecution"})
getSpaceWebhookExecution.WithParameters(QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&getSpaceWebhookExecution, new(getSpaceWebhookExecutionRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&getSpaceWebhookExecution, new(types.WebhookExecution), http.StatusOK)
_ = reflector.SetJSONResponse(&getSpaceWebhookExecution, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&getSpaceWebhookExecution, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&getSpaceWebhookExecution, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&getSpaceWebhookExecution, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet,
"/repos/{repo_ref}/webhooks/{webhook_identifier}/executions/{webhook_execution_id}", getWebhookExecution)
"/spaces/{space_ref}/webhooks/{webhook_identifier}/executions/{webhook_execution_id}",
getSpaceWebhookExecution,
)
retriggerWebhookExecution := openapi3.Operation{}
retriggerWebhookExecution.WithTags("webhook")
retriggerWebhookExecution.WithMapOfAnything(map[string]interface{}{"operationId": "retriggerWebhookExecution"})
_ = reflector.SetRequest(&retriggerWebhookExecution, new(webhookExecutionRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&retriggerWebhookExecution, new(types.WebhookExecution), http.StatusOK)
_ = reflector.SetJSONResponse(&retriggerWebhookExecution, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&retriggerWebhookExecution, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&retriggerWebhookExecution, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&retriggerWebhookExecution, new(usererror.Error), http.StatusForbidden)
retriggerSpaceWebhookExecution := openapi3.Operation{}
retriggerSpaceWebhookExecution.WithTags("webhook")
retriggerSpaceWebhookExecution.WithMapOfAnything(
map[string]interface{}{"operationId": "retriggerSpaceWebhookExecution"},
)
_ = reflector.SetRequest(&retriggerSpaceWebhookExecution, new(spaceWebhookExecutionRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&retriggerSpaceWebhookExecution, new(types.WebhookExecution), http.StatusOK)
_ = reflector.SetJSONResponse(&retriggerSpaceWebhookExecution, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&retriggerSpaceWebhookExecution, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&retriggerSpaceWebhookExecution, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&retriggerSpaceWebhookExecution, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost,
"/spaces/{space_ref}/webhooks/{webhook_identifier}/executions/{webhook_execution_id}/retrigger",
retriggerSpaceWebhookExecution,
)
// repo
createRepoWebhook := openapi3.Operation{}
createRepoWebhook.WithTags("webhook")
createRepoWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "createRepoWebhook"})
_ = reflector.SetRequest(&createRepoWebhook, new(createRepoWebhookRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&createRepoWebhook, new(webhookType), http.StatusCreated)
_ = reflector.SetJSONResponse(&createRepoWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&createRepoWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&createRepoWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&createRepoWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos/{repo_ref}/webhooks", createRepoWebhook)
listRepoWebhooks := openapi3.Operation{}
listRepoWebhooks.WithTags("webhook")
listRepoWebhooks.WithMapOfAnything(map[string]interface{}{"operationId": "listRepoWebhooks"})
listRepoWebhooks.WithParameters(queryParameterQueryWebhook, queryParameterSortWebhook, queryParameterOrder,
QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&listRepoWebhooks, new(listRepoWebhooksRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&listRepoWebhooks, new([]webhookType), http.StatusOK)
_ = reflector.SetJSONResponse(&listRepoWebhooks, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&listRepoWebhooks, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&listRepoWebhooks, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&listRepoWebhooks, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/webhooks", listRepoWebhooks)
getRepoWebhook := openapi3.Operation{}
getRepoWebhook.WithTags("webhook")
getRepoWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "getRepoWebhook"})
_ = reflector.SetRequest(&getRepoWebhook, new(getRepoWebhookRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&getRepoWebhook, new(webhookType), http.StatusOK)
_ = reflector.SetJSONResponse(&getRepoWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&getRepoWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&getRepoWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&getRepoWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/webhooks/{webhook_identifier}", getRepoWebhook)
updateRepoWebhook := openapi3.Operation{}
updateRepoWebhook.WithTags("webhook")
updateRepoWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "updateRepoWebhook"})
_ = reflector.SetRequest(&updateRepoWebhook, new(updateRepoWebhookRequest), http.MethodPatch)
_ = reflector.SetJSONResponse(&updateRepoWebhook, new(webhookType), http.StatusOK)
_ = reflector.SetJSONResponse(&updateRepoWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&updateRepoWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&updateRepoWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&updateRepoWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPatch, "/repos/{repo_ref}/webhooks/{webhook_identifier}", updateRepoWebhook)
deleteRepoWebhook := openapi3.Operation{}
deleteRepoWebhook.WithTags("webhook")
deleteRepoWebhook.WithMapOfAnything(map[string]interface{}{"operationId": "deleteRepoWebhook"})
_ = reflector.SetRequest(&deleteRepoWebhook, new(deleteRepoWebhookRequest), http.MethodDelete)
_ = reflector.SetJSONResponse(&deleteRepoWebhook, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&deleteRepoWebhook, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&deleteRepoWebhook, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&deleteRepoWebhook, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&deleteRepoWebhook, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(
http.MethodDelete, "/repos/{repo_ref}/webhooks/{webhook_identifier}", deleteRepoWebhook,
)
listRepoWebhookExecutions := openapi3.Operation{}
listRepoWebhookExecutions.WithTags("webhook")
listRepoWebhookExecutions.WithMapOfAnything(map[string]interface{}{"operationId": "listRepoWebhookExecutions"})
listRepoWebhookExecutions.WithParameters(QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&listRepoWebhookExecutions, new(listRepoWebhookExecutionsRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&listRepoWebhookExecutions, new([]types.WebhookExecution), http.StatusOK)
_ = reflector.SetJSONResponse(&listRepoWebhookExecutions, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&listRepoWebhookExecutions, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&listRepoWebhookExecutions, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&listRepoWebhookExecutions, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet,
"/repos/{repo_ref}/webhooks/{webhook_identifier}/executions", listRepoWebhookExecutions)
getRepoWebhookExecution := openapi3.Operation{}
getRepoWebhookExecution.WithTags("webhook")
getRepoWebhookExecution.WithMapOfAnything(map[string]interface{}{"operationId": "getRepoWebhookExecution"})
getRepoWebhookExecution.WithParameters(QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&getRepoWebhookExecution, new(getRepoWebhookExecutionRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&getRepoWebhookExecution, new(types.WebhookExecution), http.StatusOK)
_ = reflector.SetJSONResponse(&getRepoWebhookExecution, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&getRepoWebhookExecution, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&getRepoWebhookExecution, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&getRepoWebhookExecution, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet,
"/repos/{repo_ref}/webhooks/{webhook_identifier}/executions/{webhook_execution_id}", getRepoWebhookExecution)
retriggerRepoWebhookExecution := openapi3.Operation{}
retriggerRepoWebhookExecution.WithTags("webhook")
retriggerRepoWebhookExecution.WithMapOfAnything(map[string]interface{}{"operationId": "retriggerRepoWebhookExecution"})
_ = reflector.SetRequest(&retriggerRepoWebhookExecution, new(repoWebhookExecutionRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&retriggerRepoWebhookExecution, new(types.WebhookExecution), http.StatusOK)
_ = reflector.SetJSONResponse(&retriggerRepoWebhookExecution, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&retriggerRepoWebhookExecution, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&retriggerRepoWebhookExecution, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&retriggerRepoWebhookExecution, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost,
"/repos/{repo_ref}/webhooks/{webhook_identifier}/executions/{webhook_execution_id}/retrigger",
retriggerWebhookExecution)
retriggerRepoWebhookExecution)
}

View File

@ -158,6 +158,7 @@ var codes = map[errors.Status]int{
errors.StatusNotImplemented: http.StatusNotImplemented,
errors.StatusPreconditionFailed: http.StatusPreconditionFailed,
errors.StatusUnauthorized: http.StatusUnauthorized,
errors.StatusForbidden: http.StatusForbidden,
errors.StatusInternal: http.StatusInternalServerError,
}

View File

@ -222,7 +222,7 @@ func setupRoutesV1WithAuth(r chi.Router,
capabilitiesCtrl *capabilities.Controller,
) {
setupAccountWithAuth(r, userCtrl, config)
setupSpaces(r, appCtx, spaceCtrl, userGroupCtrl)
setupSpaces(r, appCtx, spaceCtrl, userGroupCtrl, webhookCtrl)
setupRepos(r, repoCtrl, repoSettingsCtrl, pipelineCtrl, executionCtrl, triggerCtrl,
logCtrl, pullreqCtrl, webhookCtrl, checkCtrl, uploadCtrl)
setupConnectors(r, connectorCtrl)
@ -247,6 +247,7 @@ func setupSpaces(
appCtx context.Context,
spaceCtrl *space.Controller,
userGroupCtrl *usergroup.Controller,
webhookCtrl *webhook.Controller,
) {
r.Route("/spaces", func(r chi.Router) {
@ -290,6 +291,7 @@ func setupSpaces(
})
SetupSpaceLabels(r, spaceCtrl)
SetupWebhookSpace(r, webhookCtrl)
})
})
}
@ -316,6 +318,28 @@ func SetupSpaceLabels(r chi.Router, spaceCtrl *space.Controller) {
})
}
func SetupWebhookSpace(r chi.Router, webhookCtrl *webhook.Controller) {
r.Route("/webhooks", func(r chi.Router) {
r.Post("/", handlerwebhook.HandleCreateSpace(webhookCtrl))
r.Get("/", handlerwebhook.HandleListSpace(webhookCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamWebhookIdentifier), func(r chi.Router) {
r.Get("/", handlerwebhook.HandleFindSpace(webhookCtrl))
r.Patch("/", handlerwebhook.HandleUpdateSpace(webhookCtrl))
r.Delete("/", handlerwebhook.HandleDeleteSpace(webhookCtrl))
r.Route("/executions", func(r chi.Router) {
r.Get("/", handlerwebhook.HandleListExecutionsSpace(webhookCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamWebhookExecutionID), func(r chi.Router) {
r.Get("/", handlerwebhook.HandleFindExecutionSpace(webhookCtrl))
r.Post("/retrigger", handlerwebhook.HandleRetriggerExecutionSpace(webhookCtrl))
})
})
})
})
}
func setupRepos(r chi.Router,
repoCtrl *repo.Controller,
repoSettingsCtrl *reposettings.Controller,
@ -427,7 +451,7 @@ func setupRepos(r chi.Router,
SetupPullReq(r, pullreqCtrl)
SetupWebhook(r, webhookCtrl)
SetupWebhookRepo(r, webhookCtrl)
setupPipelines(r, repoCtrl, pipelineCtrl, executionCtrl, triggerCtrl, logCtrl)
@ -681,22 +705,22 @@ func setupPullReqLabels(r chi.Router, pullreqCtrl *pullreq.Controller) {
})
}
func SetupWebhook(r chi.Router, webhookCtrl *webhook.Controller) {
func SetupWebhookRepo(r chi.Router, webhookCtrl *webhook.Controller) {
r.Route("/webhooks", func(r chi.Router) {
r.Post("/", handlerwebhook.HandleCreate(webhookCtrl))
r.Get("/", handlerwebhook.HandleList(webhookCtrl))
r.Post("/", handlerwebhook.HandleCreateRepo(webhookCtrl))
r.Get("/", handlerwebhook.HandleListRepo(webhookCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamWebhookIdentifier), func(r chi.Router) {
r.Get("/", handlerwebhook.HandleFind(webhookCtrl))
r.Patch("/", handlerwebhook.HandleUpdate(webhookCtrl))
r.Delete("/", handlerwebhook.HandleDelete(webhookCtrl))
r.Get("/", handlerwebhook.HandleFindRepo(webhookCtrl))
r.Patch("/", handlerwebhook.HandleUpdateRepo(webhookCtrl))
r.Delete("/", handlerwebhook.HandleDeleteRepo(webhookCtrl))
r.Route("/executions", func(r chi.Router) {
r.Get("/", handlerwebhook.HandleListExecutions(webhookCtrl))
r.Get("/", handlerwebhook.HandleListExecutionsRepo(webhookCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamWebhookExecutionID), func(r chi.Router) {
r.Get("/", handlerwebhook.HandleFindExecution(webhookCtrl))
r.Post("/retrigger", handlerwebhook.HandleRetriggerExecution(webhookCtrl))
r.Get("/", handlerwebhook.HandleFindExecutionRepo(webhookCtrl))
r.Post("/retrigger", handlerwebhook.HandleRetriggerExecutionRepo(webhookCtrl))
})
})
})

View File

@ -19,7 +19,6 @@ import (
"fmt"
"time"
webhookpkg "github.com/harness/gitness/app/api/controller/webhook"
"github.com/harness/gitness/app/services/webhook"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database/dbtx"
@ -62,7 +61,7 @@ func (migrate Webhook) Import(
// sanitize and convert webhooks
for i, whook := range extWebhooks {
triggers := webhookpkg.ConvertTriggers(whook.Events)
triggers := webhook.ConvertTriggers(whook.Events)
err := sanitizeWebhook(whook, triggers, migrate.allowLoopback, migrate.allowPrivateNetwork)
if err != nil {
return nil, fmt.Errorf("failed to sanitize external webhook input: %w", err)
@ -84,7 +83,7 @@ func (migrate Webhook) Import(
URL: whook.Target,
Enabled: whook.Active,
Insecure: whook.SkipVerify,
Triggers: webhookpkg.DeduplicateTriggers(triggers),
Triggers: webhook.DeduplicateTriggers(triggers),
LatestExecutionResult: nil,
}
@ -117,11 +116,11 @@ func sanitizeWebhook(
return err
}
if err := webhookpkg.CheckURL(in.Target, allowLoopback, allowPrivateNetwork, false); err != nil {
if err := webhook.CheckURL(in.Target, allowLoopback, allowPrivateNetwork, false); err != nil {
return err
}
if err := webhookpkg.CheckTriggers(triggers); err != nil { //nolint:revive
if err := webhook.CheckTriggers(triggers); err != nil { //nolint:revive
return err
}

View File

@ -18,7 +18,7 @@ import (
"net"
"net/url"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
)
@ -30,7 +30,7 @@ const (
webhookMaxSecretLength = 4096
)
var ErrInternalWebhookOperationNotAllowed = usererror.Forbidden("changes to internal webhooks are not allowed")
var ErrInternalWebhookOperationNotAllowed = errors.Forbidden("changes to internal webhooks are not allowed")
// CheckURL validates the url of a webhook.
func CheckURL(rawURL string, allowLoopback bool, allowPrivateNetwork bool, internal bool) error {
@ -58,9 +58,9 @@ func CheckURL(rawURL string, allowLoopback bool, allowPrivateNetwork bool, inter
// basic validation for loopback / private network addresses (only sanitary to give user an early error)
// IMPORTANT: during webook execution loopback / private network addresses are blocked (handles DNS resolution)
if host == "localhost" {
return check.NewValidationError("localhost is not allowed.")
}
// if host == "localhost" {
// return check.NewValidationError("localhost is not allowed.")
// }
if ip := net.ParseIP(host); ip != nil {
if !allowLoopback && ip.IsLoopback() {
@ -80,7 +80,7 @@ func CheckURL(rawURL string, allowLoopback bool, allowPrivateNetwork bool, inter
}
// checkSecret validates the secret of a webhook.
func checkSecret(secret string) error {
func CheckSecret(secret string) error {
if len(secret) > webhookMaxSecretLength {
return check.NewValidationErrorf("The secret of a webhook can be at most %d characters long.",
webhookMaxSecretLength)

View File

@ -16,13 +16,11 @@ package webhook
import (
"context"
"errors"
"fmt"
"time"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/store/database/migrate"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
@ -31,102 +29,7 @@ import (
"github.com/rs/zerolog/log"
)
type CreateInput struct {
// TODO [CODE-1363]: remove after identifier migration.
UID string `json:"uid" deprecated:"true"`
Identifier string `json:"identifier"`
// TODO [CODE-1364]: Remove once UID/Identifier migration is completed.
DisplayName string `json:"display_name"`
Description string `json:"description"`
URL string `json:"url"`
Secret string `json:"secret"`
Enabled bool `json:"enabled"`
Insecure bool `json:"insecure"`
Triggers []enum.WebhookTrigger `json:"triggers"`
}
// Create creates a new webhook.
//
//nolint:gocognit
func (c *Controller) Create(
ctx context.Context,
session *auth.Session,
repoRef string,
in *CreateInput,
internal bool,
) (*types.Webhook, error) {
// validate input
err := sanitizeCreateInput(in, c.allowLoopback, c.allowPrivateNetwork, internal)
if err != nil {
return nil, err
}
now := time.Now().UnixMilli()
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}
encryptedSecret, err := c.encrypter.Encrypt(in.Secret)
if err != nil {
return nil, fmt.Errorf("failed to encrypt webhook secret: %w", err)
}
// create new webhook object
hook := &types.Webhook{
ID: 0, // the ID will be populated in the data layer
Version: 0, // the Version will be populated in the data layer
CreatedBy: session.Principal.ID,
Created: now,
Updated: now,
ParentID: repo.ID,
ParentType: enum.WebhookParentRepo,
Internal: internal,
// user input
Identifier: in.Identifier,
DisplayName: in.DisplayName,
Description: in.Description,
URL: in.URL,
Secret: string(encryptedSecret),
Enabled: in.Enabled,
Insecure: in.Insecure,
Triggers: DeduplicateTriggers(in.Triggers),
LatestExecutionResult: nil,
}
err = c.webhookStore.Create(ctx, hook)
// internal hooks are hidden from non-internal read requests - properly communicate their existence on duplicate.
// This is best effort, any error we just ignore and fallback to original duplicate error.
if errors.Is(err, store.ErrDuplicate) && !internal {
existingHook, derr := c.webhookStore.FindByIdentifier(ctx, enum.WebhookParentRepo, repo.ID, hook.Identifier)
if derr != nil {
log.Ctx(ctx).Warn().Err(derr).Msgf(
"failed to retrieve webhook for repo %d with identifier %q on duplicate error",
repo.ID,
hook.Identifier,
)
}
if derr == nil && existingHook.Internal {
return nil, usererror.Conflict("The provided identifier is reserved for internal purposes.")
}
}
if err != nil {
return nil, fmt.Errorf("failed to store webhook: %w", err)
}
return hook, nil
}
func sanitizeCreateInput(
in *CreateInput,
allowLoopback bool,
allowPrivateNetwork bool,
internal bool,
) error {
func (s *Service) sanitizeCreateInput(in *types.WebhookCreateInput, internal bool) error {
// TODO [CODE-1363]: remove after identifier migration.
if in.Identifier == "" {
in.Identifier = in.UID
@ -154,10 +57,10 @@ func sanitizeCreateInput(
if err := check.Description(in.Description); err != nil {
return err
}
if err := CheckURL(in.URL, allowLoopback, allowPrivateNetwork, internal); err != nil {
if err := CheckURL(in.URL, s.config.AllowLoopback, s.config.AllowPrivateNetwork, internal); err != nil {
return err
}
if err := checkSecret(in.Secret); err != nil {
if err := CheckSecret(in.Secret); err != nil {
return err
}
if err := CheckTriggers(in.Triggers); err != nil { //nolint:revive
@ -166,3 +69,72 @@ func sanitizeCreateInput(
return nil
}
func (s *Service) Create(
ctx context.Context,
principalID int64,
parentID int64,
parentType enum.WebhookParent,
internal bool,
in *types.WebhookCreateInput,
) (*types.Webhook, error) {
err := s.sanitizeCreateInput(in, internal)
if err != nil {
return nil, err
}
encryptedSecret, err := s.encrypter.Encrypt(in.Secret)
if err != nil {
return nil, fmt.Errorf("failed to encrypt webhook secret: %w", err)
}
now := time.Now().UnixMilli()
// create new webhook object
hook := &types.Webhook{
ID: 0, // the ID will be populated in the data layer
Version: 0, // the Version will be populated in the data layer
CreatedBy: principalID,
Created: now,
Updated: now,
ParentID: parentID,
ParentType: parentType,
Internal: internal,
// user input
Identifier: in.Identifier,
DisplayName: in.DisplayName,
Description: in.Description,
URL: in.URL,
Secret: string(encryptedSecret),
Enabled: in.Enabled,
Insecure: in.Insecure,
Triggers: DeduplicateTriggers(in.Triggers),
LatestExecutionResult: nil,
}
err = s.webhookStore.Create(ctx, hook)
// internal hooks are hidden from non-internal read requests - properly communicate their existence on duplicate.
// This is best effort, any error we just ignore and fallback to original duplicate error.
if errors.Is(err, store.ErrDuplicate) && !internal {
existingHook, derr := s.webhookStore.FindByIdentifier(
ctx, enum.WebhookParentRepo, parentID, hook.Identifier)
if derr != nil {
log.Ctx(ctx).Warn().Err(derr).Msgf(
"failed to retrieve webhook for repo %d with identifier %q on duplicate error",
parentID,
hook.Identifier,
)
}
if derr == nil && existingHook.Internal {
return nil, errors.Conflict("The provided identifier is reserved for internal purposes.")
}
}
if err != nil {
return nil, fmt.Errorf("failed to store webhook: %w", err)
}
return hook, nil
}

View File

@ -0,0 +1,41 @@
// 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"
"github.com/harness/gitness/types/enum"
)
// Delete deletes an existing webhook.
func (s *Service) Delete(
ctx context.Context,
parentID int64,
parentType enum.WebhookParent,
webhookIdentifier string,
allowDeletingInternal bool,
) error {
hook, err := s.GetWebhookVerifyOwnership(ctx, parentID, parentType, webhookIdentifier)
if err != nil {
return err
}
if hook.Internal && !allowDeletingInternal {
return ErrInternalWebhookOperationNotAllowed
}
return s.webhookStore.Delete(ctx, hook.ID)
}

View File

@ -36,9 +36,14 @@ func generateTriggerIDFromEventID(eventID string) string {
// using the eventID to generate a deterministic triggerID and using the output of bodyFn as payload.
// The method tries to find the repository 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) triggerForEventWithRepo(ctx context.Context,
triggerType enum.WebhookTrigger, eventID string, principalID int64, repoID int64,
createBodyFn func(*types.Principal, *types.Repository) (any, error)) error {
func (s *Service) triggerForEventWithRepo(
ctx context.Context,
triggerType enum.WebhookTrigger,
eventID string,
principalID int64,
repoID int64,
createBodyFn func(*types.Principal, *types.Repository) (any, error),
) error {
principal, err := s.findPrincipalForEvent(ctx, principalID)
if err != nil {
return err
@ -55,7 +60,12 @@ func (s *Service) triggerForEventWithRepo(ctx context.Context,
return fmt.Errorf("body creation function failed: %w", err)
}
return s.triggerForEvent(ctx, eventID, enum.WebhookParentRepo, repo.ID, triggerType, body)
parents, err := s.getParentInfoRepo(ctx, repo.ID, true)
if err != nil {
return fmt.Errorf("failed to get webhook parent info for parents: %w", err)
}
return s.triggerForEvent(ctx, eventID, parents, triggerType, body)
}
// triggerForEventWithPullReq triggers all webhooks for the given repo and triggerType
@ -96,7 +106,12 @@ func (s *Service) triggerForEventWithPullReq(ctx context.Context,
return fmt.Errorf("body creation function failed: %w", err)
}
return s.triggerForEvent(ctx, eventID, enum.WebhookParentRepo, targetRepo.ID, triggerType, body)
parents, err := s.getParentInfoRepo(ctx, targetRepo.ID, true)
if err != nil {
return fmt.Errorf("failed to get webhook parent info: %w", err)
}
return s.triggerForEvent(ctx, eventID, parents, triggerType, body)
}
// findRepositoryForEvent finds the repository for the provided repoID.
@ -149,16 +164,23 @@ func (s *Service) findPrincipalForEvent(ctx context.Context, principalID int64)
// 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(ctx context.Context, eventID string,
parentType enum.WebhookParent, parentID int64, triggerType enum.WebhookTrigger, body any) error {
func (s *Service) triggerForEvent(
ctx context.Context,
eventID string,
parents []types.WebhookParentInfo,
triggerType enum.WebhookTrigger,
body any,
) error {
triggerID := generateTriggerIDFromEventID(eventID)
results, err := s.triggerWebhooksFor(ctx, parentType, parentID, triggerID, triggerType, body)
results, err := s.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 {
return fmt.Errorf("failed to trigger %s (id: '%s') for webhooks of %s %d: %w",
triggerType, triggerID, parentType, parentID, err)
return fmt.Errorf(
"failed to trigger %s (id: '%s') for webhooks %#v: %w",
triggerType, triggerID, parents, err,
)
}
// go through all events and figure out if we need to retry the event.
@ -172,8 +194,9 @@ func (s *Service) triggerForEvent(ctx context.Context, eventID string,
// combine errors of non-successful executions
if result.Execution.Result != enum.WebhookExecutionResultSuccess {
errs = multierr.Append(errs, fmt.Errorf("execution %d of webhook %d resulted in %s: %w",
result.Execution.ID, result.Webhook.ID, result.Execution.Result, result.Err))
errs = multierr.Append(errs,
fmt.Errorf("execution %d of webhook %d resulted in %s: %w",
result.Execution.ID, result.Webhook.ID, result.Execution.Result, result.Err))
}
if result.Execution.Result == enum.WebhookExecutionResultRetriableError {
@ -183,12 +206,12 @@ func (s *Service) triggerForEvent(ctx context.Context, eventID string,
// in case there was at least one error, log error details in single log to reduce log flooding
if errs != nil {
log.Ctx(ctx).Warn().Err(errs).Msgf("webhook execution for %s %d had errors", parentType, parentID)
log.Ctx(ctx).Warn().Err(errs).Msgf("webhook execution for %#v had errors", parents)
}
// in case at least one webhook has to be retried, return an error to the event framework to have it reprocessed
if retryRequired {
return fmt.Errorf("at least one webhook execution resulted in a retry for %s %d", parentType, parentID)
return fmt.Errorf("at least one webhook execution resulted in a retry for %#v", parents)
}
return nil

View File

@ -0,0 +1,106 @@
// 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"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
// FindExecution finds a webhook execution.
func (s *Service) FindExecution(
ctx context.Context,
parentID int64,
parentType enum.WebhookParent,
webhookIdentifier string,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
webhook, err := s.GetWebhookVerifyOwnership(ctx, parentID, parentType, webhookIdentifier)
if err != nil {
return nil, err
}
webhookExecution, err := s.GetWebhookExecutionVerifyOwnership(ctx, webhook.ID, webhookExecutionID)
if err != nil {
return nil, err
}
return webhookExecution, nil
}
// ListExecutions returns the executions of the webhook.
func (s *Service) ListExecutions(
ctx context.Context,
parentID int64,
parentType enum.WebhookParent,
webhookIdentifier string,
filter *types.WebhookExecutionFilter,
) ([]*types.WebhookExecution, int64, error) {
webhook, err := s.GetWebhookVerifyOwnership(ctx, parentID, parentType, webhookIdentifier)
if err != nil {
return nil, 0, fmt.Errorf("failed to verify ownership for webhook %d: %w", webhook.ID, err)
}
total, err := s.webhookExecutionStore.CountForWebhook(ctx, webhook.ID)
if err != nil {
return nil, 0, fmt.Errorf("failed to count webhook executions for webhook %d: %w", webhook.ID, err)
}
webhookExecutions, err := s.webhookExecutionStore.ListForWebhook(ctx, webhook.ID, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list webhook executions for webhook %d: %w", webhook.ID, err)
}
return webhookExecutions, total, nil
}
// RetriggerExecution retriggers an existing webhook execution.
func (s *Service) RetriggerExecution(
ctx context.Context,
parentID int64,
parentType enum.WebhookParent,
webhookIdentifier string,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
webhook, err := s.GetWebhookVerifyOwnership(
ctx, parentID, parentType, webhookIdentifier)
if err != nil {
return nil, err
}
webhookExecution, err := s.GetWebhookExecutionVerifyOwnership(
ctx, webhook.ID, webhookExecutionID)
if err != nil {
return nil, err
}
executionResult, err := s.RetriggerWebhookExecution(ctx, webhookExecution.ID)
if err != nil {
return nil, fmt.Errorf("failed to retrigger webhook execution: %w", err)
}
if executionResult.Err != nil {
log.Ctx(ctx).Warn().Err(executionResult.Err).Msgf(
"retrigger of webhhook %d execution %d (new id: %d) had an error",
webhook.ID, webhookExecution.ID, executionResult.Execution.ID)
}
return executionResult.Execution, nil
}

View File

@ -0,0 +1,125 @@
// 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"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// Listreturns the webhooks from the provided scope.
func (s *Service) List(
ctx context.Context,
parentID int64,
parentType enum.WebhookParent,
inherited bool,
filter *types.WebhookFilter,
) ([]*types.Webhook, int64, error) {
var parents []types.WebhookParentInfo
var err error
switch parentType {
case enum.WebhookParentRepo:
parents, err = s.getParentInfoRepo(ctx, parentID, inherited)
if err != nil {
return nil, 0, err
}
case enum.WebhookParentSpace:
parents, err = s.getParentInfoSpace(ctx, parentID, inherited)
if err != nil {
return nil, 0, err
}
default:
return nil, 0, fmt.Errorf("webhook type %s is not supported", parentType)
}
count, err := s.webhookStore.Count(ctx, parents, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to count webhooks for scope with id %d: %w", parentID, err)
}
webhooks, err := s.webhookStore.List(ctx, parents, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list webhooks for scope with id %d: %w", parentID, err)
}
return webhooks, count, nil
}
func (s *Service) getParentInfoRepo(
ctx context.Context,
repoID int64,
inherited bool,
) ([]types.WebhookParentInfo, error) {
var parents []types.WebhookParentInfo
parents = append(parents, types.WebhookParentInfo{
ID: repoID,
Type: enum.WebhookParentRepo,
})
if inherited {
repo, err := s.repoStore.Find(ctx, repoID)
if err != nil {
return nil, fmt.Errorf("failed to get repo: %w", err)
}
ids, err := s.spaceStore.GetAncestorIDs(ctx, repo.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
}
func (s *Service) getParentInfoSpace(
ctx context.Context,
spaceID int64,
inherited bool,
) ([]types.WebhookParentInfo, error) {
var parents []types.WebhookParentInfo
if inherited {
ids, err := s.spaceStore.GetAncestorIDs(ctx, spaceID)
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,
})
}
} else {
parents = append(parents, types.WebhookParentInfo{
Type: enum.WebhookParentSpace,
ID: spaceID,
})
}
return parents, nil
}

View File

@ -0,0 +1,97 @@
// 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"
"strconv"
"strings"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
func (s *Service) Find(
ctx context.Context,
parentID int64,
parentType enum.WebhookParent,
webhookIdentifier string,
) (*types.Webhook, error) {
hook, err := s.GetWebhookVerifyOwnership(ctx, parentID, parentType, webhookIdentifier)
if err != nil {
return nil, errors.NotFound("failed to find webhook %s: %q", webhookIdentifier, err)
}
return hook, nil
}
// GetWebhookVerifyOwnership gets the webhook and
// ensures it belongs to the scope with the specified id and type.
func (s *Service) GetWebhookVerifyOwnership(
ctx context.Context,
parentID int64,
parentType enum.WebhookParent,
webhookIdentifier string,
) (*types.Webhook, error) {
// TODO: Remove once webhook identifier migration completed
webhookID, err := strconv.ParseInt(webhookIdentifier, 10, 64)
if (err == nil && webhookID <= 0) || len(strings.TrimSpace(webhookIdentifier)) == 0 {
return nil, errors.InvalidArgument("A valid webhook identifier must be provided.")
}
var webhook *types.Webhook
if err == nil {
webhook, err = s.webhookStore.Find(ctx, webhookID)
} else {
webhook, err = s.webhookStore.FindByIdentifier(
ctx, parentType, parentID, webhookIdentifier)
}
if err != nil {
return nil, fmt.Errorf("failed to find webhook with identifier %q: %w", webhookIdentifier, err)
}
// ensure the webhook actually belongs to the repo
if webhook.ParentType != parentType || webhook.ParentID != parentID {
return nil, errors.NotFound("webhook doesn't belong to requested %s.", parentType)
}
return webhook, nil
}
// GetWebhookExecutionVerifyOwnership gets the webhook execution and
// ensures it belongs to the webhook with the specified id.
func (s *Service) GetWebhookExecutionVerifyOwnership(
ctx context.Context,
webhookID int64,
webhookExecutionID int64,
) (*types.WebhookExecution, error) {
if webhookExecutionID <= 0 {
return nil, errors.InvalidArgument("A valid webhook execution ID must be provided.")
}
webhookExecution, err := s.webhookExecutionStore.Find(ctx, webhookExecutionID)
if err != nil {
return nil, fmt.Errorf("failed to find webhook execution with id %d: %w", webhookExecutionID, err)
}
// ensure the webhook execution actually belongs to the webhook
if webhookID != webhookExecution.WebhookID {
return nil, errors.NotFound("webhook execution doesn't belong to requested webhook")
}
return webhookExecution, nil
}

View File

@ -28,6 +28,7 @@ import (
"github.com/harness/gitness/encrypt"
"github.com/harness/gitness/events"
"github.com/harness/gitness/git"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/stream"
)
@ -79,9 +80,12 @@ func (c *Config) Prepare() error {
// Service is responsible for processing webhook events.
type Service struct {
tx dbtx.Transactor
webhookStore store.WebhookStore
webhookExecutionStore store.WebhookExecutionStore
urlProvider url.Provider
spaceStore store.SpaceStore
repoStore store.RepoStore
pullreqStore store.PullReqStore
principalStore store.PrincipalStore
@ -101,10 +105,12 @@ type Service struct {
func NewService(
ctx context.Context,
config Config,
tx dbtx.Transactor,
gitReaderFactory *events.ReaderFactory[*gitevents.Reader],
prReaderFactory *events.ReaderFactory[*pullreqevents.Reader],
webhookStore store.WebhookStore,
webhookExecutionStore store.WebhookExecutionStore,
spaceStore store.SpaceStore,
repoStore store.RepoStore,
pullreqStore store.PullReqStore,
activityStore store.PullReqActivityStore,
@ -117,8 +123,10 @@ func NewService(
return nil, fmt.Errorf("provided webhook service config is invalid: %w", err)
}
service := &Service{
tx: tx,
webhookStore: webhookStore,
webhookExecutionStore: webhookExecutionStore,
spaceStore: spaceStore,
repoStore: repoStore,
pullreqStore: pullreqStore,
activityStore: activityStore,

View File

@ -66,14 +66,16 @@ func (r *TriggerResult) Skipped() bool {
return r.Execution == nil
}
func (s *Service) triggerWebhooksFor(ctx context.Context, parentType enum.WebhookParent, parentID int64,
triggerID string, triggerType enum.WebhookTrigger, body any) ([]TriggerResult, error) {
// get all webhooks for the given parent
// NOTE: there never should be even close to 1000 webhooks for a repo (that should be blocked in the future).
// We just use 1000 as a safe number to get all hooks
webhooks, err := s.webhookStore.List(ctx, parentType, parentID, &types.WebhookFilter{Size: 1000, Order: enum.OrderAsc})
func (s *Service) 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{})
if err != nil {
return nil, fmt.Errorf("failed to list webhooks for %s %d: %w", parentType, parentID, err)
return nil, fmt.Errorf("failed to list webhooks for: %w", err)
}
return s.triggerWebhooks(ctx, webhooks, triggerID, triggerType, body)

View File

@ -18,92 +18,12 @@ import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
)
type UpdateInput struct {
// TODO [CODE-1363]: remove after identifier migration.
UID *string `json:"uid" deprecated:"true"`
Identifier *string `json:"identifier"`
// TODO [CODE-1364]: Remove once UID/Identifier migration is completed.
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
URL *string `json:"url"`
Secret *string `json:"secret"`
Enabled *bool `json:"enabled"`
Insecure *bool `json:"insecure"`
Triggers []enum.WebhookTrigger `json:"triggers"`
}
// Update updates an existing webhook.
func (c *Controller) Update(
ctx context.Context,
session *auth.Session,
repoRef string,
webhookIdentifier string,
in *UpdateInput,
allowModifyingInternal bool,
) (*types.Webhook, error) {
if err := sanitizeUpdateInput(in, c.allowLoopback, c.allowPrivateNetwork); err != nil {
return nil, err
}
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}
// get the hook and ensure it belongs to us
hook, err := c.getWebhookVerifyOwnership(ctx, repo.ID, webhookIdentifier)
if err != nil {
return nil, err
}
if !allowModifyingInternal && hook.Internal {
return nil, ErrInternalWebhookOperationNotAllowed
}
// update webhook struct (only for values that are provided)
if in.Identifier != nil {
hook.Identifier = *in.Identifier
}
if in.DisplayName != nil {
hook.DisplayName = *in.DisplayName
}
if in.Description != nil {
hook.Description = *in.Description
}
if in.URL != nil {
hook.URL = *in.URL
}
if in.Secret != nil {
encryptedSecret, err := c.encrypter.Encrypt(*in.Secret)
if err != nil {
return nil, fmt.Errorf("failed to encrypt webhook secret: %w", err)
}
hook.Secret = string(encryptedSecret)
}
if in.Enabled != nil {
hook.Enabled = *in.Enabled
}
if in.Insecure != nil {
hook.Insecure = *in.Insecure
}
if in.Triggers != nil {
hook.Triggers = DeduplicateTriggers(in.Triggers)
}
if err = c.webhookStore.Update(ctx, hook); err != nil {
return nil, err
}
return hook, nil
}
func sanitizeUpdateInput(in *UpdateInput, allowLoopback bool, allowPrivateNetwork bool) error {
func (s *Service) sanitizeUpdateInput(in *types.WebhookUpdateInput) error {
// TODO [CODE-1363]: remove after identifier migration.
if in.Identifier == nil {
in.Identifier = in.UID
@ -126,12 +46,12 @@ func sanitizeUpdateInput(in *UpdateInput, allowLoopback bool, allowPrivateNetwor
}
if in.URL != nil {
// internal is set to false as internal webhooks cannot be updated
if err := CheckURL(*in.URL, allowLoopback, allowPrivateNetwork, false); err != nil {
if err := CheckURL(*in.URL, s.config.AllowLoopback, s.config.AllowPrivateNetwork, false); err != nil {
return err
}
}
if in.Secret != nil {
if err := checkSecret(*in.Secret); err != nil {
if err := CheckSecret(*in.Secret); err != nil {
return err
}
}
@ -143,3 +63,61 @@ func sanitizeUpdateInput(in *UpdateInput, allowLoopback bool, allowPrivateNetwor
return nil
}
func (s *Service) Update(
ctx context.Context,
parentID int64,
parentType enum.WebhookParent,
webhookIdentifier string,
allowModifyingInternal bool,
in *types.WebhookUpdateInput,
) (*types.Webhook, error) {
hook, err := s.GetWebhookVerifyOwnership(ctx, parentID, parentType, webhookIdentifier)
if err != nil {
return nil, fmt.Errorf("failed to verify webhook ownership: %w", err)
}
if err := s.sanitizeUpdateInput(in); err != nil {
return nil, err
}
if !allowModifyingInternal && hook.Internal {
return nil, ErrInternalWebhookOperationNotAllowed
}
// update webhook struct (only for values that are provided)
if in.Identifier != nil {
hook.Identifier = *in.Identifier
}
if in.DisplayName != nil {
hook.DisplayName = *in.DisplayName
}
if in.Description != nil {
hook.Description = *in.Description
}
if in.URL != nil {
hook.URL = *in.URL
}
if in.Secret != nil {
encryptedSecret, err := s.encrypter.Encrypt(*in.Secret)
if err != nil {
return nil, fmt.Errorf("failed to encrypt webhook secret: %w", err)
}
hook.Secret = string(encryptedSecret)
}
if in.Enabled != nil {
hook.Enabled = *in.Enabled
}
if in.Insecure != nil {
hook.Insecure = *in.Insecure
}
if in.Triggers != nil {
hook.Triggers = DeduplicateTriggers(in.Triggers)
}
if err := s.webhookStore.Update(ctx, hook); err != nil {
return nil, err
}
return hook, nil
}

View File

@ -24,6 +24,7 @@ import (
"github.com/harness/gitness/encrypt"
"github.com/harness/gitness/events"
"github.com/harness/gitness/git"
"github.com/harness/gitness/store/database/dbtx"
"github.com/google/wire"
)
@ -33,12 +34,15 @@ var WireSet = wire.NewSet(
ProvideService,
)
func ProvideService(ctx context.Context,
func ProvideService(
ctx context.Context,
config Config,
tx dbtx.Transactor,
gitReaderFactory *events.ReaderFactory[*gitevents.Reader],
prReaderFactory *events.ReaderFactory[*pullreqevents.Reader],
webhookStore store.WebhookStore,
webhookExecutionStore store.WebhookExecutionStore,
spaceStore store.SpaceStore,
repoStore store.RepoStore,
pullreqStore store.PullReqStore,
activityStore store.PullReqActivityStore,
@ -47,7 +51,7 @@ func ProvideService(ctx context.Context,
git git.Interface,
encrypter encrypt.Encrypter,
) (*Service, error) {
return NewService(ctx, config, gitReaderFactory, prReaderFactory,
webhookStore, webhookExecutionStore, repoStore, pullreqStore, activityStore,
return NewService(ctx, config, tx, gitReaderFactory, prReaderFactory,
webhookStore, webhookExecutionStore, spaceStore, repoStore, pullreqStore, activityStore,
urlProvider, principalStore, git, encrypter)
}

View File

@ -579,13 +579,15 @@ type (
// Count counts the webhooks for a given parent type and id.
Count(
ctx context.Context, parentType enum.WebhookParent, parentID int64,
ctx context.Context,
parents []types.WebhookParentInfo,
opts *types.WebhookFilter,
) (int64, error)
// List lists the webhooks for a given parent type and id.
List(
ctx context.Context, parentType enum.WebhookParent, parentID int64,
ctx context.Context,
parents []types.WebhookParentInfo,
opts *types.WebhookFilter,
) ([]*types.Webhook, error)
}
@ -607,6 +609,8 @@ type (
opts *types.WebhookExecutionFilter,
) ([]*types.WebhookExecution, 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) ([]*types.WebhookExecution, error)
}

View File

@ -27,6 +27,7 @@ import (
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/Masterminds/squirrel"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
@ -342,24 +343,21 @@ func (s *WebhookStore) DeleteByIdentifier(
}
// Count counts the webhooks for a given parent type and id.
func (s *WebhookStore) Count(ctx context.Context, parentType enum.WebhookParent, parentID int64,
opts *types.WebhookFilter) (int64, error) {
func (s *WebhookStore) Count(
ctx context.Context,
parents []types.WebhookParentInfo,
opts *types.WebhookFilter,
) (int64, error) {
stmt := database.Builder.
Select("count(*)").
From("webhooks")
switch parentType {
case enum.WebhookParentRepo:
stmt = stmt.Where("webhook_repo_id = ?", parentID)
case enum.WebhookParentSpace:
stmt = stmt.Where("webhook_space_id = ?", parentID)
default:
return 0, fmt.Errorf("webhook parent type '%s' is not supported", parentType)
err := selectParents(parents, &stmt)
if err != nil {
return 0, fmt.Errorf("failed to select parents: %w", err)
}
if opts.Query != "" {
stmt = stmt.Where("LOWER(webhook_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query)))
}
stmt = applyWebhookFilter(opts, stmt)
sql, args, err := stmt.ToSql()
if err != nil {
@ -377,29 +375,21 @@ func (s *WebhookStore) Count(ctx context.Context, parentType enum.WebhookParent,
return count, nil
}
// List lists the webhooks for a given parent type and id.
func (s *WebhookStore) List(ctx context.Context, parentType enum.WebhookParent, parentID int64,
opts *types.WebhookFilter) ([]*types.Webhook, error) {
func (s *WebhookStore) List(
ctx context.Context,
parents []types.WebhookParentInfo,
opts *types.WebhookFilter,
) ([]*types.Webhook, error) {
stmt := database.Builder.
Select(webhookColumns).
From("webhooks")
switch parentType {
case enum.WebhookParentRepo:
stmt = stmt.Where("webhook_repo_id = ?", parentID)
case enum.WebhookParentSpace:
stmt = stmt.Where("webhook_space_id = ?", parentID)
default:
return nil, fmt.Errorf("webhook parent type '%s' is not supported", parentType)
err := selectParents(parents, &stmt)
if err != nil {
return nil, fmt.Errorf("failed to select parents: %w", err)
}
if opts.Query != "" {
stmt = stmt.Where("LOWER(webhook_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query)))
}
if opts.SkipInternal {
stmt = stmt.Where("webhook_internal != ?", true)
}
stmt = applyWebhookFilter(opts, stmt)
stmt = stmt.Limit(database.Limit(opts.Size))
stmt = stmt.Offset(database.Offset(opts.Page, opts.Size))
@ -507,7 +497,7 @@ func mapToInternalWebhook(hook *types.Webhook) (*webhook, error) {
case enum.WebhookParentSpace:
res.SpaceID = null.IntFrom(hook.ParentID)
default:
return nil, fmt.Errorf("webhook parent type '%s' is not supported", hook.ParentType)
return nil, fmt.Errorf("webhook parent type %q is not supported", hook.ParentType)
}
return res, nil
@ -553,3 +543,43 @@ func triggersToString(triggers []enum.WebhookTrigger) string {
return strings.Join(rawTriggers, triggersSeparator)
}
func applyWebhookFilter(
opts *types.WebhookFilter,
stmt squirrel.SelectBuilder,
) squirrel.SelectBuilder {
if opts.Query != "" {
stmt = stmt.Where("LOWER(webhook_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query)))
}
if opts.SkipInternal {
stmt = stmt.Where("webhook_internal != ?", true)
}
return stmt
}
func selectParents(
parents []types.WebhookParentInfo,
stmt *squirrel.SelectBuilder,
) error {
var parentSelector squirrel.Or
for _, parent := range parents {
switch parent.Type {
case enum.WebhookParentRepo:
parentSelector = append(parentSelector, squirrel.Eq{
"webhook_repo_id": parent.ID,
})
case enum.WebhookParentSpace:
parentSelector = append(parentSelector, squirrel.Eq{
"webhook_space_id": parent.ID,
})
default:
return fmt.Errorf("webhook parent type '%s' is not supported", parent.Type)
}
}
*stmt = stmt.Where(parentSelector)
return nil
}

View File

@ -213,6 +213,31 @@ func (s *WebhookExecutionStore) ListForWebhook(ctx context.Context, webhookID in
return mapToWebhookExecutions(dst), nil
}
// CountForWebhook counts the total number of webhook executions for a given webhook ID.
func (s *WebhookExecutionStore) CountForWebhook(
ctx context.Context,
webhookID int64,
) (int64, error) {
stmt := database.Builder.
Select("COUNT(*)").
From("webhook_executions").
Where("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, s.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
}
// ListForTrigger lists the webhook executions for a given trigger id.
func (s *WebhookExecutionStore) ListForTrigger(ctx context.Context,
triggerID string) ([]*types.WebhookExecution, error) {

View File

@ -164,6 +164,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
reposettings.WireSet,
pullreq.WireSet,
controllerwebhook.WireSet,
controllerwebhook.ProvidePreprocessor,
svclabel.WireSet,
serviceaccount.WireSet,
user.WireSet,

View File

@ -365,11 +365,12 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
webhookConfig := server.ProvideWebhookConfig(config)
webhookStore := database.ProvideWebhookStore(db)
webhookExecutionStore := database.ProvideWebhookExecutionStore(db)
webhookService, err := webhook.ProvideService(ctx, webhookConfig, readerFactory, eventsReaderFactory, webhookStore, webhookExecutionStore, repoStore, pullReqStore, pullReqActivityStore, provider, principalStore, gitInterface, encrypter)
webhookService, err := webhook.ProvideService(ctx, webhookConfig, transactor, readerFactory, eventsReaderFactory, webhookStore, webhookExecutionStore, spaceStore, repoStore, pullReqStore, pullReqActivityStore, provider, principalStore, gitInterface, encrypter)
if err != nil {
return nil, err
}
webhookController := webhook2.ProvideController(webhookConfig, authorizer, webhookStore, webhookExecutionStore, repoStore, webhookService, encrypter)
preprocessor := webhook2.ProvidePreprocessor()
webhookController := webhook2.ProvideController(authorizer, spaceStore, repoStore, webhookService, encrypter, preprocessor)
reporter5, err := events7.ProvideReporter(eventsSystem)
if err != nil {
return nil, err

View File

@ -28,6 +28,7 @@ const (
StatusNotFound Status = "not_found"
StatusNotImplemented Status = "not_implemented"
StatusUnauthorized Status = "unauthorized"
StatusForbidden Status = "forbidden"
StatusFailed Status = "failed"
StatusPreconditionFailed Status = "precondition_failed"
StatusAborted Status = "aborted"
@ -156,6 +157,16 @@ func PreconditionFailed(format string, args ...interface{}) *Error {
return Format(StatusPreconditionFailed, format, args...)
}
// Unauthorized is a helper function to return an unauthorized error.
func Unauthorized(format string, args ...interface{}) *Error {
return Format(StatusUnauthorized, format, args...)
}
// Forbidden is a helper function to return a forbidden error.
func Forbidden(format string, args ...interface{}) *Error {
return Format(StatusForbidden, format, args...)
}
// Failed is a helper function to return failed error status.
func Failed(format string, args ...interface{}) *Error {
return Format(StatusFailed, format, args...)

View File

@ -66,6 +66,34 @@ func (w *Webhook) MarshalJSON() ([]byte, error) {
})
}
type WebhookCreateInput struct {
// TODO [CODE-1363]: remove after identifier migration.
UID string `json:"uid" deprecated:"true"`
Identifier string `json:"identifier"`
// TODO [CODE-1364]: Remove once UID/Identifier migration is completed.
DisplayName string `json:"display_name"`
Description string `json:"description"`
URL string `json:"url"`
Secret string `json:"secret"`
Enabled bool `json:"enabled"`
Insecure bool `json:"insecure"`
Triggers []enum.WebhookTrigger `json:"triggers"`
}
type WebhookUpdateInput struct {
// TODO [CODE-1363]: remove after identifier migration.
UID *string `json:"uid" deprecated:"true"`
Identifier *string `json:"identifier"`
// TODO [CODE-1364]: Remove once UID/Identifier migration is completed.
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
URL *string `json:"url"`
Secret *string `json:"secret"`
Enabled *bool `json:"enabled"`
Insecure *bool `json:"insecure"`
Triggers []enum.WebhookTrigger `json:"triggers"`
}
// WebhookExecution represents a single execution of a webhook.
type WebhookExecution struct {
ID int64 `json:"id"`
@ -112,3 +140,8 @@ type WebhookExecutionFilter struct {
Page int `json:"page"`
Size int `json:"size"`
}
type WebhookParentInfo struct {
Type enum.WebhookParent
ID int64
}