Skip to content

Commit

Permalink
feat: include CRUD action message from go-database-reconciler in logg…
Browse files Browse the repository at this point in the history
…ing error details from Konnect (#5453)

* extract APIErrors

* log CRUD action error from Konnect

* update go.mod and changelog

* address comment
  • Loading branch information
randmonkey authored Jan 25, 2024
1 parent dcea670 commit 336270f
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ Adding a new version? You'll need three changes:
It's only possible to set the same timeout for each rule in a single `HTTPRoute`. Other settings
will be rejected by the admission webhook validation.
[#5243](https://github.com/Kong/kubernetes-ingress-controller/pull/5243)
- Log the details in response fron Konnect when failed to push configuration
to Konnect.
[#5453](https://github.com/Kong/kubernetes-ingress-controller/pull/5453)

### Fixed

Expand Down
29 changes: 22 additions & 7 deletions internal/dataplane/deckerrors/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package deckerrors
import (
"errors"

"github.com/kong/go-database-reconciler/pkg/crud"
deckutils "github.com/kong/go-database-reconciler/pkg/utils"
"github.com/kong/go-kong/kong"
"github.com/samber/lo"
)

// ExtractAPIErrors tries to extract kong.APIErrors from the generic error.
Expand All @@ -18,13 +20,26 @@ func ExtractAPIErrors(err error) []*kong.APIError {
// It might be either a deckutils.ErrArray with APIErrors inside.
var deckErrArray deckutils.ErrArray
if errors.As(err, &deckErrArray) {
var apiErrs []*kong.APIError
for _, err := range deckErrArray.Errors {
if apiErr, ok := castAsErr[*kong.APIError](err); ok {
apiErrs = append(apiErrs, apiErr)
}
}
return apiErrs
return lo.FilterMap(deckErrArray.Errors, func(e error, _ int) (*kong.APIError, bool) {
return castAsErr[*kong.APIError](e)
})
}

return nil
}

func ExtractCRUDActionErrors(err error) []*crud.ActionError {
// It might be a single crud.ActionError.
if actionErr, ok := castAsErr[*crud.ActionError](err); ok {
return []*crud.ActionError{actionErr}
}

// It might be either a deckutils.ErrArray with ActionErrors inside.
var deckErrArray deckutils.ErrArray
if errors.As(err, &deckErrArray) {
return lo.FilterMap(deckErrArray.Errors, func(e error, _ int) (*crud.ActionError, bool) {
return castAsErr[*crud.ActionError](e)
})
}

return nil
Expand Down
56 changes: 55 additions & 1 deletion internal/dataplane/deckerrors/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"testing"

"github.com/kong/go-database-reconciler/pkg/crud"
deckutils "github.com/kong/go-database-reconciler/pkg/utils"
"github.com/kong/go-kong/kong"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -41,7 +42,7 @@ func TestExtractAPIErrors(t *testing.T) {
{
name: "deck array of errors with no api error",
input: deckutils.ErrArray{Errors: []error{genericErr, genericErr}},
expected: nil,
expected: []*kong.APIError{},
},
{
name: "deck array of errors with an api error among generic ones",
Expand All @@ -51,9 +52,62 @@ func TestExtractAPIErrors(t *testing.T) {
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
out := deckerrors.ExtractAPIErrors(tc.input)
require.Equal(t, tc.expected, out)
})
}
}

func TestExtractCRUDActionErrors(t *testing.T) {
var (
genericErr = errors.New("not an api error")
actionErr = &crud.ActionError{
OperationType: crud.Create,
Kind: crud.Kind("service"),
Name: "badservice",
Err: errors.New("something wrong"),
}
)

testCases := []struct {
name string
input error
expected []*crud.ActionError
}{
{
name: "nil",
input: nil,
expected: nil,
},
{
name: "single generic error",
input: genericErr,
expected: nil,
},
{
name: "single action error",
input: actionErr,
expected: []*crud.ActionError{actionErr},
},
{
name: "deck array of errors with no action errors",
input: deckutils.ErrArray{Errors: []error{genericErr, genericErr}},
expected: []*crud.ActionError{},
},
{
name: "deck array of errors with an action error among generic ones",
input: deckutils.ErrArray{Errors: []error{genericErr, actionErr, genericErr}},
expected: []*crud.ActionError{actionErr},
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
out := deckerrors.ExtractCRUDActionErrors(tc.input)
require.Equal(t, tc.expected, out)
})
}
}
24 changes: 24 additions & 0 deletions internal/dataplane/kong_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/kong/kubernetes-ingress-controller/v3/internal/clients"
dpconf "github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/config"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/configfetcher"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/deckerrors"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/deckgen"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/failures"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
Expand Down Expand Up @@ -511,13 +512,36 @@ func (c *KongClient) maybeSendOutToKonnectClient(ctx context.Context, s *kongsta
c.logger.Error(err, "Skipped pushing configuration to Konnect")
} else {
c.logger.Error(err, "Failed pushing configuration to Konnect")
logKonnectErrors(c.logger, err)
}
return err
}

return nil
}

// logKonnectErrors logs details of each error response returned from Konnect API.
func logKonnectErrors(logger logr.Logger, err error) {
if crudActionErrors := deckerrors.ExtractCRUDActionErrors(err); len(crudActionErrors) > 0 {
for _, actionErr := range crudActionErrors {
apiErr := &kong.APIError{}
if errors.As(actionErr.Err, &apiErr) {
logger.Error(actionErr, "Failed to send request to Konnect",
"operation_type", actionErr.OperationType.String(),
"entity_kind", actionErr.Kind,
"entity_name", actionErr.Name,
"details", apiErr.Details())
} else {
logger.Error(actionErr, "Failed to send request to Konnect",
"operation_type", actionErr.OperationType.String(),
"entity_kind", actionErr.Kind,
"entity_name", actionErr.Name,
)
}
}
}
}

func (c *KongClient) sendToClient(
ctx context.Context,
client sendconfig.AdminAPIClient,
Expand Down

0 comments on commit 336270f

Please sign in to comment.