From 559b1ca59e353405b78e679db0d66adc39bb68c9 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] Migrating to sink flag for --to param --- .golangci.yaml | 6 - cmd/kn-event-sender/main_test.go | 2 +- cmd/kn-event/main.go | 4 +- cmd/kn-event/main_test.go | 6 +- go.mod | 10 +- go.sum | 20 +-- go.work.sum | 4 +- 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 | 50 ++++---- 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 | 31 +++++ 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 | 8 +- pkg/k8s/jobrunner.go | 28 ++--- pkg/k8s/jobrunner_test.go | 9 +- pkg/k8s/kubeclient.go | 91 +++++++------- pkg/k8s/params.go | 55 +++++++++ pkg/k8s/test/addressresolver_cases.go | 39 ++++-- 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 | 20 +-- pkg/sender/in_cluster_test.go | 86 ++++++------- pkg/sender/namespace.go | 13 -- pkg/sender/types.go | 28 ----- pkg/system/environment.go | 65 ---------- pkg/tests/cloudevent_server.go | 2 +- pkg/tests/fakeclients.go | 22 ++-- pkg/tests/sender.go | 4 +- test/pkg/clients.go | 3 +- test/pkg/k8s/addressresolver_test.go | 69 +++++------ 72 files changed, 1305 insertions(+), 1241 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/params.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 diff --git a/.golangci.yaml b/.golangci.yaml index 1d10b2313..976e3ff28 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: 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..9352b9ea5 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,13 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +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 +106,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/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..3d51c4bbb 100644 --- a/internal/ics/app.go +++ b/internal/ics/app.go @@ -1,38 +1,48 @@ 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)) } @@ -40,19 +50,3 @@ func (a App) run(cmd *cobra.Command, _ []string) error { } 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..76d2f37b8 --- /dev/null +++ b/pkg/cli/context_test.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 cli_test + +import ( + "testing" + + "github.com/spf13/cobra" + "go.uber.org/zap/zapcore" + "knative.dev/kn-plugin-event/pkg/cli" +) + +func TestSetupContext(t *testing.T) { + cmd := &cobra.Command{} + cmd.SetContext(cli.InitialContext()) + cli.SetupContext(cmd, zapcore.WarnLevel) +} 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..6ab9e6aa4 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 + if clients, err := a.Binding.NewKubeClients(params.Parse()); err != nil { + return nil, err + } else { + namespace = clients.Namespace() } - return nil, ErrUseToURLOrToFlagIsRequired -} -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..3ff6db476 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..9f81eaacd --- /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..b03c7d09a 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..9bf44975c 100644 --- a/pkg/k8s/addressresolver_test.go +++ b/pkg/k8s/addressresolver_test.go @@ -1,19 +1,25 @@ 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) { 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 }) }) diff --git a/pkg/k8s/jobrunner.go b/pkg/k8s/jobrunner.go index c04f389d1..564579ece 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{ @@ -113,8 +112,7 @@ 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()), diff --git a/pkg/k8s/jobrunner_test.go b/pkg/k8s/jobrunner_test.go index d5a1b017a..bb5f28456 100644 --- a/pkg/k8s/jobrunner_test.go +++ b/pkg/k8s/jobrunner_test.go @@ -1,11 +1,13 @@ package k8s_test import ( + "context" "fmt" "math/rand" "strconv" "sync" "testing" + "time" "gotest.tools/v3/assert" batchv1 "k8s.io/api/batch/v1" @@ -19,17 +21,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) diff --git a/pkg/k8s/kubeclient.go b/pkg/k8s/kubeclient.go index 7f5e0b0bf..c36c81b45 100644 --- a/pkg/k8s/kubeclient.go +++ b/pkg/k8s/kubeclient.go @@ -1,90 +1,89 @@ 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(p *Configurator) (Clients, error) { + cc, err := loadClientConfig(p) if err != nil { return nil, err } - restcfg := cc.Config + restcfg, rerr := cc.ClientConfig.ClientConfig() + if rerr != nil { + return nil, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, rerr) + } typed, err := kubernetes.NewForConfig(restcfg) if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) + return nil, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, err) } dyn, err := dynamic.NewForConfig(restcfg) if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) + return nil, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, err) } servingclient, err := servingv1.NewForConfig(restcfg) if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) + return nil, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, err) } eventingclient, err := eventingv1.NewForConfig(restcfg) if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) + return nil, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, err) } messagingclient, err := messagingv1.NewForConfig(restcfg) if err != nil { - return nil, fmt.Errorf("%w: %w", ErrUnexcpected, err) + return nil, fmt.Errorf("%w: %w", ErrNoKubernetesConnection, err) } 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 } // Clients holds available Kubernetes clients. type Clients interface { Namespace() string + ClientConfig() clientcmd.ClientConfig 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 - } - } - if len(props.Path) > 0 { - loadingRules.ExplicitPath = props.Path +func loadClientConfig(params *Configurator) (clientConfig, error) { + ccFn := params.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 +91,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 params.Namespace != nil { + ns = *params.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 @@ -110,6 +111,10 @@ type clients struct { messaging messagingv1.MessagingV1Interface } +func (c *clients) ClientConfig() clientcmd.ClientConfig { + return c.clientConfig.ClientConfig +} + func (c *clients) Typed() kubernetes.Interface { return c.typed } @@ -118,10 +123,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/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..350899c45 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() { + if !testing.Testing() { + panic("For testing only") + } +} + const ( // HTTPPort is 80. HTTPPort = 80 @@ -51,15 +60,16 @@ func ResolveAddressTestCases(namespace string, casefn func(tc ResolveAddressTest // 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 +82,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 +201,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/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..93f2a5346 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,9 +62,9 @@ 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 } diff --git a/pkg/sender/in_cluster_test.go b/pkg/sender/in_cluster_test.go index 01ae594b7..837c5c905 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,13 +96,13 @@ 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(ref *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 { 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 index dd5d0d92f..519b5112a 100644 --- a/pkg/system/environment.go +++ b/pkg/system/environment.go @@ -1,67 +1,2 @@ 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..f23d53d05 100644 --- a/pkg/tests/fakeclients.go +++ b/pkg/tests/fakeclients.go @@ -1,7 +1,6 @@ package tests import ( - "context" "testing" "gotest.tools/v3/assert" @@ -11,6 +10,7 @@ import ( "k8s.io/client-go/kubernetes" fakekube "k8s.io/client-go/kubernetes/fake" kubescheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/clientcmd" eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" eventingv1fakeclient "knative.dev/eventing/pkg/client/clientset/versioned/fake" @@ -21,6 +21,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 +37,11 @@ type FakeClients struct { serving servingv1client.ServingV1Interface eventing eventingv1client.EventingV1Interface messaging messagingv1client.MessagingV1Interface - ctx context.Context +} + +func (c *FakeClients) ClientConfig() clientcmd.ClientConfig { + //TODO implement me + panic("implement me") } func (c *FakeClients) Typed() kubernetes.Interface { @@ -73,13 +84,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/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..5a27c44a8 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,6 +29,7 @@ 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" ) @@ -36,14 +38,16 @@ func TestResolveAddress(t *testing.T) { k8stest.ResolveAddressTestCases(c.KnTest.Namespace(), func(tc k8stest.ResolveAddressTestCase) { 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 }) @@ -52,56 +56,55 @@ func TestResolveAddress(t *testing.T) { }) } -func deploy(tb testing.TB, tc k8stest.ResolveAddressTestCase, clients k8s.Clients) { //nolint:thelper +func deploy(ctx context.Context, tb testing.TB, tc k8stest.ResolveAddressTestCase, clients k8s.Clients) { //nolint:thelper 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(ctx context.Context, tb testing.TB, tc k8stest.ResolveAddressTestCase, clients k8s.Clients) { //nolint:thelper 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(ctx context.Context, tb testing.TB, clients k8s.Clients, service corev1.Service) { //nolint:thelper 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(ctx context.Context, tb testing.TB, clients k8s.Clients, service corev1.Service) { //nolint:thelper 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(ctx context.Context, tb testing.TB, clients k8s.Clients, service servingv1.Service) { //nolint:thelper 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 +116,38 @@ 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(ctx context.Context, tb testing.TB, clients k8s.Clients, service servingv1.Service) { //nolint:thelper 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(ctx context.Context, tb testing.TB, clients k8s.Clients, broker eventingv1.Broker) { //nolint:thelper 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(ctx context.Context, tb testing.TB, clients k8s.Clients, broker eventingv1.Broker) { //nolint:thelper 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(ctx context.Context, tb testing.TB, clients k8s.Clients, channel messagingv1.Channel) { //nolint:thelper 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(ctx context.Context, tb testing.TB, clients k8s.Clients, channel messagingv1.Channel) { //nolint:thelper 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 +156,7 @@ 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))