diff --git a/cmd/kn-event-sender/main.go b/cmd/kn-event-sender/main.go index b48ee0424..d076eb2e5 100644 --- a/cmd/kn-event-sender/main.go +++ b/cmd/kn-event-sender/main.go @@ -1,35 +1,15 @@ package main import ( - "context" - "os" - - "go.uber.org/zap" - "knative.dev/kn-plugin-event/pkg/cli/retcode" - "knative.dev/kn-plugin-event/pkg/configuration" - "knative.dev/pkg/logging" + "github.com/wavesoftware/go-commandline" + "knative.dev/kn-plugin-event/internal/ics" ) -// ExitFunc will be used to exit Go process in case of error. -var ExitFunc = os.Exit // nolint:gochecknoglobals - func main() { - logger := createLogger() - app := configuration.CreateIcs() - if err := app.SendFromEnv(); err != nil { - logger.Error(err) - ExitFunc(retcode.Calc(err)) - } + commandline.New(ics.App{}).ExecuteOrDie(ics.Options...) } // TestMain is used by tests. -//goland:noinspection GoUnusedExportedFunction func TestMain() { //nolint:deadcode main() } - -func createLogger() *zap.SugaredLogger { - return logging. - FromContext(context.TODO()). - With("env", os.Environ()) -} diff --git a/cmd/kn-event-sender/main_test.go b/cmd/kn-event-sender/main_test.go index 60e47283c..49f0f3133 100644 --- a/cmd/kn-event-sender/main_test.go +++ b/cmd/kn-event-sender/main_test.go @@ -1,20 +1,33 @@ package main_test import ( + "bytes" "net/url" + "strings" "testing" "time" cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/google/uuid" + "github.com/wavesoftware/go-commandline" "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/tests" ) func TestMainSender(t *testing.T) { + defer func() { + internalics.Options = nil + }() + var outBuf bytes.Buffer + internalics.Options = []commandline.Option{ + commandline.WithOutput(&outBuf), + } + id := uuid.New().String() want := cloudevents.NewEvent() - want.SetID("azxswq") + want.SetID(id) want.SetTime(time.Now().UTC()) want.SetType("example") want.SetSource("tests://example") @@ -31,7 +44,10 @@ func TestMainSender(t *testing.T) { return nil }) }) + 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)) } diff --git a/cmd/kn-event/main.go b/cmd/kn-event/main.go index c9bdce3f4..ad71520e6 100644 --- a/cmd/kn-event/main.go +++ b/cmd/kn-event/main.go @@ -1,10 +1,10 @@ package main -import "knative.dev/kn-plugin-event/internal/cli/cmd" - -// Suppress global check for testing purposes. -var mainCmd = &cmd.Cmd{} //nolint:gochecknoglobals +import ( + "github.com/wavesoftware/go-commandline" + "knative.dev/kn-plugin-event/internal/cli/cmd" +) func main() { - mainCmd.Execute() + commandline.New(new(cmd.App)).ExecuteOrDie(cmd.Options...) } diff --git a/cmd/kn-event/main_test.go b/cmd/kn-event/main_test.go index 0099fffe9..3c398b10b 100644 --- a/cmd/kn-event/main_test.go +++ b/cmd/kn-event/main_test.go @@ -3,26 +3,32 @@ package main // nolint:testpackage import ( "bytes" + "math" "strings" "testing" + "github.com/wavesoftware/go-commandline" "gotest.tools/v3/assert" "knative.dev/kn-plugin-event/internal/cli/cmd" ) func TestMainFunc(t *testing.T) { - tc := cmd.TestingCmd{ - Cmd: mainCmd, + retcode := math.MinInt64 + defer func() { + cmd.Options = nil + }() + var buf bytes.Buffer + cmd.Options = []commandline.Option{ + commandline.WithExit(func(code int) { + retcode = code + }), + commandline.WithOutput(&buf), + commandline.WithArgs(""), } - buf := bytes.NewBuffer([]byte{}) - tc.Out(buf) - tc.Args("") - tc.Exit(func(code int) { - assert.Equal(t, 0, code) - }) main() out := buf.String() assert.Check(t, strings.Contains(out, "Manage CloudEvents from command line")) + assert.Check(t, retcode == math.MinInt64) } diff --git a/go.mod b/go.mod index 53865c19e..6a4f0900c 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,14 @@ require ( github.com/magefile/mage v1.11.0 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.3.0 + github.com/spf13/cobra v1.5.0 github.com/thediveo/enumflag v0.10.0 + github.com/wavesoftware/go-commandline v1.0.0 github.com/wavesoftware/go-ensure v1.0.0 github.com/wavesoftware/go-magetasks v0.6.0 go.uber.org/zap v1.21.0 gopkg.in/yaml.v2 v2.4.0 - gotest.tools/v3 v3.1.0 + gotest.tools/v3 v3.3.0 k8s.io/api v0.23.9 k8s.io/apimachinery v0.23.9 k8s.io/client-go v0.23.9 @@ -138,6 +139,7 @@ require ( github.com/stretchr/testify v1.7.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/vbatts/tar-split v0.11.2 // indirect + github.com/wavesoftware/go-retcode v1.0.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.opencensus.io v0.23.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect diff --git a/go.sum b/go.sum index b0109a5f2..88c061cc4 100644 --- a/go.sum +++ b/go.sum @@ -32,7 +32,6 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= @@ -50,7 +49,6 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= -cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -109,7 +107,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= @@ -177,7 +174,6 @@ github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -243,8 +239,6 @@ github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudevents/conformance v0.2.0 h1:NvSXOKlagcsOWMEbi8U7Ex/0oQ4JZE1HQ45bnxYf2zk= github.com/cloudevents/conformance v0.2.0/go.mod h1:rHKDwylBH89Rns6U3wL9ww8bg9/4GbwRCDNuyoC6bcc= @@ -260,9 +254,7 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= @@ -393,6 +385,7 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 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.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -464,10 +457,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= @@ -757,27 +748,21 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= -github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -796,17 +781,13 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -850,7 +831,6 @@ github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOv github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -910,7 +890,6 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls= github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= @@ -967,7 +946,6 @@ github.com/mgechev/revive v1.1.2/go.mod h1:bnXsMr+ZTH09V5rssEI+jHAZ4z+ZdyhgO/zsy github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= @@ -1107,7 +1085,6 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= @@ -1143,7 +1120,6 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= @@ -1159,7 +1135,6 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= @@ -1216,7 +1191,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= -github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= @@ -1252,7 +1226,6 @@ github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag07 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60= github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= @@ -1268,8 +1241,8 @@ github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= -github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1284,7 +1257,6 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= -github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A= @@ -1330,7 +1302,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1 github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -1359,10 +1330,14 @@ github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmF github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +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-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-magetasks v0.6.0 h1:hBfZIiBWA6FCwL/jFBOdF7LZOvuCJ8A9LbpluZdFskw= github.com/wavesoftware/go-magetasks v0.6.0/go.mod h1:8/zmKLAbJFwjAZOF8U2BnmVa9aDrLPtd4XO5b67uflY= +github.com/wavesoftware/go-retcode v1.0.0 h1:Z53+VpIHMvRMtjS6jPScdihbAN1ks3lIJ5Mj32gCpno= +github.com/wavesoftware/go-retcode v1.0.0/go.mod h1:BLqIIXhB/PQ+izkkRGfSQgu95BDtMmUBuvTJ/gkSWVM= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -1398,11 +1373,8 @@ go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= @@ -1584,14 +1556,12 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1720,7 +1690,6 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1740,7 +1709,6 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1749,12 +1717,10 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1940,9 +1906,7 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= -google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0 h1:67zQnAE0T2rB0A3CwLSas0K+SbVzSxP+zTLkQLexeiw= @@ -2027,13 +1991,9 @@ google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211016002631-37fc39342514/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211027162914-98a5263abeca/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= @@ -2147,8 +2107,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= -gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/cli/cmd/build.go b/internal/cli/cmd/build.go index 4857e40bc..ace185d58 100644 --- a/internal/cli/cmd/build.go +++ b/internal/cli/cmd/build.go @@ -13,7 +13,7 @@ import ( var ErrCantBePresented = errors.New("can't be presented") type buildCommand struct { - *Cmd + *App event *cli.EventArgs } @@ -28,14 +28,12 @@ func (b *buildCommand) command() *cobra.Command { } func (b *buildCommand) run(cmd *cobra.Command, _ []string) error { - b.options.OutWriter = cmd.OutOrStdout() - b.options.ErrWriter = cmd.ErrOrStderr() - c := configuration.CreateCli() + c := configuration.CreateCli(cmd) ce, err := c.CreateWithArgs(b.event) if err != nil { return cantBuildEventError(err) } - out, err := c.PresentWith(ce, b.options.Output) + out, err := c.PresentWith(ce, b.Output) if err != nil { return fmt.Errorf("event %w: %v", ErrCantBePresented, err) } diff --git a/internal/cli/cmd/build_test.go b/internal/cli/cmd/build_test.go index eb7e49d3f..3625c781c 100644 --- a/internal/cli/cmd/build_test.go +++ b/internal/cli/cmd/build_test.go @@ -6,8 +6,8 @@ import ( "testing" cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/wavesoftware/go-commandline" "gotest.tools/v3/assert" - "knative.dev/kn-plugin-event/internal/cli/cmd" "knative.dev/kn-plugin-event/pkg/event" "knative.dev/kn-plugin-event/pkg/tests" ) @@ -61,10 +61,10 @@ func newCmdArgs(args ...string) cmdArgs { func performTestsOnBuildSubCommand(t *testing.T, args cmdArgs, preparers ...eventPreparer) { t.Helper() buf := bytes.NewBuffer([]byte{}) - c := cmd.TestingCmd{} - c.Out(buf) - c.Args(args.args...) - assert.NilError(t, c.Execute()) + assert.NilError(t, testapp().Execute( + commandline.WithOutput(buf), + commandline.WithArgs(args.args...), + )) output := buf.Bytes() ec := newEventChecks(t) for _, preparer := range preparers { diff --git a/internal/cli/cmd/root.go b/internal/cli/cmd/root.go index 92bde5d1b..46b409553 100644 --- a/internal/cli/cmd/root.go +++ b/internal/cli/cmd/root.go @@ -2,83 +2,35 @@ package cmd import ( "fmt" - "io" - "os" "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/cli/retcode" "knative.dev/kn-plugin-event/pkg/metadata" ) -// Cmd represents a command line application entrypoint. -type Cmd struct { - options *cli.Options - root *cobra.Command - exit func(code int) -} +// Options to override the commandline for testing purposes. +var Options []commandline.Option //nolint:gochecknoglobals -// Execute will execute the application. -func (c *Cmd) Execute() { - if err := c.execute(); err != nil { - c.exit(retcode.Calc(err)) - } +type App struct { + cli.Options } -// ExecuteWithOptions will execute the application with the provided options. -func (c *Cmd) ExecuteWithOptions(options ...CommandOption) error { - return c.execute(options...) -} - -// WithArgs creates an option which sets args. -func WithArgs(args ...string) CommandOption { - return func(command *cobra.Command) { - command.SetArgs(args) - } -} - -// WithOutput creates an option witch sets os.Stdout and os.Stderr. -func WithOutput(out io.Writer) CommandOption { - return func(command *cobra.Command) { - command.SetOut(out) - command.SetErr(out) - } -} - -// CommandOption is used to configure a command in Cmd.ExecuteWithOptions. -type CommandOption func(*cobra.Command) - -func (c *Cmd) execute(configs ...CommandOption) error { - c.init() - for _, config := range configs { - config(c.root) - } - // cobra.Command should pass our own errors, no need to wrap them. - return c.root.Execute() //nolint:wrapcheck -} - -func (c *Cmd) init() { - if c.root != nil { - return - } - c.exit = os.Exit - c.options = &cli.Options{} - c.root = &cobra.Command{ +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, } - c.root.SetOut(os.Stdout) - c.root.SetErr(os.Stderr) - c.root.PersistentFlags().BoolVarP( - &c.options.Verbose, "verbose", "v", + c.PersistentFlags().BoolVarP( + &a.Verbose, "verbose", "v", false, "verbose output", ) - c.root.PersistentFlags().VarP( - enumflag.New(&c.options.Output, "output", outputModeIds(), enumflag.EnumCaseInsensitive), + c.PersistentFlags().VarP( + enumflag.New(&a.Output, "output", outputModeIds(), enumflag.EnumCaseInsensitive), "output", "o", "Output format. One of: human|json|yaml.", ) @@ -86,33 +38,32 @@ func (c *Cmd) init() { eventArgs := &cli.EventArgs{} targetArgs := &cli.TargetArgs{} commands := []subcommand{ - &buildCommand{Cmd: c, event: eventArgs}, - &sendCommand{Cmd: c, event: eventArgs, target: targetArgs}, - &versionCommand{Cmd: c}, + &buildCommand{App: a, event: eventArgs}, + &sendCommand{App: a, event: eventArgs, target: targetArgs}, + &versionCommand{App: a}, } for _, each := range commands { - c.root.AddCommand(each.command()) + c.AddCommand(each.command()) } - c.root.PersistentFlags().StringVar( - &c.options.KubeconfigOptions.Path, "kubeconfig", "", + c.PersistentFlags().StringVar( + &a.KubeconfigOptions.Path, "kubeconfig", "", "kubectl configuration file (default: ~/.kube/config)", ) - c.root.PersistentFlags().StringVar( - &c.options.KubeconfigOptions.Context, "context", "", + c.PersistentFlags().StringVar( + &a.KubeconfigOptions.Context, "context", "", "name of the kubeconfig context to use", ) - c.root.PersistentFlags().StringVar( - &c.options.KubeconfigOptions.Cluster, "cluster", "", + c.PersistentFlags().StringVar( + &a.KubeconfigOptions.Cluster, "cluster", "", "name of the kubeconfig cluster to use", ) - c.root.PersistentPreRun = func(cmd *cobra.Command, args []string) { - c.options.OutWriter = cmd.OutOrStdout() - c.options.ErrWriter = cmd.ErrOrStderr() - } + return c } +var _ commandline.CobraProvider = new(App) + func outputModeIds() map[cli.OutputMode][]string { return map[cli.OutputMode][]string{ cli.HumanReadable: {"human"}, diff --git a/internal/cli/cmd/root_test.go b/internal/cli/cmd/root_test.go index ceb234328..3de66104c 100644 --- a/internal/cli/cmd/root_test.go +++ b/internal/cli/cmd/root_test.go @@ -2,23 +2,29 @@ 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) { - called := false - c := cmd.TestingCmd{} - c.Exit(func(code int) { - t.Logf("exit code received: %d", code) - called = true - }) - c.Args("invalid-command") + retcode := math.MinInt64 buf := bytes.NewBuffer([]byte{}) - c.Out(buf) - c.ExecuteOrFail() + testapp().ExecuteOrDie( + commandline.WithOutput(buf), + commandline.WithExit(func(code int) { + retcode = code + }), + commandline.WithArgs("invalid-command"), + ) - assert.Check(t, called) + 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 index acaf76283..c97b11ba0 100644 --- a/internal/cli/cmd/send.go +++ b/internal/cli/cmd/send.go @@ -22,7 +22,7 @@ var ( type sendCommand struct { target *cli.TargetArgs event *cli.EventArgs - *Cmd + *App } func (s *sendCommand) command() *cobra.Command { @@ -74,13 +74,13 @@ isn't specified target URL will not be changed. This option can't be used with return c } -func (s *sendCommand) run(_ *cobra.Command, _ []string) error { - c := configuration.CreateCli() +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) + err = c.Send(*ce, *s.target, &s.Options) if err != nil { return cantSentEvent(err) } diff --git a/internal/cli/cmd/send_test.go b/internal/cli/cmd/send_test.go index d3a4df9e5..c016e5e83 100644 --- a/internal/cli/cmd/send_test.go +++ b/internal/cli/cmd/send_test.go @@ -6,27 +6,27 @@ import ( "strings" "testing" + "github.com/wavesoftware/go-commandline" "gotest.tools/v3/assert" - "knative.dev/kn-plugin-event/internal/cli/cmd" "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 { - tc := cmd.TestingCmd{} - tc.Args( - "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", + 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", + ), ) - tc.Out(buf) - return tc.Execute() }) assert.NilError(t, err) out := buf.String() diff --git a/internal/cli/cmd/testing.go b/internal/cli/cmd/testing.go deleted file mode 100644 index cc6440098..000000000 --- a/internal/cli/cmd/testing.go +++ /dev/null @@ -1,46 +0,0 @@ -package cmd - -import "io" - -// TestingCmd a wrapper for Cmd to ease of testing. -type TestingCmd struct { - *Cmd -} - -// ExecuteOrFail the command or fail on error. -func (c *TestingCmd) ExecuteOrFail() { - c.init() - c.Cmd.Execute() -} - -// Execute the command and return error if any. -func (c *TestingCmd) Execute() error { - c.init() - return c.execute() -} - -// Exit sets the exit command that accepts retcode. -func (c *TestingCmd) Exit(fn func(code int)) { - c.init() - c.exit = fn -} - -// Out sets output stream to cmd. -func (c *TestingCmd) Out(newOut io.Writer) { - c.init() - c.root.SetOut(newOut) - c.root.SetErr(newOut) -} - -// Args set to main command to be executed. -func (c *TestingCmd) Args(args ...string) { - c.init() - c.root.SetArgs(args) -} - -func (c *TestingCmd) init() { - if c.Cmd == nil { - c.Cmd = &Cmd{} - } - c.Cmd.init() -} diff --git a/internal/cli/cmd/version.go b/internal/cli/cmd/version.go index 224ee9a18..03cc6325c 100644 --- a/internal/cli/cmd/version.go +++ b/internal/cli/cmd/version.go @@ -16,7 +16,7 @@ import ( var ErrUnsupportedOutputMode = errors.New("unsupported mode") type versionCommand struct { - *Cmd + *App } func (v *versionCommand) command() *cobra.Command { @@ -32,7 +32,7 @@ func (v *versionCommand) run(cmd *cobra.Command, _ []string) error { Name: metadata.PluginName, Version: metadata.Version, Image: metadata.ResolveImage(), - }, v.options.Output) + }, v.Output) if err != nil { return err } diff --git a/internal/cli/cmd/version_test.go b/internal/cli/cmd/version_test.go index 430ad9e5a..ab167795b 100644 --- a/internal/cli/cmd/version_test.go +++ b/internal/cli/cmd/version_test.go @@ -6,9 +6,9 @@ import ( "regexp" "testing" + "github.com/wavesoftware/go-commandline" "gopkg.in/yaml.v2" "gotest.tools/v3/assert" - "knative.dev/kn-plugin-event/internal/cli/cmd" "knative.dev/kn-plugin-event/pkg/cli" "knative.dev/kn-plugin-event/pkg/metadata" ) @@ -38,11 +38,11 @@ type unmarshalFunc func(in []byte, out interface{}) error func versionSubCommandChecks(t *testing.T, format string, unmarshal unmarshalFunc) { t.Helper() - tc := cmd.TestingCmd{} - tc.Args("version", "-o", format) buf := bytes.NewBuffer([]byte{}) - tc.Out(buf) - assert.NilError(t, tc.Execute()) + assert.NilError(t, testapp().Execute( + commandline.WithOutput(buf), + commandline.WithArgs("version", "-o", format), + )) pv := cli.PluginVersionOutput{} assert.NilError(t, unmarshal(buf.Bytes(), &pv)) @@ -51,11 +51,11 @@ func versionSubCommandChecks(t *testing.T, format string, unmarshal unmarshalFun } func TestPresentAsWithInvalidOutput(t *testing.T) { - tc := cmd.TestingCmd{} buf := bytes.NewBuffer([]byte{}) - tc.Out(buf) - tc.Args("version", "-o", "invalid") - err := tc.Execute() + err := testapp().Execute( + commandline.WithOutput(buf), + commandline.WithArgs("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 new file mode 100644 index 000000000..e1be74a25 --- /dev/null +++ b/internal/ics/app.go @@ -0,0 +1,58 @@ +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" + "knative.dev/pkg/logging" +) + +// Options to override the commandline for testing purposes. +var Options []commandline.Option //nolint:gochecknoglobals + +type App struct{} + +func (a App) Command() *cobra.Command { + return &cobra.Command{ + Use: "ics", + SilenceUsage: true, + SilenceErrors: true, + RunE: a.run, + } +} + +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() + if err != nil { + log.Error(zap.Error(err)) + } + return err //nolint:wrapcheck +} + +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 new file mode 100644 index 000000000..b23880d23 --- /dev/null +++ b/internal/ics/app_test.go @@ -0,0 +1,48 @@ +package ics_test + +import ( + "bytes" + "net/url" + "strings" + "testing" + "time" + + cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/google/uuid" + "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/tests" +) + +func TestApp(t *testing.T) { + var outBuf bytes.Buffer + opts := []commandline.Option{ + commandline.WithOutput(&outBuf), + } + id := uuid.New().String() + want := cloudevents.NewEvent() + want.SetID(id) + want.SetTime(time.Now().UTC()) + want.SetType("example") + want.SetSource("tests://example") + kevent, err := ics.Encode(want) + assert.NilError(t, err) + + got, err := tests.WithCloudEventsServer(func(serverURL url.URL) error { + env := map[string]string{ + "K_SINK": serverURL.String(), + "K_EVENT": kevent, + } + return tests.WithEnviron(env, func() error { + 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)) +} diff --git a/pkg/cli/create.go b/pkg/cli/create.go index bc5d1d72f..e56a207b9 100644 --- a/pkg/cli/create.go +++ b/pkg/cli/create.go @@ -34,7 +34,7 @@ var ( ) // CreateWithArgs will create an event by parsing given args. -func (c *App) CreateWithArgs(args *EventArgs) (*cloudevents.Event, error) { +func (a *App) CreateWithArgs(args *EventArgs) (*cloudevents.Event, error) { spec := &event.Spec{ Type: args.Type, ID: args.ID, @@ -66,7 +66,7 @@ func (c *App) CreateWithArgs(args *EventArgs) (*cloudevents.Event, error) { } // PresentWith will present an event with specified output. -func (c *App) PresentWith(e *cloudevents.Event, output OutputMode) (string, error) { +func (a *App) PresentWith(e *cloudevents.Event, output OutputMode) (string, error) { switch output { case HumanReadable: return presentEventAsHumanReadable(e) diff --git a/pkg/cli/ics/send.go b/pkg/cli/ics/send.go index e7a6969b7..3165e6bc4 100644 --- a/pkg/cli/ics/send.go +++ b/pkg/cli/ics/send.go @@ -6,7 +6,9 @@ import ( cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/kelseyhightower/envconfig" + "go.uber.org/zap" "knative.dev/kn-plugin-event/pkg/event" + "knative.dev/pkg/logging" ) // SendFromEnv will send an event based on a values stored in environmental @@ -20,6 +22,8 @@ func (app *App) SendFromEnv() error { if err != nil { return fmt.Errorf("%w: %v", ErrCantSendWithICS, err) } + log := logging.FromContext(app.Context()) + log.Infow("Event sent", zap.String("ce-id", c.ce.ID())) return nil } diff --git a/pkg/cli/ics/send_test.go b/pkg/cli/ics/send_test.go index 9b1829ac1..5a53b27a3 100644 --- a/pkg/cli/ics/send_test.go +++ b/pkg/cli/ics/send_test.go @@ -1,6 +1,7 @@ package ics_test import ( + "context" "testing" "time" @@ -8,7 +9,9 @@ import ( "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/tests" + "knative.dev/reconciler-test/pkg/logging" ) func TestSendFromEnv(t *testing.T) { @@ -24,11 +27,14 @@ func TestSendFromEnv(t *testing.T) { "K_SINK": "http://cosmos.custer.local", "K_EVENT": kevent, } - app := ics.App{Binding: event.Binding{ - CreateSender: func(target *event.Target) (event.Sender, error) { - return sender, nil + app := ics.App{ + Binding: event.Binding{ + CreateSender: func(target *event.Target) (event.Sender, error) { + return sender, nil + }, }, - }} + Environment: system.WithContext(logging.WithTestLogger(context.TODO(), t), nil), + } err = tests.WithEnviron(env, app.SendFromEnv) assert.NilError(t, err) assert.Equal(t, 1, len(sender.Sent)) diff --git a/pkg/cli/ics/types.go b/pkg/cli/ics/types.go index c2c0a7cc5..ea2925fd4 100644 --- a/pkg/cli/ics/types.go +++ b/pkg/cli/ics/types.go @@ -4,6 +4,7 @@ import ( "errors" "knative.dev/kn-plugin-event/pkg/event" + "knative.dev/kn-plugin-event/pkg/system" ) var ( @@ -30,4 +31,5 @@ type Args struct { // App holds an ICS app binding. type App struct { event.Binding + system.Environment } diff --git a/pkg/cli/options.go b/pkg/cli/options.go index 513ce7238..843401f6f 100644 --- a/pkg/cli/options.go +++ b/pkg/cli/options.go @@ -12,10 +12,11 @@ import ( "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() (*event.Properties, error) { +func (opts *Options) WithLogger(outputs system.Outputs) (*event.Properties, error) { zc := zap.NewProductionConfig() cfg := zap.NewProductionEncoderConfig() if opts.Verbose { @@ -37,8 +38,8 @@ func (opts *Options) WithLogger() (*event.Properties, error) { case JSON: encoder = zapcore.NewJSONEncoder(cfg) } - sink := zapcore.AddSync(opts.OutWriter) - errSink := zapcore.AddSync(opts.ErrWriter) + sink := zapcore.AddSync(outputs.OutOrStdout()) + errSink := zapcore.AddSync(outputs.ErrOrStderr()) zcore := zapcore.NewCore(encoder, sink, zc.Level) log := zap.New( zcore, buildOptions(zc, errSink)..., diff --git a/pkg/cli/retcode/retcode.go b/pkg/cli/retcode/retcode.go deleted file mode 100644 index c3507cbe3..000000000 --- a/pkg/cli/retcode/retcode.go +++ /dev/null @@ -1,11 +0,0 @@ -package retcode - -import "hash/crc32" - -// Calc will calculate an POSIX retcode from an error. -func Calc(err error) int { - if err == nil { - return 0 - } - return int(crc32.ChecksumIEEE([]byte(err.Error())))%254 + 1 -} diff --git a/pkg/cli/retcode/retcode_test.go b/pkg/cli/retcode/retcode_test.go deleted file mode 100644 index 515b38a45..000000000 --- a/pkg/cli/retcode/retcode_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package retcode_test - -import ( - "fmt" - "testing" - - "knative.dev/kn-plugin-event/pkg/cli/retcode" - "knative.dev/kn-plugin-event/pkg/event" -) - -func TestCalc(t *testing.T) { - cases := testCases() - for i := range cases { - tt := cases[i] - t.Run(tt.name, func(t *testing.T) { - if got := retcode.Calc(tt.err); got != tt.want { - t.Errorf("Calc() = %v, want %v", got, tt.want) - } - }) - } -} - -func testCases() []testCase { - return []testCase{{ - name: "nil", - err: nil, - want: 0, - }, { - name: "event.ErrCantSentEvent", - err: event.ErrCantSentEvent, - want: 111, - }, { - name: "error of wrap caused by 12345", - err: fmt.Errorf("%w: 12345", event.ErrCantSentEvent), - want: 177, - }} -} - -type testCase struct { - name string - err error - want int -} diff --git a/pkg/cli/send.go b/pkg/cli/send.go index fa48253b5..4ba5cd1e6 100644 --- a/pkg/cli/send.go +++ b/pkg/cli/send.go @@ -9,16 +9,16 @@ import ( ) // Send will send CloudEvent to target. -func (c *App) Send(ce cloudevents.Event, target TargetArgs, options *Options) error { - props, err := options.WithLogger() +func (a *App) Send(ce cloudevents.Event, target TargetArgs, options *Options) error { + props, err := options.WithLogger(a) if err != nil { return err } - t, err := c.createTarget(target, props) + t, err := a.createTarget(target, props) if err != nil { return err } - s, err := c.Binding.NewSender(t) + s, err := a.Binding.NewSender(t) if err != nil { return cantSentEvent(err) } diff --git a/pkg/cli/send_test.go b/pkg/cli/send_test.go index 29603f6b9..8e83bdf7a 100644 --- a/pkg/cli/send_test.go +++ b/pkg/cli/send_test.go @@ -10,6 +10,7 @@ import ( "gotest.tools/v3/assert" "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/tests" ) @@ -34,7 +35,10 @@ func createExampleEvent() cloudevents.Event { func assertWithOutputMode(t *testing.T, want cloudevents.Event, mode cli.OutputMode) { t.Helper() - var buf bytes.Buffer + var ( + outBuf bytes.Buffer + errBuf bytes.Buffer + ) sender := &tests.Sender{} app := cli.App{ Binding: event.Binding{ @@ -42,21 +46,21 @@ func assertWithOutputMode(t *testing.T, want cloudevents.Event, mode cli.OutputM return sender, nil }, }, + Environment: system.WithOutputs(&outBuf, &errBuf, nil), } err := app.Send( want, cli.TargetArgs{URL: "http://example.org"}, &cli.Options{ - Output: mode, - OutWriter: &buf, + Output: mode, }, ) assert.NilError(t, err) - out := buf.String() assert.Equal(t, 1, len(sender.Sent)) assert.Equal(t, want.ID(), sender.Sent[0].ID()) - assert.Check(t, strings.Contains(out, + outputs := outBuf.String() + assert.Check(t, strings.Contains(outputs, fmt.Sprintf("Event (ID: %s) have been sent.", want.ID()), )) } diff --git a/pkg/cli/target.go b/pkg/cli/target.go index b9f96f57b..76a08afe7 100644 --- a/pkg/cli/target.go +++ b/pkg/cli/target.go @@ -62,9 +62,9 @@ func validateAddressableURI(uri string) error { return nil } -func (c *App) createTarget(args TargetArgs, props *event.Properties) (*event.Target, error) { +func (a *App) createTarget(args TargetArgs, props *event.Properties) (*event.Target, error) { if args.Addressable != "" { - args, err := c.fillInDefaultNamespace(args, props) + args, err := a.fillInDefaultNamespace(args, props) if err != nil { return nil, err } @@ -97,9 +97,9 @@ func (c *App) createTarget(args TargetArgs, props *event.Properties) (*event.Tar return nil, ErrUseToURLOrToFlagIsRequired } -func (c *App) fillInDefaultNamespace(args TargetArgs, props *event.Properties) (TargetArgs, error) { +func (a *App) fillInDefaultNamespace(args TargetArgs, props *event.Properties) (TargetArgs, error) { if len(args.Namespace) == 0 || len(args.SenderNamespace) == 0 { - defaultNs, err := c.DefaultNamespace(props) + defaultNs, err := a.DefaultNamespace(props) if err != nil { return TargetArgs{}, cantSentEvent(err) } diff --git a/pkg/cli/types.go b/pkg/cli/types.go index e313a1b85..bea589e4a 100644 --- a/pkg/cli/types.go +++ b/pkg/cli/types.go @@ -1,10 +1,9 @@ package cli import ( - "io" - "github.com/thediveo/enumflag" "knative.dev/kn-plugin-event/pkg/event" + "knative.dev/kn-plugin-event/pkg/system" ) // Options holds a general args for all commands. @@ -17,9 +16,6 @@ type Options struct { // Verbose tells does commands should display additional information about // what's happening? Verbose information is printed on stderr. Verbose bool - - OutWriter io.Writer - ErrWriter io.Writer } // EventArgs holds args of event to be created with. @@ -53,4 +49,5 @@ const ( // App object. type App struct { event.Binding + system.Environment } diff --git a/pkg/configuration/cli.go b/pkg/configuration/cli.go index a097374df..cd811a14d 100644 --- a/pkg/configuration/cli.go +++ b/pkg/configuration/cli.go @@ -2,10 +2,14 @@ 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() *cli.App { +func CreateCli(env system.Environment) *cli.App { binding := senderBinding() - return &cli.App{Binding: eventsBinding(binding)} + return &cli.App{ + Binding: eventsBinding(binding), + Environment: env, + } } diff --git a/pkg/configuration/ics.go b/pkg/configuration/ics.go index f1b6aa02c..bbb4f1b6b 100644 --- a/pkg/configuration/ics.go +++ b/pkg/configuration/ics.go @@ -2,10 +2,14 @@ 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() *ics.App { +func CreateIcs(env system.Environment) *ics.App { binding := senderBinding() - return &ics.App{Binding: eventsBinding(binding)} + return &ics.App{ + Binding: eventsBinding(binding), + Environment: env, + } } diff --git a/pkg/configuration/memoized_test.go b/pkg/configuration/memoized_test.go index 1298626c4..9900b0bfc 100644 --- a/pkg/configuration/memoized_test.go +++ b/pkg/configuration/memoized_test.go @@ -43,12 +43,12 @@ func testMemoizeKubeClientsCases(fn func(tc testMemoizeKubeClientsCase)) { tcs := []testMemoizeKubeClientsCase{{ name: "cli", fn: func() event.Binding { - return configuration.CreateCli().Binding + return configuration.CreateCli(nil).Binding }, }, { name: "ics", fn: func() event.Binding { - return configuration.CreateIcs().Binding + return configuration.CreateIcs(nil).Binding }, }} for _, tc := range tcs { diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 819ea9e45..67d8ae765 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -3,6 +3,7 @@ package plugin import ( "io" + "github.com/wavesoftware/go-commandline" knplugin "knative.dev/client/pkg/kn/plugin" "knative.dev/kn-plugin-event/internal/cli/cmd" "knative.dev/kn-plugin-event/pkg/metadata" @@ -24,13 +25,13 @@ func (p plugin) Name() string { } func (p plugin) Execute(args []string) error { - opts := []cmd.CommandOption{ - cmd.WithArgs(args...), + opts := []commandline.Option{ + commandline.WithArgs(args...), } if p.Writer != nil { - opts = append(opts, cmd.WithOutput(p.Writer)) + opts = append(opts, commandline.WithOutput(p.Writer)) } - return new(cmd.Cmd).ExecuteWithOptions(opts...) //nolint:wrapcheck + return commandline.New(new(cmd.App)).Execute(opts...) //nolint:wrapcheck } func (p plugin) Description() (string, error) { diff --git a/pkg/system/environment.go b/pkg/system/environment.go new file mode 100644 index 000000000..dd5d0d92f --- /dev/null +++ b/pkg/system/environment.go @@ -0,0 +1,67 @@ +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/third_party/VENDOR-LICENSE/github.com/wavesoftware/go-commandline/LICENSE b/third_party/VENDOR-LICENSE/github.com/wavesoftware/go-commandline/LICENSE new file mode 100644 index 000000000..056124684 --- /dev/null +++ b/third_party/VENDOR-LICENSE/github.com/wavesoftware/go-commandline/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Wave Software + + 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. diff --git a/third_party/VENDOR-LICENSE/github.com/wavesoftware/go-retcode/LICENSE b/third_party/VENDOR-LICENSE/github.com/wavesoftware/go-retcode/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/third_party/VENDOR-LICENSE/github.com/wavesoftware/go-retcode/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/spf13/cobra/CHANGELOG.md b/vendor/github.com/spf13/cobra/CHANGELOG.md deleted file mode 100644 index 8a23b4f85..000000000 --- a/vendor/github.com/spf13/cobra/CHANGELOG.md +++ /dev/null @@ -1,51 +0,0 @@ -# Cobra Changelog - -## v1.1.3 - -* **Fix:** release-branch.cobra1.1 only: Revert "Deprecate Go < 1.14" to maintain backward compatibility - -## v1.1.2 - -### Notable Changes - -* Bump license year to 2021 in golden files (#1309) @Bowbaq -* Enhance PowerShell completion with custom comp (#1208) @Luap99 -* Update gopkg.in/yaml.v2 to v2.4.0: The previous breaking change in yaml.v2 v2.3.0 has been reverted, see go-yaml/yaml#670 -* Documentation readability improvements (#1228 etc.) @zaataylor etc. -* Use golangci-lint: Repair warnings and errors resulting from linting (#1044) @umarcor - -## v1.1.1 - -* **Fix:** yaml.v2 2.3.0 contained a unintended breaking change. This release reverts to yaml.v2 v2.2.8 which has recent critical CVE fixes, but does not have the breaking changes. See https://github.com/spf13/cobra/pull/1259 for context. -* **Fix:** correct internal formatting for go-md2man v2 (which caused man page generation to be broken). See https://github.com/spf13/cobra/issues/1049 for context. - -## v1.1.0 - -### Notable Changes - -* Extend Go completions and revamp zsh comp (#1070) -* Fix man page doc generation - no auto generated tag when `cmd.DisableAutoGenTag = true` (#1104) @jpmcb -* Add completion for help command (#1136) -* Complete subcommands when TraverseChildren is set (#1171) -* Fix stderr printing functions (#894) -* fix: fish output redirection (#1247) - -## v1.0.0 - -Announcing v1.0.0 of Cobra. 🎉 - -### Notable Changes -* Fish completion (including support for Go custom completion) @marckhouzam -* API (urgent): Rename BashCompDirectives to ShellCompDirectives @marckhouzam -* Remove/replace SetOutput on Command - deprecated @jpmcb -* add support for autolabel stale PR @xchapter7x -* Add Labeler Actions @xchapter7x -* Custom completions coded in Go (instead of Bash) @marckhouzam -* Partial Revert of #922 @jharshman -* Add Makefile to project @jharshman -* Correct documentation for InOrStdin @desponda -* Apply formatting to templates @jharshman -* Revert change so help is printed on stdout again @marckhouzam -* Update md2man to v2.0.0 @pdf -* update viper to v1.4.0 @umarcor -* Update cmd/root.go example in README.md @jharshman diff --git a/vendor/github.com/spf13/cobra/MAINTAINERS b/vendor/github.com/spf13/cobra/MAINTAINERS new file mode 100644 index 000000000..4c5ac3dd9 --- /dev/null +++ b/vendor/github.com/spf13/cobra/MAINTAINERS @@ -0,0 +1,13 @@ +maintainers: +- spf13 +- johnSchnake +- jpmcb +- marckhouzam +inactive: +- anthonyfok +- bep +- bogem +- broady +- eparis +- jharshman +- wfernandes diff --git a/vendor/github.com/spf13/cobra/Makefile b/vendor/github.com/spf13/cobra/Makefile index 5880f04eb..443ef1a98 100644 --- a/vendor/github.com/spf13/cobra/Makefile +++ b/vendor/github.com/spf13/cobra/Makefile @@ -9,11 +9,11 @@ ifeq (, $(shell which richgo)) $(warning "could not find richgo in $(PATH), run: go get github.com/kyoh86/richgo") endif -.PHONY: fmt lint test cobra_generator install_deps clean +.PHONY: fmt lint test install_deps clean default: all -all: fmt test cobra_generator +all: fmt test fmt: $(info ******************** checking formatting ********************) @@ -27,11 +27,6 @@ test: install_deps $(info ******************** running tests ********************) richgo test -v ./... -cobra_generator: install_deps - $(info ******************** building generator ********************) - mkdir -p $(BIN) - make -C cobra all - install_deps: $(info ******************** downloading dependencies ********************) go get -v ./... diff --git a/vendor/github.com/spf13/cobra/README.md b/vendor/github.com/spf13/cobra/README.md index 1ade1081c..2bf152082 100644 --- a/vendor/github.com/spf13/cobra/README.md +++ b/vendor/github.com/spf13/cobra/README.md @@ -1,13 +1,13 @@ ![cobra logo](https://cloud.githubusercontent.com/assets/173412/10886352/ad566232-814f-11e5-9cd0-aa101788c117.png) -Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files. +Cobra is a library for creating powerful modern CLI applications. -Cobra is used in many Go projects such as [Kubernetes](http://kubernetes.io/), +Cobra is used in many Go projects such as [Kubernetes](https://kubernetes.io/), [Hugo](https://gohugo.io), and [Github CLI](https://github.com/cli/cli) to name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using Cobra. [![](https://img.shields.io/github/workflow/status/spf13/cobra/Test?longCache=tru&label=Test&logo=github%20actions&logoColor=fff)](https://github.com/spf13/cobra/actions?query=workflow%3ATest) -[![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra) +[![Go Reference](https://pkg.go.dev/badge/github.com/spf13/cobra.svg)](https://pkg.go.dev/github.com/spf13/cobra) [![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cobra)](https://goreportcard.com/report/github.com/spf13/cobra) [![Slack](https://img.shields.io/badge/Slack-cobra-brightgreen)](https://gophers.slack.com/archives/CD3LP1199) @@ -16,15 +16,11 @@ name a few. [This list](./projects_using_cobra.md) contains a more extensive lis Cobra is a library providing a simple interface to create powerful modern CLI interfaces similar to git & go tools. -Cobra is also an application that will generate your application scaffolding to rapidly -develop a Cobra-based application. - Cobra provides: * Easy subcommand-based CLIs: `app server`, `app fetch`, etc. * Fully POSIX-compliant flags (including short & long versions) * Nested subcommands * Global, local and cascading flags -* Easy generation of applications & commands with `cobra init` & `cobra add cmdname` * Intelligent suggestions (`app srver`... did you mean `app server`?) * Automatic help generation for commands and flags * Automatic help flag recognition of `-h`, `--help`, etc. @@ -32,7 +28,7 @@ Cobra provides: * Automatically generated man pages for your application * Command aliases so you can change things without breaking them * The flexibility to define your own help, usage, etc. -* Optional seamless integration with [viper](http://github.com/spf13/viper) for 12-factor apps +* Optional seamless integration with [viper](https://github.com/spf13/viper) for 12-factor apps # Concepts @@ -83,10 +79,11 @@ which maintains the same interface while adding POSIX compliance. # Installing Using Cobra is easy. First, use `go get` to install the latest version -of the library. This command will install the `cobra` generator executable -along with the library and its dependencies: +of the library. - go get -u github.com/spf13/cobra +``` +go get -u github.com/spf13/cobra@latest +``` Next, include Cobra in your application: @@ -95,10 +92,17 @@ import "github.com/spf13/cobra" ``` # Usage -Cobra provides its own program that will create your application and add any -commands you want. It's the easiest way to incorporate Cobra into your application. +`cobra-cli` is a command line program to generate cobra applications and command files. +It will bootstrap your application scaffolding to rapidly +develop a Cobra-based application. It is the easiest way to incorporate Cobra into your application. + +It can be installed by running: + +``` +go install github.com/spf13/cobra-cli@latest +``` -For complete details on using the Cobra generator, please read [The Cobra Generator README](https://github.com/spf13/cobra/blob/master/cobra/README.md) +For complete details on using the Cobra-CLI generator, please read [The Cobra Generator README](https://github.com/spf13/cobra-cli/blob/main/README.md) For complete details on using the Cobra library, please read the [The Cobra User Guide](user_guide.md). diff --git a/vendor/github.com/spf13/cobra/active_help.go b/vendor/github.com/spf13/cobra/active_help.go new file mode 100644 index 000000000..0c631913d --- /dev/null +++ b/vendor/github.com/spf13/cobra/active_help.go @@ -0,0 +1,49 @@ +package cobra + +import ( + "fmt" + "os" + "strings" +) + +const ( + activeHelpMarker = "_activeHelp_ " + // The below values should not be changed: programs will be using them explicitly + // in their user documentation, and users will be using them explicitly. + activeHelpEnvVarSuffix = "_ACTIVE_HELP" + activeHelpGlobalEnvVar = "COBRA_ACTIVE_HELP" + activeHelpGlobalDisable = "0" +) + +// AppendActiveHelp adds the specified string to the specified array to be used as ActiveHelp. +// Such strings will be processed by the completion script and will be shown as ActiveHelp +// to the user. +// The array parameter should be the array that will contain the completions. +// This function can be called multiple times before and/or after completions are added to +// the array. Each time this function is called with the same array, the new +// ActiveHelp line will be shown below the previous ones when completion is triggered. +func AppendActiveHelp(compArray []string, activeHelpStr string) []string { + return append(compArray, fmt.Sprintf("%s%s", activeHelpMarker, activeHelpStr)) +} + +// GetActiveHelpConfig returns the value of the ActiveHelp environment variable +// _ACTIVE_HELP where is the name of the root command in upper +// case, with all - replaced by _. +// It will always return "0" if the global environment variable COBRA_ACTIVE_HELP +// is set to "0". +func GetActiveHelpConfig(cmd *Command) string { + activeHelpCfg := os.Getenv(activeHelpGlobalEnvVar) + if activeHelpCfg != activeHelpGlobalDisable { + activeHelpCfg = os.Getenv(activeHelpEnvVar(cmd.Root().Name())) + } + return activeHelpCfg +} + +// activeHelpEnvVar returns the name of the program-specific ActiveHelp environment +// variable. It has the format _ACTIVE_HELP where is the name of the +// root command in upper case, with all - replaced by _. +func activeHelpEnvVar(name string) string { + // This format should not be changed: users will be using it explicitly. + activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix)) + return strings.ReplaceAll(activeHelpEnvVar, "-", "_") +} diff --git a/vendor/github.com/spf13/cobra/active_help.md b/vendor/github.com/spf13/cobra/active_help.md new file mode 100644 index 000000000..5e7f59af3 --- /dev/null +++ b/vendor/github.com/spf13/cobra/active_help.md @@ -0,0 +1,157 @@ +# Active Help + +Active Help is a framework provided by Cobra which allows a program to define messages (hints, warnings, etc) that will be printed during program usage. It aims to make it easier for your users to learn how to use your program. If configured by the program, Active Help is printed when the user triggers shell completion. + +For example, +``` +bash-5.1$ helm repo add [tab] +You must choose a name for the repo you are adding. + +bash-5.1$ bin/helm package [tab] +Please specify the path to the chart to package + +bash-5.1$ bin/helm package [tab][tab] +bin/ internal/ scripts/ pkg/ testdata/ +``` + +**Hint**: A good place to use Active Help messages is when the normal completion system does not provide any suggestions. In such cases, Active Help nicely supplements the normal shell completions to guide the user in knowing what is expected by the program. +## Supported shells + +Active Help is currently only supported for the following shells: +- Bash (using [bash completion V2](shell_completions.md#bash-completion-v2) only). Note that bash 4.4 or higher is required for the prompt to appear when an Active Help message is printed. +- Zsh + +## Adding Active Help messages + +As Active Help uses the shell completion system, the implementation of Active Help messages is done by enhancing custom dynamic completions. If you are not familiar with dynamic completions, please refer to [Shell Completions](shell_completions.md). + +Adding Active Help is done through the use of the `cobra.AppendActiveHelp(...)` function, where the program repeatedly adds Active Help messages to the list of completions. Keep reading for details. + +### Active Help for nouns + +Adding Active Help when completing a noun is done within the `ValidArgsFunction(...)` of a command. Please notice the use of `cobra.AppendActiveHelp(...)` in the following example: + +```go +cmd := &cobra.Command{ + Use: "add [NAME] [URL]", + Short: "add a chart repository", + Args: require.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + return addRepo(args) + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var comps []string + if len(args) == 0 { + comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding") + } else if len(args) == 1 { + comps = cobra.AppendActiveHelp(comps, "You must specify the URL for the repo you are adding") + } else { + comps = cobra.AppendActiveHelp(comps, "This command does not take any more arguments") + } + return comps, cobra.ShellCompDirectiveNoFileComp + }, +} +``` +The example above defines the completions (none, in this specific example) as well as the Active Help messages for the `helm repo add` command. It yields the following behavior: +``` +bash-5.1$ helm repo add [tab] +You must choose a name for the repo you are adding + +bash-5.1$ helm repo add grafana [tab] +You must specify the URL for the repo you are adding + +bash-5.1$ helm repo add grafana https://grafana.github.io/helm-charts [tab] +This command does not take any more arguments +``` +**Hint**: As can be seen in the above example, a good place to use Active Help messages is when the normal completion system does not provide any suggestions. In such cases, Active Help nicely supplements the normal shell completions. + +### Active Help for flags + +Providing Active Help for flags is done in the same fashion as for nouns, but using the completion function registered for the flag. For example: +```go +_ = cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 2 { + return cobra.AppendActiveHelp(nil, "You must first specify the chart to install before the --version flag can be completed"), cobra.ShellCompDirectiveNoFileComp + } + return compVersionFlag(args[1], toComplete) + }) +``` +The example above prints an Active Help message when not enough information was given by the user to complete the `--version` flag. +``` +bash-5.1$ bin/helm install myrelease --version 2.0.[tab] +You must first specify the chart to install before the --version flag can be completed + +bash-5.1$ bin/helm install myrelease bitnami/solr --version 2.0.[tab][tab] +2.0.1 2.0.2 2.0.3 +``` + +## User control of Active Help + +You may want to allow your users to disable Active Help or choose between different levels of Active Help. It is entirely up to the program to define the type of configurability of Active Help that it wants to offer, if any. +Allowing to configure Active Help is entirely optional; you can use Active Help in your program without doing anything about Active Help configuration. + +The way to configure Active Help is to use the program's Active Help environment +variable. That variable is named `_ACTIVE_HELP` where `` is the name of your +program in uppercase with any `-` replaced by an `_`. The variable should be set by the user to whatever +Active Help configuration values are supported by the program. + +For example, say `helm` has chosen to support three levels for Active Help: `on`, `off`, `local`. Then a user +would set the desired behavior to `local` by doing `export HELM_ACTIVE_HELP=local` in their shell. + +For simplicity, when in `cmd.ValidArgsFunction(...)` or a flag's completion function, the program should read the +Active Help configuration using the `cobra.GetActiveHelpConfig(cmd)` function and select what Active Help messages +should or should not be added (instead of reading the environment variable directly). + +For example: +```go +ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + activeHelpLevel := cobra.GetActiveHelpConfig(cmd) + + var comps []string + if len(args) == 0 { + if activeHelpLevel != "off" { + comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding") + } + } else if len(args) == 1 { + if activeHelpLevel != "off" { + comps = cobra.AppendActiveHelp(comps, "You must specify the URL for the repo you are adding") + } + } else { + if activeHelpLevel == "local" { + comps = cobra.AppendActiveHelp(comps, "This command does not take any more arguments") + } + } + return comps, cobra.ShellCompDirectiveNoFileComp +}, +``` +**Note 1**: If the `_ACTIVE_HELP` environment variable is set to the string "0", Cobra will automatically disable all Active Help output (even if some output was specified by the program using the `cobra.AppendActiveHelp(...)` function). Using "0" can simplify your code in situations where you want to blindly disable Active Help without having to call `cobra.GetActiveHelpConfig(cmd)` explicitly. + +**Note 2**: If a user wants to disable Active Help for every single program based on Cobra, she can set the environment variable `COBRA_ACTIVE_HELP` to "0". In this case `cobra.GetActiveHelpConfig(cmd)` will return "0" no matter what the variable `_ACTIVE_HELP` is set to. + +**Note 3**: If the user does not set `_ACTIVE_HELP` or `COBRA_ACTIVE_HELP` (which will be a common case), the default value for the Active Help configuration returned by `cobra.GetActiveHelpConfig(cmd)` will be the empty string. +## Active Help with Cobra's default completion command + +Cobra provides a default `completion` command for programs that wish to use it. +When using the default `completion` command, Active Help is configurable in the same +fashion as described above using environment variables. You may wish to document this in more +details for your users. + +## Debugging Active Help + +Debugging your Active Help code is done in the same way as debugging your dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](shell_completions.md#debugging) for details. + +When debugging with the `__complete` command, if you want to specify different Active Help configurations, you should use the active help environment variable. That variable is named `_ACTIVE_HELP` where any `-` is replaced by an `_`. For example, we can test deactivating some Active Help as shown below: +``` +$ HELM_ACTIVE_HELP=1 bin/helm __complete install wordpress bitnami/h +bitnami/haproxy +bitnami/harbor +_activeHelp_ WARNING: cannot re-use a name that is still in use +:0 +Completion ended with directive: ShellCompDirectiveDefault + +$ HELM_ACTIVE_HELP=0 bin/helm __complete install wordpress bitnami/h +bitnami/haproxy +bitnami/harbor +:0 +Completion ended with directive: ShellCompDirectiveDefault +``` diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go index 6c360c595..cb7e19537 100644 --- a/vendor/github.com/spf13/cobra/bash_completions.go +++ b/vendor/github.com/spf13/cobra/bash_completions.go @@ -73,7 +73,8 @@ __%[1]s_handle_go_custom_completion() # Prepare the command to request completions for the program. # Calling ${words[0]} instead of directly %[1]s allows to handle aliases args=("${words[@]:1}") - requestComp="${words[0]} %[2]s ${args[*]}" + # Disable ActiveHelp which is not supported for bash completion v1 + requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}" lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} @@ -99,7 +100,7 @@ __%[1]s_handle_go_custom_completion() directive=0 fi __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" - __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}" + __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out}" if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then # Error code. No completion. @@ -125,7 +126,7 @@ __%[1]s_handle_go_custom_completion() local fullFilter filter filteringCmd # Do not use quotes around the $out variable or else newline # characters will be kept. - for filter in ${out[*]}; do + for filter in ${out}; do fullFilter+="$filter|" done @@ -136,7 +137,7 @@ __%[1]s_handle_go_custom_completion() # File completion for directories only local subdir # Use printf to strip any trailing newline - subdir=$(printf "%%s" "${out[0]}") + subdir=$(printf "%%s" "${out}") if [ -n "$subdir" ]; then __%[1]s_debug "Listing directories in $subdir" __%[1]s_handle_subdirs_in_dir_flag "$subdir" @@ -147,7 +148,7 @@ __%[1]s_handle_go_custom_completion() else while IFS='' read -r comp; do COMPREPLY+=("$comp") - done < <(compgen -W "${out[*]}" -- "$cur") + done < <(compgen -W "${out}" -- "$cur") fi } @@ -383,11 +384,11 @@ __%[1]s_handle_word() `, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) } func writePostscript(buf io.StringWriter, name string) { - name = strings.Replace(name, ":", "__", -1) + name = strings.ReplaceAll(name, ":", "__") WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name)) WriteStringAndCheck(buf, fmt.Sprintf(`{ local cur prev words cword split @@ -645,8 +646,8 @@ func gen(buf io.StringWriter, cmd *Command) { gen(buf, c) } commandName := cmd.CommandPath() - commandName = strings.Replace(commandName, " ", "_", -1) - commandName = strings.Replace(commandName, ":", "__", -1) + commandName = strings.ReplaceAll(commandName, " ", "_") + commandName = strings.ReplaceAll(commandName, ":", "__") if cmd.Root() == cmd { WriteStringAndCheck(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName)) diff --git a/vendor/github.com/spf13/cobra/bash_completionsV2.go b/vendor/github.com/spf13/cobra/bash_completionsV2.go index 82d26c175..767bf0312 100644 --- a/vendor/github.com/spf13/cobra/bash_completionsV2.go +++ b/vendor/github.com/spf13/cobra/bash_completionsV2.go @@ -78,7 +78,7 @@ __%[1]s_get_completion_results() { directive=0 fi __%[1]s_debug "The completion directive is: ${directive}" - __%[1]s_debug "The completions are: ${out[*]}" + __%[1]s_debug "The completions are: ${out}" } __%[1]s_process_completion_results() { @@ -111,13 +111,18 @@ __%[1]s_process_completion_results() { fi fi + # Separate activeHelp from normal completions + local completions=() + local activeHelp=() + __%[1]s_extract_activeHelp + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then # File extension filtering local fullFilter filter filteringCmd - # Do not use quotes around the $out variable or else newline + # Do not use quotes around the $completions variable or else newline # characters will be kept. - for filter in ${out[*]}; do + for filter in ${completions[*]}; do fullFilter+="$filter|" done @@ -129,7 +134,7 @@ __%[1]s_process_completion_results() { # Use printf to strip any trailing newline local subdir - subdir=$(printf "%%s" "${out[0]}") + subdir=$(printf "%%s" "${completions[0]}") if [ -n "$subdir" ]; then __%[1]s_debug "Listing directories in $subdir" pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return @@ -143,6 +148,43 @@ __%[1]s_process_completion_results() { __%[1]s_handle_special_char "$cur" : __%[1]s_handle_special_char "$cur" = + + # Print the activeHelp statements before we finish + if [ ${#activeHelp} -ne 0 ]; then + printf "\n"; + printf "%%s\n" "${activeHelp[@]}" + printf "\n" + + # The prompt format is only available from bash 4.4. + # We test if it is available before using it. + if (x=${PS1@P}) 2> /dev/null; then + printf "%%s" "${PS1@P}${COMP_LINE[@]}" + else + # Can't print the prompt. Just print the + # text the user had typed, it is workable enough. + printf "%%s" "${COMP_LINE[@]}" + fi + fi +} + +# Separate activeHelp lines from real completions. +# Fills the $activeHelp and $completions arrays. +__%[1]s_extract_activeHelp() { + local activeHelpMarker="%[8]s" + local endIndex=${#activeHelpMarker} + + while IFS='' read -r comp; do + if [ "${comp:0:endIndex}" = "$activeHelpMarker" ]; then + comp=${comp:endIndex} + __%[1]s_debug "ActiveHelp found: $comp" + if [ -n "$comp" ]; then + activeHelp+=("$comp") + fi + else + # Not an activeHelp line but a normal completion + completions+=("$comp") + fi + done < <(printf "%%s\n" "${out}") } __%[1]s_handle_completion_types() { @@ -154,17 +196,16 @@ __%[1]s_handle_completion_types() { # If the user requested inserting one completion at a time, or all # completions at once on the command-line we must remove the descriptions. # https://github.com/spf13/cobra/issues/1508 - local tab comp - tab=$(printf '\t') + local tab=$'\t' comp while IFS='' read -r comp; do + [[ -z $comp ]] && continue # Strip any description comp=${comp%%%%$tab*} # Only consider the completions that match - comp=$(compgen -W "$comp" -- "$cur") - if [ -n "$comp" ]; then + if [[ $comp == "$cur"* ]]; then COMPREPLY+=("$comp") fi - done < <(printf "%%s\n" "${out[@]}") + done < <(printf "%%s\n" "${completions[@]}") ;; *) @@ -175,44 +216,37 @@ __%[1]s_handle_completion_types() { } __%[1]s_handle_standard_completion_case() { - local tab comp - tab=$(printf '\t') + local tab=$'\t' comp + + # Short circuit to optimize if we don't have descriptions + if [[ "${completions[*]}" != *$tab* ]]; then + IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur") + return 0 + fi local longest=0 + local compline # Look for the longest completion so that we can format things nicely - while IFS='' read -r comp; do + while IFS='' read -r compline; do + [[ -z $compline ]] && continue # Strip any description before checking the length - comp=${comp%%%%$tab*} + comp=${compline%%%%$tab*} # Only consider the completions that match - comp=$(compgen -W "$comp" -- "$cur") + [[ $comp == "$cur"* ]] || continue + COMPREPLY+=("$compline") if ((${#comp}>longest)); then longest=${#comp} fi - done < <(printf "%%s\n" "${out[@]}") - - local completions=() - while IFS='' read -r comp; do - if [ -z "$comp" ]; then - continue - fi - - __%[1]s_debug "Original comp: $comp" - comp="$(__%[1]s_format_comp_descriptions "$comp" "$longest")" - __%[1]s_debug "Final comp: $comp" - completions+=("$comp") - done < <(printf "%%s\n" "${out[@]}") - - while IFS='' read -r comp; do - COMPREPLY+=("$comp") - done < <(compgen -W "${completions[*]}" -- "$cur") + done < <(printf "%%s\n" "${completions[@]}") # If there is a single completion left, remove the description text if [ ${#COMPREPLY[*]} -eq 1 ]; then __%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}" - comp="${COMPREPLY[0]%%%% *}" + comp="${COMPREPLY[0]%%%%$tab*}" __%[1]s_debug "Removed description from single completion, which is now: ${comp}" - COMPREPLY=() - COMPREPLY+=("$comp") + COMPREPLY[0]=$comp + else # Format the descriptions + __%[1]s_format_comp_descriptions $longest fi } @@ -231,45 +265,48 @@ __%[1]s_handle_special_char() __%[1]s_format_comp_descriptions() { - local tab - tab=$(printf '\t') - local comp="$1" - local longest=$2 - - # Properly format the description string which follows a tab character if there is one - if [[ "$comp" == *$tab* ]]; then - desc=${comp#*$tab} - comp=${comp%%%%$tab*} - - # $COLUMNS stores the current shell width. - # Remove an extra 4 because we add 2 spaces and 2 parentheses. - maxdesclength=$(( COLUMNS - longest - 4 )) - - # Make sure we can fit a description of at least 8 characters - # if we are to align the descriptions. - if [[ $maxdesclength -gt 8 ]]; then - # Add the proper number of spaces to align the descriptions - for ((i = ${#comp} ; i < longest ; i++)); do - comp+=" " - done - else - # Don't pad the descriptions so we can fit more text after the completion - maxdesclength=$(( COLUMNS - ${#comp} - 4 )) - fi + local tab=$'\t' + local comp desc maxdesclength + local longest=$1 + + local i ci + for ci in ${!COMPREPLY[*]}; do + comp=${COMPREPLY[ci]} + # Properly format the description string which follows a tab character if there is one + if [[ "$comp" == *$tab* ]]; then + __%[1]s_debug "Original comp: $comp" + desc=${comp#*$tab} + comp=${comp%%%%$tab*} + + # $COLUMNS stores the current shell width. + # Remove an extra 4 because we add 2 spaces and 2 parentheses. + maxdesclength=$(( COLUMNS - longest - 4 )) + + # Make sure we can fit a description of at least 8 characters + # if we are to align the descriptions. + if [[ $maxdesclength -gt 8 ]]; then + # Add the proper number of spaces to align the descriptions + for ((i = ${#comp} ; i < longest ; i++)); do + comp+=" " + done + else + # Don't pad the descriptions so we can fit more text after the completion + maxdesclength=$(( COLUMNS - ${#comp} - 4 )) + fi - # If there is enough space for any description text, - # truncate the descriptions that are too long for the shell width - if [ $maxdesclength -gt 0 ]; then - if [ ${#desc} -gt $maxdesclength ]; then - desc=${desc:0:$(( maxdesclength - 1 ))} - desc+="…" + # If there is enough space for any description text, + # truncate the descriptions that are too long for the shell width + if [ $maxdesclength -gt 0 ]; then + if [ ${#desc} -gt $maxdesclength ]; then + desc=${desc:0:$(( maxdesclength - 1 ))} + desc+="…" + fi + comp+=" ($desc)" fi - comp+=" ($desc)" + COMPREPLY[ci]=$comp + __%[1]s_debug "Final comp: $comp" fi - fi - - # Must use printf to escape all special characters - printf "%%q" "${comp}" + done } __start_%[1]s() @@ -310,7 +347,8 @@ fi # ex: ts=4 sw=4 et filetype=sh `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + activeHelpMarker)) } // GenBashCompletionFileV2 generates Bash completion version 2. diff --git a/vendor/github.com/spf13/cobra/command.go b/vendor/github.com/spf13/cobra/command.go index 2cc18891d..675bb1340 100644 --- a/vendor/github.com/spf13/cobra/command.go +++ b/vendor/github.com/spf13/cobra/command.go @@ -18,6 +18,7 @@ package cobra import ( "bytes" "context" + "errors" "fmt" "io" "os" @@ -166,7 +167,7 @@ type Command struct { // errWriter is a writer defined by the user that replaces stderr errWriter io.Writer - //FParseErrWhitelist flag parse errors to be ignored + // FParseErrWhitelist flag parse errors to be ignored FParseErrWhitelist FParseErrWhitelist // CompletionOptions is a set of options to control the handling of shell completion @@ -224,12 +225,23 @@ type Command struct { SuggestionsMinimumDistance int } -// Context returns underlying command context. If command wasn't -// executed with ExecuteContext Context returns Background context. +// Context returns underlying command context. If command was executed +// with ExecuteContext or the context was set with SetContext, the +// previously set context will be returned. Otherwise, nil is returned. +// +// Notice that a call to Execute and ExecuteC will replace a nil context of +// a command with a context.Background, so a background context will be +// returned by Context after one of these functions has been called. func (c *Command) Context() context.Context { return c.ctx } +// SetContext sets context for the command. It is set to context.Background by default and will be overwritten by +// Command.ExecuteContext or Command.ExecuteContextC +func (c *Command) SetContext(ctx context.Context) { + c.ctx = ctx +} + // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden // particularly useful when testing. func (c *Command) SetArgs(a []string) { @@ -852,6 +864,10 @@ func (c *Command) execute(a []string) (err error) { if err := c.validateRequiredFlags(); err != nil { return err } + if err := c.validateFlagGroups(); err != nil { + return err + } + if c.RunE != nil { if err := c.RunE(c, argWoFlags); err != nil { return err @@ -975,7 +991,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { if err != nil { // Always show help if requested, even if SilenceErrors is in // effect - if err == flag.ErrHelp { + if errors.Is(err, flag.ErrHelp) { cmd.HelpFunc()(cmd, args) return cmd, nil } @@ -997,7 +1013,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { func (c *Command) ValidateArgs(args []string) error { if c.Args == nil { - return nil + return ArbitraryArgs(c, args) } return c.Args(c, args) } diff --git a/vendor/github.com/spf13/cobra/completions.go b/vendor/github.com/spf13/cobra/completions.go index 9ecd56a47..2c2483998 100644 --- a/vendor/github.com/spf13/cobra/completions.go +++ b/vendor/github.com/spf13/cobra/completions.go @@ -103,6 +103,14 @@ func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string return nil, ShellCompDirectiveNoFileComp } +// FixedCompletions can be used to create a completion function which always +// returns the same results. +func FixedCompletions(choices []string, directive ShellCompDirective) func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return choices, directive + } +} + // RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag. func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error { flag := c.Flag(flagName) @@ -170,6 +178,12 @@ func (c *Command) initCompleteCmd(args []string) { noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd) for _, comp := range completions { + if GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable { + // Remove all activeHelp entries in this case + if strings.HasPrefix(comp, activeHelpMarker) { + continue + } + } if noDescriptions { // Remove any description that may be included following a tab character. comp = strings.Split(comp, "\t")[0] @@ -311,8 +325,11 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi var completions []string var directive ShellCompDirective + // Enforce flag groups before doing flag completions + finalCmd.enforceFlagGroupsForCompletion() + // Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true; - // doing this allows for completion of persistant flag names even for commands that disable flag parsing. + // doing this allows for completion of persistent flag names even for commands that disable flag parsing. // // When doing completion of a flag name, as soon as an argument starts with // a '-' we know it is a flag. We cannot use isFlagArg() here as it requires @@ -644,7 +661,7 @@ To load completions for every new session, execute once: #### macOS: - %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s + %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s You will need to start a new shell for this setup to take effect. `, c.Root().Name()), @@ -669,6 +686,10 @@ to enable it. You can execute the following once: echo "autoload -U compinit; compinit" >> ~/.zshrc +To load completions in your current shell session: + + source <(%[1]s completion zsh); compdef _%[1]s %[1]s + To load completions for every new session, execute once: #### Linux: @@ -677,7 +698,7 @@ To load completions for every new session, execute once: #### macOS: - %[1]s completion zsh > /usr/local/share/zsh/site-functions/_%[1]s + %[1]s completion zsh > $(brew --prefix)/share/zsh/site-functions/_%[1]s You will need to start a new shell for this setup to take effect. `, c.Root().Name()), diff --git a/vendor/github.com/spf13/cobra/fish_completions.go b/vendor/github.com/spf13/cobra/fish_completions.go index bb57fd568..005ee6be7 100644 --- a/vendor/github.com/spf13/cobra/fish_completions.go +++ b/vendor/github.com/spf13/cobra/fish_completions.go @@ -11,8 +11,8 @@ import ( func genFishComp(buf io.StringWriter, name string, includeDesc bool) { // Variables should not contain a '-' or ':' character nameForVar := name - nameForVar = strings.Replace(nameForVar, "-", "_", -1) - nameForVar = strings.Replace(nameForVar, ":", "_", -1) + nameForVar = strings.ReplaceAll(nameForVar, "-", "_") + nameForVar = strings.ReplaceAll(nameForVar, ":", "_") compCmd := ShellCompRequestCmd if !includeDesc { @@ -38,7 +38,8 @@ function __%[1]s_perform_completion __%[1]s_debug "args: $args" __%[1]s_debug "last arg: $lastArg" - set -l requestComp "$args[1] %[3]s $args[2..-1] $lastArg" + # Disable ActiveHelp which is not supported for fish shell + set -l requestComp "%[9]s=0 $args[1] %[3]s $args[2..-1] $lastArg" __%[1]s_debug "Calling $requestComp" set -l results (eval $requestComp 2> /dev/null) @@ -196,7 +197,7 @@ complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' `, nameForVar, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) } // GenFishCompletion generates fish completion file and writes to the passed writer. diff --git a/vendor/github.com/spf13/cobra/flag_groups.go b/vendor/github.com/spf13/cobra/flag_groups.go new file mode 100644 index 000000000..dc7843119 --- /dev/null +++ b/vendor/github.com/spf13/cobra/flag_groups.go @@ -0,0 +1,223 @@ +// Copyright © 2022 Steve Francia . +// +// 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 cobra + +import ( + "fmt" + "sort" + "strings" + + flag "github.com/spf13/pflag" +) + +const ( + requiredAsGroup = "cobra_annotation_required_if_others_set" + mutuallyExclusive = "cobra_annotation_mutually_exclusive" +) + +// MarkFlagsRequiredTogether marks the given flags with annotations so that Cobra errors +// if the command is invoked with a subset (but not all) of the given flags. +func (c *Command) MarkFlagsRequiredTogether(flagNames ...string) { + c.mergePersistentFlags() + for _, v := range flagNames { + f := c.Flags().Lookup(v) + if f == nil { + panic(fmt.Sprintf("Failed to find flag %q and mark it as being required in a flag group", v)) + } + if err := c.Flags().SetAnnotation(v, requiredAsGroup, append(f.Annotations[requiredAsGroup], strings.Join(flagNames, " "))); err != nil { + // Only errs if the flag isn't found. + panic(err) + } + } +} + +// MarkFlagsMutuallyExclusive marks the given flags with annotations so that Cobra errors +// if the command is invoked with more than one flag from the given set of flags. +func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) { + c.mergePersistentFlags() + for _, v := range flagNames { + f := c.Flags().Lookup(v) + if f == nil { + panic(fmt.Sprintf("Failed to find flag %q and mark it as being in a mutually exclusive flag group", v)) + } + // Each time this is called is a single new entry; this allows it to be a member of multiple groups if needed. + if err := c.Flags().SetAnnotation(v, mutuallyExclusive, append(f.Annotations[mutuallyExclusive], strings.Join(flagNames, " "))); err != nil { + panic(err) + } + } +} + +// validateFlagGroups validates the mutuallyExclusive/requiredAsGroup logic and returns the +// first error encountered. +func (c *Command) validateFlagGroups() error { + if c.DisableFlagParsing { + return nil + } + + flags := c.Flags() + + // groupStatus format is the list of flags as a unique ID, + // then a map of each flag name and whether it is set or not. + groupStatus := map[string]map[string]bool{} + mutuallyExclusiveGroupStatus := map[string]map[string]bool{} + flags.VisitAll(func(pflag *flag.Flag) { + processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus) + processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus) + }) + + if err := validateRequiredFlagGroups(groupStatus); err != nil { + return err + } + if err := validateExclusiveFlagGroups(mutuallyExclusiveGroupStatus); err != nil { + return err + } + return nil +} + +func hasAllFlags(fs *flag.FlagSet, flagnames ...string) bool { + for _, fname := range flagnames { + f := fs.Lookup(fname) + if f == nil { + return false + } + } + return true +} + +func processFlagForGroupAnnotation(flags *flag.FlagSet, pflag *flag.Flag, annotation string, groupStatus map[string]map[string]bool) { + groupInfo, found := pflag.Annotations[annotation] + if found { + for _, group := range groupInfo { + if groupStatus[group] == nil { + flagnames := strings.Split(group, " ") + + // Only consider this flag group at all if all the flags are defined. + if !hasAllFlags(flags, flagnames...) { + continue + } + + groupStatus[group] = map[string]bool{} + for _, name := range flagnames { + groupStatus[group][name] = false + } + } + + groupStatus[group][pflag.Name] = pflag.Changed + } + } +} + +func validateRequiredFlagGroups(data map[string]map[string]bool) error { + keys := sortedKeys(data) + for _, flagList := range keys { + flagnameAndStatus := data[flagList] + + unset := []string{} + for flagname, isSet := range flagnameAndStatus { + if !isSet { + unset = append(unset, flagname) + } + } + if len(unset) == len(flagnameAndStatus) || len(unset) == 0 { + continue + } + + // Sort values, so they can be tested/scripted against consistently. + sort.Strings(unset) + return fmt.Errorf("if any flags in the group [%v] are set they must all be set; missing %v", flagList, unset) + } + + return nil +} + +func validateExclusiveFlagGroups(data map[string]map[string]bool) error { + keys := sortedKeys(data) + for _, flagList := range keys { + flagnameAndStatus := data[flagList] + var set []string + for flagname, isSet := range flagnameAndStatus { + if isSet { + set = append(set, flagname) + } + } + if len(set) == 0 || len(set) == 1 { + continue + } + + // Sort values, so they can be tested/scripted against consistently. + sort.Strings(set) + return fmt.Errorf("if any flags in the group [%v] are set none of the others can be; %v were all set", flagList, set) + } + return nil +} + +func sortedKeys(m map[string]map[string]bool) []string { + keys := make([]string, len(m)) + i := 0 + for k := range m { + keys[i] = k + i++ + } + sort.Strings(keys) + return keys +} + +// enforceFlagGroupsForCompletion will do the following: +// - when a flag in a group is present, other flags in the group will be marked required +// - when a flag in a mutually exclusive group is present, other flags in the group will be marked as hidden +// This allows the standard completion logic to behave appropriately for flag groups +func (c *Command) enforceFlagGroupsForCompletion() { + if c.DisableFlagParsing { + return + } + + flags := c.Flags() + groupStatus := map[string]map[string]bool{} + mutuallyExclusiveGroupStatus := map[string]map[string]bool{} + c.Flags().VisitAll(func(pflag *flag.Flag) { + processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus) + processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus) + }) + + // If a flag that is part of a group is present, we make all the other flags + // of that group required so that the shell completion suggests them automatically + for flagList, flagnameAndStatus := range groupStatus { + for _, isSet := range flagnameAndStatus { + if isSet { + // One of the flags of the group is set, mark the other ones as required + for _, fName := range strings.Split(flagList, " ") { + _ = c.MarkFlagRequired(fName) + } + } + } + } + + // If a flag that is mutually exclusive to others is present, we hide the other + // flags of that group so the shell completion does not suggest them + for flagList, flagnameAndStatus := range mutuallyExclusiveGroupStatus { + for flagName, isSet := range flagnameAndStatus { + if isSet { + // One of the flags of the mutually exclusive group is set, mark the other ones as hidden + // Don't mark the flag that is already set as hidden because it may be an + // array or slice flag and therefore must continue being suggested + for _, fName := range strings.Split(flagList, " ") { + if fName != flagName { + flag := c.Flags().Lookup(fName) + flag.Hidden = true + } + } + } + } + } +} diff --git a/vendor/github.com/spf13/cobra/powershell_completions.go b/vendor/github.com/spf13/cobra/powershell_completions.go index 62d719f0b..379e7c088 100644 --- a/vendor/github.com/spf13/cobra/powershell_completions.go +++ b/vendor/github.com/spf13/cobra/powershell_completions.go @@ -61,6 +61,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { # Prepare the command to request completions for the program. # Split the command at the first space to separate the program and arguments. $Program,$Arguments = $Command.Split(" ",2) + $RequestComp="$Program %[2]s $Arguments" __%[1]s_debug "RequestComp: $RequestComp" @@ -90,11 +91,13 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { } __%[1]s_debug "Calling $RequestComp" + # First disable ActiveHelp which is not supported for Powershell + $env:%[8]s=0 + #call the command store the output in $out and redirect stderr and stdout to null # $Out is an array contains each line per element Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null - # get directive from last line [int]$Directive = $Out[-1].TrimStart(':') if ($Directive -eq "") { @@ -242,7 +245,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { } `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) } func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error { diff --git a/vendor/github.com/spf13/cobra/projects_using_cobra.md b/vendor/github.com/spf13/cobra/projects_using_cobra.md index 8410c9938..ac680118e 100644 --- a/vendor/github.com/spf13/cobra/projects_using_cobra.md +++ b/vendor/github.com/spf13/cobra/projects_using_cobra.md @@ -1,8 +1,8 @@ ## Projects using Cobra - [Arduino CLI](https://github.com/arduino/arduino-cli) -- [Bleve](http://www.blevesearch.com/) -- [CockroachDB](http://www.cockroachlabs.com/) +- [Bleve](https://blevesearch.com/) +- [CockroachDB](https://www.cockroachlabs.com/) - [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) - [Datree](https://github.com/datreeio/datree) - [Delve](https://github.com/derekparker/delve) @@ -14,14 +14,15 @@ - [Github CLI](https://github.com/cli/cli) - [GitHub Labeler](https://github.com/erdaltsksn/gh-label) - [Golangci-lint](https://golangci-lint.run) -- [GopherJS](http://www.gopherjs.org/) +- [GopherJS](https://github.com/gopherjs/gopherjs) - [GoReleaser](https://goreleaser.com) - [Helm](https://helm.sh) - [Hugo](https://gohugo.io) - [Infracost](https://github.com/infracost/infracost) - [Istio](https://istio.io) - [Kool](https://github.com/kool-dev/kool) -- [Kubernetes](http://kubernetes.io/) +- [Kubernetes](https://kubernetes.io/) +- [Kubescape](https://github.com/armosec/kubescape) - [Linkerd](https://linkerd.io/) - [Mattermost-server](https://github.com/mattermost/mattermost-server) - [Mercure](https://mercure.rocks/) @@ -35,9 +36,12 @@ - [OpenShift](https://www.openshift.com/) - [Ory Hydra](https://github.com/ory/hydra) - [Ory Kratos](https://github.com/ory/kratos) +- [Pixie](https://github.com/pixie-io/pixie) +- [Polygon Edge](https://github.com/0xPolygon/polygon-edge) - [Pouch](https://github.com/alibaba/pouch) -- [ProjectAtomic (enterprise)](http://www.projectatomic.io/) +- [ProjectAtomic (enterprise)](https://www.projectatomic.io/) - [Prototool](https://github.com/uber/prototool) +- [Pulumi](https://www.pulumi.com) - [QRcp](https://github.com/claudiodangelis/qrcp) - [Random](https://github.com/erdaltsksn/random) - [Rclone](https://rclone.org/) diff --git a/vendor/github.com/spf13/cobra/shell_completions.md b/vendor/github.com/spf13/cobra/shell_completions.md index 03add869b..1e2058ed6 100644 --- a/vendor/github.com/spf13/cobra/shell_completions.md +++ b/vendor/github.com/spf13/cobra/shell_completions.md @@ -16,10 +16,12 @@ If you do not wish to use the default `completion` command, you can choose to provide your own, which will take precedence over the default one. (This also provides backwards-compatibility with programs that already have their own `completion` command.) -If you are using the generator, you can create a completion command by running +If you are using the `cobra-cli` generator, +which can be found at [spf13/cobra-cli](https://github.com/spf13/cobra-cli), +you can create a completion command by running ```bash -cobra add completion +cobra-cli add completion ``` and then modifying the generated `cmd/completion.go` file to look something like this (writing the shell script to stdout allows the most flexible use): @@ -38,7 +40,7 @@ Bash: # Linux: $ %[1]s completion bash > /etc/bash_completion.d/%[1]s # macOS: - $ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s + $ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s Zsh: @@ -120,7 +122,7 @@ For example, if you want `kubectl get [tab][tab]` to show a list of valid "nouns Some simplified code from `kubectl get` looks like: ```go -validArgs []string = { "pod", "node", "service", "replicationcontroller" } +validArgs = []string{ "pod", "node", "service", "replicationcontroller" } cmd := &cobra.Command{ Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", @@ -146,7 +148,7 @@ node pod replicationcontroller service If your nouns have aliases, you can define them alongside `ValidArgs` using `ArgAliases`: ```go -argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } +argAliases = []string { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } cmd := &cobra.Command{ ... diff --git a/vendor/github.com/spf13/cobra/user_guide.md b/vendor/github.com/spf13/cobra/user_guide.md index e87cdf218..5a7acf88e 100644 --- a/vendor/github.com/spf13/cobra/user_guide.md +++ b/vendor/github.com/spf13/cobra/user_guide.md @@ -29,10 +29,10 @@ func main() { ## Using the Cobra Generator -Cobra provides its own program that will create your application and add any +Cobra-CLI is its own program that will create your application and add any commands you want. It's the easiest way to incorporate Cobra into your application. -For complete details on using the Cobra generator, please read [The Cobra Generator README](https://github.com/spf13/cobra/blob/master/cobra/README.md) +For complete details on using the Cobra generator, please refer to [The Cobra-CLI Generator README](https://github.com/spf13/cobra-cli/blob/main/README.md) ## Using the Cobra Library @@ -51,7 +51,7 @@ var rootCmd = &cobra.Command{ Short: "Hugo is a very fast static site generator", Long: `A Fast and Flexible Static Site Generator built with love by spf13 and friends in Go. - Complete documentation is available at http://hugo.spf13.com`, + Complete documentation is available at https://gohugo.io/documentation/`, Run: func(cmd *cobra.Command, args []string) { // Do Stuff Here }, @@ -86,7 +86,7 @@ var ( userLicense string rootCmd = &cobra.Command{ - Use: "cobra", + Use: "cobra-cli", Short: "A generator for Cobra based Applications", Long: `Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files @@ -300,10 +300,34 @@ rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (re rootCmd.MarkPersistentFlagRequired("region") ``` +### Flag Groups + +If you have different flags that must be provided together (e.g. if they provide the `--username` flag they MUST provide the `--password` flag as well) then +Cobra can enforce that requirement: +```go +rootCmd.Flags().StringVarP(&u, "username", "u", "", "Username (required if password is set)") +rootCmd.Flags().StringVarP(&pw, "password", "p", "", "Password (required if username is set)") +rootCmd.MarkFlagsRequiredTogether("username", "password") +``` + +You can also prevent different flags from being provided together if they represent mutually +exclusive options such as specifying an output format as either `--json` or `--yaml` but never both: +```go +rootCmd.Flags().BoolVar(&u, "json", false, "Output in JSON") +rootCmd.Flags().BoolVar(&pw, "yaml", false, "Output in YAML") +rootCmd.MarkFlagsMutuallyExclusive("json", "yaml") +``` + +In both of these cases: + - both local and persistent flags can be used + - **NOTE:** the group is only enforced on commands where every flag is defined + - a flag may appear in multiple groups + - a group may contain any number of flags + ## Positional and Custom Arguments -Validation of positional arguments can be specified using the `Args` field -of `Command`. +Validation of positional arguments can be specified using the `Args` field of `Command`. +If `Args` is undefined or `nil`, it defaults to `ArbitraryArgs`. The following validators are built in: @@ -405,7 +429,7 @@ a count and a string.`, } ``` -For a more complete example of a larger application, please checkout [Hugo](http://gohugo.io/). +For a more complete example of a larger application, please checkout [Hugo](https://gohugo.io/). ## Help Command @@ -603,7 +627,7 @@ Did you mean this? Run 'hugo --help' for usage. ``` -Suggestions are automatic based on every subcommand registered and use an implementation of [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion. +Suggestions are automatic based on every subcommand registered and use an implementation of [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion. If you need to disable suggestions or tweak the string distance in your command, use: @@ -636,3 +660,7 @@ Cobra can generate documentation based on subcommands, flags, etc. Read more abo ## Generating shell completions Cobra can generate a shell-completion file for the following shells: bash, zsh, fish, PowerShell. If you add more information to your commands, these completions can be amazingly powerful and flexible. Read more about it in [Shell Completions](shell_completions.md). + +## Providing Active Help + +Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users. Active Help are messages (hints, warnings, etc) printed as the program is being used. Read more about it in [Active Help](active_help.md). diff --git a/vendor/github.com/spf13/cobra/zsh_completions.go b/vendor/github.com/spf13/cobra/zsh_completions.go index 624adab53..65cd94c60 100644 --- a/vendor/github.com/spf13/cobra/zsh_completions.go +++ b/vendor/github.com/spf13/cobra/zsh_completions.go @@ -75,7 +75,7 @@ func genZshComp(buf io.StringWriter, name string, includeDesc bool) { if !includeDesc { compCmd = ShellCompNoDescRequestCmd } - WriteStringAndCheck(buf, fmt.Sprintf(`#compdef _%[1]s %[1]s + WriteStringAndCheck(buf, fmt.Sprintf(`#compdef %[1]s # zsh completion for %-36[1]s -*- shell-script -*- @@ -163,7 +163,24 @@ _%[1]s() return fi + local activeHelpMarker="%[8]s" + local endIndex=${#activeHelpMarker} + local startIndex=$((${#activeHelpMarker}+1)) + local hasActiveHelp=0 while IFS='\n' read -r comp; do + # Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker) + if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then + __%[1]s_debug "ActiveHelp found: $comp" + comp="${comp[$startIndex,-1]}" + if [ -n "$comp" ]; then + compadd -x "${comp}" + __%[1]s_debug "ActiveHelp will need delimiter" + hasActiveHelp=1 + fi + + continue + fi + if [ -n "$comp" ]; then # If requested, completions are returned with a description. # The description is preceded by a TAB character. @@ -171,7 +188,7 @@ _%[1]s() # We first need to escape any : as part of the completion itself. comp=${comp//:/\\:} - local tab=$(printf '\t') + local tab="$(printf '\t')" comp=${comp//$tab/:} __%[1]s_debug "Adding completion: ${comp}" @@ -180,6 +197,17 @@ _%[1]s() fi done < <(printf "%%s\n" "${out[@]}") + # Add a delimiter after the activeHelp statements, but only if: + # - there are completions following the activeHelp statements, or + # - file completion will be performed (so there will be choices after the activeHelp) + if [ $hasActiveHelp -eq 1 ]; then + if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then + __%[1]s_debug "Adding activeHelp delimiter" + compadd -x "--" + hasActiveHelp=0 + fi + fi + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then __%[1]s_debug "Activating nospace." noSpace="-S ''" @@ -254,5 +282,6 @@ if [ "$funcstack[1]" = "_%[1]s" ]; then fi `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + activeHelpMarker)) } diff --git a/vendor/github.com/wavesoftware/go-commandline/.editorconfig b/vendor/github.com/wavesoftware/go-commandline/.editorconfig new file mode 100644 index 000000000..56a5c5958 --- /dev/null +++ b/vendor/github.com/wavesoftware/go-commandline/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 + +[{*.go,go.mod,Makefile}] +indent_style = tab + +# Ignore paths +[LICENSE] +indent_size = unset diff --git a/vendor/github.com/wavesoftware/go-commandline/.gitignore b/vendor/github.com/wavesoftware/go-commandline/.gitignore new file mode 100644 index 000000000..66fd13c90 --- /dev/null +++ b/vendor/github.com/wavesoftware/go-commandline/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/vendor/github.com/wavesoftware/go-commandline/LICENSE b/vendor/github.com/wavesoftware/go-commandline/LICENSE new file mode 100644 index 000000000..056124684 --- /dev/null +++ b/vendor/github.com/wavesoftware/go-commandline/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Wave Software + + 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. diff --git a/vendor/github.com/wavesoftware/go-commandline/README.md b/vendor/github.com/wavesoftware/go-commandline/README.md new file mode 100644 index 000000000..3374ed7db --- /dev/null +++ b/vendor/github.com/wavesoftware/go-commandline/README.md @@ -0,0 +1,4 @@ +# commandline - cobra integration package + +Types to allow to write CLI tools using cobra in easy way. + diff --git a/vendor/github.com/wavesoftware/go-commandline/types.go b/vendor/github.com/wavesoftware/go-commandline/types.go new file mode 100644 index 000000000..0f9d4e6cc --- /dev/null +++ b/vendor/github.com/wavesoftware/go-commandline/types.go @@ -0,0 +1,99 @@ +package commandline + +import ( + "errors" + "io" + "os" + + "github.com/spf13/cobra" + "github.com/wavesoftware/go-retcode" +) + +// ErrNoRootCommand is returned when no root command is provided. +var ErrNoRootCommand = errors.New("no root command provided") + +// App represents a command line application. +type App struct { + CobraProvider + Exit func(code int) + root *cobra.Command +} + +// CobraProvider is used to provide a Cobra command. +type CobraProvider interface { + Command() *cobra.Command +} + +// Option is used to configure an App. +type Option func(*App) + +// New creates a new App from CobraProvider. +func New(cp CobraProvider) *App { + return &App{ + CobraProvider: cp, + Exit: os.Exit, + } +} + +// ExecuteOrDie will execute the application or perform os.Exit in case of error. +func (a *App) ExecuteOrDie(options ...Option) { + if err := a.Execute(options...); err != nil { + a.Exit(retcode.Calc(err)) + } +} + +// WithArgs creates an option which sets args. +func WithArgs(args ...string) Option { + return func(app *App) { + app.root.SetArgs(args) + } +} + +// WithInput creates an option witch sets os.Stdin. +func WithInput(in io.Reader) Option { + return func(app *App) { + app.root.SetIn(in) + } +} + +// WithOutput creates an option witch sets os.Stdout and os.Stderr. +func WithOutput(out io.Writer) Option { + return func(app *App) { + app.root.SetOut(out) + app.root.SetErr(out) + } +} + +// WithExit creates an option which sets the exit function. +func WithExit(fn func(code int)) Option { + return func(app *App) { + app.Exit = fn + } +} + +// Execute will execute the application with the provided options and return +// error if any. +func (a *App) Execute(options ...Option) error { + if err := a.init(); err != nil { + return err + } + for _, config := range options { + config(a) + } + // cobra.Command should pass our own errors, no need to wrap them. + return a.root.Execute() //nolint:wrapcheck +} + +func (a *App) init() error { + if a.Exit == nil { + a.Exit = os.Exit + } + if a.CobraProvider == nil { + return ErrNoRootCommand + } + a.root = a.Command() + if a.root == nil { + return ErrNoRootCommand + } + return nil +} diff --git a/vendor/github.com/wavesoftware/go-retcode/.editorconfig b/vendor/github.com/wavesoftware/go-retcode/.editorconfig new file mode 100644 index 000000000..56a5c5958 --- /dev/null +++ b/vendor/github.com/wavesoftware/go-retcode/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 + +[{*.go,go.mod,Makefile}] +indent_style = tab + +# Ignore paths +[LICENSE] +indent_size = unset diff --git a/vendor/github.com/wavesoftware/go-retcode/.gitignore b/vendor/github.com/wavesoftware/go-retcode/.gitignore new file mode 100644 index 000000000..66fd13c90 --- /dev/null +++ b/vendor/github.com/wavesoftware/go-retcode/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/vendor/github.com/wavesoftware/go-retcode/.golangci.yaml b/vendor/github.com/wavesoftware/go-retcode/.golangci.yaml new file mode 100644 index 000000000..4ad7c825d --- /dev/null +++ b/vendor/github.com/wavesoftware/go-retcode/.golangci.yaml @@ -0,0 +1,36 @@ +run: + timeout: 5m + build-tags: + - e2e + +linters: + disable-all: false + presets: + - bugs + - unused + - complexity + - format + - performance + - style + enable: + - gci + disable: + - paralleltest + - nlreturn + - exhaustivestruct + - wsl + - godox + - scopelint + - maligned + - interfacer + - golint + - ireturn + - varnamelen + - gochecknoglobals + +issues: + exclude-rules: + - path: _test\.go + linters: + - wrapcheck + diff --git a/vendor/github.com/wavesoftware/go-retcode/LICENSE b/vendor/github.com/wavesoftware/go-retcode/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/wavesoftware/go-retcode/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/wavesoftware/go-retcode/README.md b/vendor/github.com/wavesoftware/go-retcode/README.md new file mode 100644 index 000000000..119c2e429 --- /dev/null +++ b/vendor/github.com/wavesoftware/go-retcode/README.md @@ -0,0 +1,11 @@ +# retcode package for Go + +Deterministic process exit codes based on Go errors. + +## Usage + +```go +err := fmt.Errorf("example error") +os.Exit(retcode.Calc(err)) +``` + diff --git a/vendor/github.com/wavesoftware/go-retcode/retcode.go b/vendor/github.com/wavesoftware/go-retcode/retcode.go new file mode 100644 index 000000000..061828241 --- /dev/null +++ b/vendor/github.com/wavesoftware/go-retcode/retcode.go @@ -0,0 +1,21 @@ +package retcode + +import "hash/crc32" + +var ( + // LowerBound is the lower bound of the POSIX retcode range. Use this to + // configure the package. + LowerBound = 1 + // UpperBound is the upper bound of the POSIX retcode range. Use this to + // configure the package. + UpperBound = 255 +) + +// Calc will calculate an POSIX retcode from an error. +func Calc(err error) int { + if err == nil { + return 0 + } + upper := UpperBound - LowerBound + return int(crc32.ChecksumIEEE([]byte(err.Error())))%upper + LowerBound +} diff --git a/vendor/gotest.tools/v3/assert/assert.go b/vendor/gotest.tools/v3/assert/assert.go index f3f805dde..dbd4f5a01 100644 --- a/vendor/gotest.tools/v3/assert/assert.go +++ b/vendor/gotest.tools/v3/assert/assert.go @@ -1,51 +1,80 @@ /*Package assert provides assertions for comparing expected values to actual -values. When an assertion fails a helpful error message is printed. +values in tests. When an assertion fails a helpful error message is printed. -Assert and Check +Example usage -Assert() and Check() both accept a Comparison, and fail the test when the -comparison fails. The one difference is that Assert() will end the test execution -immediately (using t.FailNow()) whereas Check() will fail the test (using t.Fail()), -return the value of the comparison, then proceed with the rest of the test case. +All the assertions in this package use testing.T.Helper to mark themselves as +test helpers. This allows the testing package to print the filename and line +number of the file function that failed. -Example usage + assert.NilError(t, err) + // filename_test.go:212: assertion failed: error is not nil: file not found -The example below shows assert used with some common types. +If any assertion is called from a helper function, make sure to call t.Helper +from the helper function so that the filename and line number remain correct. +The examples below show assert used with some common types and the failure +messages it produces. The filename and line number portion of the failure +message is omitted from these examples for brevity. - import ( - "testing" + // booleans - "gotest.tools/assert" - is "gotest.tools/assert/cmp" - ) + assert.Assert(t, ok) + // assertion failed: ok is false + assert.Assert(t, !missing) + // assertion failed: missing is true - func TestEverything(t *testing.T) { - // booleans - assert.Assert(t, ok) - assert.Assert(t, !missing) + // primitives - // primitives - assert.Equal(t, count, 1) - assert.Equal(t, msg, "the message") - assert.Assert(t, total != 10) // NotEqual + assert.Equal(t, count, 1) + // assertion failed: 0 (count int) != 1 (int) + assert.Equal(t, msg, "the message") + // assertion failed: my message (msg string) != the message (string) + assert.Assert(t, total != 10) // use Assert for NotEqual + // assertion failed: total is 10 + assert.Assert(t, count > 20, "count=%v", count) + // assertion failed: count is <= 20: count=1 - // errors - assert.NilError(t, closer.Close()) - assert.Error(t, err, "the exact error message") - assert.ErrorContains(t, err, "includes this") - assert.ErrorType(t, err, os.IsNotExist) + // errors - // complex types - assert.DeepEqual(t, result, myStruct{Name: "title"}) - assert.Assert(t, is.Len(items, 3)) - assert.Assert(t, len(sequence) != 0) // NotEmpty - assert.Assert(t, is.Contains(mapping, "key")) + assert.NilError(t, closer.Close()) + // assertion failed: error is not nil: close /file: errno 11 + assert.Error(t, err, "the exact error message") + // assertion failed: expected error "the exact error message", got "oops" + assert.ErrorContains(t, err, "includes this") + // assertion failed: expected error to contain "includes this", got "oops" + assert.ErrorIs(t, err, os.ErrNotExist) + // assertion failed: error is "oops", not "file does not exist" (os.ErrNotExist) - // pointers and interface - assert.Assert(t, is.Nil(ref)) - assert.Assert(t, ref != nil) // NotNil - } + // complex types + + assert.DeepEqual(t, result, myStruct{Name: "title"}) + // assertion failed: ... (diff of the two structs) + assert.Assert(t, is.Len(items, 3)) + // assertion failed: expected [] (length 0) to have length 3 + assert.Assert(t, len(sequence) != 0) // use Assert for NotEmpty + // assertion failed: len(sequence) is 0 + assert.Assert(t, is.Contains(mapping, "key")) + // assertion failed: map[other:1] does not contain key + + // pointers and interface + + assert.Assert(t, ref == nil) + // assertion failed: ref is not nil + assert.Assert(t, ref != nil) // use Assert for NotNil + // assertion failed: ref is nil + +Assert and Check + +Assert and Check are very similar, they both accept a Comparison, and fail +the test when the comparison fails. The one difference is that Assert uses +testing.T.FailNow to fail the test, which will end the test execution immediately. +Check uses testing.T.Fail to fail the test, which allows it to return the +result of the comparison, then proceed with the rest of the test case. + +Like testing.T.FailNow, Assert must be called from the goroutine running the test, +not from other goroutines created during the test. Check is safe to use from any +goroutine. Comparisons @@ -70,7 +99,8 @@ import ( "gotest.tools/v3/internal/assert" ) -// BoolOrComparison can be a bool, or cmp.Comparison. See Assert() for usage. +// BoolOrComparison can be a bool, cmp.Comparison, or error. See Assert for +// details about how this type is used. type BoolOrComparison interface{} // TestingT is the subset of testing.T used by the assert package. @@ -88,16 +118,28 @@ type helperT interface { // failed, a failure message is logged, and execution is stopped immediately. // // The comparison argument may be one of three types: +// // bool -// True is success. False is a failure. -// The failure message will contain the literal source code of the expression. +// True is success. False is a failure. The failure message will contain +// the literal source code of the expression. +// // cmp.Comparison -// Uses cmp.Result.Success() to check for success of failure. -// The comparison is responsible for producing a helpful failure message. -// http://pkg.go.dev/gotest.tools/v3/assert/cmp provides many common comparisons. +// Uses cmp.Result.Success() to check for success or failure. +// The comparison is responsible for producing a helpful failure message. +// http://pkg.go.dev/gotest.tools/v3/assert/cmp provides many common comparisons. +// // error -// A nil value is considered success. -// A non-nil error is a failure, err.Error() is used as the failure message. +// A nil value is considered success, and a non-nil error is a failure. +// The return value of error.Error is used as the failure message. +// +// +// Extra details can be added to the failure message using msgAndArgs. msgAndArgs +// may be either a single string, or a format string and args that will be +// passed to fmt.Sprintf. +// +// Assert uses t.FailNow to fail the test. Like t.FailNow, Assert must be called +// from the goroutine running the test function, not from other +// goroutines created during the test. Use Check from other goroutines. func Assert(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() @@ -108,8 +150,8 @@ func Assert(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) } // Check performs a comparison. If the comparison fails the test is marked as -// failed, a failure message is logged, and Check returns false. Otherwise returns -// true. +// failed, a failure message is printed, and Check returns false. If the comparison +// is successful Check returns true. Check may be called from any goroutine. // // See Assert for details about the comparison arg and failure messages. func Check(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) bool { @@ -123,8 +165,12 @@ func Check(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) b return true } -// NilError fails the test immediately if err is not nil. -// This is equivalent to Assert(t, err) +// NilError fails the test immediately if err is not nil, and includes err.Error +// in the failure message. +// +// NilError uses t.FailNow to fail the test. Like t.FailNow, NilError must be +// called from the goroutine running the test function, not from other +// goroutines created during the test. Use Check from other goroutines. func NilError(t TestingT, err error, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() @@ -137,15 +183,22 @@ func NilError(t TestingT, err error, msgAndArgs ...interface{}) { // Equal uses the == operator to assert two values are equal and fails the test // if they are not equal. // -// If the comparison fails Equal will use the variable names for x and y as part -// of the failure message to identify the actual and expected values. +// If the comparison fails Equal will use the variable names and types of +// x and y as part of the failure message to identify the actual and expected +// values. +// +// assert.Equal(t, actual, expected) +// // main_test.go:41: assertion failed: 1 (actual int) != 21 (expected int32) // // If either x or y are a multi-line string the failure message will include a // unified diff of the two values. If the values only differ by whitespace // the unified diff will be augmented by replacing whitespace characters with // visible characters to identify the whitespace difference. // -// This is equivalent to Assert(t, cmp.Equal(x, y)). +// Equal uses t.FailNow to fail the test. Like t.FailNow, Equal must be +// called from the goroutine running the test function, not from other +// goroutines created during the test. Use Check with cmp.Equal from other +// goroutines. func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() @@ -161,7 +214,10 @@ func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) { // Package http://pkg.go.dev/gotest.tools/v3/assert/opt provides some additional // commonly used Options. // -// This is equivalent to Assert(t, cmp.DeepEqual(x, y)). +// DeepEqual uses t.FailNow to fail the test. Like t.FailNow, DeepEqual must be +// called from the goroutine running the test function, not from other +// goroutines created during the test. Use Check with cmp.DeepEqual from other +// goroutines. func DeepEqual(t TestingT, x, y interface{}, opts ...gocmp.Option) { if ht, ok := t.(helperT); ok { ht.Helper() @@ -171,21 +227,33 @@ func DeepEqual(t TestingT, x, y interface{}, opts ...gocmp.Option) { } } -// Error fails the test if err is nil, or the error message is not the expected -// message. -// Equivalent to Assert(t, cmp.Error(err, message)). -func Error(t TestingT, err error, message string, msgAndArgs ...interface{}) { +// Error fails the test if err is nil, or if err.Error is not equal to expected. +// Both err.Error and expected will be included in the failure message. +// Error performs an exact match of the error text. Use ErrorContains if only +// part of the error message is relevant. Use ErrorType or ErrorIs to compare +// errors by type. +// +// Error uses t.FailNow to fail the test. Like t.FailNow, Error must be +// called from the goroutine running the test function, not from other +// goroutines created during the test. Use Check with cmp.Error from other +// goroutines. +func Error(t TestingT, err error, expected string, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } - if !assert.Eval(t, assert.ArgsAfterT, cmp.Error(err, message), msgAndArgs...) { + if !assert.Eval(t, assert.ArgsAfterT, cmp.Error(err, expected), msgAndArgs...) { t.FailNow() } } -// ErrorContains fails the test if err is nil, or the error message does not -// contain the expected substring. -// Equivalent to Assert(t, cmp.ErrorContains(err, substring)). +// ErrorContains fails the test if err is nil, or if err.Error does not +// contain the expected substring. Both err.Error and the expected substring +// will be included in the failure message. +// +// ErrorContains uses t.FailNow to fail the test. Like t.FailNow, ErrorContains +// must be called from the goroutine running the test function, not from other +// goroutines created during the test. Use Check with cmp.ErrorContains from other +// goroutines. func ErrorContains(t TestingT, err error, substring string, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() @@ -196,19 +264,29 @@ func ErrorContains(t TestingT, err error, substring string, msgAndArgs ...interf } // ErrorType fails the test if err is nil, or err is not the expected type. -// Equivalent to Assert(t, cmp.ErrorType(err, expected)). +// Most new code should use ErrorIs instead. ErrorType may be deprecated in the +// future. // // Expected can be one of: +// // func(error) bool -// Function should return true if the error is the expected type. -// type struct{}, type &struct{} -// A struct or a pointer to a struct. -// Fails if the error is not of the same type as expected. -// type &interface{} -// A pointer to an interface type. -// Fails if err does not implement the interface. +// The function should return true if the error is the expected type. +// +// struct{} or *struct{} +// A struct or a pointer to a struct. The assertion fails if the error is +// not of the same type. +// +// *interface{} +// A pointer to an interface type. The assertion fails if err does not +// implement the interface. +// // reflect.Type -// Fails if err does not implement the reflect.Type +// The assertion fails if err does not implement the reflect.Type. +// +// ErrorType uses t.FailNow to fail the test. Like t.FailNow, ErrorType +// must be called from the goroutine running the test function, not from other +// goroutines created during the test. Use Check with cmp.ErrorType from other +// goroutines. func ErrorType(t TestingT, err error, expected interface{}, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() @@ -217,3 +295,20 @@ func ErrorType(t TestingT, err error, expected interface{}, msgAndArgs ...interf t.FailNow() } } + +// ErrorIs fails the test if err is nil, or the error does not match expected +// when compared using errors.Is. See https://golang.org/pkg/errors/#Is for +// accepted arguments. +// +// ErrorIs uses t.FailNow to fail the test. Like t.FailNow, ErrorIs +// must be called from the goroutine running the test function, not from other +// goroutines created during the test. Use Check with cmp.ErrorIs from other +// goroutines. +func ErrorIs(t TestingT, err error, expected error, msgAndArgs ...interface{}) { + if ht, ok := t.(helperT); ok { + ht.Helper() + } + if !assert.Eval(t, assert.ArgsAfterT, cmp.ErrorIs(err, expected), msgAndArgs...) { + t.FailNow() + } +} diff --git a/vendor/gotest.tools/v3/assert/assert_go113.go b/vendor/gotest.tools/v3/assert/assert_go113.go deleted file mode 100644 index 1e3cede48..000000000 --- a/vendor/gotest.tools/v3/assert/assert_go113.go +++ /dev/null @@ -1,20 +0,0 @@ -// +build go1.13 - -package assert - -import ( - "gotest.tools/v3/assert/cmp" - "gotest.tools/v3/internal/assert" -) - -// ErrorIs fails the test if err is nil, or the error does not match expected -// when compared using errors.Is. See https://golang.org/pkg/errors/#Is for -// accepted argument values. -func ErrorIs(t TestingT, err error, expected error, msgAndArgs ...interface{}) { - if ht, ok := t.(helperT); ok { - ht.Helper() - } - if !assert.Eval(t, assert.ArgsAfterT, cmp.ErrorIs(err, expected), msgAndArgs...) { - t.FailNow() - } -} diff --git a/vendor/gotest.tools/v3/assert/cmp/compare.go b/vendor/gotest.tools/v3/assert/cmp/compare.go index 3c0e05ab5..78f76e4e8 100644 --- a/vendor/gotest.tools/v3/assert/cmp/compare.go +++ b/vendor/gotest.tools/v3/assert/cmp/compare.go @@ -2,6 +2,7 @@ package cmp // import "gotest.tools/v3/assert/cmp" import ( + "errors" "fmt" "reflect" "regexp" @@ -34,7 +35,7 @@ func DeepEqual(x, y interface{}, opts ...cmp.Option) Comparison { if diff == "" { return ResultSuccess } - return multiLineDiffResult(diff) + return multiLineDiffResult(diff, x, y) } } @@ -101,7 +102,7 @@ func Equal(x, y interface{}) Comparison { return ResultSuccess case isMultiLineStringCompare(x, y): diff := format.UnifiedDiff(format.DiffConfig{A: x.(string), B: y.(string)}) - return multiLineDiffResult(diff) + return multiLineDiffResult(diff, x, y) } return ResultFailureTemplate(` {{- printf "%v" .Data.x}} ( @@ -127,12 +128,12 @@ func isMultiLineStringCompare(x, y interface{}) bool { return strings.Contains(strX, "\n") || strings.Contains(strY, "\n") } -func multiLineDiffResult(diff string) Result { +func multiLineDiffResult(diff string, x, y interface{}) Result { return ResultFailureTemplate(` --- {{ with callArg 0 }}{{ formatNode . }}{{else}}←{{end}} +++ {{ with callArg 1 }}{{ formatNode . }}{{else}}→{{end}} {{ .Data.diff }}`, - map[string]interface{}{"diff": diff}) + map[string]interface{}{"diff": diff, "x": x, "y": y}) } // Len succeeds if the sequence has the expected length. @@ -165,7 +166,7 @@ func Contains(collection interface{}, item interface{}) Comparison { return func() Result { colValue := reflect.ValueOf(collection) if !colValue.IsValid() { - return ResultFailure(fmt.Sprintf("nil does not contain items")) + return ResultFailure("nil does not contain items") } msg := fmt.Sprintf("%v does not contain %v", collection, item) @@ -247,6 +248,7 @@ type causer interface { } func formatErrorMessage(err error) string { + // nolint: errorlint // unwrapping is not appropriate here if _, ok := err.(causer); ok { return fmt.Sprintf("%q\n%+v", err, err) } @@ -307,7 +309,7 @@ func ErrorType(err error, expected interface{}) Comparison { } return cmpErrorTypeEqualType(err, expectedType) case nil: - return ResultFailure(fmt.Sprintf("invalid type for expected: nil")) + return ResultFailure("invalid type for expected: nil") } expectedType := reflect.TypeOf(expected) @@ -363,3 +365,30 @@ func isPtrToInterface(typ reflect.Type) bool { func isPtrToStruct(typ reflect.Type) bool { return typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct } + +var ( + stdlibErrorNewType = reflect.TypeOf(errors.New("")) + stdlibFmtErrorType = reflect.TypeOf(fmt.Errorf("%w", fmt.Errorf(""))) +) + +// ErrorIs succeeds if errors.Is(actual, expected) returns true. See +// https://golang.org/pkg/errors/#Is for accepted argument values. +func ErrorIs(actual error, expected error) Comparison { + return func() Result { + if errors.Is(actual, expected) { + return ResultSuccess + } + + // The type of stdlib errors is excluded because the type is not relevant + // in those cases. The type is only important when it is a user defined + // custom error type. + return ResultFailureTemplate(`error is + {{- if not .Data.a }} nil,{{ else }} + {{- printf " \"%v\"" .Data.a }} + {{- if notStdlibErrorType .Data.a }} ({{ printf "%T" .Data.a }}){{ end }}, + {{- end }} not {{ printf "\"%v\"" .Data.x }} ( + {{- with callArg 1 }}{{ formatNode . }}{{ end }} + {{- if notStdlibErrorType .Data.x }}{{ printf " %T" .Data.x }}{{ end }})`, + map[string]interface{}{"a": actual, "x": expected}) + } +} diff --git a/vendor/gotest.tools/v3/assert/cmp/compare_go113.go b/vendor/gotest.tools/v3/assert/cmp/compare_go113.go deleted file mode 100644 index 58af74450..000000000 --- a/vendor/gotest.tools/v3/assert/cmp/compare_go113.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build go1.13 - -package cmp - -import ( - "errors" -) - -// ErrorIs succeeds if errors.Is(actual, expected) returns true. See -// https://golang.org/pkg/errors/#Is for accepted argument values. -func ErrorIs(actual error, expected error) Comparison { - return func() Result { - if errors.Is(actual, expected) { - return ResultSuccess - } - - return ResultFailureTemplate(`error is - {{- if not .Data.a }} nil,{{ else }} - {{- printf " \"%v\"" .Data.a}} ( - {{- with callArg 0 }}{{ formatNode . }} {{end -}} - {{- printf "%T" .Data.a -}} - ), - {{- end }} not {{ printf "\"%v\"" .Data.x}} ( - {{- with callArg 1 }}{{ formatNode . }} {{end -}} - {{- printf "%T" .Data.x -}} - )`, - map[string]interface{}{"a": actual, "x": expected}) - } -} diff --git a/vendor/gotest.tools/v3/assert/cmp/result.go b/vendor/gotest.tools/v3/assert/cmp/result.go index 3b48d9bf0..28ef8d3d4 100644 --- a/vendor/gotest.tools/v3/assert/cmp/result.go +++ b/vendor/gotest.tools/v3/assert/cmp/result.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "go/ast" + "reflect" "text/template" "gotest.tools/v3/internal/source" @@ -68,6 +69,11 @@ func (r templatedResult) FailureMessage(args []ast.Expr) string { return msg } +func (r templatedResult) UpdatedExpected(stackIndex int) error { + // TODO: would be nice to have structured data instead of a map + return source.UpdateExpectedValue(stackIndex+1, r.data["x"], r.data["y"]) +} + // ResultFailureTemplate returns a Result with a template string and data which // can be used to format a failure message. The template may access data from .Data, // the comparison args with the callArg function, and the formatNode function may @@ -85,6 +91,11 @@ func renderMessage(result templatedResult, args []ast.Expr) (string, error) { } return args[index] }, + // TODO: any way to include this from ErrorIS instead of here? + "notStdlibErrorType": func(typ interface{}) bool { + r := reflect.TypeOf(typ) + return r != stdlibFmtErrorType && r != stdlibErrorNewType + }, }) var err error tmpl, err = tmpl.Parse(result.template) diff --git a/vendor/gotest.tools/v3/icmd/command.go b/vendor/gotest.tools/v3/icmd/command.go index 822ee94bf..961332280 100644 --- a/vendor/gotest.tools/v3/icmd/command.go +++ b/vendor/gotest.tools/v3/icmd/command.go @@ -77,7 +77,6 @@ func (r *Result) Compare(exp Expected) error { return r.match(exp) } -// nolint: gocyclo func (r *Result) match(exp Expected) error { errors := []string{} add := func(format string, args ...interface{}) { diff --git a/vendor/gotest.tools/v3/icmd/exitcode.go b/vendor/gotest.tools/v3/icmd/exitcode.go index 751254a0e..2e98f86c1 100644 --- a/vendor/gotest.tools/v3/icmd/exitcode.go +++ b/vendor/gotest.tools/v3/icmd/exitcode.go @@ -1,32 +1,24 @@ package icmd import ( - "syscall" + "errors" - "github.com/pkg/errors" exec "golang.org/x/sys/execabs" ) -// getExitCode returns the ExitStatus of a process from the error returned by -// exec.Run(). If the exit status could not be parsed an error is returned. -func getExitCode(err error) (int, error) { - if exiterr, ok := err.(*exec.ExitError); ok { - if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok { - return procExit.ExitStatus(), nil - } - } - return 0, errors.Wrap(err, "failed to get exit code") -} - -func processExitCode(err error) (exitCode int) { +func processExitCode(err error) int { if err == nil { return 0 } - exitCode, exiterr := getExitCode(err) - if exiterr != nil { - // TODO: Fix this so we check the error's text. - // we've failed to retrieve exit code, so we set it to 127 - return 127 + + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + if exitErr.ProcessState == nil { + return 0 + } + if code := exitErr.ProcessState.ExitCode(); code != -1 { + return code + } } - return exitCode + return 127 } diff --git a/vendor/gotest.tools/v3/internal/assert/assert.go b/vendor/gotest.tools/v3/internal/assert/assert.go index 8dc01f4b5..0d67751da 100644 --- a/vendor/gotest.tools/v3/internal/assert/assert.go +++ b/vendor/gotest.tools/v3/internal/assert/assert.go @@ -23,7 +23,6 @@ type helperT interface { const failureMessage = "assertion failed: " // Eval the comparison and print a failure messages if the comparison has failed. -// nolint: gocyclo func Eval( t LogT, argSelector argSelector, @@ -115,7 +114,7 @@ func failureMsgFromError(err error) string { } func boolFailureMessage(expr ast.Expr) (string, error) { - if binaryExpr, ok := expr.(*ast.BinaryExpr); ok && binaryExpr.Op == token.NEQ { + if binaryExpr, ok := expr.(*ast.BinaryExpr); ok { x, err := source.FormatNode(binaryExpr.X) if err != nil { return "", err @@ -124,7 +123,21 @@ func boolFailureMessage(expr ast.Expr) (string, error) { if err != nil { return "", err } - return x + " is " + y, nil + + switch binaryExpr.Op { + case token.NEQ: + return x + " is " + y, nil + case token.EQL: + return x + " is not " + y, nil + case token.GTR: + return x + " is <= " + y, nil + case token.LSS: + return x + " is >= " + y, nil + case token.GEQ: + return x + " is less than " + y, nil + case token.LEQ: + return x + " is greater than " + y, nil + } } if unaryExpr, ok := expr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.NOT { @@ -135,6 +148,10 @@ func boolFailureMessage(expr ast.Expr) (string, error) { return x + " is true", nil } + if ident, ok := expr.(*ast.Ident); ok { + return ident.Name + " is false", nil + } + formatted, err := source.FormatNode(expr) if err != nil { return "", err diff --git a/vendor/gotest.tools/v3/internal/assert/result.go b/vendor/gotest.tools/v3/internal/assert/result.go index 20cd54129..360320614 100644 --- a/vendor/gotest.tools/v3/internal/assert/result.go +++ b/vendor/gotest.tools/v3/internal/assert/result.go @@ -1,6 +1,7 @@ package assert import ( + "errors" "fmt" "go/ast" @@ -25,6 +26,22 @@ func RunComparison( return true } + if source.Update { + if updater, ok := result.(updateExpected); ok { + const stackIndex = 3 // Assert/Check, assert, RunComparison + err := updater.UpdatedExpected(stackIndex) + switch { + case err == nil: + return true + case errors.Is(err, source.ErrNotFound): + // do nothing, fallthrough to regular failure message + default: + t.Log("failed to update source", err) + return false + } + } + } + var message string switch typed := result.(type) { case resultWithComparisonArgs: @@ -52,6 +69,10 @@ type resultBasic interface { FailureMessage() string } +type updateExpected interface { + UpdatedExpected(stackIndex int) error +} + // filterPrintableExpr filters the ast.Expr slice to only include Expr that are // easy to read when printed and contain relevant information to an assertion. // diff --git a/vendor/gotest.tools/v3/internal/source/defers.go b/vendor/gotest.tools/v3/internal/source/defers.go index 66cfafbb6..392d9fe07 100644 --- a/vendor/gotest.tools/v3/internal/source/defers.go +++ b/vendor/gotest.tools/v3/internal/source/defers.go @@ -1,10 +1,9 @@ package source import ( + "fmt" "go/ast" "go/token" - - "github.com/pkg/errors" ) func scanToDeferLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node { @@ -29,11 +28,11 @@ func guessDefer(node ast.Node) (ast.Node, error) { defers := collectDefers(node) switch len(defers) { case 0: - return nil, errors.New("failed to expression in defer") + return nil, fmt.Errorf("failed to find expression in defer") case 1: return defers[0].Call, nil default: - return nil, errors.Errorf( + return nil, fmt.Errorf( "ambiguous call expression: multiple (%d) defers in call block", len(defers)) } diff --git a/vendor/gotest.tools/v3/internal/source/source.go b/vendor/gotest.tools/v3/internal/source/source.go index c2eef0337..a3f70086d 100644 --- a/vendor/gotest.tools/v3/internal/source/source.go +++ b/vendor/gotest.tools/v3/internal/source/source.go @@ -2,6 +2,7 @@ package source // import "gotest.tools/v3/internal/source" import ( "bytes" + "errors" "fmt" "go/ast" "go/format" @@ -9,14 +10,8 @@ import ( "go/token" "os" "runtime" - "strconv" - "strings" - - "github.com/pkg/errors" ) -const baseStackIndex = 1 - // FormattedCallExprArg returns the argument from an ast.CallExpr at the // index in the call stack. The argument is formatted using FormatNode. func FormattedCallExprArg(stackIndex int, argPos int) (string, error) { @@ -33,28 +28,26 @@ func FormattedCallExprArg(stackIndex int, argPos int) (string, error) { // CallExprArgs returns the ast.Expr slice for the args of an ast.CallExpr at // the index in the call stack. func CallExprArgs(stackIndex int) ([]ast.Expr, error) { - _, filename, lineNum, ok := runtime.Caller(baseStackIndex + stackIndex) + _, filename, line, ok := runtime.Caller(stackIndex + 1) if !ok { return nil, errors.New("failed to get call stack") } - debug("call stack position: %s:%d", filename, lineNum) + debug("call stack position: %s:%d", filename, line) - node, err := getNodeAtLine(filename, lineNum) + fileset := token.NewFileSet() + astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse source file %s: %w", filename, err) } - debug("found node: %s", debugFormatNode{node}) - return getCallExprArgs(node) -} - -func getNodeAtLine(filename string, lineNum int) (ast.Node, error) { - fileset := token.NewFileSet() - astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors) + expr, err := getCallExprArgs(fileset, astFile, line) if err != nil { - return nil, errors.Wrapf(err, "failed to parse source file: %s", filename) + return nil, fmt.Errorf("call from %s:%d: %w", filename, line, err) } + return expr, nil +} +func getNodeAtLine(fileset *token.FileSet, astFile ast.Node, lineNum int) (ast.Node, error) { if node := scanToLine(fileset, astFile, lineNum); node != nil { return node, nil } @@ -64,8 +57,7 @@ func getNodeAtLine(filename string, lineNum int) (ast.Node, error) { return node, err } } - return nil, errors.Errorf( - "failed to find an expression on line %d in %s", lineNum, filename) + return nil, nil } func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node { @@ -74,7 +66,7 @@ func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node { switch { case node == nil || matchedNode != nil: return false - case nodePosition(fileset, node).Line == lineNum: + case fileset.Position(node.Pos()).Line == lineNum: matchedNode = node return false } @@ -83,46 +75,17 @@ func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node { return matchedNode } -// In golang 1.9 the line number changed from being the line where the statement -// ended to the line where the statement began. -func nodePosition(fileset *token.FileSet, node ast.Node) token.Position { - if goVersionBefore19 { - return fileset.Position(node.End()) - } - return fileset.Position(node.Pos()) -} - -// GoVersionLessThan returns true if runtime.Version() is semantically less than -// version major.minor. Returns false if a release version can not be parsed from -// runtime.Version(). -func GoVersionLessThan(major, minor int64) bool { - version := runtime.Version() - // not a release version - if !strings.HasPrefix(version, "go") { - return false - } - version = strings.TrimPrefix(version, "go") - parts := strings.Split(version, ".") - if len(parts) < 2 { - return false - } - rMajor, err := strconv.ParseInt(parts[0], 10, 32) - if err != nil { - return false - } - if rMajor != major { - return rMajor < major - } - rMinor, err := strconv.ParseInt(parts[1], 10, 32) - if err != nil { - return false +func getCallExprArgs(fileset *token.FileSet, astFile ast.Node, line int) ([]ast.Expr, error) { + node, err := getNodeAtLine(fileset, astFile, line) + switch { + case err != nil: + return nil, err + case node == nil: + return nil, fmt.Errorf("failed to find an expression") } - return rMinor < minor -} -var goVersionBefore19 = GoVersionLessThan(1, 9) + debug("found node: %s", debugFormatNode{node}) -func getCallExprArgs(node ast.Node) ([]ast.Expr, error) { visitor := &callExprVisitor{} ast.Walk(visitor, node) if visitor.expr == nil { @@ -173,6 +136,9 @@ type debugFormatNode struct { } func (n debugFormatNode) String() string { + if n.Node == nil { + return "none" + } out, err := FormatNode(n.Node) if err != nil { return fmt.Sprintf("failed to format %s: %s", n.Node, err) diff --git a/vendor/gotest.tools/v3/internal/source/update.go b/vendor/gotest.tools/v3/internal/source/update.go new file mode 100644 index 000000000..bd9678b83 --- /dev/null +++ b/vendor/gotest.tools/v3/internal/source/update.go @@ -0,0 +1,138 @@ +package source + +import ( + "bytes" + "errors" + "flag" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "os" + "runtime" + "strings" +) + +// Update is set by the -update flag. It indicates the user running the tests +// would like to update any golden values. +var Update bool + +func init() { + flag.BoolVar(&Update, "update", false, "update golden values") +} + +// ErrNotFound indicates that UpdateExpectedValue failed to find the +// variable to update, likely because it is not a package level variable. +var ErrNotFound = fmt.Errorf("failed to find variable for update of golden value") + +// UpdateExpectedValue looks for a package-level variable with a name that +// starts with expected in the arguments to the caller. If the variable is +// found, the value of the variable will be updated to value of the other +// argument to the caller. +func UpdateExpectedValue(stackIndex int, x, y interface{}) error { + _, filename, line, ok := runtime.Caller(stackIndex + 1) + if !ok { + return errors.New("failed to get call stack") + } + debug("call stack position: %s:%d", filename, line) + + fileset := token.NewFileSet() + astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors|parser.ParseComments) + if err != nil { + return fmt.Errorf("failed to parse source file %s: %w", filename, err) + } + + expr, err := getCallExprArgs(fileset, astFile, line) + if err != nil { + return fmt.Errorf("call from %s:%d: %w", filename, line, err) + } + + if len(expr) < 3 { + debug("not enough arguments %d: %v", + len(expr), debugFormatNode{Node: &ast.CallExpr{Args: expr}}) + return ErrNotFound + } + + argIndex, varName := getVarNameForExpectedValueArg(expr) + if argIndex < 0 || varName == "" { + debug("no arguments started with the word 'expected': %v", + debugFormatNode{Node: &ast.CallExpr{Args: expr}}) + return ErrNotFound + } + + value := x + if argIndex == 1 { + value = y + } + + strValue, ok := value.(string) + if !ok { + debug("value must be type string, got %T", value) + return ErrNotFound + } + return UpdateVariable(filename, fileset, astFile, varName, strValue) +} + +// UpdateVariable writes to filename the contents of astFile with the value of +// the variable updated to value. +func UpdateVariable( + filename string, + fileset *token.FileSet, + astFile *ast.File, + varName string, + value string, +) error { + obj := astFile.Scope.Objects[varName] + if obj == nil { + return ErrNotFound + } + if obj.Kind != ast.Con && obj.Kind != ast.Var { + debug("can only update var and const, found %v", obj.Kind) + return ErrNotFound + } + + spec, ok := obj.Decl.(*ast.ValueSpec) + if !ok { + debug("can only update *ast.ValueSpec, found %T", obj.Decl) + return ErrNotFound + } + if len(spec.Names) != 1 { + debug("more than one name in ast.ValueSpec") + return ErrNotFound + } + + spec.Values[0] = &ast.BasicLit{ + Kind: token.STRING, + Value: "`" + value + "`", + } + + var buf bytes.Buffer + if err := format.Node(&buf, fileset, astFile); err != nil { + return fmt.Errorf("failed to format file after update: %w", err) + } + + fh, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to open file %v: %w", filename, err) + } + if _, err = fh.Write(buf.Bytes()); err != nil { + return fmt.Errorf("failed to write file %v: %w", filename, err) + } + if err := fh.Sync(); err != nil { + return fmt.Errorf("failed to sync file %v: %w", filename, err) + } + return nil +} + +func getVarNameForExpectedValueArg(expr []ast.Expr) (int, string) { + for i := 1; i < 3; i++ { + switch e := expr[i].(type) { + case *ast.Ident: + if strings.HasPrefix(strings.ToLower(e.Name), "expected") { + return i, e.Name + } + } + } + return -1, "" +} diff --git a/vendor/gotest.tools/v3/internal/source/version.go b/vendor/gotest.tools/v3/internal/source/version.go new file mode 100644 index 000000000..5fa8a9031 --- /dev/null +++ b/vendor/gotest.tools/v3/internal/source/version.go @@ -0,0 +1,35 @@ +package source + +import ( + "runtime" + "strconv" + "strings" +) + +// GoVersionLessThan returns true if runtime.Version() is semantically less than +// version major.minor. Returns false if a release version can not be parsed from +// runtime.Version(). +func GoVersionLessThan(major, minor int64) bool { + version := runtime.Version() + // not a release version + if !strings.HasPrefix(version, "go") { + return false + } + version = strings.TrimPrefix(version, "go") + parts := strings.Split(version, ".") + if len(parts) < 2 { + return false + } + rMajor, err := strconv.ParseInt(parts[0], 10, 32) + if err != nil { + return false + } + if rMajor != major { + return rMajor < major + } + rMinor, err := strconv.ParseInt(parts[1], 10, 32) + if err != nil { + return false + } + return rMinor < minor +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ccb6edc09..d069a2f38 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -486,7 +486,7 @@ github.com/spf13/afero/mem # github.com/spf13/cast v1.4.1 ## explicit github.com/spf13/cast -# github.com/spf13/cobra v1.3.0 +# github.com/spf13/cobra v1.5.0 ## explicit; go 1.15 github.com/spf13/cobra # github.com/spf13/jwalterweatherman v1.1.0 @@ -516,6 +516,9 @@ github.com/thediveo/enumflag # github.com/vbatts/tar-split v0.11.2 ## explicit; go 1.15 github.com/vbatts/tar-split/archive/tar +# github.com/wavesoftware/go-commandline v1.0.0 +## explicit; go 1.18 +github.com/wavesoftware/go-commandline # github.com/wavesoftware/go-ensure v1.0.0 ## explicit; go 1.13 github.com/wavesoftware/go-ensure @@ -540,6 +543,9 @@ github.com/wavesoftware/go-magetasks/pkg/output/color github.com/wavesoftware/go-magetasks/pkg/strings github.com/wavesoftware/go-magetasks/pkg/tasks github.com/wavesoftware/go-magetasks/pkg/version +# github.com/wavesoftware/go-retcode v1.0.0 +## explicit; go 1.18 +github.com/wavesoftware/go-retcode # github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca ## explicit github.com/xlab/treeprint @@ -825,8 +831,8 @@ gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# gotest.tools/v3 v3.1.0 -## explicit; go 1.11 +# gotest.tools/v3 v3.3.0 +## explicit; go 1.13 gotest.tools/v3/assert gotest.tools/v3/assert/cmp gotest.tools/v3/icmd