diff --git a/cmd/kn-event/main.go b/cmd/kn-event/main.go index de60ad3c6..38439f3bc 100644 --- a/cmd/kn-event/main.go +++ b/cmd/kn-event/main.go @@ -6,5 +6,5 @@ import ( ) func main() { - commandline.New(new(cli.App)).ExecuteOrDie(cli.Options...) + commandline.New(new(cli.App)).ExecuteOrDie(cli.EffectiveOptions()...) } diff --git a/go.mod b/go.mod index 5b62b9529..b4554eef7 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.0 require ( github.com/cloudevents/sdk-go/v2 v2.15.2 + github.com/fatih/color v1.14.1 github.com/ghodss/yaml v1.0.0 github.com/gobuffalo/flect v1.0.2 github.com/google/go-containerregistry v0.19.1 @@ -15,7 +16,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 github.com/thediveo/enumflag v0.10.0 - github.com/wavesoftware/go-commandline v1.1.0 + github.com/wavesoftware/go-commandline v1.3.0 github.com/wavesoftware/go-ensure v1.0.0 go.uber.org/zap v1.27.0 gopkg.in/yaml.v2 v2.4.0 @@ -77,6 +78,8 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/term v0.5.0 // indirect diff --git a/go.sum b/go.sum index 2779c3ade..fe19124ab 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= -github.com/cardil/knative-client/pkg v0.0.0-20240923095307-3a2bcba04752 h1:9w5oYFdZ8KA6Xx5CpXGR7/2t8+j3JA0A9ziTRfjB3eo= -github.com/cardil/knative-client/pkg v0.0.0-20240923095307-3a2bcba04752/go.mod h1:JR3XomuVf2cBqgvXFONkX6Ebf1/gJwUnl/1OH47U18g= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -117,6 +115,8 @@ github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lSh github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -307,6 +307,11 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -483,8 +488,8 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/wavesoftware/go-commandline v1.1.0 h1:Lm9WS8UWG55tnGl/Ke1PepyTPF/1YvasBsOtXWz/fsw= -github.com/wavesoftware/go-commandline v1.1.0/go.mod h1:msUGDOY3s8jITVYse8ANZn8H6YE5x7h2bWMmKha4ftw= +github.com/wavesoftware/go-commandline v1.3.0 h1:mlX4aa8wHFVqAEYvPmsXwuGVF9DKhRvxtR2QjqHt5Ec= +github.com/wavesoftware/go-commandline v1.3.0/go.mod h1:msUGDOY3s8jITVYse8ANZn8H6YE5x7h2bWMmKha4ftw= github.com/wavesoftware/go-ensure v1.0.0 h1:6X3gQL5psBWwtu/H9a+69xQ+JGTUELaLhgOB/iB3AQk= github.com/wavesoftware/go-ensure v1.0.0/go.mod h1:K2UAFSwMTvpiRGay/M3aEYYuurcR8S4A6HkQlJPV8k4= github.com/wavesoftware/go-retcode v1.0.0 h1:Z53+VpIHMvRMtjS6jPScdihbAN1ks3lIJ5Mj32gCpno= @@ -676,7 +681,9 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/go.work.sum b/go.work.sum index 4c6697cf5..42c8d4836 100644 --- a/go.work.sum +++ b/go.work.sum @@ -566,6 +566,7 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vtolstov/go-ioctl v0.0.0-20151206205506-6be9cced4810 h1:X6ps8XHfpQjw8dUStzlMi2ybiKQ2Fmdw7UM+TinwvyM= github.com/vtolstov/go-ioctl v0.0.0-20151206205506-6be9cced4810/go.mod h1:dF0BBJ2YrV1+2eAIyEI+KeSidgA6HqoIP1u5XTlMq/o= +github.com/wavesoftware/go-commandline v1.3.0/go.mod h1:msUGDOY3s8jITVYse8ANZn8H6YE5x7h2bWMmKha4ftw= github.com/weppos/publicsuffix-go v0.30.2-0.20240219083929-48f3a5ae027a h1:s0Yp4S5jdEQFTJE1blGE5o+n7T0uI386YHXzocLKLR4= github.com/weppos/publicsuffix-go v0.30.2-0.20240219083929-48f3a5ae027a/go.mod h1:v7j8MuFp1CIYgAd2n7xEUctTbsreRd1vPmOwyzmGFiE= github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I= @@ -642,6 +643,7 @@ golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= diff --git a/internal/cli/build.go b/internal/cli/build.go index 9d9e1bcb8..1e4316764 100644 --- a/internal/cli/build.go +++ b/internal/cli/build.go @@ -17,7 +17,6 @@ package cli import ( - "errors" "fmt" "github.com/spf13/cobra" @@ -25,6 +24,7 @@ import ( outlogging "knative.dev/client/pkg/output/logging" "knative.dev/kn-plugin-event/pkg/binding" "knative.dev/kn-plugin-event/pkg/cli" + "knative.dev/kn-plugin-event/pkg/errors" ) // ErrCantBePresented is returned if data can't be presented. @@ -62,8 +62,5 @@ func (b *buildCommand) run(cmd *cobra.Command, _ []string) error { } func cantBuildEventError(err error) error { - if errors.Is(err, cli.ErrCantBuildEvent) { - return err - } - return fmt.Errorf("%w: %w", cli.ErrCantBuildEvent, err) + return errors.Wrap(err, cli.ErrCantBuildEvent) } diff --git a/internal/cli/errors.go b/internal/cli/errors.go new file mode 100644 index 000000000..603c7e5fc --- /dev/null +++ b/internal/cli/errors.go @@ -0,0 +1,38 @@ +/* + Copyright 2024 The Knative Authors + + 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 cli + +import ( + "github.com/fatih/color" + "github.com/spf13/cobra" + outlogging "knative.dev/client/pkg/output/logging" + "knative.dev/client/pkg/output/term" +) + +func errorHandler(_ error, cmd *cobra.Command) bool { + ctx := cmd.Context() + if logfile := outlogging.LogFileFrom(ctx); logfile != nil { + logpath := logfile.Name() + if term.IsFancy(cmd.ErrOrStderr()) { + logpath = color.CyanString(logpath) + } + cmd.PrintErrln() + cmd.PrintErrln("The logs could help to debug the failure reason.") + cmd.PrintErrln("Take a look at the log file:", logpath) + } + return false +} diff --git a/internal/cli/root.go b/internal/cli/root.go index 1af0d124c..e16944e81 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -29,6 +29,16 @@ import ( // Options to override the commandline for testing purposes. var Options []commandline.Option //nolint:gochecknoglobals +// EffectiveOptions are the options used for command run. +// +// TODO: Consider migrating to Cobra' error handler, see: https://github.com/spf13/cobra/pull/2199 +func EffectiveOptions() []commandline.Option { + return append( + []commandline.Option{commandline.WithErrorHandler(errorHandler)}, + Options..., + ) +} + type App struct { cli.Params } diff --git a/internal/cli/send.go b/internal/cli/send.go index 793fc8a45..eb6ffa2e3 100644 --- a/internal/cli/send.go +++ b/internal/cli/send.go @@ -17,24 +17,17 @@ package cli import ( - "errors" - "fmt" - "github.com/spf13/cobra" "knative.dev/client/pkg/flags/sink" "knative.dev/kn-plugin-event/pkg/binding" "knative.dev/kn-plugin-event/pkg/cli" + "knative.dev/kn-plugin-event/pkg/errors" "knative.dev/kn-plugin-event/pkg/event" ) -var ( - // ErrSendTargetValidationFailed is returned if a send target can't pass a - // validation. - ErrSendTargetValidationFailed = errors.New("send target validation failed") - - // ErrCantSendEvent is returned if event can't be sent. - ErrCantSendEvent = errors.New("can't send event") -) +// ErrSendTargetValidationFailed is returned if the send target can't pass a +// validation. +var ErrSendTargetValidationFailed = errors.New("send target validation failed") type sendCommand struct { target *cli.TargetArgs @@ -63,7 +56,7 @@ option isn't specified target URL will not be changed.`, c.PreRunE = func(*cobra.Command, []string) error { err := cli.ValidateTarget(s.target) if err != nil { - return fmt.Errorf("%w: %w", ErrSendTargetValidationFailed, err) + return errors.Wrap(err, ErrSendTargetValidationFailed) } return nil } @@ -84,8 +77,5 @@ func (s *sendCommand) run(cmd *cobra.Command, _ []string) error { } func cantSentEvent(err error) error { - if errors.Is(err, event.ErrCantSentEvent) { - return err - } - return fmt.Errorf("%w: %w", event.ErrCantSentEvent, err) + return errors.Wrap(err, event.ErrCantSentEvent) } diff --git a/pkg/cli/context.go b/pkg/cli/context.go index 592ba9ebc..c462ad6e9 100644 --- a/pkg/cli/context.go +++ b/pkg/cli/context.go @@ -21,10 +21,13 @@ import ( "io" "os" + "github.com/fatih/color" + "github.com/spf13/cobra" "go.uber.org/zap" "go.uber.org/zap/zapcore" "knative.dev/client/pkg/output" outlogging "knative.dev/client/pkg/output/logging" + "knative.dev/client/pkg/output/term" "knative.dev/pkg/logging" "knative.dev/pkg/signals" ) @@ -37,6 +40,9 @@ type Cobralike interface { SetOut(out io.Writer) OutOrStderr() io.Writer + SetErrPrefix(prefix string) + HasParent() bool + Parent() *cobra.Command } // InitialContext returns the initial context object, so it could be set ahead @@ -95,6 +101,14 @@ func SetupOutput(cbr Cobralike, loggingSetup LoggingSetup) { ctx = output.WithContext(ctx, cbr) ctx = loggingSetup(ctx) cbr.SetContext(ctx) + + if term.IsFancy(cbr.OutOrStdout()) { + cbr.SetErrPrefix(color.RedString("Error:")) + } + if cbr.HasParent() { + cmd := cbr.Parent() + cmd.SetContext(ctx) + } } var ( diff --git a/pkg/errors/delegate.go b/pkg/errors/delegate.go new file mode 100644 index 000000000..9374edb60 --- /dev/null +++ b/pkg/errors/delegate.go @@ -0,0 +1,25 @@ +/* + Copyright 2024 The Knative Authors + + 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 errors + +import "errors" + +// New returns an error that formats as the given text. +// Each call to New returns a distinct error value even if the text is identical. +func New(text string) error { + return errors.New(text) //nolint:err113 +} diff --git a/pkg/errors/wrap.go b/pkg/errors/wrap.go new file mode 100644 index 000000000..ca6ee69ce --- /dev/null +++ b/pkg/errors/wrap.go @@ -0,0 +1,31 @@ +/* + Copyright 2024 The Knative Authors + + 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 errors + +import ( + "errors" + "fmt" +) + +// Wrap an error with provided wrap error. Will check if the error is already +// of given type to prevent over-wrapping. +func Wrap(err, wrap error) error { + if errors.Is(err, wrap) { + return err + } + return fmt.Errorf("%w: %w", wrap, err) +} diff --git a/pkg/event/sender.go b/pkg/event/sender.go index 34a9b135a..a64245937 100644 --- a/pkg/event/sender.go +++ b/pkg/event/sender.go @@ -6,12 +6,12 @@ import ( "fmt" cloudevents "github.com/cloudevents/sdk-go/v2" + outlogging "knative.dev/client/pkg/output/logging" "knative.dev/kn-plugin-event/pkg/k8s" - "knative.dev/pkg/logging" ) -// ErrCantSentEvent if event can't be sent. -var ErrCantSentEvent = errors.New("can't sent event") +// ErrCantSentEvent if the event can't be sent. +var ErrCantSentEvent = errors.New("can't sent the event") // Sender will send event to specified target. type Sender interface { @@ -43,8 +43,12 @@ type sendLogic struct { } func (l *sendLogic) Send(ctx context.Context, ce cloudevents.Event) error { + log := outlogging.LoggerFrom(ctx) + cebytes, _ := ce.MarshalJSON() + log.WithFields(outlogging.Fields{ + "event": string(cebytes), + }).Debug("Sending the event") err := l.Sender.Send(ctx, ce) - log := logging.FromContext(ctx) if err == nil { log.Infof("Event (ID: %s) have been sent.", ce.ID()) return nil diff --git a/pkg/ics/send.go b/pkg/ics/send.go index 746658d89..ddbc8a650 100644 --- a/pkg/ics/send.go +++ b/pkg/ics/send.go @@ -2,12 +2,12 @@ package ics import ( "context" - "fmt" cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/kelseyhightower/envconfig" "go.uber.org/zap" "knative.dev/client/pkg/flags/sink" + "knative.dev/kn-plugin-event/pkg/errors" "knative.dev/kn-plugin-event/pkg/event" "knative.dev/kn-plugin-event/pkg/k8s" "knative.dev/pkg/apis" @@ -23,7 +23,7 @@ func (app *App) SendFromEnv(ctx context.Context, cfg *k8s.Configurator) error { } err = c.sender.Send(ctx, *c.ce) if err != nil { - return fmt.Errorf("%w: %w", ErrCantSendWithICS, err) + return errors.Wrap(err, ErrICSFailed) } log := logging.FromContext(ctx) log.Infow("Event sent", zap.String("ce-id", c.ce.ID())) @@ -36,18 +36,18 @@ func (app *App) configure(cfg *k8s.Configurator) (config, error) { } err := envconfig.Process("K", args) if err != nil { - return config{}, fmt.Errorf("%w: %w", ErrCantConfigureICS, err) + return config{}, errors.Wrap(err, ErrCantConfigureICS) } u, err := apis.ParseURL(args.Sink) if err != nil { - return config{}, fmt.Errorf("%w: %w", ErrCantConfigureICS, err) + return config{}, errors.Wrap(err, ErrCantConfigureICS) } target := &event.Target{ Reference: &sink.Reference{URL: u}, } s, err := app.Binding.CreateSender(cfg, target) if err != nil { - return config{}, fmt.Errorf("%w: %w", ErrCantConfigureICS, err) + return config{}, errors.Wrap(err, ErrCantConfigureICS) } ce, err := Decode(args.Event) if err != nil { diff --git a/pkg/ics/types.go b/pkg/ics/types.go index 1356ca775..dae0e393f 100644 --- a/pkg/ics/types.go +++ b/pkg/ics/types.go @@ -15,9 +15,9 @@ var ( ErrCouldntDecode = errors.New("couldn't decode an event") // ErrCantConfigureICS is returned when the problem occurs while trying to // configure ICS sender. - ErrCantConfigureICS = errors.New("can't configure ICS sender") - // ErrCantSendWithICS if can't send with ICS sender. - ErrCantSendWithICS = errors.New("can't send with ICS sender") + ErrCantConfigureICS = errors.New("can't configure in-cluster sender") + // ErrICSFailed if the in-cluster sender has failed. + ErrICSFailed = errors.New("the in-cluster sender failed") ) // Args holds a list of args for in-cluster-sender. diff --git a/pkg/k8s/errors.go b/pkg/k8s/errors.go index 4e227512b..10a06cbe9 100644 --- a/pkg/k8s/errors.go +++ b/pkg/k8s/errors.go @@ -12,6 +12,6 @@ var ( // ErrUnexcpected if something unexpected actually has happened. ErrUnexcpected = errors.New("something unexpected actually has happened") - // ErrICSenderJobFailed if the ICS job runner has failed. - ErrICSenderJobFailed = errors.New("the ICS job runner has failed") + // ErrJobFailed if the job has failed. + ErrJobFailed = errors.New("the Kubernetes job failed") ) diff --git a/pkg/k8s/jobrunner.go b/pkg/k8s/jobrunner.go index 6a8b62633..472279473 100644 --- a/pkg/k8s/jobrunner.go +++ b/pkg/k8s/jobrunner.go @@ -8,6 +8,9 @@ import ( batchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" + outlogging "knative.dev/client/pkg/output/logging" + "knative.dev/kn-plugin-event/pkg/errors" + "sigs.k8s.io/yaml" ) // JobRunner will launch a Job and monitor it for completion. @@ -38,8 +41,10 @@ func (j *jobRunner) Run(ctx context.Context, job *batchv1.Job) error { tsk := task{ errs, ready, &sync.WaitGroup{}, } + logdumpJob(ctx, job) + tasks := []func(context.Context, *batchv1.Job, task){ - // wait is started first, making sure to capture success, even the ultra-fast one. + // wait is started first, making sure to capture success, even the ultra-fast one. j.waitForSuccess, j.createJob, } @@ -66,25 +71,30 @@ func (j *jobRunner) createJob(ctx context.Context, job *batchv1.Job, tsk task) { jobs := j.kube.Typed().BatchV1().Jobs(job.Namespace) _, err := jobs.Create(ctx, job, metav1.CreateOptions{}) if err != nil { - tsk.errs <- fmt.Errorf("%w: %w", ErrICSenderJobFailed, err) + tsk.errs <- errors.Wrap(err, ErrJobFailed) } } func (j *jobRunner) waitForSuccess(ctx context.Context, job *batchv1.Job, tsk task) { defer tsk.wg.Done() err := j.watchJob(ctx, job, tsk, func(job *batchv1.Job) (bool, error) { - if job.Status.CompletionTime == nil && job.Status.Failed == 0 { - return false, nil + if job.Status.Succeeded >= 1 { + return true, nil + } + limit := int32(0) + if job.Spec.BackoffLimit != nil { + limit = *job.Spec.BackoffLimit } - // We should be done if we reach here. - if job.Status.Succeeded < 1 { - return false, fmt.Errorf("%w: %s", ErrICSenderJobFailed, - "expected to have successful job") + if job.Status.Failed >= limit { + logdumpJob(ctx, job) + return false, fmt.Errorf( + "%w \"%s\" %d times, exceeding the limit of %d", + ErrJobFailed, job.GetName(), job.Status.Failed, limit) } - return true, nil + return false, nil }) if err != nil { - tsk.errs <- fmt.Errorf("%w: %w", ErrICSenderJobFailed, err) + tsk.errs <- errors.Wrap(err, ErrJobFailed) } } @@ -100,14 +110,14 @@ func (j *jobRunner) deleteJob(ctx context.Context, job *batchv1.Job) error { PropagationPolicy: &policy, }) if err != nil { - return fmt.Errorf("%w: %w", ErrICSenderJobFailed, err) + return errors.Wrap(err, ErrJobFailed) } pods := j.kube.Typed().CoreV1().Pods(job.GetNamespace()) err = pods.DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{ LabelSelector: "job-name=" + job.GetName(), }) if err != nil { - return fmt.Errorf("%w: %w", ErrICSenderJobFailed, err) + return errors.Wrap(err, ErrJobFailed) } return nil } @@ -123,7 +133,7 @@ func (j *jobRunner) watchJob( FieldSelector: "metadata.name=" + obj.GetName(), }) if err != nil { - return fmt.Errorf("%w: %w", ErrICSenderJobFailed, err) + return errors.Wrap(err, ErrJobFailed) } defer watcher.Stop() resultCh := watcher.ResultChan() @@ -132,13 +142,13 @@ func (j *jobRunner) watchJob( if result.Type == watch.Added || result.Type == watch.Modified { job, ok := result.Object.(*batchv1.Job) if !ok { - return fmt.Errorf("%w: %s: %T", ErrICSenderJobFailed, + return fmt.Errorf("%w: %s: %T", ErrJobFailed, "expected to watch batchv1.Job, got", result.Object) } var brk bool brk, err = changeFn(job) if err != nil { - return fmt.Errorf("%w: %w", ErrICSenderJobFailed, err) + return errors.Wrap(err, ErrJobFailed) } if brk { return nil @@ -147,3 +157,18 @@ func (j *jobRunner) watchJob( } return nil } + +func logdumpJob(ctx context.Context, job *batchv1.Job) { + log := outlogging.LoggerFrom(ctx) + image := "" + if len(job.Spec.Template.Spec.Containers) == 1 { + image = job.Spec.Template.Spec.Containers[0].Image + } + if jobBytes, yerr := yaml.Marshal(job); yerr == nil { + log.WithFields(outlogging.Fields{"job": string(jobBytes)}). + Debug("Sender job image: ", image) + } else { + log.WithFields(outlogging.Fields{"err": yerr.Error()}). + Debug("Sender job image: ", image) + } +} diff --git a/pkg/sender/in_cluster.go b/pkg/sender/in_cluster.go index b56dde072..ce9521a08 100644 --- a/pkg/sender/in_cluster.go +++ b/pkg/sender/in_cluster.go @@ -2,20 +2,25 @@ package sender import ( "context" - "fmt" cloudevents "github.com/cloudevents/sdk-go/v2" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" + "knative.dev/kn-plugin-event/pkg/errors" "knative.dev/kn-plugin-event/pkg/event" - ics2 "knative.dev/kn-plugin-event/pkg/ics" + "knative.dev/kn-plugin-event/pkg/ics" "knative.dev/kn-plugin-event/pkg/k8s" "knative.dev/kn-plugin-event/pkg/metadata" + "knative.dev/pkg/ptr" ) -const idLength = 16 +const ( + idLength = 6 + defaultRetries = 3 + defaultDeadline = 5 // seconds +) type inClusterSender struct { namespace string @@ -29,11 +34,11 @@ func (i *inClusterSender) Send(ctx context.Context, ce cloudevents.Event) error ctx, i.target.Reference, i.target.RelativeURI, ) if err != nil { - return fmt.Errorf("%w: %w", k8s.ErrInvalidReference, err) + return errors.Wrap(err, k8s.ErrInvalidReference) } - kevent, err := ics2.Encode(ce) + kevent, err := ics.Encode(ce) if err != nil { - return fmt.Errorf("%w: %w", ics2.ErrCouldntEncode, err) + return errors.Wrap(err, ics.ErrCouldntEncode) } job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ @@ -44,9 +49,11 @@ func (i *inClusterSender) Send(ctx context.Context, ce cloudevents.Event) error }, }, Spec: batchv1.JobSpec{ + BackoffLimit: ptr.Int32(defaultRetries), Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ - RestartPolicy: corev1.RestartPolicyNever, + RestartPolicy: corev1.RestartPolicyNever, + ActiveDeadlineSeconds: ptr.Int64(defaultDeadline), Containers: []corev1.Container{{ Name: "kn-event-sender", Image: metadata.ResolveImage(), @@ -64,7 +71,7 @@ func (i *inClusterSender) Send(ctx context.Context, ce cloudevents.Event) error } err = i.jobRunner.Run(ctx, job) if err != nil { - return fmt.Errorf("%w: %w", ics2.ErrCantSendWithICS, err) + return errors.Wrap(err, ics.ErrICSFailed) } return nil }