From ceb59ae06f7737adea57117964347a814b540b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Suszy=C5=84ski?= Date: Fri, 20 Sep 2024 15:33:22 +0200 Subject: [PATCH 1/3] Migrating to sink flag for --to param --- .env | 2 +- .golangci.yaml | 14 +- README.md | 10 +- build/Magefile.go | 2 +- cmd/kn-event-sender/main_test.go | 2 +- cmd/kn-event/main.go | 4 +- cmd/kn-event/main_test.go | 6 +- go.mod | 11 +- go.sum | 20 ++- go.work.sum | 4 +- hack/release.sh | 2 +- internal/cli/{cmd => }/build.go | 28 +++- internal/cli/{cmd => }/build_test.go | 25 +++- internal/cli/{cmd => }/builder.go | 18 ++- internal/cli/cmd/root.go | 74 ---------- internal/cli/cmd/root_test.go | 30 ---- internal/cli/cmd/send.go | 95 ------------- internal/cli/cmd/send_test.go | 47 ------- internal/cli/cmd/types.go | 7 - internal/cli/root.go | 93 +++++++++++++ internal/cli/root_test.go | 61 ++++++++ internal/cli/send.go | 91 ++++++++++++ internal/cli/send_test.go | 67 +++++++++ internal/cli/types.go | 23 +++ internal/cli/{cmd => }/version.go | 20 ++- internal/cli/{cmd => }/version_test.go | 32 ++++- internal/ics/app.go | 52 +++---- internal/ics/app_test.go | 18 ++- pkg/binding/binding.go | 38 +++++ pkg/binding/memoized.go | 27 ++++ .../memoized_test.go | 77 +++++----- pkg/cli/context.go | 60 ++++++++ pkg/cli/context_test.go | 35 +++++ pkg/cli/options.go | 115 --------------- pkg/cli/send.go | 14 +- pkg/cli/send_test.go | 32 ++--- pkg/cli/target.go | 112 ++++++--------- pkg/cli/target_test.go | 36 ++--- pkg/cli/types.go | 25 ++-- pkg/configuration/cli.go | 15 -- pkg/configuration/defaults.go | 22 --- pkg/configuration/ics.go | 15 -- pkg/configuration/memoized.go | 29 ---- pkg/event/constants.go | 2 +- pkg/event/sender.go | 33 ++++- pkg/event/sender_test.go | 61 +++++--- pkg/event/spec.go | 31 +++++ pkg/event/target.go | 25 ++++ pkg/event/types.go | 92 ------------ pkg/{cli => }/ics/encoding.go | 0 pkg/{cli => }/ics/encoding_test.go | 2 +- pkg/{cli => }/ics/send.go | 22 +-- pkg/{cli => }/ics/send_test.go | 22 +-- pkg/{cli => }/ics/types.go | 12 +- pkg/k8s/addressresolver.go | 86 ++++-------- pkg/k8s/addressresolver_test.go | 12 +- pkg/k8s/errors.go | 6 - pkg/k8s/jobrunner.go | 37 ++--- pkg/k8s/jobrunner_test.go | 14 +- pkg/k8s/kubeclient.go | 101 ++++++-------- pkg/k8s/kubeclient_test.go | 98 +++++++++++++ pkg/k8s/params.go | 55 ++++++++ pkg/k8s/test/addressresolver_cases.go | 47 ++++--- pkg/k8s/test/addressresolver_cases_test.go | 42 ++++++ pkg/plugin/plugin.go | 4 +- pkg/sender/binding.go | 58 ++++++++ pkg/sender/create.go | 38 ----- pkg/sender/direct.go | 8 +- pkg/sender/direct_test.go | 29 ++-- pkg/sender/in_cluster.go | 22 +-- pkg/sender/in_cluster_test.go | 88 ++++++------ pkg/sender/namespace.go | 13 -- pkg/sender/types.go | 28 ---- pkg/system/environment.go | 67 --------- pkg/tests/cloudevent_server.go | 2 +- pkg/tests/fakeclients.go | 16 +-- pkg/tests/sender.go | 4 +- test/e2e/broker.go | 4 +- test/e2e/channel.go | 4 +- test/e2e/ics_send.go | 13 +- test/e2e/k8s_service.go | 6 +- test/e2e/kn_service.go | 7 +- test/e2e/main_test.go | 2 +- test/e2e/sut.go | 2 +- test/e2e/wathola_image.go | 2 +- test/images/resolver.go | 4 +- test/pkg/clients.go | 3 +- test/pkg/k8s/addressresolver_test.go | 131 +++++++++++++----- 88 files changed, 1548 insertions(+), 1317 deletions(-) rename internal/cli/{cmd => }/build.go (53%) rename internal/cli/{cmd => }/build_test.go (77%) rename internal/cli/{cmd => }/builder.go (65%) delete mode 100644 internal/cli/cmd/root.go delete mode 100644 internal/cli/cmd/root_test.go delete mode 100644 internal/cli/cmd/send.go delete mode 100644 internal/cli/cmd/send_test.go delete mode 100644 internal/cli/cmd/types.go create mode 100644 internal/cli/root.go create mode 100644 internal/cli/root_test.go create mode 100644 internal/cli/send.go create mode 100644 internal/cli/send_test.go create mode 100644 internal/cli/types.go rename internal/cli/{cmd => }/version.go (71%) rename internal/cli/{cmd => }/version_test.go (62%) create mode 100644 pkg/binding/binding.go create mode 100644 pkg/binding/memoized.go rename pkg/{configuration => binding}/memoized_test.go (62%) create mode 100644 pkg/cli/context.go create mode 100644 pkg/cli/context_test.go delete mode 100644 pkg/cli/options.go delete mode 100644 pkg/configuration/cli.go delete mode 100644 pkg/configuration/defaults.go delete mode 100644 pkg/configuration/ics.go delete mode 100644 pkg/configuration/memoized.go create mode 100644 pkg/event/spec.go create mode 100644 pkg/event/target.go delete mode 100644 pkg/event/types.go rename pkg/{cli => }/ics/encoding.go (100%) rename pkg/{cli => }/ics/encoding_test.go (93%) rename pkg/{cli => }/ics/send.go (68%) rename pkg/{cli => }/ics/send_test.go (58%) rename pkg/{cli => }/ics/types.go (66%) create mode 100644 pkg/k8s/kubeclient_test.go create mode 100644 pkg/k8s/params.go create mode 100644 pkg/k8s/test/addressresolver_cases_test.go create mode 100644 pkg/sender/binding.go delete mode 100644 pkg/sender/create.go delete mode 100644 pkg/sender/namespace.go delete mode 100644 pkg/sender/types.go delete mode 100644 pkg/system/environment.go diff --git a/.env b/.env index 83c900cf1..f35361ed4 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -IMAGE_BASENAME=ghcr.io/knative-sandbox/kn-plugin-event +IMAGE_BASENAME=ghcr.io/knative-extensions/kn-plugin-event diff --git a/.golangci.yaml b/.golangci.yaml index 1d10b2313..c194f6cb3 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -30,12 +30,6 @@ linters: - varnamelen - exhaustruct - depguard - # FIXME: remove `containedctx` exclude after fixing knative-sandbox/kn-plugin-event#202 - - containedctx - # FIXME: consider enabling and fixing - - revive - - copyloopvar - - perfsprint issues: exclude-rules: @@ -44,6 +38,12 @@ issues: - wrapcheck linters-settings: + wrapcheck: + ignorePackageGlobs: + - knative.dev/kn-plugin-event/pkg/* + - knative.dev/client/pkg/* gomoddirectives: # List of allowed `replace` directives. Default is empty. - replace-allow-list: [] + replace-allow-list: + # TODO: Remove when https://github.com/knative/client/pull/1968 is merged + - knative.dev/client/pkg diff --git a/README.md b/README.md index 3a9dcbc60..8ab2b9c24 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # kn-plugin-event -[![Mage](https://github.com/knative-sandbox/kn-plugin-event/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/knative-sandbox/kn-plugin-event/actions/workflows/go.yml) -[![Go Report Card](https://goreportcard.com/badge/knative-sandbox/kn-plugin-event)](https://goreportcard.com/report/knative-sandbox/kn-plugin-event) -[![Releases](https://img.shields.io/github/release-pre/knative-sandbox/kn-plugin-event.svg?sort=semver)](https://github.com/knative-sandbox/kn-plugin-event/releases) -[![LICENSE](https://img.shields.io/github/license/knative-sandbox/kn-plugin-event.svg)](https://github.com/knative-sandbox/kn-plugin-event/blob/main/LICENSE) +[![Mage](https://github.com/knative-extensions/kn-plugin-event/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/knative-extensions/kn-plugin-event/actions/workflows/go.yml) +[![Go Report Card](https://goreportcard.com/badge/knative-extensions/kn-plugin-event)](https://goreportcard.com/report/knative-extensions/kn-plugin-event) +[![Releases](https://img.shields.io/github/release-pre/knative-extensions/kn-plugin-event.svg?sort=semver)](https://github.com/knative-extensions/kn-plugin-event/releases) +[![LICENSE](https://img.shields.io/github/license/knative-extensions/kn-plugin-event.svg)](https://github.com/knative-extensions/kn-plugin-event/blob/main/LICENSE) [![Maturity level](https://img.shields.io/badge/Maturity%20level-ALPHA-red)](https://github.com/knative/community/tree/main/mechanics/MATURITY-LEVELS.md) `kn-plugin-event` is a plugin of Knative Client, for managing cloud events from @@ -115,7 +115,7 @@ $ kn event send \ ## Install You can download a pre-built version of `kn-plugin-event` from -[our release page](https://github.com/knative-sandbox/kn-plugin-event/releases) +[our release page](https://github.com/knative-extensions/kn-plugin-event/releases) . Choose the one that fits your platform. When the download is ready, you should be ready to use `kn-plugin-event` as a diff --git a/build/Magefile.go b/build/Magefile.go index 0557172ea..a9cd75ee9 100644 --- a/build/Magefile.go +++ b/build/Magefile.go @@ -53,7 +53,7 @@ func init() { //nolint:gochecknoinits Resolver: knative.NewVersionResolver( knative.WithGit( git.WithRemote(git.Remote{ - URL: "https://github.com/knative-sandbox/kn-plugin-event.git", + URL: "https://github.com/knative-extensions/kn-plugin-event.git", }), ), ), diff --git a/cmd/kn-event-sender/main_test.go b/cmd/kn-event-sender/main_test.go index 49f0f3133..019fd292c 100644 --- a/cmd/kn-event-sender/main_test.go +++ b/cmd/kn-event-sender/main_test.go @@ -13,7 +13,7 @@ import ( "gotest.tools/v3/assert" kes "knative.dev/kn-plugin-event/cmd/kn-event-sender" internalics "knative.dev/kn-plugin-event/internal/ics" - "knative.dev/kn-plugin-event/pkg/cli/ics" + "knative.dev/kn-plugin-event/pkg/ics" "knative.dev/kn-plugin-event/pkg/tests" ) diff --git a/cmd/kn-event/main.go b/cmd/kn-event/main.go index ad71520e6..de60ad3c6 100644 --- a/cmd/kn-event/main.go +++ b/cmd/kn-event/main.go @@ -2,9 +2,9 @@ package main import ( "github.com/wavesoftware/go-commandline" - "knative.dev/kn-plugin-event/internal/cli/cmd" + "knative.dev/kn-plugin-event/internal/cli" ) func main() { - commandline.New(new(cmd.App)).ExecuteOrDie(cmd.Options...) + commandline.New(new(cli.App)).ExecuteOrDie(cli.Options...) } diff --git a/cmd/kn-event/main_test.go b/cmd/kn-event/main_test.go index bec67c30e..73a1db00e 100644 --- a/cmd/kn-event/main_test.go +++ b/cmd/kn-event/main_test.go @@ -9,16 +9,16 @@ import ( "github.com/wavesoftware/go-commandline" "gotest.tools/v3/assert" - "knative.dev/kn-plugin-event/internal/cli/cmd" + "knative.dev/kn-plugin-event/internal/cli" ) func TestMainFunc(t *testing.T) { retcode := math.MinInt64 defer func() { - cmd.Options = nil + cli.Options = nil }() var buf bytes.Buffer - cmd.Options = []commandline.Option{ + cli.Options = []commandline.Option{ commandline.WithExit(func(code int) { retcode = code }), diff --git a/go.mod b/go.mod index 1952a545c..6a154bc95 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,17 @@ go 1.22.0 require ( github.com/cloudevents/sdk-go/v2 v2.15.2 github.com/ghodss/yaml v1.0.0 + github.com/gobuffalo/flect v1.0.2 github.com/google/go-containerregistry v0.19.1 github.com/google/uuid v1.6.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 + 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.0.0 + github.com/wavesoftware/go-commandline v1.1.0 github.com/wavesoftware/go-ensure v1.0.0 go.uber.org/zap v1.27.0 gopkg.in/yaml.v2 v2.4.0 @@ -30,10 +32,14 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +// TODO: Remove when https://github.com/knative/client/pull/1968 is merged +replace knative.dev/client/pkg => github.com/cardil/knative-client/pkg v0.0.0-20240923095307-3a2bcba04752 + require ( contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect contrib.go.opencensus.io/exporter/zipkin v0.1.2 // indirect + emperror.dev/errors v0.8.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -101,7 +107,6 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.18.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/wavesoftware/go-retcode v1.0.0 // indirect diff --git a/go.sum b/go.sum index 92a1cb28a..2a7a744ee 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9 contrib.go.opencensus.io/exporter/zipkin v0.1.2 h1:YqE293IZrKtqPnpwDPH/lOqTWD/s3Iwabycam74JV3g= contrib.go.opencensus.io/exporter/zipkin v0.1.2/go.mod h1:mP5xM3rrgOjpn79MM8fZbj3gsxcuytSqtH0dxSWW1RE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0= +emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -60,6 +62,8 @@ 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= @@ -90,7 +94,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -153,6 +157,8 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -438,8 +444,8 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -477,8 +483,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.0.0 h1:n7nrFr1unfiUcF7shA1rYf+YhXB12pY8uNYqPgFsHio= -github.com/wavesoftware/go-commandline v1.0.0/go.mod h1:C9yRtwZxJSck99kk6SRRkOtC2ppQF/KDRy0yrzWJuHU= +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-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= @@ -503,11 +509,13 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -892,8 +900,6 @@ k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/client-pkg v0.0.0-20240808015000-22f598931483 h1:jBfmxcR0H5Z9IzamelZtmmg9jfeOXfssllUVX5M4Xzs= knative.dev/client-pkg v0.0.0-20240808015000-22f598931483/go.mod h1:Y56KfZx3gJJpju88l86jQ9csxywLiopR0GkxCWW3+Kg= -knative.dev/client/pkg v0.0.0-20240903134911-f09e7164ceaf h1:QPGwYLkkMwssyEw0ek8T1u4Q3+FSeVPSH4IIKThdngc= -knative.dev/client/pkg v0.0.0-20240903134911-f09e7164ceaf/go.mod h1:JR3XomuVf2cBqgvXFONkX6Ebf1/gJwUnl/1OH47U18g= knative.dev/eventing v0.42.1-0.20240828134450-34f9cd384dea h1:j3bFBE797vD6IZJsECQ5lEENumLp817rkQxANrbKxHs= knative.dev/eventing v0.42.1-0.20240828134450-34f9cd384dea/go.mod h1:Clx8z37Nwg321H9+vGNxp5C6bVdo4l4XM5g6T5CgZVI= knative.dev/hack v0.0.0-20240814130635-06f7aff93954 h1:dGMK5VoL75szvrYQTL9NqhPYHu1f5dGaXx1hJI8fAFM= diff --git a/go.work.sum b/go.work.sum index 8e519479c..b39702801 100644 --- a/go.work.sum +++ b/go.work.sum @@ -136,6 +136,7 @@ github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPY github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4= github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4= github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/cardil/knative-client/pkg v0.0.0-20240923095307-3a2bcba04752/go.mod h1:JR3XomuVf2cBqgvXFONkX6Ebf1/gJwUnl/1OH47U18g= github.com/cavaliercoder/badio v0.0.0-20160213150051-ce5280129e9e h1:YYUjy5BRwO5zPtfk+aa2gw255FIIoi93zMmuy19o0bc= github.com/cavaliercoder/badio v0.0.0-20160213150051-ce5280129e9e/go.mod h1:V284PjgVwSk4ETmz84rpu9ehpGg7swlIH8npP9k2bGw= github.com/cavaliercoder/go-rpm v0.0.0-20200122174316-8cb9fd9c31a8 h1:jP7ki8Tzx9ThnFPLDhBYAhEpI2+jOURnHQNURgsMvnY= @@ -183,6 +184,7 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h1:2Dx4IHfC1yHWI12AxQDJM1QbRCDfk6M+blLzlZCXdrc= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= @@ -246,8 +248,6 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-training/helloworld v0.0.0-20200225145412-ba5f4379d78b h1:0pOrjn0UzTcHdhDVdxrH8LwM7QLnAp8qiUtwXM04JEE= github.com/go-training/helloworld v0.0.0-20200225145412-ba5f4379d78b/go.mod h1:hGGmX3bRUkYkc9aKA6mkUxi6d+f1GmZF1je0FlVTgwU= -github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= -github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= diff --git a/hack/release.sh b/hack/release.sh index 91b3fe46d..e388d915c 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -17,7 +17,7 @@ set -Eeuo pipefail # Coordinates in GitHub. -ORG_NAME="${ORG_NAME:-knative-sandbox}" +ORG_NAME="${ORG_NAME:-knative-extensions}" source "$(go run knative.dev/hack/cmd/script release.sh)" diff --git a/internal/cli/cmd/build.go b/internal/cli/build.go similarity index 53% rename from internal/cli/cmd/build.go rename to internal/cli/build.go index fe260094f..a98644957 100644 --- a/internal/cli/cmd/build.go +++ b/internal/cli/build.go @@ -1,12 +1,29 @@ -package cmd +/* + 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 ( "errors" "fmt" "github.com/spf13/cobra" + "knative.dev/client/pkg/output" + "knative.dev/kn-plugin-event/pkg/binding" "knative.dev/kn-plugin-event/pkg/cli" - "knative.dev/kn-plugin-event/pkg/configuration" ) // ErrCantBePresented is returned if data can't be presented. @@ -28,16 +45,17 @@ func (b *buildCommand) command() *cobra.Command { } func (b *buildCommand) run(cmd *cobra.Command, _ []string) error { - c := configuration.CreateCli(cmd) + c := binding.CliApp() ce, err := c.CreateWithArgs(b.event) if err != nil { return cantBuildEventError(err) } - out, err := c.PresentWith(ce, b.Output) + out, err := c.PresentWith(ce, b.OutputMode) if err != nil { return fmt.Errorf("event %w: %w", ErrCantBePresented, err) } - cmd.Println(out) + prt := output.PrinterFrom(cmd.Context()) + prt.Println(out) return nil } diff --git a/internal/cli/cmd/build_test.go b/internal/cli/build_test.go similarity index 77% rename from internal/cli/cmd/build_test.go rename to internal/cli/build_test.go index 3625c781c..97c223f7e 100644 --- a/internal/cli/cmd/build_test.go +++ b/internal/cli/build_test.go @@ -1,4 +1,20 @@ -package cmd_test +/* + 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_test import ( "bytes" @@ -6,6 +22,7 @@ import ( "testing" cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/spf13/cobra" "github.com/wavesoftware/go-commandline" "gotest.tools/v3/assert" "knative.dev/kn-plugin-event/pkg/event" @@ -62,8 +79,10 @@ func performTestsOnBuildSubCommand(t *testing.T, args cmdArgs, preparers ...even t.Helper() buf := bytes.NewBuffer([]byte{}) assert.NilError(t, testapp().Execute( - commandline.WithOutput(buf), - commandline.WithArgs(args.args...), + commandline.WithCommand(func(cmd *cobra.Command) { + cmd.SetOut(buf) + cmd.SetArgs(args.args) + }), )) output := buf.Bytes() ec := newEventChecks(t) diff --git a/internal/cli/cmd/builder.go b/internal/cli/builder.go similarity index 65% rename from internal/cli/cmd/builder.go rename to internal/cli/builder.go index 5fd51630f..79ed86ad8 100644 --- a/internal/cli/cmd/builder.go +++ b/internal/cli/builder.go @@ -1,4 +1,20 @@ -package cmd +/* + 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/spf13/cobra" diff --git a/internal/cli/cmd/root.go b/internal/cli/cmd/root.go deleted file mode 100644 index 003b49340..000000000 --- a/internal/cli/cmd/root.go +++ /dev/null @@ -1,74 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/thediveo/enumflag" - "github.com/wavesoftware/go-commandline" - _ "k8s.io/client-go/plugin/pkg/client/auth" // for kubeconfig auth plugins to work correctly see issue #24 . - "knative.dev/kn-plugin-event/pkg/cli" - "knative.dev/kn-plugin-event/pkg/metadata" -) - -// Options to override the commandline for testing purposes. -var Options []commandline.Option //nolint:gochecknoglobals - -type App struct { - cli.Options -} - -func (a *App) Command() *cobra.Command { - c := &cobra.Command{ - Use: metadata.PluginUse, - Aliases: []string{fmt.Sprintf("kn %s", metadata.PluginUse)}, - Short: metadata.PluginDescription, - Long: metadata.PluginLongDescription, - SilenceUsage: true, - } - c.PersistentFlags().BoolVarP( - &a.Verbose, "verbose", "v", - false, "verbose output", - ) - c.PersistentFlags().VarP( - enumflag.New(&a.Output, "output", outputModeIds(), enumflag.EnumCaseInsensitive), - "output", "o", - "Output format. One of: human|json|yaml.", - ) - - eventArgs := &cli.EventArgs{} - targetArgs := &cli.TargetArgs{} - commands := []subcommand{ - &buildCommand{App: a, event: eventArgs}, - &sendCommand{App: a, event: eventArgs, target: targetArgs}, - &versionCommand{App: a}, - } - for _, each := range commands { - c.AddCommand(each.command()) - } - - c.PersistentFlags().StringVar( - &a.KubeconfigOptions.Path, "kubeconfig", "", - "kubectl configuration file (default: ~/.kube/config)", - ) - c.PersistentFlags().StringVar( - &a.KubeconfigOptions.Context, "context", "", - "name of the kubeconfig context to use", - ) - c.PersistentFlags().StringVar( - &a.KubeconfigOptions.Cluster, "cluster", "", - "name of the kubeconfig cluster to use", - ) - - return c -} - -var _ commandline.CobraProvider = new(App) - -func outputModeIds() map[cli.OutputMode][]string { - return map[cli.OutputMode][]string{ - cli.HumanReadable: {"human"}, - cli.JSON: {"json"}, - cli.YAML: {"yaml"}, - } -} diff --git a/internal/cli/cmd/root_test.go b/internal/cli/cmd/root_test.go deleted file mode 100644 index 3de66104c..000000000 --- a/internal/cli/cmd/root_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package cmd_test - -import ( - "bytes" - "math" - "testing" - - "github.com/wavesoftware/go-commandline" - "gotest.tools/v3/assert" - "knative.dev/kn-plugin-event/internal/cli/cmd" -) - -func TestRootInvalidCommand(t *testing.T) { - retcode := math.MinInt64 - buf := bytes.NewBuffer([]byte{}) - testapp().ExecuteOrDie( - commandline.WithOutput(buf), - commandline.WithExit(func(code int) { - retcode = code - }), - commandline.WithArgs("invalid-command"), - ) - - assert.Check(t, retcode != math.MinInt64) - assert.Check(t, retcode != 0) -} - -func testapp() *commandline.App { - return commandline.New(new(cmd.App)) -} diff --git a/internal/cli/cmd/send.go b/internal/cli/cmd/send.go deleted file mode 100644 index ef6594b9a..000000000 --- a/internal/cli/cmd/send.go +++ /dev/null @@ -1,95 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/cobra" - "knative.dev/kn-plugin-event/pkg/cli" - "knative.dev/kn-plugin-event/pkg/configuration" - "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") -) - -type sendCommand struct { - target *cli.TargetArgs - event *cli.EventArgs - *App -} - -func (s *sendCommand) command() *cobra.Command { - c := &cobra.Command{ - Use: "send", - Short: "Builds and sends a CloudEvent to recipient", - RunE: s.run, - } - addBuilderFlags(s.event, c) - c.Flags().StringVarP( - &s.target.URL, "to-url", "u", "", - `Specify an URL to send event to. This option can't be used with ---to option.`, - ) - c.Flags().StringVarP( - &s.target.Addressable, "to", "r", "", - `Specify an addressable resource to send event to. This argument -takes format kind:apiVersion:name for named resources or -kind:apiVersion:labelKey1=value1,labelKey2=value2 for matching via a -label selector. This option can't be used with --to-url option.`, - ) - c.Flags().StringVarP( - &s.target.Namespace, "namespace", "n", "", - `Specify a namespace of addressable resource defined with --to -option. If this option isn't specified a current context namespace will be used -to find addressable resource. This option can't be used with --to-url option.`, - ) - c.Flags().StringVar( - &s.target.SenderNamespace, "sender-namespace", "", - `Specify a namespace of sender job to be created. While using --to -option, event is send within a cluster. To do that kn-event uses a special Job -that is deployed to cluster in namespace dictated by --sender-namespace. If -this option isn't specified a current context namespace will be used. This -option can't be used with --to-url option.`, - ) - c.Flags().StringVar( - &s.target.AddressableURI, "addressable-uri", "", - `Specify an URI of a target addressable resource. If this option -isn't specified target URL will not be changed. This option can't be used with ---to-url option.`, - ) - c.PreRunE = func(cmd *cobra.Command, args []string) error { - err := cli.ValidateTarget(s.target) - if err != nil { - return fmt.Errorf("%w: %w", ErrSendTargetValidationFailed, err) - } - return nil - } - return c -} - -func (s *sendCommand) run(cmd *cobra.Command, _ []string) error { - c := configuration.CreateCli(cmd) - ce, err := c.CreateWithArgs(s.event) - if err != nil { - return cantBuildEventError(err) - } - err = c.Send(*ce, *s.target, &s.Options) - if err != nil { - return cantSentEvent(err) - } - return nil -} - -func cantSentEvent(err error) error { - if errors.Is(err, event.ErrCantSentEvent) { - return err - } - return fmt.Errorf("%w: %w", event.ErrCantSentEvent, err) -} diff --git a/internal/cli/cmd/send_test.go b/internal/cli/cmd/send_test.go deleted file mode 100644 index c016e5e83..000000000 --- a/internal/cli/cmd/send_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package cmd_test - -import ( - "bytes" - "net/url" - "strings" - "testing" - - "github.com/wavesoftware/go-commandline" - "gotest.tools/v3/assert" - "knative.dev/kn-plugin-event/pkg/tests" -) - -func TestSendToAddress(t *testing.T) { - buf := bytes.NewBuffer([]byte{}) - ce, err := tests.WithCloudEventsServer(func(serverURL url.URL) error { - return testapp().Execute( - commandline.WithOutput(buf), - commandline.WithArgs( - "send", - "--to-url", serverURL.String(), - "--id", "654321", - "--field", "person.name=Chris", - "--field", "person.email=ksuszyns@example.com", - "--field", "ping=123", - "--field", "active=true", - "--raw-field", "ref=321", - ), - ) - }) - assert.NilError(t, err) - out := buf.String() - assert.Check(t, strings.Contains(out, "Event (ID: 654321) have been sent.")) - assert.Check(t, ce != nil) - assert.Equal(t, "654321", ce.ID()) - payload, err := tests.UnmarshalCloudEventData(ce.Data()) - assert.NilError(t, err) - assert.DeepEqual(t, map[string]interface{}{ - "person": map[string]interface{}{ - "name": "Chris", - "email": "ksuszyns@example.com", - }, - "ping": 123., - "active": true, - "ref": "321", - }, payload) -} diff --git a/internal/cli/cmd/types.go b/internal/cli/cmd/types.go deleted file mode 100644 index 01048b77e..000000000 --- a/internal/cli/cmd/types.go +++ /dev/null @@ -1,7 +0,0 @@ -package cmd - -import "github.com/spf13/cobra" - -type subcommand interface { - command() *cobra.Command -} diff --git a/internal/cli/root.go b/internal/cli/root.go new file mode 100644 index 000000000..b72c2a2e0 --- /dev/null +++ b/internal/cli/root.go @@ -0,0 +1,93 @@ +/* + 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/spf13/cobra" + "github.com/thediveo/enumflag" + "github.com/wavesoftware/go-commandline" + "go.uber.org/zap/zapcore" + outlogging "knative.dev/client/pkg/output/logging" + "knative.dev/kn-plugin-event/pkg/cli" + "knative.dev/kn-plugin-event/pkg/metadata" +) + +// Options to override the commandline for testing purposes. +var Options []commandline.Option //nolint:gochecknoglobals + +type App struct { + cli.Params +} + +func (a *App) Command() *cobra.Command { + c := &cobra.Command{ + Use: metadata.PluginUse, + Aliases: []string{"kn " + metadata.PluginUse}, + Short: metadata.PluginDescription, + Long: metadata.PluginLongDescription, + SilenceUsage: true, + } + + eventArgs := &cli.EventArgs{} + targetArgs := &cli.TargetArgs{} + subcommands := []subcommand{ + &buildCommand{App: a, event: eventArgs}, + &sendCommand{App: a, event: eventArgs, target: targetArgs}, + &versionCommand{App: a}, + } + for _, each := range subcommands { + c.AddCommand(each.command()) + } + c.SetContext(cli.InitialContext()) + c.PersistentPreRun = func(cmd *cobra.Command, _ []string) { + lvl := zapcore.InfoLevel + if a.Verbose { + lvl = zapcore.DebugLevel + } + cli.SetupContext(cmd, lvl) + } + c.PersistentPostRunE = func(cmd *cobra.Command, _ []string) error { + closer := outlogging.LogFileCloserFrom(cmd.Context()) + // ensure to close the log file + return closer() + } + a.setGlobalFlags(c) + + return c +} + +func (a *App) setGlobalFlags(c *cobra.Command) { + c.PersistentFlags().BoolVarP( + &a.Verbose, "verbose", "v", + false, "verbose output", + ) + c.PersistentFlags().VarP( + enumflag.New(&a.OutputMode, "output", outputModeIDs(), enumflag.EnumCaseInsensitive), + "output", "o", + "OutputMode format. One of: human|json|yaml.", + ) +} + +var _ commandline.CobraProvider = new(App) + +func outputModeIDs() map[cli.OutputMode][]string { + return map[cli.OutputMode][]string{ + cli.HumanReadable: {"human"}, + cli.JSON: {"json"}, + cli.YAML: {"yaml"}, + } +} diff --git a/internal/cli/root_test.go b/internal/cli/root_test.go new file mode 100644 index 000000000..9d7b6013d --- /dev/null +++ b/internal/cli/root_test.go @@ -0,0 +1,61 @@ +/* + 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_test + +import ( + "bytes" + "context" + "math" + "testing" + + "github.com/spf13/cobra" + "github.com/wavesoftware/go-commandline" + "gotest.tools/v3/assert" + "knative.dev/kn-plugin-event/internal/cli" +) + +func TestRootInvalidCommand(t *testing.T) { + retcode := math.MinInt64 + buf := bytes.NewBuffer([]byte{}) + testapp().ExecuteOrDie( + commandline.WithCommand(func(cmd *cobra.Command) { + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.SetArgs([]string{"invalid-command"}) + }), + commandline.WithExit(func(code int) { + retcode = code + }), + ) + + assert.Check(t, retcode != math.MinInt64) + assert.Check(t, retcode != 0) +} + +func testapp() *commandline.App { + return commandline.New(&wrap{new(cli.App)}) +} + +type wrap struct { + delagate commandline.CobraProvider +} + +func (w *wrap) Command() *cobra.Command { + c := w.delagate.Command() + c.SetContext(context.TODO()) + return c +} diff --git a/internal/cli/send.go b/internal/cli/send.go new file mode 100644 index 000000000..793fc8a45 --- /dev/null +++ b/internal/cli/send.go @@ -0,0 +1,91 @@ +/* + 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 ( + "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/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") +) + +type sendCommand struct { + target *cli.TargetArgs + event *cli.EventArgs + *App +} + +func (s *sendCommand) command() *cobra.Command { + c := &cobra.Command{ + Use: "send", + Short: "Builds and sends a CloudEvent to recipient", + RunE: s.run, + } + addBuilderFlags(s.event, c) + c.Flags().StringVarP( + &s.target.Sink, "to", "r", "", + sink.Usage("to"), + ) + c.Flags().StringVar( + &s.target.AddressableURI, "addressable-uri", "", + `Specify a relative URI of a target addressable resource. If this +option isn't specified target URL will not be changed.`, + ) + s.SetGlobalFlags(c.Flags()) + s.SetCommandFlags(c.Flags()) + c.PreRunE = func(*cobra.Command, []string) error { + err := cli.ValidateTarget(s.target) + if err != nil { + return fmt.Errorf("%w: %w", ErrSendTargetValidationFailed, err) + } + return nil + } + return c +} + +func (s *sendCommand) run(cmd *cobra.Command, _ []string) error { + c := binding.CliApp() + ce, err := c.CreateWithArgs(s.event) + if err != nil { + return cantBuildEventError(err) + } + err = c.Send(cmd.Context(), *ce, *s.target, &s.Params) + if err != nil { + return cantSentEvent(err) + } + return nil +} + +func cantSentEvent(err error) error { + if errors.Is(err, event.ErrCantSentEvent) { + return err + } + return fmt.Errorf("%w: %w", event.ErrCantSentEvent, err) +} diff --git a/internal/cli/send_test.go b/internal/cli/send_test.go new file mode 100644 index 000000000..3aa116b9c --- /dev/null +++ b/internal/cli/send_test.go @@ -0,0 +1,67 @@ +/* + 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_test + +import ( + "bytes" + "net/url" + "strings" + "testing" + + "github.com/spf13/cobra" + "github.com/wavesoftware/go-commandline" + "gotest.tools/v3/assert" + "knative.dev/kn-plugin-event/pkg/tests" +) + +func TestSendToAddress(t *testing.T) { + buf := bytes.NewBuffer([]byte{}) + ce, err := tests.WithCloudEventsServer(func(serverURL url.URL) error { + return testapp().Execute( + commandline.WithCommand(func(cmd *cobra.Command) { + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.SetArgs([]string{ + "send", + "--to", serverURL.String(), + "--id", "654321", + "--field", "person.name=Chris", + "--field", "person.email=ksuszyns@example.com", + "--field", "ping=123", + "--field", "active=true", + "--raw-field", "ref=321", + }) + }), + ) + }) + assert.NilError(t, err) + out := buf.String() + assert.Check(t, strings.Contains(out, "Event (ID: 654321) have been sent.")) + assert.Check(t, ce != nil) + assert.Equal(t, "654321", ce.ID()) + payload, err := tests.UnmarshalCloudEventData(ce.Data()) + assert.NilError(t, err) + assert.DeepEqual(t, map[string]interface{}{ + "person": map[string]interface{}{ + "name": "Chris", + "email": "ksuszyns@example.com", + }, + "ping": 123., + "active": true, + "ref": "321", + }, payload) +} diff --git a/internal/cli/types.go b/internal/cli/types.go new file mode 100644 index 000000000..1fa975c85 --- /dev/null +++ b/internal/cli/types.go @@ -0,0 +1,23 @@ +/* + 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/spf13/cobra" + +type subcommand interface { + command() *cobra.Command +} diff --git a/internal/cli/cmd/version.go b/internal/cli/version.go similarity index 71% rename from internal/cli/cmd/version.go rename to internal/cli/version.go index 7fd18e7f0..43513a439 100644 --- a/internal/cli/cmd/version.go +++ b/internal/cli/version.go @@ -1,4 +1,20 @@ -package cmd +/* + 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 ( "encoding/json" @@ -32,7 +48,7 @@ func (v *versionCommand) run(cmd *cobra.Command, _ []string) error { Name: metadata.PluginName, Version: metadata.Version, Image: metadata.ResolveImage(), - }, v.Output) + }, v.OutputMode) if err != nil { return err } diff --git a/internal/cli/cmd/version_test.go b/internal/cli/version_test.go similarity index 62% rename from internal/cli/cmd/version_test.go rename to internal/cli/version_test.go index ab167795b..24572fa0c 100644 --- a/internal/cli/cmd/version_test.go +++ b/internal/cli/version_test.go @@ -1,4 +1,20 @@ -package cmd_test +/* + 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_test import ( "bytes" @@ -6,6 +22,7 @@ import ( "regexp" "testing" + "github.com/spf13/cobra" "github.com/wavesoftware/go-commandline" "gopkg.in/yaml.v2" "gotest.tools/v3/assert" @@ -40,8 +57,10 @@ func versionSubCommandChecks(t *testing.T, format string, unmarshal unmarshalFun t.Helper() buf := bytes.NewBuffer([]byte{}) assert.NilError(t, testapp().Execute( - commandline.WithOutput(buf), - commandline.WithArgs("version", "-o", format), + commandline.WithCommand(func(cmd *cobra.Command) { + cmd.SetOut(buf) + cmd.SetArgs([]string{"version", "-o", format}) + }), )) pv := cli.PluginVersionOutput{} @@ -53,8 +72,11 @@ func versionSubCommandChecks(t *testing.T, format string, unmarshal unmarshalFun func TestPresentAsWithInvalidOutput(t *testing.T) { buf := bytes.NewBuffer([]byte{}) err := testapp().Execute( - commandline.WithOutput(buf), - commandline.WithArgs("version", "-o", "invalid"), + commandline.WithCommand(func(cmd *cobra.Command) { + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.SetArgs([]string{"version", "-o", "invalid"}) + }), ) assert.Error(t, err, "invalid argument \"invalid\" for "+ "\"-o, --output\" flag: must be 'human', 'json', 'yaml'") diff --git a/internal/ics/app.go b/internal/ics/app.go index e1be74a25..7a9452cb0 100644 --- a/internal/ics/app.go +++ b/internal/ics/app.go @@ -1,58 +1,52 @@ package ics import ( - "os" - "github.com/spf13/cobra" "github.com/wavesoftware/go-commandline" "go.uber.org/zap" "go.uber.org/zap/zapcore" - "knative.dev/kn-plugin-event/pkg/configuration" - "knative.dev/kn-plugin-event/pkg/system" + 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/k8s" "knative.dev/pkg/logging" ) // Options to override the commandline for testing purposes. var Options []commandline.Option //nolint:gochecknoglobals -type App struct{} +type App struct { + k8s.Params +} func (a App) Command() *cobra.Command { - return &cobra.Command{ + c := &cobra.Command{ Use: "ics", SilenceUsage: true, SilenceErrors: true, RunE: a.run, } + c.SetContext(cli.InitialContext()) + c.PersistentPreRun = func(cmd *cobra.Command, _ []string) { + cli.SetupContext(cmd, zapcore.DebugLevel) + } + c.PersistentPostRunE = func(cmd *cobra.Command, _ []string) error { + closer := outlogging.LogFileCloserFrom(cmd.Context()) + // ensure to close the log file + return closer() + } + a.SetGlobalFlags(c.PersistentFlags()) + return c } func (a App) run(cmd *cobra.Command, _ []string) error { - env := withLogger(cmd) - log := logging.FromContext(env.Context()) - defer func(log *zap.SugaredLogger) { - _ = log.Sync() - }(log) - err := configuration.CreateIcs(env).SendFromEnv() + ctx := cmd.Context() + log := logging.FromContext(ctx) + err := binding.IcsApp().SendFromEnv(ctx, a.Parse()) if err != nil { log.Error(zap.Error(err)) } - return err //nolint:wrapcheck + return err } var _ commandline.CobraProvider = new(App) - -func withLogger(env system.Environment) system.Environment { - ctx := env.Context() - ctx = logging.WithLogger(ctx, createLogger(env)) - return system.WithContext(ctx, env) -} - -func createLogger(env system.Environment) *zap.SugaredLogger { - cfg := zap.NewProductionConfig() - encoder := zapcore.NewJSONEncoder(cfg.EncoderConfig) - sink := zapcore.AddSync(env.OutOrStdout()) - zcore := zapcore.NewCore(encoder, sink, cfg.Level) - return zap.New(zcore). - With(zap.Strings("env", os.Environ())). - Sugar() -} diff --git a/internal/ics/app_test.go b/internal/ics/app_test.go index b23880d23..c1d24b51b 100644 --- a/internal/ics/app_test.go +++ b/internal/ics/app_test.go @@ -2,6 +2,7 @@ package ics_test import ( "bytes" + "context" "net/url" "strings" "testing" @@ -9,17 +10,22 @@ import ( cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/google/uuid" + "github.com/spf13/cobra" "github.com/wavesoftware/go-commandline" "gotest.tools/v3/assert" internalics "knative.dev/kn-plugin-event/internal/ics" - "knative.dev/kn-plugin-event/pkg/cli/ics" + "knative.dev/kn-plugin-event/pkg/ics" "knative.dev/kn-plugin-event/pkg/tests" ) func TestApp(t *testing.T) { - var outBuf bytes.Buffer + var outBuf, errBuf bytes.Buffer opts := []commandline.Option{ - commandline.WithOutput(&outBuf), + commandline.WithCommand(func(cmd *cobra.Command) { + cmd.SetOut(&outBuf) + cmd.SetErr(&errBuf) + cmd.SetContext(context.TODO()) + }), } id := uuid.New().String() want := cloudevents.NewEvent() @@ -39,10 +45,10 @@ func TestApp(t *testing.T) { return commandline.New(internalics.App{}).Execute(opts...) }) }) - out := outBuf.String() assert.NilError(t, err) assert.DeepEqual(t, want, *got) - assert.Check(t, strings.Contains(out, "Event sent")) - assert.Check(t, strings.Contains(out, id)) + assert.Check(t, strings.Contains(errBuf.String(), "Event sent")) + assert.Check(t, strings.Contains(errBuf.String(), id)) + assert.Equal(t, "", outBuf.String()) } diff --git a/pkg/binding/binding.go b/pkg/binding/binding.go new file mode 100644 index 000000000..702bb48b8 --- /dev/null +++ b/pkg/binding/binding.go @@ -0,0 +1,38 @@ +package binding + +import ( + "knative.dev/kn-plugin-event/pkg/cli" + "knative.dev/kn-plugin-event/pkg/event" + "knative.dev/kn-plugin-event/pkg/ics" + "knative.dev/kn-plugin-event/pkg/k8s" + "knative.dev/kn-plugin-event/pkg/sender" +) + +// CliApp creates the configured cli.App to work with. +func CliApp() *cli.App { + return &cli.App{ + Binding: eventsBinding(senderBinding()), + } +} + +// IcsApp creates the configured ics.App to work with. +func IcsApp() *ics.App { + return &ics.App{ + Binding: eventsBinding(senderBinding()), + } +} + +func senderBinding() sender.Binding { + return sender.Binding{ + NewKubeClients: memoizeKubeClients(k8s.NewClients), + NewJobRunner: k8s.NewJobRunner, + NewAddressResolver: k8s.NewAddressResolver, + } +} + +func eventsBinding(binding sender.Binding) event.Binding { + return event.Binding{ + CreateSender: binding.New, + NewKubeClients: binding.NewKubeClients, + } +} diff --git a/pkg/binding/memoized.go b/pkg/binding/memoized.go new file mode 100644 index 000000000..d6686f2c6 --- /dev/null +++ b/pkg/binding/memoized.go @@ -0,0 +1,27 @@ +package binding + +import ( + "knative.dev/kn-plugin-event/pkg/k8s" +) + +func memoizeKubeClients(delegate k8s.NewKubeClients) k8s.NewKubeClients { + mem := kubeClientsMemoizer{delegate: delegate} + return mem.computeClients +} + +type kubeClientsMemoizer struct { + delegate k8s.NewKubeClients + result k8s.Clients +} + +func (m *kubeClientsMemoizer) computeClients(params *k8s.Configurator) (k8s.Clients, error) { + if m.result != nil { + return m.result, nil + } + cl, err := m.delegate(params) + if err != nil { + return nil, err + } + m.result = cl + return m.result, nil +} diff --git a/pkg/configuration/memoized_test.go b/pkg/binding/memoized_test.go similarity index 62% rename from pkg/configuration/memoized_test.go rename to pkg/binding/memoized_test.go index 4eb38cfa3..4d69cf6af 100644 --- a/pkg/configuration/memoized_test.go +++ b/pkg/binding/memoized_test.go @@ -1,58 +1,65 @@ -package configuration_test +package binding_test import ( "os" + "path" "testing" "gotest.tools/v3/assert" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "knative.dev/kn-plugin-event/pkg/configuration" + knk8s "knative.dev/client/pkg/k8s" + "knative.dev/kn-plugin-event/pkg/binding" "knative.dev/kn-plugin-event/pkg/event" + "knative.dev/kn-plugin-event/pkg/k8s" "sigs.k8s.io/yaml" ) func TestMemoizeKubeClients(t *testing.T) { t.Parallel() - testMemoizeKubeClientsCases(func(tc testMemoizeKubeClientsCase) { + tcs := []testMemoizeKubeClientsCase{{ + name: "cli", + fn: func() event.Binding { return binding.CliApp().Binding }, + }, { + name: "ics", + fn: func() event.Binding { return binding.IcsApp().Binding }, + }} + for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { t.Parallel() cfgfile := tempConfigFile(t) - defer func() { - assert.NilError(t, os.Remove(cfgfile)) - }() - props := &event.Properties{ - KnPluginOptions: event.KnPluginOptions{ - KubeconfigOptions: event.KubeconfigOptions{Path: cfgfile}, + params := k8s.Params{ + Params: knk8s.Params{ + KubeCfgPath: cfgfile, }, } - app := tc.fn() - ns1, err := app.DefaultNamespace(props) + cfg := &k8s.Configurator{ + ClientConfig: params.GetClientConfig, + } + b := tc.fn() + cl, err := b.NewKubeClients(cfg) assert.NilError(t, err) + cl2, err2 := b.NewKubeClients(cfg) + assert.NilError(t, err2) + assert.Equal(t, cl, cl2) + + ns1 := cl.Namespace() + assert.Equal(t, "expected", ns1) updateConfig(t, cfgfile, func(cfg *clientcmdapi.Config) { cfg.Contexts[cfg.CurrentContext].Namespace = "replaced" }) - ns2, err := app.DefaultNamespace(props) - assert.NilError(t, err) + ns2 := cl.Namespace() assert.Equal(t, ns1, ns2) - }) - }) -} + cl, err = b.NewKubeClients(cfg) + assert.NilError(t, err) + ns3 := cl.Namespace() + assert.Equal(t, ns1, ns3) -func testMemoizeKubeClientsCases(fn func(tc testMemoizeKubeClientsCase)) { - tcs := []testMemoizeKubeClientsCase{{ - name: "cli", - fn: func() event.Binding { - return configuration.CreateCli(nil).Binding - }, - }, { - name: "ics", - fn: func() event.Binding { - return configuration.CreateIcs(nil).Binding - }, - }} - for _, tc := range tcs { - tc := tc - fn(tc) + b = tc.fn() + cl, err = b.NewKubeClients(cfg) + assert.NilError(t, err) + ns4 := cl.Namespace() + assert.Equal(t, "replaced", ns4) + }) } } @@ -70,12 +77,10 @@ func updateConfig(tb testing.TB, cfgfile string, fn func(cfg *clientcmdapi.Confi func tempConfigFile(tb testing.TB) string { tb.Helper() - tmpfile, err := os.CreateTemp("", "kubeconfig") - assert.NilError(tb, err) - assert.NilError(tb, tmpfile.Close()) + tmpfile := path.Join(tb.TempDir(), "kubeconfig") cfg := stubConfig() - safeConfig(tb, tmpfile.Name(), cfg) - return tmpfile.Name() + safeConfig(tb, tmpfile, cfg) + return tmpfile } func safeConfig(tb testing.TB, cfgfile string, config clientcmdapi.Config) { diff --git a/pkg/cli/context.go b/pkg/cli/context.go new file mode 100644 index 000000000..5aa00a65a --- /dev/null +++ b/pkg/cli/context.go @@ -0,0 +1,60 @@ +/* + 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 ( + "context" + + "go.uber.org/zap/zapcore" + "knative.dev/client/pkg/output" + outlogging "knative.dev/client/pkg/output/logging" + "knative.dev/pkg/signals" +) + +// Contextual represents a contextual entity that also can serve as an +// output.Printer. +type Contextual interface { + SetContext(ctx context.Context) + Context() context.Context + output.Printer +} + +// InitialContext returns the initial context object, so it could be set ahead +// of time the setup is called. +func InitialContext() context.Context { + return initialCtx +} + +// SetupContext will set the context commonly for all CLIs. +func SetupContext(ctxual Contextual, defaultLogLevel zapcore.Level) { + ctx := ctxual.Context() + if ctx == initialCtx { + // TODO: knative.dev/pkg/signals should allow for resetting the + // context for testing purposes. + ctx = signals.NewContext() + } + ctx = output.WithContext(ctx, ctxual) + ctx = outlogging.WithLogLevel(ctx, defaultLogLevel) + ctx = outlogging.EnsureLogger(ctx) + ctxual.SetContext(ctx) +} + +var ( + initialCtxKey = struct{}{} //nolint:gochecknoglobals + initialCtx = context.WithValue( //nolint:gochecknoglobals + context.Background(), initialCtxKey, true) +) diff --git a/pkg/cli/context_test.go b/pkg/cli/context_test.go new file mode 100644 index 000000000..789b159c3 --- /dev/null +++ b/pkg/cli/context_test.go @@ -0,0 +1,35 @@ +/* + 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_test + +import ( + "testing" + + "github.com/spf13/cobra" + "go.uber.org/zap/zapcore" + "gotest.tools/v3/assert" + outlogging "knative.dev/client/pkg/output/logging" + "knative.dev/kn-plugin-event/pkg/cli" +) + +func TestSetupContext(t *testing.T) { + cmd := &cobra.Command{} + cmd.SetContext(cli.InitialContext()) + cli.SetupContext(cmd, zapcore.InvalidLevel) + ctx := cmd.Context() + assert.Equal(t, zapcore.InvalidLevel, outlogging.LogLevelFromContext(ctx)) +} diff --git a/pkg/cli/options.go b/pkg/cli/options.go deleted file mode 100644 index c79e70034..000000000 --- a/pkg/cli/options.go +++ /dev/null @@ -1,115 +0,0 @@ -package cli - -import ( - "encoding/json" - "errors" - "fmt" - "strings" - "time" - - "github.com/ghodss/yaml" - "go.uber.org/zap" - "go.uber.org/zap/buffer" - "go.uber.org/zap/zapcore" - "knative.dev/kn-plugin-event/pkg/event" - "knative.dev/kn-plugin-event/pkg/system" -) - -// WithLogger will create an event suitable Options from CLI ones. -func (opts *Options) WithLogger(outputs system.Outputs) (*event.Properties, error) { - zc := zap.NewProductionConfig() - cfg := zap.NewProductionEncoderConfig() - if opts.Verbose { - cfg = zap.NewDevelopmentEncoderConfig() - } - cfg.EncodeTime = zapcore.RFC3339NanoTimeEncoder - var encoder zapcore.Encoder - switch opts.Output { - case HumanReadable: - if !opts.Verbose { - cfg.CallerKey = "" - } - cfg.ConsoleSeparator = " " - cfg.EncodeLevel = alignCapitalColorLevelEncoder - cfg.EncodeTime = zapcore.TimeEncoderOfLayout(time.StampMilli) - encoder = zapcore.NewConsoleEncoder(cfg) - case YAML: - encoder = &yamlEncoder{zapcore.NewJSONEncoder(cfg)} - case JSON: - encoder = zapcore.NewJSONEncoder(cfg) - } - sink := zapcore.AddSync(outputs.OutOrStdout()) - errSink := zapcore.AddSync(outputs.ErrOrStderr()) - zcore := zapcore.NewCore(encoder, sink, zc.Level) - log := zap.New( - zcore, buildOptions(zc, errSink)..., - ) - - return &event.Properties{ - KnPluginOptions: opts.KnPluginOptions, - Log: log.Sugar(), - }, nil -} - -func alignCapitalColorLevelEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { - spaces := len(zapcore.FatalLevel.CapitalString()) - len(l.CapitalString()) - if spaces > 0 { - enc.AppendString(strings.Repeat(" ", spaces)) - } - zapcore.CapitalColorLevelEncoder(l, enc) -} - -func buildOptions(cfg zap.Config, errSink zapcore.WriteSyncer) []zap.Option { - opts := []zap.Option{zap.ErrorOutput(errSink)} - - if cfg.Development { - opts = append(opts, zap.Development()) - } - - if !cfg.DisableCaller { - opts = append(opts, zap.AddCaller()) - } - - stackLevel := zap.ErrorLevel - if cfg.Development { - stackLevel = zap.WarnLevel - } - if !cfg.DisableStacktrace { - opts = append(opts, zap.AddStacktrace(stackLevel)) - } - - return opts -} - -type yamlEncoder struct { - zapcore.Encoder -} - -func (y *yamlEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { - buf, err := y.Encoder.EncodeEntry(entry, fields) - if err != nil { - return nil, unexpected(err) - } - var v interface{} - err = json.Unmarshal(buf.Bytes(), &v) - if err != nil { - return nil, unexpected(err) - } - bytes, err := yaml.Marshal(v) - if err != nil { - return nil, unexpected(err) - } - buf = buffer.NewPool().Get() - _, _ = buf.Write([]byte("---\n")) - if _, err = buf.Write(bytes); err != nil { - return nil, unexpected(err) - } - return buf, nil -} - -func unexpected(err error) error { - if errors.Is(err, event.ErrUnexpected) { - return err - } - return fmt.Errorf("%w: %w", event.ErrUnexpected, err) -} diff --git a/pkg/cli/send.go b/pkg/cli/send.go index 35259c149..293c5d1b1 100644 --- a/pkg/cli/send.go +++ b/pkg/cli/send.go @@ -1,6 +1,7 @@ package cli import ( + "context" "errors" "fmt" @@ -9,20 +10,17 @@ import ( ) // Send will send CloudEvent to target. -func (a *App) Send(ce cloudevents.Event, target TargetArgs, options *Options) error { - props, err := options.WithLogger(a) +func (a *App) Send(ctx context.Context, ce cloudevents.Event, tArgs TargetArgs, params *Params) error { + target, err := a.createTarget(tArgs, params) if err != nil { return err } - t, err := a.createTarget(target, props) - if err != nil { - return err - } - s, err := a.Binding.NewSender(t) + var sender event.Sender + sender, err = a.NewSender(params.Parse(), target) if err != nil { return cantSentEvent(err) } - err = s.Send(ce) + err = sender.Send(ctx, ce) if err == nil { return nil } diff --git a/pkg/cli/send_test.go b/pkg/cli/send_test.go index 8e83bdf7a..624b90666 100644 --- a/pkg/cli/send_test.go +++ b/pkg/cli/send_test.go @@ -1,16 +1,19 @@ package cli_test import ( - "bytes" + "context" "fmt" - "strings" "testing" cloudevents "github.com/cloudevents/sdk-go/v2" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" "gotest.tools/v3/assert" + outlogging "knative.dev/client/pkg/output/logging" "knative.dev/kn-plugin-event/pkg/cli" "knative.dev/kn-plugin-event/pkg/event" - "knative.dev/kn-plugin-event/pkg/system" + "knative.dev/kn-plugin-event/pkg/k8s" "knative.dev/kn-plugin-event/pkg/tests" ) @@ -35,32 +38,27 @@ func createExampleEvent() cloudevents.Event { func assertWithOutputMode(t *testing.T, want cloudevents.Event, mode cli.OutputMode) { t.Helper() - var ( - outBuf bytes.Buffer - errBuf bytes.Buffer - ) + c, logs := observer.New(zapcore.DebugLevel) + log := &outlogging.ZapLogger{SugaredLogger: zap.New(c).Sugar()} + ctx := outlogging.WithLogger(context.TODO(), log) sender := &tests.Sender{} app := cli.App{ Binding: event.Binding{ - CreateSender: func(target *event.Target) (event.Sender, error) { + CreateSender: func(*k8s.Configurator, *event.Target) (event.Sender, error) { return sender, nil }, }, - Environment: system.WithOutputs(&outBuf, &errBuf, nil), } err := app.Send( + ctx, want, - cli.TargetArgs{URL: "http://example.org"}, - &cli.Options{ - Output: mode, - }, + cli.TargetArgs{Sink: "https://example.org"}, + &cli.Params{OutputMode: mode}, ) assert.NilError(t, err) assert.Equal(t, 1, len(sender.Sent)) assert.Equal(t, want.ID(), sender.Sent[0].ID()) - outputs := outBuf.String() - assert.Check(t, strings.Contains(outputs, - fmt.Sprintf("Event (ID: %s) have been sent.", want.ID()), - )) + msg := fmt.Sprintf("Event (ID: %s) have been sent.", want.ID()) + assert.Equal(t, 1, logs.FilterMessage(msg).Len()) } diff --git a/pkg/cli/target.go b/pkg/cli/target.go index 76a08afe7..a4df95956 100644 --- a/pkg/cli/target.go +++ b/pkg/cli/target.go @@ -4,50 +4,37 @@ import ( "errors" "fmt" "net/url" - "regexp" - clientutil "knative.dev/client/pkg/util" + "knative.dev/client/pkg/flags/sink" "knative.dev/kn-plugin-event/pkg/event" - "knative.dev/pkg/apis" ) var ( - // ErrCantUseBothToURLAndToFlags will be raised if user use both --to and - // --to-url flags. - ErrCantUseBothToURLAndToFlags = errors.New("can't use both --to and --to-url flags") - // ErrUseToURLOrToFlagIsRequired will be raised if user didn't used --to or - // --to-url flags. - ErrUseToURLOrToFlagIsRequired = errors.New("use --to or --to-url flag is required") + // ErrUseToFlagIsRequired will be raised if user hasn't used --to flag. + ErrUseToFlagIsRequired = errors.New("use --to flag is required") // ErrInvalidURLFormat will be raised if given URL is invalid. ErrInvalidURLFormat = errors.New("invalid URL format") - // ErrInvalidToFormat will be raised if given addressable doesn't have valid + // ErrInvalidToFormat will be raised if given addressable doesn't have a valid // expected format. - ErrInvalidToFormat = errors.New("--to flag needs to be in format " + - "kind:apiVersion:name for named resources or " + - "kind:apiVersion:labelKey1=value1,labelKey2=value2 for matching via " + - "a label selector") + ErrInvalidToFormat = errors.New("--to flag has invalid format") ) // ValidateTarget will perform validation on App element of target. func ValidateTarget(args *TargetArgs) error { - if args.URL == "" && args.Addressable == "" { - return ErrUseToURLOrToFlagIsRequired + if args.Sink == "" { + return ErrUseToFlagIsRequired } - if args.URL != "" && args.Addressable != "" { - return ErrCantUseBothToURLAndToFlags + ref, err := sink.Parse(args.Sink, "default", sink.ComputeWithDefaultMappings(nil)) + if err != nil { + return fmt.Errorf("%w: %s", ErrInvalidToFormat, args.Sink) } - if args.URL != "" { - _, err := url.ParseRequestURI(args.URL) - if err != nil { - return fmt.Errorf("--to-url %w: %s", ErrInvalidURLFormat, err.Error()) + if ref.Type() == sink.TypeReference { + if ref.Name == "" { + return fmt.Errorf("%w: %s", ErrInvalidToFormat, args.Sink) } } - if args.Addressable != "" { - // ref: https://regex101.com/r/TcxsLO/3 - r := regexp.MustCompile("([a-zA-Z0-9]+):([a-zA-Z0-9/.]+):([a-zA-Z0-9=,_-]+)") - if !r.MatchString(args.Addressable) { - return ErrInvalidToFormat - } + if ref.Type() == sink.TypeURL && !isValidAbsURL(args.Sink) { + return fmt.Errorf("%w: %s", ErrInvalidURLFormat, args.Sink) } return validateAddressableURI(args.AddressableURI) } @@ -56,59 +43,40 @@ func validateAddressableURI(uri string) error { if len(uri) > 0 { _, err := url.ParseRequestURI(uri) if err != nil { - return fmt.Errorf("--addressable-uri %w: %s", ErrInvalidURLFormat, err.Error()) + return fmt.Errorf("--addressable-uri %s: %w: %w", + uri, ErrInvalidURLFormat, err) } } return nil } -func (a *App) createTarget(args TargetArgs, props *event.Properties) (*event.Target, error) { - if args.Addressable != "" { - args, err := a.fillInDefaultNamespace(args, props) - if err != nil { - return nil, err - } - ref, err := clientutil.ToTrackerReference(args.Addressable, args.Namespace) - if err != nil { - return nil, fmt.Errorf("%w: %s", ErrInvalidToFormat, err.Error()) - } - uri := &apis.URL{Path: args.AddressableURI} +func (a *App) createTarget(args TargetArgs, params *Params) (*event.Target, error) { + mappings := sink.ComputeWithDefaultMappings(nil) + if ref, err := sink.Parse(args.Sink, "default", mappings); err == nil && ref.Type() == sink.TypeURL { + // a special case to avoid K8s connection if unnecessary return &event.Target{ - Type: event.TargetTypeAddressable, - AddressableVal: &event.AddressableSpec{ - Reference: ref, - URI: uri, - SenderNamespace: args.SenderNamespace, - }, - Properties: props, + Reference: ref, + RelativeURI: args.AddressableURI, }, nil } - if args.URL != "" { - u, err := url.Parse(args.URL) - if err != nil { - return nil, fmt.Errorf("--to-url %w: %s", ErrInvalidURLFormat, err.Error()) - } - return &event.Target{ - Type: event.TargetTypeReachable, - URLVal: u, - Properties: props, - }, nil + var namespace string + clients, kerr := a.Binding.NewKubeClients(params.Parse()) + if kerr != nil { + return nil, kerr } - return nil, ErrUseToURLOrToFlagIsRequired -} + namespace = clients.Namespace() -func (a *App) fillInDefaultNamespace(args TargetArgs, props *event.Properties) (TargetArgs, error) { - if len(args.Namespace) == 0 || len(args.SenderNamespace) == 0 { - defaultNs, err := a.DefaultNamespace(props) - if err != nil { - return TargetArgs{}, cantSentEvent(err) - } - if len(args.Namespace) == 0 { - args.Namespace = defaultNs - } - if len(args.SenderNamespace) == 0 { - args.SenderNamespace = defaultNs - } + ref, err := sink.Parse(args.Sink, namespace, mappings) + if err != nil { + return nil, err } - return args, nil + return &event.Target{ + Reference: ref, + RelativeURI: args.AddressableURI, + }, nil +} + +func isValidAbsURL(uri string) bool { + u, err := url.Parse(uri) + return err == nil && u.Host != "" && u.IsAbs() } diff --git a/pkg/cli/target_test.go b/pkg/cli/target_test.go index 35c584393..4a97574a2 100644 --- a/pkg/cli/target_test.go +++ b/pkg/cli/target_test.go @@ -10,57 +10,49 @@ import ( func TestValidateTarget(t *testing.T) { tests := []struct { name string - args *cli.TargetArgs + args cli.TargetArgs wantErr error }{{ name: "empty is invalid", - args: &cli.TargetArgs{}, - wantErr: cli.ErrUseToURLOrToFlagIsRequired, + wantErr: cli.ErrUseToFlagIsRequired, }, { name: "valid URL", - args: &cli.TargetArgs{ - URL: "http://example.org", + args: cli.TargetArgs{ + Sink: "https://example.org", AddressableURI: "/", }, wantErr: nil, }, { name: "invalid URL", - args: &cli.TargetArgs{ - URL: "foo.html", + args: cli.TargetArgs{ + Sink: "https://", }, wantErr: cli.ErrInvalidURLFormat, }, { name: "invalid addressable URI", - args: &cli.TargetArgs{ - URL: "http://example.org", + args: cli.TargetArgs{ + Sink: "https://example.org", AddressableURI: "This is not an URI", }, wantErr: cli.ErrInvalidURLFormat, }, { name: "valid addressable", - args: &cli.TargetArgs{ - Addressable: "service:serving.knative.dev/v1:showcase", + args: cli.TargetArgs{ + Sink: "service:serving.knative.dev/v1:showcase", AddressableURI: "/", }, wantErr: nil, }, { - name: "invalid addressable", - args: &cli.TargetArgs{ - Addressable: "service::showcase", + name: "invalid sink", + args: cli.TargetArgs{ + Sink: "service::showcase", AddressableURI: "/", }, wantErr: cli.ErrInvalidToFormat, - }, { - name: "both URL and addressable aren't valid", - args: &cli.TargetArgs{ - URL: "https://example.org/", - Addressable: "service:serving.knative.dev/v1:showcase", - }, - wantErr: cli.ErrCantUseBothToURLAndToFlags, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := cli.ValidateTarget(tt.args); !errors.Is(err, tt.wantErr) { + if err := cli.ValidateTarget(&tt.args); !errors.Is(err, tt.wantErr) { t.Errorf("ValidateTarget():\n error = %#v\n wantErr = %#v", err, tt.wantErr) } }) diff --git a/pkg/cli/types.go b/pkg/cli/types.go index bea589e4a..e912c7c5f 100644 --- a/pkg/cli/types.go +++ b/pkg/cli/types.go @@ -3,19 +3,20 @@ package cli import ( "github.com/thediveo/enumflag" "knative.dev/kn-plugin-event/pkg/event" - "knative.dev/kn-plugin-event/pkg/system" + "knative.dev/kn-plugin-event/pkg/k8s" ) -// Options holds a general args for all commands. -type Options struct { - event.KnPluginOptions +// Params holds a general args for all commands. +type Params struct { + // OutputMode define the type of output commands should be producing. + OutputMode - // Output define type of output commands should be producing. - Output OutputMode - - // Verbose tells does commands should display additional information about + // Verbose tells should commands display additional information about // what's happening? Verbose information is printed on stderr. Verbose bool + + // Kubernetes related parameters. + k8s.Params } // EventArgs holds args of event to be created with. @@ -29,11 +30,8 @@ type EventArgs struct { // TargetArgs holds args specific for even sending. type TargetArgs struct { - URL string - Addressable string - Namespace string - SenderNamespace string - AddressableURI string + Sink string + AddressableURI string } // OutputMode is type of output to produce. @@ -49,5 +47,4 @@ const ( // App object. type App struct { event.Binding - system.Environment } diff --git a/pkg/configuration/cli.go b/pkg/configuration/cli.go deleted file mode 100644 index cd811a14d..000000000 --- a/pkg/configuration/cli.go +++ /dev/null @@ -1,15 +0,0 @@ -package configuration - -import ( - "knative.dev/kn-plugin-event/pkg/cli" - "knative.dev/kn-plugin-event/pkg/system" -) - -// CreateCli creates the configured cli.App to work with. -func CreateCli(env system.Environment) *cli.App { - binding := senderBinding() - return &cli.App{ - Binding: eventsBinding(binding), - Environment: env, - } -} diff --git a/pkg/configuration/defaults.go b/pkg/configuration/defaults.go deleted file mode 100644 index 9d947ae58..000000000 --- a/pkg/configuration/defaults.go +++ /dev/null @@ -1,22 +0,0 @@ -package configuration - -import ( - "knative.dev/kn-plugin-event/pkg/event" - "knative.dev/kn-plugin-event/pkg/k8s" - "knative.dev/kn-plugin-event/pkg/sender" -) - -func senderBinding() sender.Binding { - return sender.Binding{ - CreateKubeClients: memoizeKubeClients(k8s.CreateKubeClient), - CreateJobRunner: k8s.CreateJobRunner, - CreateAddressResolver: k8s.CreateAddressResolver, - } -} - -func eventsBinding(binding sender.Binding) event.Binding { - return event.Binding{ - CreateSender: binding.New, - DefaultNamespace: binding.DefaultNamespace, - } -} diff --git a/pkg/configuration/ics.go b/pkg/configuration/ics.go deleted file mode 100644 index bbb4f1b6b..000000000 --- a/pkg/configuration/ics.go +++ /dev/null @@ -1,15 +0,0 @@ -package configuration - -import ( - "knative.dev/kn-plugin-event/pkg/cli/ics" - "knative.dev/kn-plugin-event/pkg/system" -) - -// CreateIcs creates the configured ics.App to work with. -func CreateIcs(env system.Environment) *ics.App { - binding := senderBinding() - return &ics.App{ - Binding: eventsBinding(binding), - Environment: env, - } -} diff --git a/pkg/configuration/memoized.go b/pkg/configuration/memoized.go deleted file mode 100644 index 4aa24dd8f..000000000 --- a/pkg/configuration/memoized.go +++ /dev/null @@ -1,29 +0,0 @@ -package configuration - -import ( - "knative.dev/kn-plugin-event/pkg/event" - "knative.dev/kn-plugin-event/pkg/k8s" - "knative.dev/kn-plugin-event/pkg/sender" -) - -func memoizeKubeClients(delegate sender.CreateKubeClients) sender.CreateKubeClients { - mem := kubeClientsMemoizer{delegate: delegate} - return mem.computeClients -} - -type kubeClientsMemoizer struct { - delegate sender.CreateKubeClients - result k8s.Clients -} - -func (m *kubeClientsMemoizer) computeClients(props *event.Properties) (k8s.Clients, error) { - if m.result != nil { - return m.result, nil - } - cl, err := m.delegate(props) - if err != nil { - return nil, err - } - m.result = cl - return m.result, nil -} diff --git a/pkg/event/constants.go b/pkg/event/constants.go index 48b76fd32..1a84263f5 100644 --- a/pkg/event/constants.go +++ b/pkg/event/constants.go @@ -9,7 +9,7 @@ import ( ) const ( - // DefaultType holds a default type for a event. + // DefaultType holds a default type for an event. DefaultType = "dev.knative.cli.plugin.event.generic" ) diff --git a/pkg/event/sender.go b/pkg/event/sender.go index 8302be6c9..34a9b135a 100644 --- a/pkg/event/sender.go +++ b/pkg/event/sender.go @@ -1,33 +1,52 @@ package event import ( + "context" "errors" "fmt" cloudevents "github.com/cloudevents/sdk-go/v2" + "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") +// Sender will send event to specified target. +type Sender interface { + // Send will send cloudevents.Event to configured target, or return an error + // if one occurs. + Send(ctx context.Context, ce cloudevents.Event) error +} + +// CreateSender creates a Sender. +type CreateSender func(cfg *k8s.Configurator, target *Target) (Sender, error) + +// Binding holds injectable dependencies. +type Binding struct { + CreateSender + k8s.NewKubeClients +} + // NewSender will create a sender that can send event to cluster. -func (b Binding) NewSender(target *Target) (Sender, error) { - sender, err := b.CreateSender(target) +func (b Binding) NewSender(cfg *k8s.Configurator, target *Target) (Sender, error) { + sndr, err := b.CreateSender(cfg, target) if err != nil { return nil, err } - return &sendLogic{Sender: sender, Properties: target.Properties}, nil + return &sendLogic{Sender: sndr}, nil } type sendLogic struct { Sender - *Properties } -func (l *sendLogic) Send(ce cloudevents.Event) error { - err := l.Sender.Send(ce) +func (l *sendLogic) Send(ctx context.Context, ce cloudevents.Event) error { + err := l.Sender.Send(ctx, ce) + log := logging.FromContext(ctx) if err == nil { - l.Log.Infof("Event (ID: %s) have been sent.", ce.ID()) + log.Infof("Event (ID: %s) have been sent.", ce.ID()) return nil } return cantSentEvent(err) diff --git a/pkg/event/sender_test.go b/pkg/event/sender_test.go index 8c674ded5..c0a1441ca 100644 --- a/pkg/event/sender_test.go +++ b/pkg/event/sender_test.go @@ -1,9 +1,11 @@ package event_test import ( + "context" "errors" "strings" "testing" + "time" cloudevents "github.com/cloudevents/sdk-go/v2" "go.uber.org/zap" @@ -11,59 +13,72 @@ import ( "go.uber.org/zap/zaptest" "gotest.tools/v3/assert" "knative.dev/kn-plugin-event/pkg/event" + "knative.dev/kn-plugin-event/pkg/k8s" + pkglogging "knative.dev/pkg/logging" + "knative.dev/reconciler-test/pkg/logging" ) var errTestError = errors.New("test error") func TestSendingAnEvent(t *testing.T) { - tests := []testCase{ + testCases := []testCase{ passingCase(), failingSend(), } - for i := range tests { - tt := tests[i] + for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - binding := event.Binding{CreateSender: tt.CreateSender} - s, err := binding.NewSender(tt.target) + ctx, cancel := context.WithTimeout(context.TODO(), time.Second) + defer cancel() + ctx = logging.WithTestLogger(ctx, t) + s, err := tt.binding.NewSender(nil, tt.target) if err != nil { if !errors.Is(err, tt.want) { t.Errorf("want: %#v\n got: %#v", tt.want, err) } return } - got := s.Send(tt.ce) + var buf *zaptest.Buffer + buf, ctx = setupLoggingBuffer(ctx) + got := s.Send(ctx, tt.ce) if !errors.Is(got, tt.want) { t.Errorf("want: %#v\n got: %#v", tt.want, got) } if tt.bufTest != nil { - tt.bufTest(t) + tt.bufTest(t, buf) } }) } } -func passingCase() testCase { +func setupLoggingBuffer(ctx context.Context) (*zaptest.Buffer, context.Context) { var buf zaptest.Buffer cfg := zap.NewDevelopmentConfig() enc := zapcore.NewJSONEncoder(cfg.EncoderConfig) log := zap.New(zapcore.NewCore(enc, &buf, cfg.Level)) + ctx = pkglogging.WithLogger(ctx, log.Sugar()) + return &buf, ctx +} + +func passingCase() testCase { ce := cloudevents.NewEvent("1.0") ce.SetID("123456") target := &event.Target{ - Properties: &event.Properties{ - Log: log.Sugar(), - }, + Reference: nil, + RelativeURI: "", } return testCase{ - bufTest: func(t *testing.T) { + bufTest: func(t *testing.T, buf *zaptest.Buffer) { t.Helper() + text := buf.String() assert.Check(t, strings.Contains(text, "Event (ID: 123456) have been sent.")) }, - name: "passing", - ce: ce, - CreateSender: stubSenderFactory, - target: target, + name: "passing", + ce: ce, + binding: event.Binding{ + CreateSender: stubSenderFactory, + }, + target: target, } } @@ -71,27 +86,29 @@ func failingSend() testCase { return testCase{ name: "failingSend", want: errTestError, - CreateSender: func(target *event.Target) (event.Sender, error) { - return nil, errTestError + binding: event.Binding{ + CreateSender: func(_ *k8s.Configurator, _ *event.Target) (event.Sender, error) { + return nil, errTestError + }, }, } } type stubSender struct{} -func (m *stubSender) Send(_ cloudevents.Event) error { +func (m *stubSender) Send(context.Context, cloudevents.Event) error { return nil } -func stubSenderFactory(*event.Target) (event.Sender, error) { +func stubSenderFactory(*k8s.Configurator, *event.Target) (event.Sender, error) { return &stubSender{}, nil } type testCase struct { name string - bufTest func(t *testing.T) + bufTest func(t *testing.T, buf *zaptest.Buffer) target *event.Target ce cloudevents.Event want error - event.CreateSender + binding event.Binding } diff --git a/pkg/event/spec.go b/pkg/event/spec.go new file mode 100644 index 000000000..caa6bb6b5 --- /dev/null +++ b/pkg/event/spec.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 event + +// Spec holds specification of event to be created. +type Spec struct { + Type string + ID string + Source string + Fields []FieldSpec +} + +// FieldSpec holds a specification of a event's data field. +type FieldSpec struct { + Path string + Value interface{} +} diff --git a/pkg/event/target.go b/pkg/event/target.go new file mode 100644 index 000000000..ababfa814 --- /dev/null +++ b/pkg/event/target.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 event + +import "knative.dev/client/pkg/flags/sink" + +// Target represents the endpoint to which the event should be sent. +type Target struct { + *sink.Reference + RelativeURI string +} diff --git a/pkg/event/types.go b/pkg/event/types.go deleted file mode 100644 index 052d402ae..000000000 --- a/pkg/event/types.go +++ /dev/null @@ -1,92 +0,0 @@ -package event - -import ( - "net/url" - - cloudevents "github.com/cloudevents/sdk-go/v2" - "go.uber.org/zap" - "knative.dev/pkg/apis" - "knative.dev/pkg/tracker" -) - -// Spec holds specification of event to be created. -type Spec struct { - Type string - ID string - Source string - Fields []FieldSpec -} - -// FieldSpec holds a specification of a event's data field. -type FieldSpec struct { - Path string - Value interface{} -} - -// TargetType specify a type of a event target. -type TargetType int - -const ( - // TargetTypeReachable specify a type of event target that is network - // reachable, and direct HTTP communication can be performed. - TargetTypeReachable TargetType = iota - - // TargetTypeAddressable represent a type of event target that is cluster - // private, and direct communication can't be performed. In this case in - // cluster sender Job will be created to send the event. - TargetTypeAddressable -) - -// AddressableSpec specify destination of a event to be sent, as well as sender -// namespace that should be used to create a sender Job in. -type AddressableSpec struct { - *tracker.Reference - URI *apis.URL - SenderNamespace string -} - -// Target is a target to send event to. -type Target struct { - Type TargetType - URLVal *url.URL - AddressableVal *AddressableSpec - *Properties -} - -// KubeconfigOptions holds options for Kubernetes Client. -type KubeconfigOptions struct { - Path string - Context string - Cluster string -} - -// KnPluginOptions holds options inherited to every Kn plugin. -type KnPluginOptions struct { - KubeconfigOptions -} - -// Properties holds a general properties. -type Properties struct { - KnPluginOptions - Log *zap.SugaredLogger -} - -// Sender will send event to specified target. -type Sender interface { - // Send will send cloudevents.Event to configured target, or return an error - // if one occur. - Send(ce cloudevents.Event) error -} - -// CreateSender creates a Sender. -type CreateSender func(target *Target) (Sender, error) - -// DefaultNamespace returns a default namespace for connected K8s cluster or -// error is namespace can't be determined. -type DefaultNamespace func(props *Properties) (string, error) - -// Binding holds injectable dependencies. -type Binding struct { - CreateSender - DefaultNamespace -} diff --git a/pkg/cli/ics/encoding.go b/pkg/ics/encoding.go similarity index 100% rename from pkg/cli/ics/encoding.go rename to pkg/ics/encoding.go diff --git a/pkg/cli/ics/encoding_test.go b/pkg/ics/encoding_test.go similarity index 93% rename from pkg/cli/ics/encoding_test.go rename to pkg/ics/encoding_test.go index ca9697d0e..19a00f93b 100644 --- a/pkg/cli/ics/encoding_test.go +++ b/pkg/ics/encoding_test.go @@ -6,7 +6,7 @@ import ( cloudevents "github.com/cloudevents/sdk-go/v2" "gotest.tools/v3/assert" - "knative.dev/kn-plugin-event/pkg/cli/ics" + "knative.dev/kn-plugin-event/pkg/ics" ) func TestEncodeDecode(t *testing.T) { diff --git a/pkg/cli/ics/send.go b/pkg/ics/send.go similarity index 68% rename from pkg/cli/ics/send.go rename to pkg/ics/send.go index 28b4dea0a..746658d89 100644 --- a/pkg/cli/ics/send.go +++ b/pkg/ics/send.go @@ -1,33 +1,36 @@ package ics import ( + "context" "fmt" - "net/url" 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/event" + "knative.dev/kn-plugin-event/pkg/k8s" + "knative.dev/pkg/apis" "knative.dev/pkg/logging" ) // SendFromEnv will send an event based on a values stored in environmental // variables. -func (app *App) SendFromEnv() error { - c, err := app.configure() +func (app *App) SendFromEnv(ctx context.Context, cfg *k8s.Configurator) error { + c, err := app.configure(cfg) if err != nil { return err } - err = c.sender.Send(*c.ce) + err = c.sender.Send(ctx, *c.ce) if err != nil { return fmt.Errorf("%w: %w", ErrCantSendWithICS, err) } - log := logging.FromContext(app.Context()) + log := logging.FromContext(ctx) log.Infow("Event sent", zap.String("ce-id", c.ce.ID())) return nil } -func (app *App) configure() (config, error) { +func (app *App) configure(cfg *k8s.Configurator) (config, error) { args := &Args{ Sink: "localhost", } @@ -35,15 +38,14 @@ func (app *App) configure() (config, error) { if err != nil { return config{}, fmt.Errorf("%w: %w", ErrCantConfigureICS, err) } - u, err := url.Parse(args.Sink) + u, err := apis.ParseURL(args.Sink) if err != nil { return config{}, fmt.Errorf("%w: %w", ErrCantConfigureICS, err) } target := &event.Target{ - Type: event.TargetTypeReachable, - URLVal: u, + Reference: &sink.Reference{URL: u}, } - s, err := app.Binding.CreateSender(target) + s, err := app.Binding.CreateSender(cfg, target) if err != nil { return config{}, fmt.Errorf("%w: %w", ErrCantConfigureICS, err) } diff --git a/pkg/cli/ics/send_test.go b/pkg/ics/send_test.go similarity index 58% rename from pkg/cli/ics/send_test.go rename to pkg/ics/send_test.go index 5a53b27a3..97f455b72 100644 --- a/pkg/cli/ics/send_test.go +++ b/pkg/ics/send_test.go @@ -6,12 +6,14 @@ import ( "time" cloudevents "github.com/cloudevents/sdk-go/v2" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" "gotest.tools/v3/assert" - "knative.dev/kn-plugin-event/pkg/cli/ics" "knative.dev/kn-plugin-event/pkg/event" - "knative.dev/kn-plugin-event/pkg/system" + "knative.dev/kn-plugin-event/pkg/ics" + "knative.dev/kn-plugin-event/pkg/k8s" "knative.dev/kn-plugin-event/pkg/tests" - "knative.dev/reconciler-test/pkg/logging" + "knative.dev/pkg/logging" ) func TestSendFromEnv(t *testing.T) { @@ -23,19 +25,19 @@ func TestSendFromEnv(t *testing.T) { kevent, err := ics.Encode(want) assert.NilError(t, err) sender := &tests.Sender{} - env := map[string]string{ - "K_SINK": "http://cosmos.custer.local", - "K_EVENT": kevent, - } + t.Setenv("K_SINK", "http://cosmos.custer.local") + t.Setenv("K_EVENT", kevent) app := ics.App{ Binding: event.Binding{ - CreateSender: func(target *event.Target) (event.Sender, error) { + CreateSender: func(*k8s.Configurator, *event.Target) (event.Sender, error) { return sender, nil }, }, - Environment: system.WithContext(logging.WithTestLogger(context.TODO(), t), nil), } - err = tests.WithEnviron(env, app.SendFromEnv) + cfg := &k8s.Configurator{} + log := zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)) + ctx := logging.WithLogger(context.TODO(), log.Sugar()) + err = app.SendFromEnv(ctx, cfg) assert.NilError(t, err) assert.Equal(t, 1, len(sender.Sent)) got := sender.Sent[0] diff --git a/pkg/cli/ics/types.go b/pkg/ics/types.go similarity index 66% rename from pkg/cli/ics/types.go rename to pkg/ics/types.go index ea2925fd4..1356ca775 100644 --- a/pkg/cli/ics/types.go +++ b/pkg/ics/types.go @@ -4,17 +4,16 @@ import ( "errors" "knative.dev/kn-plugin-event/pkg/event" - "knative.dev/kn-plugin-event/pkg/system" ) var ( - // ErrCouldntEncode is returned when problem occur while trying to encode an - // event. + // ErrCouldntEncode is returned when the problem occurs while trying to encode + // an event. ErrCouldntEncode = errors.New("couldn't encode an event") - // ErrCouldntDecode is returned when problem occur while trying to decode an - // event. + // ErrCouldntDecode is returned when the problem occurs while trying to + // decode an event. ErrCouldntDecode = errors.New("couldn't decode an event") - // ErrCantConfigureICS is returned when problem occur while trying to + // 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. @@ -31,5 +30,4 @@ type Args struct { // App holds an ICS app binding. type App struct { event.Binding - system.Environment } diff --git a/pkg/k8s/addressresolver.go b/pkg/k8s/addressresolver.go index 2935a1a54..af8696aac 100644 --- a/pkg/k8s/addressresolver.go +++ b/pkg/k8s/addressresolver.go @@ -4,11 +4,12 @@ import ( "context" "fmt" "net/url" + "path" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "knative.dev/client/pkg/dynamic" + "knative.dev/client/pkg/flags/sink" "knative.dev/pkg/apis" duckv1 "knative.dev/pkg/apis/duck/v1" "knative.dev/pkg/client/injection/ducks/duck/v1/addressable" @@ -22,85 +23,58 @@ import ( // ReferenceAddressResolver will resolve the tracker.Reference to an url.URL, or // return an error. type ReferenceAddressResolver interface { - ResolveAddress(ref *tracker.Reference, uri *apis.URL) (*url.URL, error) + ResolveAddress(ctx context.Context, ref *sink.Reference, relativeURI string) (*url.URL, error) } -// CreateAddressResolver will create ReferenceAddressResolver, or return an +// NewAddressResolver will create ReferenceAddressResolver or return an // error. -func CreateAddressResolver(kube Clients) ReferenceAddressResolver { - ctx := ctxWithDynamic(kube) +func NewAddressResolver(kube Clients) ReferenceAddressResolver { return &addressResolver{ - kube: kube, ctx: addressable.WithDuck(ctx), + kube: kube, } } type addressResolver struct { kube Clients - ctx context.Context } // ResolveAddress of a tracker.Reference with given uri (as apis.URL). func (a *addressResolver) ResolveAddress( - ref *tracker.Reference, - uri *apis.URL, + ctx context.Context, ref *sink.Reference, relativeURI string, ) (*url.URL, error) { - gvr := a.toGVR(ref) - dest, err := a.toDestination(gvr, ref, uri) + dest, err := ref.Resolve(ctx, a.knclients()) if err != nil { return nil, err } - parent := toAccessor(ref) - tr := tracker.New(noopCallback, controller.GetTrackerLease(a.ctx)) - r := resolver.NewURIResolverFromTracker(a.ctx, tr) - u, err := r.URIFromDestinationV1(a.ctx, *dest, parent) + if dest.URI != nil { + return relativize(dest.URI, relativeURI), nil + } + parent := toAccessor(dest.Ref) + ctx = context.WithValue(ctx, dynamicclient.Key{}, a.kube.Dynamic()) + ctx = addressable.WithDuck(ctx) + tr := tracker.New(noopCallback, controller.GetTrackerLease(ctx)) + r := resolver.NewURIResolverFromTracker(ctx, tr) + u, err := r.URIFromDestinationV1(ctx, *dest, parent) if err != nil { return nil, fmt.Errorf("%w: %w", ErrNotAddressable, err) } - resolved := u.URL() - return resolved, nil + return relativize(u, relativeURI), nil } -func (a *addressResolver) toDestination( - gvr schema.GroupVersionResource, - ref *tracker.Reference, - uri *apis.URL, -) (*duckv1.Destination, error) { - dest := &duckv1.Destination{ - Ref: &duckv1.KReference{ - Kind: ref.Kind, - Namespace: ref.Namespace, - Name: ref.Name, - APIVersion: ref.APIVersion, - }, - URI: uri, - } - if ref.Selector != nil { - list, err := a.kube.Dynamic().Resource(gvr). - Namespace(ref.Namespace).List(a.ctx, metav1.ListOptions{ - LabelSelector: ref.Selector.String(), - }) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrNotFound, err) - } - count := len(list.Items) - if count == 0 { - return nil, ErrNotFound - } - if count > 1 { - return nil, fmt.Errorf("%w: %d", ErrMoreThenOneFound, count) - } - dest.Ref.Name = list.Items[0].GetName() +func relativize(uri *apis.URL, relativeURI string) *url.URL { + if relativeURI == "" { + return uri.URL() } - return dest, nil + u := uri.URL() + u.Path = path.Clean(path.Join(u.Path, relativeURI)) + return u } -func (a *addressResolver) toGVR(ref *tracker.Reference) schema.GroupVersionResource { - gvk := ref.GroupVersionKind() - gvr := apis.KindToResource(gvk) - return gvr +func (a *addressResolver) knclients() dynamic.KnDynamicClient { + return dynamic.NewKnDynamicClient(a.kube.Dynamic(), a.kube.Namespace()) } -func toAccessor(ref *tracker.Reference) kmeta.Accessor { +func toAccessor(ref *duckv1.KReference) kmeta.Accessor { return &unstructured.Unstructured{Object: map[string]interface{}{ "apiVersion": ref.APIVersion, "kind": ref.Kind, @@ -111,9 +85,5 @@ func toAccessor(ref *tracker.Reference) kmeta.Accessor { }} } -func ctxWithDynamic(kube Clients) context.Context { - return context.WithValue(kube.Context(), dynamicclient.Key{}, kube.Dynamic()) -} - func noopCallback(_ types.NamespacedName) { } diff --git a/pkg/k8s/addressresolver_test.go b/pkg/k8s/addressresolver_test.go index 04e925884..42b70adfe 100644 --- a/pkg/k8s/addressresolver_test.go +++ b/pkg/k8s/addressresolver_test.go @@ -1,23 +1,29 @@ package k8s_test import ( + "context" "testing" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" clienttest "knative.dev/client/pkg/util/test" "knative.dev/kn-plugin-event/pkg/k8s" k8stest "knative.dev/kn-plugin-event/pkg/k8s/test" "knative.dev/kn-plugin-event/pkg/tests" + "knative.dev/pkg/logging" ) func TestResolveAddress(t *testing.T) { ns := clienttest.NextNamespace() - k8stest.ResolveAddressTestCases(ns, func(tc k8stest.ResolveAddressTestCase) { + for _, tc := range k8stest.ResolveAddressTestCases(ns) { t.Run(tc.Name, func(t *testing.T) { - k8stest.EnsureResolveAddress(t, tc, func() (k8s.Clients, func(tb testing.TB)) { + log := zaptest.NewLogger(t, zaptest.Level(zapcore.WarnLevel)) + ctx := logging.WithLogger(context.TODO(), log.Sugar()) + k8stest.EnsureResolveAddress(ctx, t, tc, func() (k8s.Clients, func(tb testing.TB)) { return fakeClients(t, tc), noCleanup }) }) - }) + } } func noCleanup(tb testing.TB) { diff --git a/pkg/k8s/errors.go b/pkg/k8s/errors.go index 1ca84639c..4e227512b 100644 --- a/pkg/k8s/errors.go +++ b/pkg/k8s/errors.go @@ -6,15 +6,9 @@ var ( // ErrInvalidReference if given reference is invalid. ErrInvalidReference = errors.New("reference is invalid") - // ErrNotFound if given reference do not point to any resource. - ErrNotFound = errors.New("resource not found") - // ErrNotAddressable if found resource isn't addressable. ErrNotAddressable = errors.New("resource isn't addressable") - // ErrMoreThenOneFound if more then one resource has been found. - ErrMoreThenOneFound = errors.New("more then one resource has been found") - // ErrUnexcpected if something unexpected actually has happened. ErrUnexcpected = errors.New("something unexpected actually has happened") diff --git a/pkg/k8s/jobrunner.go b/pkg/k8s/jobrunner.go index c04f389d1..6a8b62633 100644 --- a/pkg/k8s/jobrunner.go +++ b/pkg/k8s/jobrunner.go @@ -1,6 +1,7 @@ package k8s import ( + "context" "fmt" "sync" @@ -11,11 +12,11 @@ import ( // JobRunner will launch a Job and monitor it for completion. type JobRunner interface { - Run(job *batchv1.Job) error + Run(ctx context.Context, job *batchv1.Job) error } -// CreateJobRunner will create a JobRunner, or return an error. -func CreateJobRunner(kube Clients) JobRunner { +// NewJobRunner will create a JobRunner, or return an error. +func NewJobRunner(kube Clients) JobRunner { return &jobRunner{ kube: kube, } @@ -31,13 +32,13 @@ type task struct { wg *sync.WaitGroup } -func (j *jobRunner) Run(job *batchv1.Job) error { +func (j *jobRunner) Run(ctx context.Context, job *batchv1.Job) error { ready := make(chan bool) errs := make(chan error) tsk := task{ errs, ready, &sync.WaitGroup{}, } - tasks := []func(*batchv1.Job, task){ + tasks := []func(context.Context, *batchv1.Job, task){ // wait is started first, making sure to capture success, even the ultra-fast one. j.waitForSuccess, j.createJob, @@ -45,7 +46,7 @@ func (j *jobRunner) Run(job *batchv1.Job) error { tsk.wg.Add(len(tasks)) // run all tasks in parallel for _, fn := range tasks { - go fn(job, tsk) + go fn(ctx, job, tsk) <-ready } go waitAndClose(tsk) @@ -56,13 +57,12 @@ func (j *jobRunner) Run(job *batchv1.Job) error { } } - return j.deleteJob(job) + return j.deleteJob(ctx, job) } -func (j *jobRunner) createJob(job *batchv1.Job, tsk task) { +func (j *jobRunner) createJob(ctx context.Context, job *batchv1.Job, tsk task) { defer tsk.wg.Done() tsk.ready <- true - ctx := j.kube.Context() jobs := j.kube.Typed().BatchV1().Jobs(job.Namespace) _, err := jobs.Create(ctx, job, metav1.CreateOptions{}) if err != nil { @@ -70,9 +70,9 @@ func (j *jobRunner) createJob(job *batchv1.Job, tsk task) { } } -func (j *jobRunner) waitForSuccess(job *batchv1.Job, tsk task) { +func (j *jobRunner) waitForSuccess(ctx context.Context, job *batchv1.Job, tsk task) { defer tsk.wg.Done() - err := j.watchJob(job, tsk, func(job *batchv1.Job) (bool, error) { + err := j.watchJob(ctx, job, tsk, func(job *batchv1.Job) (bool, error) { if job.Status.CompletionTime == nil && job.Status.Failed == 0 { return false, nil } @@ -93,8 +93,7 @@ func waitAndClose(tsk task) { close(tsk.errs) } -func (j *jobRunner) deleteJob(job *batchv1.Job) error { - ctx := j.kube.Context() +func (j *jobRunner) deleteJob(ctx context.Context, job *batchv1.Job) error { jobs := j.kube.Typed().BatchV1().Jobs(job.GetNamespace()) policy := metav1.DeletePropagationBackground err := jobs.Delete(ctx, job.GetName(), metav1.DeleteOptions{ @@ -105,7 +104,7 @@ func (j *jobRunner) deleteJob(job *batchv1.Job) error { } pods := j.kube.Typed().CoreV1().Pods(job.GetNamespace()) err = pods.DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("job-name=%s", job.GetName()), + LabelSelector: "job-name=" + job.GetName(), }) if err != nil { return fmt.Errorf("%w: %w", ErrICSenderJobFailed, err) @@ -113,11 +112,15 @@ func (j *jobRunner) deleteJob(job *batchv1.Job) error { return nil } -func (j *jobRunner) watchJob(obj metav1.Object, tsk task, changeFn func(job *batchv1.Job) (bool, error)) error { - ctx := j.kube.Context() +func (j *jobRunner) watchJob( + ctx context.Context, + obj metav1.Object, + tsk task, + changeFn func(job *batchv1.Job) (bool, error), +) error { jobs := j.kube.Typed().BatchV1().Jobs(obj.GetNamespace()) watcher, err := jobs.Watch(ctx, metav1.ListOptions{ - FieldSelector: fmt.Sprintf("metadata.name=%s", obj.GetName()), + FieldSelector: "metadata.name=" + obj.GetName(), }) if err != nil { return fmt.Errorf("%w: %w", ErrICSenderJobFailed, err) diff --git a/pkg/k8s/jobrunner_test.go b/pkg/k8s/jobrunner_test.go index d5a1b017a..75cf0b30d 100644 --- a/pkg/k8s/jobrunner_test.go +++ b/pkg/k8s/jobrunner_test.go @@ -1,11 +1,12 @@ package k8s_test import ( - "fmt" + "context" "math/rand" "strconv" "sync" "testing" + "time" "gotest.tools/v3/assert" batchv1 "k8s.io/api/batch/v1" @@ -19,17 +20,18 @@ import ( func TestJobRunnerRun(t *testing.T) { clients := &tests.FakeClients{TB: t, Objects: make([]runtime.Object, 0)} - runner := k8s.CreateJobRunner(clients) + runner := k8s.NewJobRunner(clients) job := examplePiJob() jobs := clients.Typed().BatchV1().Jobs(job.Namespace) - ctx := clients.Context() + ctx, cancel := context.WithTimeout(context.TODO(), time.Second) + defer cancel() watcher, err := jobs.Watch(ctx, metav1.ListOptions{}) assert.NilError(t, err) wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() - assert.NilError(t, runner.Run(&job)) + assert.NilError(t, runner.Run(ctx, &job)) }() ev := <-watcher.ResultChan() assert.Equal(t, ev.Type, watch.Added) @@ -62,8 +64,8 @@ func jobSuccess(job batchv1.Job) batchv1.Job { } func examplePiJob() batchv1.Job { - name := fmt.Sprintf("test-%s", - strconv.FormatInt(rand.Int63(), 36)) //nolint:gosec + name := "test-" + + strconv.FormatInt(rand.Int63(), 36) //nolint:gosec return batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: name, diff --git a/pkg/k8s/kubeclient.go b/pkg/k8s/kubeclient.go index 7f5e0b0bf..a69958a4c 100644 --- a/pkg/k8s/kubeclient.go +++ b/pkg/k8s/kubeclient.go @@ -1,59 +1,58 @@ package k8s import ( - "context" "errors" "fmt" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" // see: https://github.com/kubernetes/client-go/issues/242 - "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + knk8s "knative.dev/client/pkg/k8s" eventingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1" messagingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1" - "knative.dev/kn-plugin-event/pkg/event" servingv1 "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1" ) -// ErrNoKubernetesConnection if can't connect to Kube API server. +// ErrNoKubernetesConnection if we can't connect to Kube API server. var ErrNoKubernetesConnection = errors.New("no Kubernetes connection") -// CreateKubeClient creates kubernetes.Interface. -func CreateKubeClient(props *event.Properties) (Clients, error) { - cc, err := loadClientConfig(props) +// NewKubeClients creates Clients. +type NewKubeClients func(configurator *Configurator) (Clients, error) + +// Configurator for creating the Kube's clients. +type Configurator struct { + ClientConfig func() (clientcmd.ClientConfig, error) + Namespace *string +} + +// NewClients creates kubernetes clients. +func NewClients(cfg *Configurator) (Clients, error) { + cc, err := loadClientConfig(cfg) if err != nil { return nil, err } - restcfg := cc.Config - typed, err := kubernetes.NewForConfig(restcfg) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) - } - dyn, err := dynamic.NewForConfig(restcfg) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) - } - servingclient, err := servingv1.NewForConfig(restcfg) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) - } - eventingclient, err := eventingv1.NewForConfig(restcfg) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) + restcfg, rerr := cc.ClientConfig.ClientConfig() + if rerr != nil { + return nil, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, rerr) } - messagingclient, err := messagingv1.NewForConfig(restcfg) + typed, err := kubernetes.NewForConfig(restcfg) if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) + return nil, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, err) } + // NOTE: No new error can happen, as all connection related issues are already + // checked above. + dyn, _ := dynamic.NewForConfig(restcfg) + servingclient, _ := servingv1.NewForConfig(restcfg) + eventingclient, _ := eventingv1.NewForConfig(restcfg) + messagingclient, _ := messagingv1.NewForConfig(restcfg) return &clients{ - ctx: context.Background(), - namespace: cc.namespace, - typed: typed, - dynamic: dyn, - serving: servingclient, - eventing: eventingclient, - messaging: messagingclient, + clientConfig: cc, + typed: typed, + dynamic: dyn, + serving: servingclient, + eventing: eventingclient, + messaging: messagingclient, }, nil } @@ -62,29 +61,21 @@ type Clients interface { Namespace() string Typed() kubernetes.Interface Dynamic() dynamic.Interface - Context() context.Context Serving() servingv1.ServingV1Interface Eventing() eventingv1.EventingV1Interface Messaging() messagingv1.MessagingV1Interface } -func loadClientConfig(props *event.Properties) (clientConfig, error) { - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - var configOverrides *clientcmd.ConfigOverrides - if props.Context != "" && props.Cluster != "" { - configOverrides = &clientcmd.ConfigOverrides{} - if props.Context != "" { - configOverrides.CurrentContext = props.Context - } - if props.Cluster != "" { - configOverrides.Context.Cluster = props.Cluster - } +func loadClientConfig(cfg *Configurator) (clientConfig, error) { + if cfg == nil { + return clientConfig{}, fmt.Errorf("%w: no config", ErrNoKubernetesConnection) } - if len(props.Path) > 0 { - loadingRules.ExplicitPath = props.Path + ccFn := cfg.ClientConfig + if ccFn == nil { + kn := knk8s.Params{} + ccFn = kn.GetClientConfig } - cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) - cfg, err := cc.ClientConfig() + cc, err := ccFn() if err != nil { return clientConfig{}, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, err) } @@ -92,17 +83,19 @@ func loadClientConfig(props *event.Properties) (clientConfig, error) { if err != nil { return clientConfig{}, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, err) } - return clientConfig{Config: cfg, namespace: ns}, nil + if cfg.Namespace != nil { + ns = *cfg.Namespace + } + return clientConfig{ClientConfig: cc, namespace: ns}, nil } type clientConfig struct { - *rest.Config + clientcmd.ClientConfig namespace string } type clients struct { - namespace string - ctx context.Context + clientConfig typed kubernetes.Interface dynamic dynamic.Interface serving servingv1.ServingV1Interface @@ -118,10 +111,6 @@ func (c *clients) Dynamic() dynamic.Interface { return c.dynamic } -func (c *clients) Context() context.Context { - return c.ctx -} - func (c *clients) Serving() servingv1.ServingV1Interface { return c.serving } diff --git a/pkg/k8s/kubeclient_test.go b/pkg/k8s/kubeclient_test.go new file mode 100644 index 000000000..89a4bf700 --- /dev/null +++ b/pkg/k8s/kubeclient_test.go @@ -0,0 +1,98 @@ +/* + 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 k8s_test + +import ( + "testing" + + "gotest.tools/v3/assert" + "k8s.io/client-go/tools/clientcmd" + "knative.dev/kn-plugin-event/pkg/k8s" +) + +func TestNewClients(t *testing.T) { + basic, err := clientcmd.NewClientConfigFromBytes([]byte(basicKubeconfig)) + assert.NilError(t, err) + t.Setenv("KUBECONFIG", "/var/blackhole/not-existing.yaml") + tcs := []newClientsTestCase{{ + name: "nil", + wantErr: k8s.ErrNoKubernetesConnection, + }, { + name: "invalid configurator", + config: &k8s.Configurator{}, + wantErr: k8s.ErrNoKubernetesConnection, + }, { + name: "provided configurator", + config: &k8s.Configurator{ + ClientConfig: just(basic), + }, + }} + for _, tc := range tcs { + t.Run(tc.name, tc.test) + } +} + +func just(cc clientcmd.ClientConfig) func() (clientcmd.ClientConfig, error) { + return func() (clientcmd.ClientConfig, error) { + return cc, nil + } +} + +const basicKubeconfig = `apiVersion: v1 +kind: Config +preferences: {} +users: +- name: a + user: + client-certificate-data: "" + client-key-data: "" +clusters: +- name: a + cluster: + insecure-skip-tls-verify: true + server: https://127.0.0.1:8080 +contexts: +- name: a + context: + cluster: a + user: a +current-context: a +` + +type newClientsTestCase struct { + name string + config *k8s.Configurator + wantErr error +} + +func (tc *newClientsTestCase) test(t *testing.T) { + cl, err := k8s.NewClients(tc.config) + if tc.wantErr != nil { + assert.ErrorIs(t, err, tc.wantErr) + } else { + ns := cl.Namespace() + assert.Check(t, ns != "") + if tc.config.Namespace != nil { + assert.Equal(t, *tc.config.Namespace, ns) + } + assert.Check(t, cl.Dynamic() != nil) + assert.Check(t, cl.Eventing() != nil) + assert.Check(t, cl.Messaging() != nil) + assert.Check(t, cl.Serving() != nil) + assert.Check(t, cl.Typed() != nil) + } +} diff --git a/pkg/k8s/params.go b/pkg/k8s/params.go new file mode 100644 index 000000000..e186a2f9a --- /dev/null +++ b/pkg/k8s/params.go @@ -0,0 +1,55 @@ +/* + 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 k8s + +import ( + "github.com/spf13/pflag" + knk8s "knative.dev/client/pkg/k8s" +) + +// Params contain Kubernetes specific params, that CLI should comply to. +type Params struct { + Namespace string + knk8s.Params +} + +// Parse will build k8s.Configurator struct which could be used to initiate +// the k8s.Clients. +func (kp *Params) Parse() *Configurator { + var ns *string + if kp.Namespace != "" { + ns = &kp.Namespace + } + return &Configurator{ + ClientConfig: kp.GetClientConfig, + Namespace: ns, + } +} + +func (kp *Params) SetGlobalFlags(flags *pflag.FlagSet) { + kp.Params.SetFlags(flags) +} + +func (kp *Params) SetCommandFlags(flags *pflag.FlagSet) { + flags.StringVarP( + &kp.Namespace, "namespace", "n", "", + `Specify a namespace of sender job to be created, while event is send +within a cluster. To do that kn-event uses a special Job that is deployed to +cluster in namespace dictated by --namespace. If this option isn't specified +a current context namespace will be used.`, + ) +} diff --git a/pkg/k8s/test/addressresolver_cases.go b/pkg/k8s/test/addressresolver_cases.go index abc899dff..ed2ae1380 100644 --- a/pkg/k8s/test/addressresolver_cases.go +++ b/pkg/k8s/test/addressresolver_cases.go @@ -1,17 +1,21 @@ package test import ( + "context" "errors" "fmt" "net/url" "strings" "testing" + "github.com/gobuffalo/flect" "gotest.tools/v3/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/intstr" + "knative.dev/client/pkg/flags/sink" eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" @@ -19,10 +23,15 @@ import ( "knative.dev/pkg/apis" duckv1 "knative.dev/pkg/apis/duck/v1" "knative.dev/pkg/kmeta" - "knative.dev/pkg/tracker" servingv1 "knative.dev/serving/pkg/apis/serving/v1" ) +func init() { //nolint:gochecknoinits + if !testing.Testing() { + panic("For testing only") + } +} + const ( // HTTPPort is 80. HTTPPort = 80 @@ -36,30 +45,29 @@ var ( ErrDontContain = errors.New("don't contain") ) -func ResolveAddressTestCases(namespace string, casefn func(tc ResolveAddressTestCase)) { - tcs := []ResolveAddressTestCase{ +// ResolveAddressTestCases will return the test cases for resolving the address. +func ResolveAddressTestCases(namespace string) []ResolveAddressTestCase { + return []ResolveAddressTestCase{ k8sService(namespace), knService(namespace), mtBroker(namespace), channel(namespace), } - for _, tc := range tcs { - casefn(tc) - } } // EnsureResolveAddress thelper lint skipped for greater visibility of // failure location. func EnsureResolveAddress( //nolint:thelper + ctx context.Context, tb testing.TB, tc ResolveAddressTestCase, clientsFn func() (k8s.Clients, func(tb testing.TB)), ) { - uri := &apis.URL{} + uri := "" clients, cleanup := clientsFn() defer cleanup(tb) - resolver := k8s.CreateAddressResolver(clients) - u, err := resolver.ResolveAddress(tc.ref, uri) + resolver := k8s.NewAddressResolver(clients) + u, err := resolver.ResolveAddress(ctx, tc.ref, uri) if tc.err != nil { assert.ErrorIs(tb, err, tc.err) } else { @@ -72,7 +80,7 @@ type ResolveAddressTestCase struct { Name string matches func(url *url.URL) error err error - ref *tracker.Reference + ref *sink.Reference Objects []runtime.Object } @@ -191,17 +199,22 @@ func channel(namespace string) ResolveAddressTestCase { } } -func toTrackerRef(accessor kmeta.Accessor) *tracker.Reference { +func toTrackerRef(accessor kmeta.Accessor) *sink.Reference { gvk := accessor.GroupVersionKind() - return &tracker.Reference{ - APIVersion: gvk.GroupVersion().String(), - Kind: gvk.Kind, - Namespace: accessor.GetNamespace(), - Name: accessor.GetName(), - Selector: nil, + return &sink.Reference{ + KubeReference: &sink.KubeReference{ + GVR: toGVR(gvk), + Namespace: accessor.GetNamespace(), + Name: accessor.GetName(), + }, } } +func toGVR(gvk schema.GroupVersionKind) schema.GroupVersionResource { + resource := flect.Pluralize(strings.ToLower(gvk.Kind)) + return gvk.GroupVersion().WithResource(resource) +} + type matcher struct { url *apis.URL name string diff --git a/pkg/k8s/test/addressresolver_cases_test.go b/pkg/k8s/test/addressresolver_cases_test.go new file mode 100644 index 000000000..22cd29110 --- /dev/null +++ b/pkg/k8s/test/addressresolver_cases_test.go @@ -0,0 +1,42 @@ +/* + 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 test_test + +import ( + "context" + "testing" + + "gotest.tools/v3/assert" + "knative.dev/kn-plugin-event/pkg/k8s" + "knative.dev/kn-plugin-event/pkg/k8s/test" + "knative.dev/kn-plugin-event/pkg/tests" +) + +func TestResolveAddressTestCases(t *testing.T) { + tcs := test.ResolveAddressTestCases("default") + assert.Check(t, len(tcs) >= 1) +} + +func TestEnsureResolveAddress(t *testing.T) { + tcs := test.ResolveAddressTestCases("default") + tc := tcs[0] + ctx := context.TODO() + doNothing := func(testing.TB) {} + test.EnsureResolveAddress(ctx, t, tc, func() (k8s.Clients, func(tb testing.TB)) { + return &tests.FakeClients{TB: t, Objects: tc.Objects}, doNothing + }) +} diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 79aa21fa3..e4d966e82 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -5,7 +5,7 @@ import ( "github.com/wavesoftware/go-commandline" knplugin "knative.dev/client/pkg/plugin" - "knative.dev/kn-plugin-event/internal/cli/cmd" + "knative.dev/kn-plugin-event/internal/cli" "knative.dev/kn-plugin-event/pkg/metadata" ) @@ -30,7 +30,7 @@ func (p plugin) Execute(args []string) error { if p.Writer != nil { opts = append(opts, commandline.WithOutput(p.Writer)) } - return commandline.New(new(cmd.App)).Execute(opts...) //nolint:wrapcheck + return commandline.New(new(cli.App)).Execute(opts...) //nolint:wrapcheck } func (p plugin) Description() (string, error) { diff --git a/pkg/sender/binding.go b/pkg/sender/binding.go new file mode 100644 index 000000000..e08c6796a --- /dev/null +++ b/pkg/sender/binding.go @@ -0,0 +1,58 @@ +package sender + +import ( + "errors" + "fmt" + + "knative.dev/client/pkg/flags/sink" + "knative.dev/kn-plugin-event/pkg/event" + "knative.dev/kn-plugin-event/pkg/k8s" +) + +// ErrUnsupportedTargetType is an error if user pass unsupported event target +// type. Only supporting: reachable or reference. +var ErrUnsupportedTargetType = errors.New("unsupported target type") + +// NewJobRunner creates a k8s.JobRunner. +type NewJobRunner func(kube k8s.Clients) k8s.JobRunner + +// NewAddressResolver creates a k8s.ReferenceAddressResolver. +type NewAddressResolver func(kube k8s.Clients) k8s.ReferenceAddressResolver + +// Binding holds injectable dependencies. +type Binding struct { + NewJobRunner + NewAddressResolver + k8s.NewKubeClients +} + +// New creates a new Sender. +func (b *Binding) New(cfg *k8s.Configurator, target *event.Target) (event.Sender, error) { + switch target.Type() { + case sink.TypeURL: + return &directSender{ + url: *target.URL, + }, nil + case sink.TypeReference: + kube, err := b.NewKubeClients(cfg) + if err != nil { + return nil, err + } + jr := b.NewJobRunner(kube) + ar := b.NewAddressResolver(kube) + return &inClusterSender{ + namespace: kube.Namespace(), + target: target, + jobRunner: jr, + addressResolver: ar, + }, nil + } + return nil, fmt.Errorf("%w: %v", ErrUnsupportedTargetType, target.Type()) +} + +func cantSentEvent(err error) error { + if errors.Is(err, event.ErrCantSentEvent) { + return err + } + return fmt.Errorf("%w: %w", event.ErrCantSentEvent, err) +} diff --git a/pkg/sender/create.go b/pkg/sender/create.go deleted file mode 100644 index b287ad4e6..000000000 --- a/pkg/sender/create.go +++ /dev/null @@ -1,38 +0,0 @@ -package sender - -import ( - "errors" - "fmt" - - "knative.dev/kn-plugin-event/pkg/event" -) - -// New creates a new Sender. -func (b *Binding) New(target *event.Target) (event.Sender, error) { - switch target.Type { - case event.TargetTypeReachable: - return &directSender{ - url: *target.URLVal, - }, nil - case event.TargetTypeAddressable: - kube, err := b.CreateKubeClients(target.Properties) - if err != nil { - return nil, err - } - jr := b.CreateJobRunner(kube) - ar := b.CreateAddressResolver(kube) - return &inClusterSender{ - addressable: target.AddressableVal, - jobRunner: jr, - addressResolver: ar, - }, nil - } - return nil, fmt.Errorf("%w: %v", ErrUnsupportedTargetType, target.Type) -} - -func cantSentEvent(err error) error { - if errors.Is(err, event.ErrCantSentEvent) { - return err - } - return fmt.Errorf("%w: %w", event.ErrCantSentEvent, err) -} diff --git a/pkg/sender/direct.go b/pkg/sender/direct.go index b591c9b80..9f737b86e 100644 --- a/pkg/sender/direct.go +++ b/pkg/sender/direct.go @@ -2,23 +2,23 @@ package sender import ( "context" - "net/url" cloudevents "github.com/cloudevents/sdk-go/v2" + "knative.dev/pkg/apis" ) type directSender struct { - url url.URL + url apis.URL } -func (d *directSender) Send(ce cloudevents.Event) error { +func (d *directSender) Send(ctx context.Context, ce cloudevents.Event) error { c, err := cloudevents.NewClientHTTP() if err != nil { return cantSentEvent(err) } // Set a target. - ctx := cloudevents.ContextWithTarget(context.TODO(), d.url.String()) + ctx = cloudevents.ContextWithTarget(ctx, d.url.String()) // Send that Event. err = c.Send(ctx, ce) diff --git a/pkg/sender/direct_test.go b/pkg/sender/direct_test.go index e64ae3171..de53125d1 100644 --- a/pkg/sender/direct_test.go +++ b/pkg/sender/direct_test.go @@ -2,6 +2,7 @@ package sender_test import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -11,9 +12,12 @@ import ( cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/phayes/freeport" + "knative.dev/client/pkg/flags/sink" "knative.dev/kn-plugin-event/pkg/event" + "knative.dev/kn-plugin-event/pkg/k8s" "knative.dev/kn-plugin-event/pkg/sender" "knative.dev/kn-plugin-event/pkg/tests" + "knative.dev/pkg/apis" ) func TestDirectSenderSend(t *testing.T) { @@ -24,12 +28,12 @@ func TestDirectSenderSend(t *testing.T) { for i := range testsCases { tt := testsCases[i] t.Run(tt.name, func(t *testing.T) { - tt.context(func(u url.URL) { + tt.context(func(u apis.URL) { binding := sender.Binding{} - s, err := binding.New(&event.Target{ - Type: event.TargetTypeReachable, - URLVal: &u, - }) + cfg := &k8s.Configurator{} + s, err := binding.New(cfg, &event.Target{Reference: &sink.Reference{ + URL: &u, + }}) if err != nil { t.Error(err) return @@ -40,7 +44,8 @@ func TestDirectSenderSend(t *testing.T) { if tt.validateErr != nil { validateErr = tt.validateErr } - validateErr(s.Send(tt.ce)) + ctx := context.TODO() + validateErr(s.Send(ctx, tt.ce)) }) }) } @@ -64,7 +69,7 @@ func undelivered(t *testing.T) testCase { t.Error(err) return testCase{} } - u, err := url.Parse(fmt.Sprintf("http://localhost:%d/ce-not-supported", port)) + u, err := apis.ParseURL(fmt.Sprintf("http://localhost:%d/ce-not-supported", port)) if err != nil { t.Error(err) return testCase{} @@ -72,7 +77,7 @@ func undelivered(t *testing.T) testCase { return testCase{ name: "undelivered", ce: ce, - context: func(handler func(u url.URL)) { + context: func(handler func(u apis.URL)) { handler(*u) }, validateErr: func(err error) { @@ -99,11 +104,11 @@ func newEvent(id string) cloudevents.Event { return ce } -func sentEventIsValid(t *testing.T, want cloudevents.Event) func(hand func(u url.URL)) { +func sentEventIsValid(t *testing.T, want cloudevents.Event) func(hand func(u apis.URL)) { t.Helper() - return func(hand func(u url.URL)) { + return func(hand func(u apis.URL)) { sent, err := tests.WithCloudEventsServer(func(serverURL url.URL) error { - hand(serverURL) + hand(apis.URL(serverURL)) return nil }) if err != nil { @@ -136,5 +141,5 @@ type testCase struct { name string ce cloudevents.Event validateErr func(error) - context func(func(u url.URL)) + context func(func(u apis.URL)) } diff --git a/pkg/sender/in_cluster.go b/pkg/sender/in_cluster.go index 4f7651bef..b56dde072 100644 --- a/pkg/sender/in_cluster.go +++ b/pkg/sender/in_cluster.go @@ -1,6 +1,7 @@ package sender import ( + "context" "fmt" cloudevents "github.com/cloudevents/sdk-go/v2" @@ -8,8 +9,8 @@ import ( 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/cli/ics" "knative.dev/kn-plugin-event/pkg/event" + ics2 "knative.dev/kn-plugin-event/pkg/ics" "knative.dev/kn-plugin-event/pkg/k8s" "knative.dev/kn-plugin-event/pkg/metadata" ) @@ -17,26 +18,27 @@ import ( const idLength = 16 type inClusterSender struct { - addressable *event.AddressableSpec + namespace string + target *event.Target jobRunner k8s.JobRunner addressResolver k8s.ReferenceAddressResolver } -func (i *inClusterSender) Send(ce cloudevents.Event) error { +func (i *inClusterSender) Send(ctx context.Context, ce cloudevents.Event) error { url, err := i.addressResolver.ResolveAddress( - i.addressable.Reference, i.addressable.URI, + ctx, i.target.Reference, i.target.RelativeURI, ) if err != nil { return fmt.Errorf("%w: %w", k8s.ErrInvalidReference, err) } - kevent, err := ics.Encode(ce) + kevent, err := ics2.Encode(ce) if err != nil { - return fmt.Errorf("%w: %w", ics.ErrCouldntEncode, err) + return fmt.Errorf("%w: %w", ics2.ErrCouldntEncode, err) } job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: newJobName(), - Namespace: i.addressable.SenderNamespace, + Namespace: i.namespace, Labels: map[string]string{ "event-id": ce.ID(), }, @@ -60,14 +62,14 @@ func (i *inClusterSender) Send(ce cloudevents.Event) error { }, }, } - err = i.jobRunner.Run(job) + err = i.jobRunner.Run(ctx, job) if err != nil { - return fmt.Errorf("%w: %w", ics.ErrCantSendWithICS, err) + return fmt.Errorf("%w: %w", ics2.ErrCantSendWithICS, err) } return nil } func newJobName() string { id := rand.String(idLength) - return fmt.Sprintf("kn-event-sender-%s", id) + return "kn-event-sender-" + id } diff --git a/pkg/sender/in_cluster_test.go b/pkg/sender/in_cluster_test.go index 01ae594b7..edd4081a5 100644 --- a/pkg/sender/in_cluster_test.go +++ b/pkg/sender/in_cluster_test.go @@ -1,6 +1,7 @@ package sender_test import ( + "context" "errors" "fmt" "net/url" @@ -10,17 +11,20 @@ import ( cloudevents "github.com/cloudevents/sdk-go/v2" cetest "github.com/cloudevents/sdk-go/v2/test" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" "gotest.tools/v3/assert" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/validation" + "knative.dev/client/pkg/flags/sink" + "knative.dev/eventing/test/rekt/resources/broker" "knative.dev/kn-plugin-event/pkg/event" "knative.dev/kn-plugin-event/pkg/k8s" "knative.dev/kn-plugin-event/pkg/sender" "knative.dev/kn-plugin-event/pkg/tests" - "knative.dev/pkg/apis" - "knative.dev/pkg/tracker" + "knative.dev/pkg/logging" ) const toLongForRFC1123 = 64 @@ -33,29 +37,28 @@ func TestInClusterSenderSend(t *testing.T) { couldResolveAddress(t), idViolatesRFC1123(t), } - for i := range testCases { - tt := testCases[i] + for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - createKubeClient := func(_ *event.Properties) (k8s.Clients, error) { + createKubeClient := func(*k8s.Configurator) (k8s.Clients, error) { return &tests.FakeClients{}, nil } - createJobRunner := func(_ k8s.Clients) k8s.JobRunner { + createJobRunner := func(k8s.Clients) k8s.JobRunner { return tt.fields.jobRunner } - createAddressResolver := func(_ k8s.Clients) k8s.ReferenceAddressResolver { + createAddressResolver := func(k8s.Clients) k8s.ReferenceAddressResolver { return tt.fields.addressResolver } binding := sender.Binding{ - CreateKubeClients: createKubeClient, - CreateJobRunner: createJobRunner, - CreateAddressResolver: createAddressResolver, + NewKubeClients: createKubeClient, + NewJobRunner: createJobRunner, + NewAddressResolver: createAddressResolver, } - s, err := binding.New(&event.Target{ - Type: event.TargetTypeAddressable, - AddressableVal: tt.fields.addressable, - }) + cfg := &k8s.Configurator{} + s, err := binding.New(cfg, &event.Target{Reference: tt.fields.reference}) assert.NilError(t, err) - if err = s.Send(tt.args.ce); !errors.Is(err, tt.err) { + log := zaptest.NewLogger(t, zaptest.Level(zapcore.DebugLevel)) + ctx := logging.WithLogger(context.TODO(), log.Sugar()) + if err = s.Send(ctx, tt.args.ce); !errors.Is(err, tt.err) { t.Errorf("Send() error = %v, wantErr = %v", err, tt.err) } }) @@ -67,10 +70,10 @@ func passingInClusterSenderSend(t *testing.T) inClusterTestCase { return inClusterTestCase{ name: "passing", fields: fields{ - addressable: exampleBrokerAddressableSpec(t), + reference: exampleBrokerReference(t), jobRunner: stubJobRunner(func(job *batchv1.Job) bool { - if sink, ok := envof(job.Spec.Template.Spec.Containers[0].Env, "K_SINK"); ok { - if sink != "default.demo.broker.eventing.dev.cluster.local" { + if snk, ok := envof(job.Spec.Template.Spec.Containers[0].Env, "K_SINK"); ok { + if snk != "default.demo.brokers.cluster.local" { return false } } else { @@ -93,15 +96,15 @@ func passingInClusterSenderSend(t *testing.T) inClusterTestCase { func couldResolveAddress(t *testing.T) inClusterTestCase { t.Helper() sar := stubAddressResolver() - sar.isValid = func(ref *tracker.Reference) error { + sar.isValid = func(*sink.Reference) error { return errExampleValidationFault } return inClusterTestCase{ name: "couldResolveAddress", fields: fields{ - addressable: exampleBrokerAddressableSpec(t), + reference: exampleBrokerReference(t), addressResolver: sar, - jobRunner: stubJobRunner(func(job *batchv1.Job) bool { + jobRunner: stubJobRunner(func(*batchv1.Job) bool { return true }), }, @@ -119,9 +122,9 @@ func idViolatesRFC1123(t *testing.T) inClusterTestCase { return inClusterTestCase{ name: "idViolatesRFC1123", fields: fields{ - addressable: exampleBrokerAddressableSpec(t), + reference: exampleBrokerReference(t), addressResolver: stubAddressResolver(), - jobRunner: fnJobRunner(func(job *batchv1.Job) error { + jobRunner: fnJobRunner(func(_ context.Context, job *batchv1.Job) error { name := job.GetName() errs := validation.IsDNS1035Label(name) if len(errs) > 0 { @@ -153,24 +156,24 @@ func envof(envs []corev1.EnvVar, name string) (string, bool) { return "", false } -type fnJobRunner func(job *batchv1.Job) error +type fnJobRunner func(_ context.Context, job *batchv1.Job) error -func (f fnJobRunner) Run(job *batchv1.Job) error { - return f(job) +func (f fnJobRunner) Run(ctx context.Context, job *batchv1.Job) error { + return f(ctx, job) } type ar struct { - isValid func(ref *tracker.Reference) error + isValid func(ref *sink.Reference) error } -func (a *ar) ResolveAddress(ref *tracker.Reference, _ *apis.URL) (*url.URL, error) { +func (a *ar) ResolveAddress(_ context.Context, ref *sink.Reference, _ string) (*url.URL, error) { if a.isValid != nil { if err := a.isValid(ref); err != nil { return nil, err } } u, err := url.Parse(fmt.Sprintf("%s.%s.%s.cluster.local", - ref.Name, ref.Namespace, ref.Kind)) + ref.Name, ref.Namespace, ref.GVR.Resource)) if err != nil { return nil, fmt.Errorf("bad url: %w", err) } @@ -178,7 +181,7 @@ func (a *ar) ResolveAddress(ref *tracker.Reference, _ *apis.URL) (*url.URL, erro } func stubJobRunner(isValid func(job *batchv1.Job) bool) k8s.JobRunner { - return fnJobRunner(func(job *batchv1.Job) error { + return fnJobRunner(func(_ context.Context, job *batchv1.Job) error { if !isValid(job) { return event.ErrCantSentEvent } @@ -190,13 +193,6 @@ func stubAddressResolver() *ar { return &ar{} } -func uri(t *testing.T, uri string) *apis.URL { - t.Helper() - u, err := apis.ParseURL(uri) - assert.NilError(t, err) - return u -} - func exampleEvent(t *testing.T) cloudevents.Event { t.Helper() e := cloudevents.NewEvent() @@ -216,23 +212,19 @@ func exampleEvent(t *testing.T) cloudevents.Event { return e } -func exampleBrokerAddressableSpec(t *testing.T) *event.AddressableSpec { +func exampleBrokerReference(t *testing.T) *sink.Reference { t.Helper() - return &event.AddressableSpec{ - Reference: &tracker.Reference{ - APIVersion: "betav1", - Kind: "broker.eventing.dev", - Namespace: "demo", - Name: "default", - Selector: nil, + return &sink.Reference{ + KubeReference: &sink.KubeReference{ + GVR: broker.GVR(), + Name: "default", + Namespace: "demo", }, - URI: uri(t, "/"), - SenderNamespace: "default", } } type fields struct { - addressable *event.AddressableSpec + reference *sink.Reference addressResolver k8s.ReferenceAddressResolver jobRunner k8s.JobRunner } diff --git a/pkg/sender/namespace.go b/pkg/sender/namespace.go deleted file mode 100644 index 364bcf24c..000000000 --- a/pkg/sender/namespace.go +++ /dev/null @@ -1,13 +0,0 @@ -package sender - -import "knative.dev/kn-plugin-event/pkg/event" - -// DefaultNamespace returns a default namespace of connected K8s cluster or -// error if such namespace can't be determined. -func (b *Binding) DefaultNamespace(props *event.Properties) (string, error) { - clients, err := b.CreateKubeClients(props) - if err != nil { - return "", err - } - return clients.Namespace(), nil -} diff --git a/pkg/sender/types.go b/pkg/sender/types.go deleted file mode 100644 index e87a5fe50..000000000 --- a/pkg/sender/types.go +++ /dev/null @@ -1,28 +0,0 @@ -package sender - -import ( - "errors" - - "knative.dev/kn-plugin-event/pkg/event" - "knative.dev/kn-plugin-event/pkg/k8s" -) - -// ErrUnsupportedTargetType is an error if user pass unsupported event target -// type. Only supporting: reachable or addressable. -var ErrUnsupportedTargetType = errors.New("unsupported target type") - -// CreateKubeClients creates k8s.Clients. -type CreateKubeClients func(props *event.Properties) (k8s.Clients, error) - -// CreateJobRunner creates a k8s.JobRunner. -type CreateJobRunner func(kube k8s.Clients) k8s.JobRunner - -// CreateAddressResolver creates a k8s.ReferenceAddressResolver. -type CreateAddressResolver func(kube k8s.Clients) k8s.ReferenceAddressResolver - -// Binding holds injectable dependencies. -type Binding struct { - CreateJobRunner - CreateAddressResolver - CreateKubeClients -} diff --git a/pkg/system/environment.go b/pkg/system/environment.go deleted file mode 100644 index dd5d0d92f..000000000 --- a/pkg/system/environment.go +++ /dev/null @@ -1,67 +0,0 @@ -package system - -import ( - "context" - "io" -) - -// Environment represents a execution environment. -type Environment interface { - Contextual - Outputs -} - -// Contextual returns a context.Context object. -type Contextual interface { - Context() context.Context -} - -// Outputs holds current program outputs. -type Outputs interface { - OutOrStdout() io.Writer - ErrOrStderr() io.Writer -} - -// WithOutputs returns a new Environment with the given outputs. -func WithOutputs(out, err io.Writer, env Environment) Environment { - return &outputs{out, err, env} -} - -// WithContext returns a new Environment with the given context. -func WithContext(ctx context.Context, env Environment) Environment { - return &contextual{ctx, env} -} - -type outputs struct { - out, err io.Writer - env Environment -} - -func (o outputs) Context() context.Context { - return o.env.Context() -} - -func (o outputs) OutOrStdout() io.Writer { - return o.out -} - -func (o outputs) ErrOrStderr() io.Writer { - return o.err -} - -type contextual struct { - ctx context.Context - env Environment -} - -func (c contextual) Context() context.Context { - return c.ctx -} - -func (c contextual) OutOrStdout() io.Writer { - return c.env.OutOrStdout() -} - -func (c contextual) ErrOrStderr() io.Writer { - return c.env.ErrOrStderr() -} diff --git a/pkg/tests/cloudevent_server.go b/pkg/tests/cloudevent_server.go index 8670bfce9..9f5a43ca5 100644 --- a/pkg/tests/cloudevent_server.go +++ b/pkg/tests/cloudevent_server.go @@ -17,7 +17,7 @@ var ErrCantStartCloudEventsServer = errors.New("can't start cloutevents server") // HTTP server which can catch a sent event. func WithCloudEventsServer(test func(serverURL url.URL) error) (*cloudevents.Event, error) { var ce *cloudevents.Event - receive := func(ctx context.Context, event cloudevents.Event) { + receive := func(_ context.Context, event cloudevents.Event) { ce = &event } ctx := context.Background() diff --git a/pkg/tests/fakeclients.go b/pkg/tests/fakeclients.go index 7ed852679..5508bd250 100644 --- a/pkg/tests/fakeclients.go +++ b/pkg/tests/fakeclients.go @@ -1,7 +1,6 @@ package tests import ( - "context" "testing" "gotest.tools/v3/assert" @@ -21,6 +20,13 @@ import ( servingv1client "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1" ) +//nolint:gochecknoinits +func init() { + if !testing.Testing() { + panic("For testing only!") + } +} + // FakeClients creates K8s clients from a list of objects using fake packages. type FakeClients struct { testing.TB @@ -30,7 +36,6 @@ type FakeClients struct { serving servingv1client.ServingV1Interface eventing eventingv1client.EventingV1Interface messaging messagingv1client.MessagingV1Interface - ctx context.Context } func (c *FakeClients) Typed() kubernetes.Interface { @@ -73,13 +78,6 @@ func (c *FakeClients) Messaging() messagingv1client.MessagingV1Interface { return c.messaging } -func (c *FakeClients) Context() context.Context { - if c.ctx == nil { - c.ctx = context.Background() - } - return c.ctx -} - func (c *FakeClients) Namespace() string { return "default" } diff --git a/pkg/tests/sender.go b/pkg/tests/sender.go index 21db2b705..7835ef9c3 100644 --- a/pkg/tests/sender.go +++ b/pkg/tests/sender.go @@ -1,6 +1,8 @@ package tests import ( + "context" + cloudevents "github.com/cloudevents/sdk-go/v2" ) @@ -10,7 +12,7 @@ type Sender struct { } // Send will send event to specified target. -func (m *Sender) Send(ce cloudevents.Event) error { +func (m *Sender) Send(_ context.Context, ce cloudevents.Event) error { m.Sent = append(m.Sent, ce) return nil } diff --git a/test/e2e/broker.go b/test/e2e/broker.go index 010550d37..5c7bcd0f4 100644 --- a/test/e2e/broker.go +++ b/test/e2e/broker.go @@ -4,7 +4,6 @@ package e2e import ( "context" - "fmt" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -49,8 +48,7 @@ func (b brokerSutImpl) step(ctx context.Context, t feature.T) { func (b brokerSutImpl) sink() Sink { return sinkFn(func() string { - return fmt.Sprintf("Broker:%s:%s", - eventingv1.SchemeGroupVersion, b.name()) + return "broker:" + b.name() }) } diff --git a/test/e2e/channel.go b/test/e2e/channel.go index 8b8cbf3dc..902b7ce34 100644 --- a/test/e2e/channel.go +++ b/test/e2e/channel.go @@ -4,7 +4,6 @@ package e2e import ( "context" - "fmt" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -52,8 +51,7 @@ func (c channelSutImpl) step(ctx context.Context, t feature.T) { func (c channelSutImpl) sink() Sink { return sinkFn(func() string { - return fmt.Sprintf("Channel:%s:%s", - messagingv1.SchemeGroupVersion, c.name()) + return "channel:" + c.name() }) } diff --git a/test/e2e/ics_send.go b/test/e2e/ics_send.go index 97a493b7e..d89c4f4e2 100644 --- a/test/e2e/ics_send.go +++ b/test/e2e/ics_send.go @@ -62,18 +62,13 @@ func sendEvent(ev cloudevents.Event, sink Sink) feature.StepFn { "--field", fmt.Sprintf("data=%s", ev.Data()), "--to", sink.String(), } - // FIXME: remove --sender-namespace after fixing issue - // knative-sandbox/kn-plugin-event#160 - log.Warn("FIXME: knative-sandbox/kn-plugin-event#160") - args = append(args, "--sender-namespace", ns) - cmd := test.ResolveKnEventCommand(t).ToIcmd(args...) log = log.With(json("cmd", cmd)) log.Info("Running") result := icmd.RunCmd(cmd) if err := result.Compare(icmd.Expected{ ExitCode: 0, - Out: fmt.Sprintf("Event (ID: %s) have been sent.", ev.ID()), + Err: fmt.Sprintf("Event (ID: %s) have been sent.", ev.ID()), }); err != nil { handleSendErr(ctx, t, err, ev) } @@ -92,7 +87,7 @@ func receiveEvent(ev cloudevents.Event, sinkName string) feature.StepFn { func handleSendErr(ctx context.Context, t feature.T, err error, ev cloudevents.Event) { // TODO: most of this code should be moved to production CLI, so that in case // of send error, a nice, report is produced. - // See: https://github.com/knative-sandbox/kn-plugin-event/issues/129 + // See: https://github.com/knative-extensions/kn-plugin-event/issues/129 if err == nil { return } @@ -103,7 +98,7 @@ func handleSendErr(ctx context.Context, t feature.T, err error, ev cloudevents.E pods := kube.CoreV1().Pods(ns) events := kube.CoreV1().Events(ns) jlist, kerr := jobs.List(ctx, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("event-id=%s", ev.ID()), + LabelSelector: "event-id=" + ev.ID(), }) if kerr != nil { log.Error(kerr) @@ -113,7 +108,7 @@ func handleSendErr(ctx context.Context, t feature.T, err error, ev cloudevents.E } jobName := jlist.Items[0].Name plist, kerr := pods.List(ctx, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("job-name=%s", jobName), + LabelSelector: "job-name=" + jobName, }) if kerr != nil { log.Error(kerr) diff --git a/test/e2e/k8s_service.go b/test/e2e/k8s_service.go index 3f0a932e8..1534317f0 100644 --- a/test/e2e/k8s_service.go +++ b/test/e2e/k8s_service.go @@ -3,9 +3,6 @@ package e2e import ( - "fmt" - - corev1 "k8s.io/api/core/v1" "knative.dev/reconciler-test/pkg/feature" ) @@ -23,7 +20,6 @@ func (k kubeServiceSut) Name() string { func (k kubeServiceSut) Deploy(_ *feature.Feature, sinkName string) Sink { return sinkFn(func() string { - return fmt.Sprintf("Service:%s:%s", - corev1.SchemeGroupVersion, sinkName) + return "svc:" + sinkName }) } diff --git a/test/e2e/kn_service.go b/test/e2e/kn_service.go index ef89083aa..06223173a 100644 --- a/test/e2e/kn_service.go +++ b/test/e2e/kn_service.go @@ -166,10 +166,7 @@ func (wf watholaForwarder) name() string { } func (wf watholaForwarder) sink() Sink { - return sinkFn(func() string { - return fmt.Sprintf("Service:%s:%s", - servingv1.SchemeGroupVersion, wf.name()) - }) + return sinkFn(wf.name) } func (wf watholaForwarder) configPath() string { @@ -177,5 +174,5 @@ func (wf watholaForwarder) configPath() string { if homedirEnv, ok := os.LookupEnv("KN_PLUGIN_EVENT_WATHOLA_HOMEDIR"); ok { homedir = homedirEnv } - return fmt.Sprintf("%s/.config/wathola/config.toml", homedir) + return path.Join(homedir, ".config", "wathola", "config.toml") } diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go index 2c829a224..5e1945607 100644 --- a/test/e2e/main_test.go +++ b/test/e2e/main_test.go @@ -11,7 +11,7 @@ import ( "knative.dev/reconciler-test/pkg/environment" ) -// global is the singleton instance of GlobalEnvironment. It is used to parse +// Global is the singleton instance of GlobalEnvironment. It is used to parse // the testing config for the test run. The config will specify the cluster // config as well as the parsing level and state flags. var global environment.GlobalEnvironment //nolint:gochecknoglobals diff --git a/test/e2e/sut.go b/test/e2e/sut.go index 8b074c279..981d68079 100644 --- a/test/e2e/sut.go +++ b/test/e2e/sut.go @@ -8,7 +8,7 @@ import ( "knative.dev/reconciler-test/pkg/feature" ) -// SystemUnderTest is a part of cluster we are testing the event propagation +// SystemUnderTest is a part of the cluster we are testing the event propagation // though. type SystemUnderTest interface { Name() string diff --git a/test/e2e/wathola_image.go b/test/e2e/wathola_image.go index 86ca3516e..9107bc588 100644 --- a/test/e2e/wathola_image.go +++ b/test/e2e/wathola_image.go @@ -23,7 +23,7 @@ func WatholaForwarderImageFromContext(ctx context.Context) string { // WithCustomWatholaForwarderImage allows you to specify a custom wathola // forwarder image to be used when invoking watholaForwarder.step. func WithCustomWatholaForwarderImage(image string) environment.EnvOpts { - return func(ctx context.Context, env environment.Environment) (context.Context, error) { + return func(ctx context.Context, _ environment.Environment) (context.Context, error) { return context.WithValue(ctx, watholaForwarderImageKey{}, image), nil } } diff --git a/test/images/resolver.go b/test/images/resolver.go index 141452226..acff10233 100644 --- a/test/images/resolver.go +++ b/test/images/resolver.go @@ -17,7 +17,7 @@ var ErrCouldNotResolve = errors.New("could not resolve image with registered res // Resolver will resolve given KO package paths into real OCI images references. // This interface probably should be moved into reconciler-test framework. See: -// https://github.com/knative-sandbox/reconciler-test/issues/303 +// https://github.com/knative-extensions/reconciler-test/issues/303 type Resolver interface { // Resolve will resolve given KO package path into real OCI image reference. Resolve(kopath string) (name.Reference, error) @@ -31,7 +31,7 @@ type PackageResolver func(ctx context.Context) string // ExplicitPackage will return given package. func ExplicitPackage(pack string) PackageResolver { - return func(ctx context.Context) string { + return func(context.Context) string { return pack } } diff --git a/test/pkg/clients.go b/test/pkg/clients.go index 7b7dfec58..458491629 100644 --- a/test/pkg/clients.go +++ b/test/pkg/clients.go @@ -6,7 +6,6 @@ import ( "gotest.tools/v3/assert" clienttest "knative.dev/client/pkg/util/test" - "knative.dev/kn-plugin-event/pkg/event" "knative.dev/kn-plugin-event/pkg/k8s" plugintest "knative.dev/kn-plugin-event/test" ) @@ -28,7 +27,7 @@ type ClientsContext struct { func WithClients(tb testing.TB, handler func(c ClientsContext)) { tb.Helper() plugintest.MaybeSkip(tb) - clients, err := k8s.CreateKubeClient(&event.Properties{}) + clients, err := k8s.NewClients(&k8s.Configurator{}) if err != nil && errors.Is(err, k8s.ErrNoKubernetesConnection) { tb.Skip("AUTO-SKIP:", err) } else { diff --git a/test/pkg/k8s/addressresolver_test.go b/test/pkg/k8s/addressresolver_test.go index a96076286..b3d2e2ce9 100644 --- a/test/pkg/k8s/addressresolver_test.go +++ b/test/pkg/k8s/addressresolver_test.go @@ -1,5 +1,4 @@ //go:build e2e -// +build e2e package k8s_test @@ -8,6 +7,8 @@ import ( "testing" "time" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" "gotest.tools/v3/assert" "gotest.tools/v3/poll" corev1 "k8s.io/api/core/v1" @@ -28,80 +29,107 @@ import ( "knative.dev/pkg/apis" duckv1 "knative.dev/pkg/apis/duck/v1" "knative.dev/pkg/kmeta" + "knative.dev/pkg/logging" servingv1 "knative.dev/serving/pkg/apis/serving/v1" ) func TestResolveAddress(t *testing.T) { plugintestpkg.WithClients(t, func(c plugintestpkg.ClientsContext) { - k8stest.ResolveAddressTestCases(c.KnTest.Namespace(), func(tc k8stest.ResolveAddressTestCase) { + for _, tc := range k8stest.ResolveAddressTestCases(c.KnTest.Namespace()) { t.Run(tc.Name, func(t *testing.T) { t.Parallel() - k8stest.EnsureResolveAddress(t, tc, func() (k8s.Clients, func(tb testing.TB)) { - deploy(t, tc, c.Clients) + log := zaptest.NewLogger(t, zaptest.Level(zapcore.InfoLevel)) + ctx := logging.WithLogger(context.TODO(), log.Sugar()) + k8stest.EnsureResolveAddress(ctx, t, tc, func() (k8s.Clients, func(tb testing.TB)) { + deploy(ctx, t, tc, c.Clients) cleanup := func(tb testing.TB) { //nolint:thelper if tb.Failed() { tb.Logf("Skipping undeploy, because test '%s' failed", tb.Name()) return } - undeploy(tb, tc, c.Clients) + undeploy(ctx, tb, tc, c.Clients) } return c.Clients, cleanup }) }) - }) + } }) } -func deploy(tb testing.TB, tc k8stest.ResolveAddressTestCase, clients k8s.Clients) { //nolint:thelper +func deploy( //nolint:thelper + ctx context.Context, + tb testing.TB, + tc k8stest.ResolveAddressTestCase, + clients k8s.Clients, +) { for _, object := range tc.Objects { switch v := object.(type) { case *servingv1.Service: - deployKnService(tb, clients, *(v)) + deployKnService(ctx, tb, clients, *(v)) case *corev1.Service: - deployK8sService(tb, clients, *(v)) + deployK8sService(ctx, tb, clients, *(v)) case *eventingv1.Broker: - deployBroker(tb, clients, *(v)) + deployBroker(ctx, tb, clients, *(v)) case *messagingv1.Channel: - deployChannel(tb, clients, *(v)) + deployChannel(ctx, tb, clients, *(v)) default: tb.Fatalf("unsupported type: %#v", v) } } } -func undeploy(tb testing.TB, tc k8stest.ResolveAddressTestCase, clients k8s.Clients) { //nolint:thelper +func undeploy( //nolint:thelper + ctx context.Context, + tb testing.TB, + tc k8stest.ResolveAddressTestCase, + clients k8s.Clients, +) { for _, object := range tc.Objects { switch v := object.(type) { case *servingv1.Service: - undeployKnService(tb, clients, *(v)) + undeployKnService(ctx, tb, clients, *(v)) case *corev1.Service: - undeployK8sService(tb, clients, *(v)) + undeployK8sService(ctx, tb, clients, *(v)) case *eventingv1.Broker: - undeployBroker(tb, clients, *(v)) + undeployBroker(ctx, tb, clients, *(v)) case *messagingv1.Channel: - undeployChannel(tb, clients, *(v)) + undeployChannel(ctx, tb, clients, *(v)) default: tb.Fatalf("unsupported type: %#v", v) } } } -func deployK8sService(tb testing.TB, clients k8s.Clients, service corev1.Service) { //nolint:thelper +func deployK8sService( //nolint:thelper + ctx context.Context, + tb testing.TB, + clients k8s.Clients, + service corev1.Service, +) { service.Status = corev1.ServiceStatus{} _, err := clients.Typed().CoreV1().Services(service.Namespace). - Create(clients.Context(), &service, metav1.CreateOptions{}) + Create(ctx, &service, metav1.CreateOptions{}) assert.NilError(tb, err) } -func undeployK8sService(tb testing.TB, clients k8s.Clients, service corev1.Service) { //nolint:thelper +func undeployK8sService( //nolint:thelper + ctx context.Context, + tb testing.TB, + clients k8s.Clients, + service corev1.Service, +) { err := clients.Typed().CoreV1().Services(service.Namespace). - Delete(clients.Context(), service.Name, metav1.DeleteOptions{}) + Delete(ctx, service.Name, metav1.DeleteOptions{}) assert.NilError(tb, err) } -func deployKnService(tb testing.TB, clients k8s.Clients, service servingv1.Service) { //nolint:thelper +func deployKnService( //nolint:thelper + ctx context.Context, + tb testing.TB, + clients k8s.Clients, + service servingv1.Service, +) { service.Status = servingv1.ServiceStatus{} - ctx := clients.Context() knclient := clientservingv1.NewKnServingClient(clients.Serving(), service.Namespace) err := knclient.CreateService(ctx, &service) assert.NilError(tb, err) @@ -113,39 +141,63 @@ func deployKnService(tb testing.TB, clients k8s.Clients, service servingv1.Servi assert.NilError(tb, err) } -func undeployKnService(tb testing.TB, clients k8s.Clients, service servingv1.Service) { //nolint:thelper +func undeployKnService( //nolint:thelper + ctx context.Context, + tb testing.TB, + clients k8s.Clients, + service servingv1.Service, +) { err := clientservingv1. NewKnServingClient(clients.Serving(), service.Namespace). - DeleteService(clients.Context(), service.GetName(), time.Minute) + DeleteService(ctx, service.GetName(), time.Minute) assert.NilError(tb, err) } -func deployBroker(tb testing.TB, clients k8s.Clients, broker eventingv1.Broker) { //nolint:thelper +func deployBroker( //nolint:thelper + ctx context.Context, + tb testing.TB, + clients k8s.Clients, + broker eventingv1.Broker, +) { broker.Status = eventingv1.BrokerStatus{} - ctx := clients.Context() knclient := clienteventingv1.NewKnEventingClient(clients.Eventing(), broker.Namespace) assert.NilError(tb, knclient.CreateBroker(ctx, &broker)) - waitForReady(tb, clients, &broker, time.Minute) + waitForReady(ctx, tb, clients, &broker, time.Minute) } -func undeployBroker(tb testing.TB, clients k8s.Clients, broker eventingv1.Broker) { //nolint:thelper +func undeployBroker( //nolint:thelper + ctx context.Context, + tb testing.TB, + clients k8s.Clients, + broker eventingv1.Broker, +) { err := clients.Eventing().Brokers(broker.Namespace). - Delete(clients.Context(), broker.Name, metav1.DeleteOptions{}) + Delete(ctx, broker.Name, metav1.DeleteOptions{}) assert.NilError(tb, err) } -func deployChannel(tb testing.TB, clients k8s.Clients, channel messagingv1.Channel) { //nolint:thelper +func deployChannel( //nolint:thelper + ctx context.Context, + tb testing.TB, + clients k8s.Clients, + channel messagingv1.Channel, +) { channel.Status = messagingv1.ChannelStatus{} knclient := clientmessagingv1.NewKnMessagingClient(clients.Messaging(), channel.Namespace).ChannelsClient() - assert.NilError(tb, knclient.CreateChannel(clients.Context(), &channel)) - waitForReady(tb, clients, &channel, time.Minute) + assert.NilError(tb, knclient.CreateChannel(ctx, &channel)) + waitForReady(ctx, tb, clients, &channel, time.Minute) } -func undeployChannel(tb testing.TB, clients k8s.Clients, channel messagingv1.Channel) { //nolint:thelper +func undeployChannel( //nolint:thelper + ctx context.Context, + tb testing.TB, + clients k8s.Clients, + channel messagingv1.Channel, +) { err := clients.Messaging().Channels(channel.Namespace). - Delete(clients.Context(), channel.Name, metav1.DeleteOptions{}) + Delete(ctx, channel.Name, metav1.DeleteOptions{}) assert.NilError(tb, err) } @@ -154,8 +206,13 @@ func gvr(accessor kmeta.Accessor) schema.GroupVersionResource { return apis.KindToResource(gvk) } -func waitForReady(t poll.TestingT, clients k8s.Clients, accessor kmeta.Accessor, timeout time.Duration) { - ctx := clients.Context() +func waitForReady( + ctx context.Context, + t poll.TestingT, + clients k8s.Clients, + accessor kmeta.Accessor, + timeout time.Duration, +) { dynclient := clients.Dynamic() poll.WaitOn(t, isReady(ctx, dynclient, accessor), poll.WithTimeout(timeout), poll.WithDelay(time.Second)) @@ -164,7 +221,7 @@ func waitForReady(t poll.TestingT, clients k8s.Clients, accessor kmeta.Accessor, func isReady(ctx context.Context, dynclient dynamic.Interface, accessor kmeta.Accessor) poll.Check { resources := dynclient.Resource(gvr(accessor)). Namespace(accessor.GetNamespace()) - return func(t poll.LogT) poll.Result { + return func(poll.LogT) poll.Result { res, err := resources.Get(ctx, accessor.GetName(), metav1.GetOptions{}) if err != nil { return poll.Error(err) From 76a4108e8bb401f336f678b34972b2b35a484583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Suszy=C5=84ski?= Date: Wed, 25 Sep 2024 20:06:16 +0200 Subject: [PATCH 2/3] Set up simplified logging for in-cluster event sender This prevents it from failing with (in some setups): ``` mkdir /.cache: permission denied ``` --- .golangci.yaml | 4 +--- go.mod | 5 +---- go.sum | 2 ++ go.work.sum | 3 ++- internal/cli/root.go | 2 +- internal/ics/app.go | 8 +------- pkg/cli/context.go | 37 ++++++++++++++++++++++++++++++++++--- pkg/cli/context_test.go | 3 ++- 8 files changed, 44 insertions(+), 20 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index c194f6cb3..33cc6a09a 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -44,6 +44,4 @@ linters-settings: - knative.dev/client/pkg/* gomoddirectives: # List of allowed `replace` directives. Default is empty. - replace-allow-list: - # TODO: Remove when https://github.com/knative/client/pull/1968 is merged - - knative.dev/client/pkg + replace-allow-list: [] diff --git a/go.mod b/go.mod index 6a154bc95..5b62b9529 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( k8s.io/api v0.30.3 k8s.io/apimachinery v0.30.3 k8s.io/client-go v0.30.3 - knative.dev/client/pkg v0.0.0-20240903134911-f09e7164ceaf + knative.dev/client/pkg v0.0.0-20240925104631-c9f128423b58 knative.dev/eventing v0.42.1-0.20240828134450-34f9cd384dea knative.dev/hack v0.0.0-20240814130635-06f7aff93954 knative.dev/pkg v0.0.0-20240815051656-89743d9bbf7c @@ -32,9 +32,6 @@ require ( sigs.k8s.io/yaml v1.4.0 ) -// TODO: Remove when https://github.com/knative/client/pull/1968 is merged -replace knative.dev/client/pkg => github.com/cardil/knative-client/pkg v0.0.0-20240923095307-3a2bcba04752 - require ( contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect diff --git a/go.sum b/go.sum index 2a7a744ee..2779c3ade 100644 --- a/go.sum +++ b/go.sum @@ -900,6 +900,8 @@ k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/client-pkg v0.0.0-20240808015000-22f598931483 h1:jBfmxcR0H5Z9IzamelZtmmg9jfeOXfssllUVX5M4Xzs= knative.dev/client-pkg v0.0.0-20240808015000-22f598931483/go.mod h1:Y56KfZx3gJJpju88l86jQ9csxywLiopR0GkxCWW3+Kg= +knative.dev/client/pkg v0.0.0-20240925104631-c9f128423b58 h1:EXXrHvJNGuWakfy27sJz0GvVSNuf5uzH6BW+m2dBq3w= +knative.dev/client/pkg v0.0.0-20240925104631-c9f128423b58/go.mod h1:JR3XomuVf2cBqgvXFONkX6Ebf1/gJwUnl/1OH47U18g= knative.dev/eventing v0.42.1-0.20240828134450-34f9cd384dea h1:j3bFBE797vD6IZJsECQ5lEENumLp817rkQxANrbKxHs= knative.dev/eventing v0.42.1-0.20240828134450-34f9cd384dea/go.mod h1:Clx8z37Nwg321H9+vGNxp5C6bVdo4l4XM5g6T5CgZVI= knative.dev/hack v0.0.0-20240814130635-06f7aff93954 h1:dGMK5VoL75szvrYQTL9NqhPYHu1f5dGaXx1hJI8fAFM= diff --git a/go.work.sum b/go.work.sum index b39702801..4c6697cf5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -136,7 +136,6 @@ github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPY github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4= github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4= github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= -github.com/cardil/knative-client/pkg v0.0.0-20240923095307-3a2bcba04752/go.mod h1:JR3XomuVf2cBqgvXFONkX6Ebf1/gJwUnl/1OH47U18g= github.com/cavaliercoder/badio v0.0.0-20160213150051-ce5280129e9e h1:YYUjy5BRwO5zPtfk+aa2gw255FIIoi93zMmuy19o0bc= github.com/cavaliercoder/badio v0.0.0-20160213150051-ce5280129e9e/go.mod h1:V284PjgVwSk4ETmz84rpu9ehpGg7swlIH8npP9k2bGw= github.com/cavaliercoder/go-rpm v0.0.0-20200122174316-8cb9fd9c31a8 h1:jP7ki8Tzx9ThnFPLDhBYAhEpI2+jOURnHQNURgsMvnY= @@ -676,6 +675,8 @@ k8s.io/kms v0.30.3 h1:NLg+oN45S2Y3U0WiLRzbS61AY/XrS5JBMZp531Z+Pho= k8s.io/kms v0.30.3/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4= knative.dev/caching v0.0.0-20240812133420-93e6a0a5b46d h1:pj+XufdayIdxV/PUFpuIB1Y/pFoiWPJT6VrAXja4OcE= knative.dev/caching v0.0.0-20240812133420-93e6a0a5b46d/go.mod h1:vxgDv5XOAYFa2LrreP8quBiyKB2HB2fqWG6nc6LmDR4= +knative.dev/client/pkg v0.0.0-20240903134911-f09e7164ceaf h1:QPGwYLkkMwssyEw0ek8T1u4Q3+FSeVPSH4IIKThdngc= +knative.dev/client/pkg v0.0.0-20240903134911-f09e7164ceaf/go.mod h1:JR3XomuVf2cBqgvXFONkX6Ebf1/gJwUnl/1OH47U18g= knative.dev/hack/schema v0.0.0-20240814130635-06f7aff93954 h1:0yjDplGHUnZ8NpcfgmH0thXSzG28VSM16hb3Vz171l8= knative.dev/hack/schema v0.0.0-20240814130635-06f7aff93954/go.mod h1:jRH/sx6mwwuMVhvJgnzSaoYA1N4qaIkJa+zxEGtVA5I= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= diff --git a/internal/cli/root.go b/internal/cli/root.go index b72c2a2e0..6a100b65a 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -58,7 +58,7 @@ func (a *App) Command() *cobra.Command { if a.Verbose { lvl = zapcore.DebugLevel } - cli.SetupContext(cmd, lvl) + cli.SetupContext(cmd, cli.DefaultLoggingSetup(lvl)) } c.PersistentPostRunE = func(cmd *cobra.Command, _ []string) error { closer := outlogging.LogFileCloserFrom(cmd.Context()) diff --git a/internal/ics/app.go b/internal/ics/app.go index 7a9452cb0..8360607f1 100644 --- a/internal/ics/app.go +++ b/internal/ics/app.go @@ -5,7 +5,6 @@ import ( "github.com/wavesoftware/go-commandline" "go.uber.org/zap" "go.uber.org/zap/zapcore" - 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/k8s" @@ -28,12 +27,7 @@ func (a App) Command() *cobra.Command { } c.SetContext(cli.InitialContext()) c.PersistentPreRun = func(cmd *cobra.Command, _ []string) { - cli.SetupContext(cmd, zapcore.DebugLevel) - } - c.PersistentPostRunE = func(cmd *cobra.Command, _ []string) error { - closer := outlogging.LogFileCloserFrom(cmd.Context()) - // ensure to close the log file - return closer() + cli.SetupContext(cmd, cli.SimplifiedLoggingSetup(zapcore.DebugLevel)) } a.SetGlobalFlags(c.PersistentFlags()) return c diff --git a/pkg/cli/context.go b/pkg/cli/context.go index 5aa00a65a..d4592d925 100644 --- a/pkg/cli/context.go +++ b/pkg/cli/context.go @@ -19,9 +19,11 @@ package cli import ( "context" + "go.uber.org/zap" "go.uber.org/zap/zapcore" "knative.dev/client/pkg/output" outlogging "knative.dev/client/pkg/output/logging" + "knative.dev/pkg/logging" "knative.dev/pkg/signals" ) @@ -39,8 +41,38 @@ func InitialContext() context.Context { return initialCtx } +// LoggingSetup is a func that sets the logging into the context. +type LoggingSetup func(ctx context.Context) context.Context + +// DefaultLoggingSetup is the default logging setup. +func DefaultLoggingSetup(logLevel zapcore.Level) func(ctx context.Context) context.Context { + return func(ctx context.Context) context.Context { + ctx = outlogging.WithLogLevel(ctx, logLevel) + return outlogging.EnsureLogger(ctx) + } +} + +// SimplifiedLoggingSetup is just a production logger to avoid creating +// additional log files. +// +// TODO: Remove this after simplified logging is supported in +// knative.dev/client/pkg/output/logging package. +func SimplifiedLoggingSetup(logLevel zapcore.Level) func(ctx context.Context) context.Context { + return func(ctx context.Context) context.Context { + prtr := output.PrinterFrom(ctx) + errout := prtr.ErrOrStderr() + ec := zap.NewProductionEncoderConfig() + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(ec), + zapcore.AddSync(errout), + logLevel, + )) + return logging.WithLogger(ctx, logger.Sugar()) + } +} + // SetupContext will set the context commonly for all CLIs. -func SetupContext(ctxual Contextual, defaultLogLevel zapcore.Level) { +func SetupContext(ctxual Contextual, loggingSetup LoggingSetup) { ctx := ctxual.Context() if ctx == initialCtx { // TODO: knative.dev/pkg/signals should allow for resetting the @@ -48,8 +80,7 @@ func SetupContext(ctxual Contextual, defaultLogLevel zapcore.Level) { ctx = signals.NewContext() } ctx = output.WithContext(ctx, ctxual) - ctx = outlogging.WithLogLevel(ctx, defaultLogLevel) - ctx = outlogging.EnsureLogger(ctx) + ctx = loggingSetup(ctx) ctxual.SetContext(ctx) } diff --git a/pkg/cli/context_test.go b/pkg/cli/context_test.go index 789b159c3..96f6e3f76 100644 --- a/pkg/cli/context_test.go +++ b/pkg/cli/context_test.go @@ -29,7 +29,8 @@ import ( func TestSetupContext(t *testing.T) { cmd := &cobra.Command{} cmd.SetContext(cli.InitialContext()) - cli.SetupContext(cmd, zapcore.InvalidLevel) + cli.SetupContext(cmd, cli.SimplifiedLoggingSetup(zapcore.InvalidLevel)) + cli.SetupContext(cmd, cli.DefaultLoggingSetup(zapcore.InvalidLevel)) ctx := cmd.Context() assert.Equal(t, zapcore.InvalidLevel, outlogging.LogLevelFromContext(ctx)) } From da9f541b6692a42034713d660ee0f1192e8c7110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Suszy=C5=84ski?= Date: Thu, 26 Sep 2024 13:53:56 +0200 Subject: [PATCH 3/3] Print to stdout, by default --- internal/cli/build.go | 6 ++++-- internal/cli/root.go | 2 +- internal/ics/app.go | 2 +- pkg/cli/context.go | 31 ++++++++++++++++++++++--------- pkg/cli/context_test.go | 4 ++-- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/internal/cli/build.go b/internal/cli/build.go index a98644957..9d9e1bcb8 100644 --- a/internal/cli/build.go +++ b/internal/cli/build.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/cobra" "knative.dev/client/pkg/output" + outlogging "knative.dev/client/pkg/output/logging" "knative.dev/kn-plugin-event/pkg/binding" "knative.dev/kn-plugin-event/pkg/cli" ) @@ -46,16 +47,17 @@ func (b *buildCommand) command() *cobra.Command { func (b *buildCommand) run(cmd *cobra.Command, _ []string) error { c := binding.CliApp() + ctx := cmd.Context() ce, err := c.CreateWithArgs(b.event) if err != nil { return cantBuildEventError(err) } + outlogging.LoggerFrom(ctx).Debugf("Event: %#v", ce) out, err := c.PresentWith(ce, b.OutputMode) if err != nil { return fmt.Errorf("event %w: %w", ErrCantBePresented, err) } - prt := output.PrinterFrom(cmd.Context()) - prt.Println(out) + output.PrinterFrom(ctx).Println(out) return nil } diff --git a/internal/cli/root.go b/internal/cli/root.go index 6a100b65a..1af0d124c 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -58,7 +58,7 @@ func (a *App) Command() *cobra.Command { if a.Verbose { lvl = zapcore.DebugLevel } - cli.SetupContext(cmd, cli.DefaultLoggingSetup(lvl)) + cli.SetupOutput(cmd, cli.DefaultLoggingSetup(lvl)) } c.PersistentPostRunE = func(cmd *cobra.Command, _ []string) error { closer := outlogging.LogFileCloserFrom(cmd.Context()) diff --git a/internal/ics/app.go b/internal/ics/app.go index 8360607f1..5653374d7 100644 --- a/internal/ics/app.go +++ b/internal/ics/app.go @@ -27,7 +27,7 @@ func (a App) Command() *cobra.Command { } c.SetContext(cli.InitialContext()) c.PersistentPreRun = func(cmd *cobra.Command, _ []string) { - cli.SetupContext(cmd, cli.SimplifiedLoggingSetup(zapcore.DebugLevel)) + cli.SetupOutput(cmd, cli.SimplifiedLoggingSetup(zapcore.DebugLevel)) } a.SetGlobalFlags(c.PersistentFlags()) return c diff --git a/pkg/cli/context.go b/pkg/cli/context.go index d4592d925..592ba9ebc 100644 --- a/pkg/cli/context.go +++ b/pkg/cli/context.go @@ -18,6 +18,8 @@ package cli import ( "context" + "io" + "os" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -27,12 +29,14 @@ import ( "knative.dev/pkg/signals" ) -// Contextual represents a contextual entity that also can serve as an -// output.Printer. -type Contextual interface { +// Cobralike represents a cobra.Command-like entity. +type Cobralike interface { SetContext(ctx context.Context) Context() context.Context output.Printer + + SetOut(out io.Writer) + OutOrStderr() io.Writer } // InitialContext returns the initial context object, so it could be set ahead @@ -71,21 +75,30 @@ func SimplifiedLoggingSetup(logLevel zapcore.Level) func(ctx context.Context) co } } -// SetupContext will set the context commonly for all CLIs. -func SetupContext(ctxual Contextual, loggingSetup LoggingSetup) { - ctx := ctxual.Context() +// SetupOutput will set the output for all CLIs commonly. +func SetupOutput(cbr Cobralike, loggingSetup LoggingSetup) { + ctx := cbr.Context() if ctx == initialCtx { // TODO: knative.dev/pkg/signals should allow for resetting the // context for testing purposes. ctx = signals.NewContext() } - ctx = output.WithContext(ctx, ctxual) + // Cobra uses OutOrStderr for printing, and by default the out is nil, + // resulting in printing all messages to stderr. + // We want just logs to be printed on the stderr. + // + // TODO: This should be handled by knative.dev/client/pkg/output package. + if cbr.OutOrStderr() == os.Stderr { + // default to stdout + cbr.SetOut(os.Stdout) + } + ctx = output.WithContext(ctx, cbr) ctx = loggingSetup(ctx) - ctxual.SetContext(ctx) + cbr.SetContext(ctx) } var ( initialCtxKey = struct{}{} //nolint:gochecknoglobals initialCtx = context.WithValue( //nolint:gochecknoglobals - context.Background(), initialCtxKey, true) + context.TODO(), initialCtxKey, true) ) diff --git a/pkg/cli/context_test.go b/pkg/cli/context_test.go index 96f6e3f76..9f29e86b9 100644 --- a/pkg/cli/context_test.go +++ b/pkg/cli/context_test.go @@ -29,8 +29,8 @@ import ( func TestSetupContext(t *testing.T) { cmd := &cobra.Command{} cmd.SetContext(cli.InitialContext()) - cli.SetupContext(cmd, cli.SimplifiedLoggingSetup(zapcore.InvalidLevel)) - cli.SetupContext(cmd, cli.DefaultLoggingSetup(zapcore.InvalidLevel)) + cli.SetupOutput(cmd, cli.SimplifiedLoggingSetup(zapcore.InvalidLevel)) + cli.SetupOutput(cmd, cli.DefaultLoggingSetup(zapcore.InvalidLevel)) ctx := cmd.Context() assert.Equal(t, zapcore.InvalidLevel, outlogging.LogLevelFromContext(ctx)) }