horizoncd/horizon

View on GitHub
pkg/admission/webhook.go

Summary

Maintainability
B
4 hrs
Test Coverage
D
61%
package admission
 
import (
"context"
"encoding/json"
 
"k8s.io/apimachinery/pkg/util/runtime"
 
herrors "github.com/horizoncd/horizon/core/errors"
"github.com/horizoncd/horizon/pkg/admission/models"
perror "github.com/horizoncd/horizon/pkg/errors"
"github.com/horizoncd/horizon/pkg/util/log"
jsonpatch "gopkg.in/evanphx/json-patch.v5"
)
 
var (
mutatingWebhooks []Webhook
validatingWebhooks []Webhook
)
 
func Register(kind models.Kind, webhook Webhook) {
switch kind {
case models.KindMutating:
mutatingWebhooks = append(mutatingWebhooks, webhook)
case models.KindValidating:
validatingWebhooks = append(validatingWebhooks, webhook)
}
}
 
type validateResult struct {
req Request
err error
resp *Response
}
 
type Request struct {
Operation models.Operation `json:"operation"`
Resource string `json:"resource"`
Name string `json:"name"`
SubResource string `json:"subResource"`
Version string `json:"version"`
Object interface{} `json:"object"`
OldObject interface{} `json:"oldObject"`
Options map[string]interface{} `json:"options,omitempty"`
}
 
type Response struct {
Allowed *bool `json:"allowed"`
Result string `json:"result,omitempty"`
Patch []byte `json:"patch,omitempty"`
PatchType string `json:"patchType,omitempty"`
}
 
type Webhook interface {
Handle(context.Context, *Request) (*Response, error)
IgnoreError() bool
Interest(*Request) bool
}
 
func Mutating(ctx context.Context, request *Request) (*Request, error) {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
if request.Object == nil {
return request, nil
}
for _, webhook := range mutatingWebhooks {
if !webhook.Interest(request) {
continue
}
response, err := webhook.Handle(ctx, request)
if err = loggingError(ctx, err, webhook); err != nil {
return nil, err
}
if response != nil && response.Patch != nil {
request.Object, err = jsonPatch(request.Object, response.Patch)
if err = loggingError(ctx, err, webhook); err != nil {
return nil, err
}
}
}
return request, nil
}
 
Function `Validating` has a Cognitive Complexity of 28 (exceeds 20 allowed). Consider refactoring.
Function `Validating` has 54 lines of code (exceeds 50 allowed). Consider refactoring.
Function `Validating` has 9 return statements (exceeds 4 allowed).
func Validating(ctx context.Context, request *Request) error {
if len(validatingWebhooks) < 1 {
return nil
}
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
finishedCount := 0
resCh := make(chan validateResult)
for _, webhook := range validatingWebhooks {
go func(webhook Webhook) {
defer runtime.HandleCrash()
if !webhook.Interest(request) {
resCh <- validateResult{*request, nil, nil}
return
}
response, err := webhook.Handle(ctx, request)
if err != nil {
if webhook.IgnoreError() {
log.Errorf(ctx, "failed to admit request: %v", err)
resCh <- validateResult{*request, nil, nil}
return
}
resCh <- validateResult{*request, err, nil}
return
}
if response == nil || response.Allowed == nil {
if webhook.IgnoreError() {
log.Errorf(ctx, "failed to admit request: response is nil or allowed is nil")
resCh <- validateResult{*request, nil, nil}
return
}
resCh <- validateResult{*request, perror.New("response is nil or allowed is nil"), nil}
return
}
resCh <- validateResult{*request, nil, response}
}(webhook)
}
 
for res := range resCh {
finishedCount++
if res.err != nil {
return res.err
}
if res.resp != nil && res.resp.Allowed != nil && !*res.resp.Allowed {
log.Infof(ctx,
"request (resource: %s, resourceName: %s, subresource: %s, operation: %s) denied by webhook: %s",
res.req.Resource, res.req.Name, res.req.SubResource,
res.req.Operation, res.resp.Result)
return perror.Wrapf(herrors.ErrForbidden, "request denied by webhook: %s", res.resp.Result)
}
if finishedCount >= len(validatingWebhooks) {
close(resCh)
break
}
}
 
return nil
}
 
func loggingError(ctx context.Context, err error, webhook Webhook) error {
if err != nil {
if webhook.IgnoreError() {
log.Warningf(ctx, "failed to admit request: %v", err.Error())
return nil
}
log.Errorf(ctx, "failed to admit request: %v", err.Error())
return err
}
return nil
}
 
Function `jsonPatch` has 5 return statements (exceeds 4 allowed).
func jsonPatch(obj interface{}, patchJSON []byte) (interface{}, error) {
objJSON, err := json.Marshal(obj)
if err != nil {
return nil, err
}
patch, err := jsonpatch.DecodePatch(patchJSON)
if err != nil {
return nil, err
}
 
objPatched, err := patch.Apply(objJSON)
if err != nil {
return nil, err
}
err = json.Unmarshal(objPatched, &obj)
if err != nil {
return nil, err
}
return obj, nil
}