diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go new file mode 100644 index 0000000..3754d28 --- /dev/null +++ b/cmd/ctl/main.go @@ -0,0 +1,31 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + + "github.com/etcd-io/auger/pkg/ctl" +) + +func main() { + if err := ctl.RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index eed8961..1653f78 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,15 @@ module github.com/etcd-io/auger go 1.22.0 require ( + github.com/bgentry/speakeasy v0.1.0 github.com/coreos/bbolt v1.3.1-coreos.3 github.com/coreos/etcd v3.1.11+incompatible github.com/google/safetext v0.0.0-20220914124124-e18e3fe012bf github.com/spf13/cobra v1.8.0 + go.etcd.io/etcd/client/pkg/v3 v3.5.13 + go.etcd.io/etcd/client/v3 v3.5.13 + go.etcd.io/etcd/etcdctl/v3 v3.5.13 + go.etcd.io/etcd/pkg/v3 v3.5.13 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.30.1 k8s.io/apimachinery v0.30.1 @@ -14,20 +19,59 @@ require ( ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.0.1 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jonboulle/clockwork v0.2.2 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/prometheus/client_golang v1.11.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.9.0 // indirect + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.etcd.io/etcd/api/v3 v3.5.13 // indirect + go.etcd.io/etcd/client/v2 v2.305.13 // indirect + go.etcd.io/etcd/etcdutl/v3 v3.5.13 // indirect + go.etcd.io/etcd/raft/v3 v3.5.13 // indirect + go.etcd.io/etcd/server/v3 v3.5.13 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect + go.opentelemetry.io/otel v1.20.0 // indirect + go.opentelemetry.io/otel/metric v1.20.0 // indirect + go.opentelemetry.io/otel/trace v1.20.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.17.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect diff --git a/go.sum b/go.sum index 7e1f4be..5d512b8 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,82 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/coreos/bbolt v1.3.1-coreos.3 h1:sP70znHBV8469pbVsmR2G6wvd2oQwgxtgWyZvV3KVBo= github.com/coreos/bbolt v1.3.1-coreos.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.1.11+incompatible h1:8Zwb+fsI+3wXGp23Pjn1eEFE/3P5ib5yXVoAMRxFOgM= github.com/coreos/etcd v3.1.11+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -22,57 +87,180 @@ github.com/google/safetext v0.0.0-20220914124124-e18e3fe012bf h1:F57w3YBUKQis5sQ github.com/google/safetext v0.0.0-20220914124124-e18e3fe012bf/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +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.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= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4= +go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c= +go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg= +go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8= +go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8= +go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg= +go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js= +go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI= +go.etcd.io/etcd/etcdctl/v3 v3.5.13 h1:bqdaPQe+vnw9efmn8W+riArgdl3uUvuw4yk2QhSZaI0= +go.etcd.io/etcd/etcdctl/v3 v3.5.13/go.mod h1:+EKywV/K3xA/OIUWlTKzzhYMpepqQN+KZQ71F85YTuk= +go.etcd.io/etcd/etcdutl/v3 v3.5.13 h1:GEAIyquWCRS0P9UAs6QmMgo36t9tT6hHNLb3g25DGNg= +go.etcd.io/etcd/etcdutl/v3 v3.5.13/go.mod h1:2vhvTIQobP+Cb04qzlcbKGvX6J5oq/N1kquk1yCDIQY= +go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= +go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0= +go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= +go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= +go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= +go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= +go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= +go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= +go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= +go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= +go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= +go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -81,16 +269,45 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= diff --git a/pkg/client/client.go b/pkg/client/client.go new file mode 100644 index 0000000..aa12c19 --- /dev/null +++ b/pkg/client/client.go @@ -0,0 +1,450 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + "fmt" + "strings" + + "github.com/etcd-io/auger/pkg/encoding" + + "k8s.io/apimachinery/pkg/runtime/schema" + + clientv3 "go.etcd.io/etcd/client/v3" +) + +// Client is an interface that defines the operations that can be performed on an etcd client. +type Client interface { + // Get is a method that retrieves a key-value pair from the etcd server. + // It returns the revision of the key-value pair + Get(ctx context.Context, prefix string, opOpts ...OpOption) (rev int64, err error) + + // Watch is a method that watches for changes to a key-value pair on the etcd server. + Watch(ctx context.Context, prefix string, opOpts ...OpOption) error + + // Delete is a method that deletes a key-value pair from the etcd server. + Delete(ctx context.Context, prefix string, opOpts ...OpOption) error + + // Put is a method that sets a key-value pair on the etcd server. + Put(ctx context.Context, prefix string, value []byte, opOpts ...OpOption) error +} + +// client is the etcd client. +type client struct { + client *clientv3.Client +} + +type Config = clientv3.Config + +// NewClient creates a new etcd client. +func NewClient(conf Config) (Client, error) { + cli, err := clientv3.New(conf) + if err != nil { + return nil, err + } + return &client{ + client: cli, + }, nil +} + +func (c *client) getPrefix(prefix string, opt Op) (string, bool, error) { + var single bool + var arr [4]string + s := arr[:0] + s = append(s, prefix) + + if !opt.gr.Empty() { + p, err := PrefixFromGR(opt.gr) + if err != nil { + return "", false, err + } + s = append(s, p) + if opt.namespace != "" { + s = append(s, opt.namespace) + } + if opt.name != "" { + s = append(s, opt.name) + single = true + } + } + return strings.Join(s, "/"), single, nil +} + +// Op is the option for the operation. +type Op struct { + gr schema.GroupResource + name string + namespace string + response func(kv *KeyValue) error + pageLimit int64 + keysOnly bool + revision int64 +} + +// OpOption is the option for the operation. +type OpOption func(*Op) + +// WithGR sets the gr for the target. +func WithGR(gr schema.GroupResource) OpOption { + return func(o *Op) { + o.gr = gr + } +} + +// WithName sets the name and namespace for the target. +func WithName(name, namespace string) OpOption { + return func(o *Op) { + o.name = name + o.namespace = namespace + } +} + +// WithResponse sets the response callback for the target. +func WithResponse(response func(kv *KeyValue) error) OpOption { + return func(o *Op) { + o.response = response + } +} + +// WithPageLimit sets the page limit for the target. +func WithPageLimit(pageLimit int64) OpOption { + return func(o *Op) { + o.pageLimit = pageLimit + } +} + +// WithKeysOnly sets the keys only for the target. +func WithKeysOnly() OpOption { + return func(o *Op) { + o.keysOnly = true + } +} + +// WithRevision sets the revision for the target. +func WithRevision(revision int64) OpOption { + return func(o *Op) { + o.revision = revision + } +} + +func opOption(opts []OpOption) Op { + var opt Op + for _, o := range opts { + o(&opt) + } + return opt +} + +func (c *client) Get(ctx context.Context, prefix string, opOpts ...OpOption) (rev int64, err error) { + opt := opOption(opOpts) + if opt.response == nil { + return 0, fmt.Errorf("response is required") + } + + prefix, single, err := c.getPrefix(prefix, opt) + if err != nil { + return 0, err + } + + opts := []clientv3.OpOption{} + if opt.keysOnly { + opts = append(opts, clientv3.WithKeysOnly()) + } + + if single || opt.pageLimit == 0 { + if !single { + opts = append(opts, clientv3.WithPrefix()) + } + resp, err := c.client.Get(ctx, prefix, opts...) + if err != nil { + return 0, err + } + for _, kv := range resp.Kvs { + r := &KeyValue{ + Key: kv.Key, + Value: kv.Value, + } + err := opt.response(r) + if err != nil { + return 0, err + } + } + return resp.Header.Revision, nil + } + + respchan := make(chan clientv3.GetResponse, 10) + errchan := make(chan error, 1) + var revision int64 + + go func() { + defer close(respchan) + defer close(errchan) + + var key string + + opts := append(opts, clientv3.WithLimit(opt.pageLimit)) + if opt.revision != 0 { + revision = opt.revision + opts = append(opts, clientv3.WithRev(revision)) + } + + if len(prefix) == 0 { + // If len(s.prefix) == 0, we will sync the entire key-value space. + // We then range from the smallest key (0x00) to the end. + opts = append(opts, clientv3.WithFromKey()) + key = "\x00" + } else { + // If len(s.prefix) != 0, we will sync key-value space with given prefix. + // We then range from the prefix to the next prefix if exists. Or we will + // range from the prefix to the end if the next prefix does not exists. + opts = append(opts, clientv3.WithRange(clientv3.GetPrefixRangeEnd(prefix))) + key = prefix + } + + for { + resp, err := c.client.Get(ctx, key, opts...) + if err != nil { + errchan <- err + return + } + + respchan <- *resp + + if revision == 0 { + revision = resp.Header.Revision + opts = append(opts, clientv3.WithRev(resp.Header.Revision)) + } + + if !resp.More { + return + } + + // move to next key + key = string(append(resp.Kvs[len(resp.Kvs)-1].Key, 0)) + } + }() + + for resp := range respchan { + for _, kv := range resp.Kvs { + r := &KeyValue{ + Key: kv.Key, + Value: kv.Value, + } + err := opt.response(r) + if err != nil { + return 0, err + } + } + } + + err = <-errchan + if err != nil { + return 0, err + } + + return revision, nil +} + +func (c *client) Watch(ctx context.Context, prefix string, opOpts ...OpOption) error { + opt := opOption(opOpts) + if opt.response == nil { + return fmt.Errorf("response is required") + } + + prefix, single, err := c.getPrefix(prefix, opt) + if err != nil { + return err + } + + opts := []clientv3.OpOption{} + if opt.keysOnly { + opts = append(opts, clientv3.WithKeysOnly()) + } + + if !single { + opts = append(opts, clientv3.WithPrefix()) + } + + if opt.revision != 0 { + opts = append(opts, clientv3.WithRev(opt.revision)) + } + + opts = append(opts, clientv3.WithPrevKV()) + + watchChan := c.client.Watch(ctx, prefix, opts...) + for watchResp := range watchChan { + for _, event := range watchResp.Events { + r := &KeyValue{ + Key: event.Kv.Key, + Value: event.Kv.Value, + } + if event.PrevKv != nil { + r.PrevValue = event.PrevKv.Value + } + err := opt.response(r) + if err != nil { + return err + } + } + } + return nil +} + +func (c *client) Delete(ctx context.Context, prefix string, opOpts ...OpOption) error { + opt := opOption(opOpts) + prefix, _, err := c.getPrefix(prefix, opt) + if err != nil { + return err + } + + opts := []clientv3.OpOption{} + + if opt.name == "" { + opts = append(opts, clientv3.WithPrefix()) + } + + if opt.response != nil { + if opt.keysOnly { + opts = append(opts, clientv3.WithKeysOnly()) + } + opts = append(opts, clientv3.WithPrevKV()) + } + + resp, err := c.client.Delete(ctx, prefix, opts...) + if err != nil { + return err + } + + if opt.response != nil { + for _, kv := range resp.PrevKvs { + r := &KeyValue{ + Key: kv.Key, + PrevValue: kv.Value, + } + err = opt.response(r) + if err != nil { + return err + } + } + } + return nil +} + +func (c *client) Put(ctx context.Context, prefix string, value []byte, opOpts ...OpOption) error { + opt := opOption(opOpts) + prefix, single, err := c.getPrefix(prefix, opt) + if err != nil { + return err + } + if !single { + return fmt.Errorf("put only support single") + } + + opts := []clientv3.OpOption{} + + if opt.response != nil { + if opt.keysOnly { + opts = append(opts, clientv3.WithKeysOnly()) + } + opts = append(opts, clientv3.WithPrevKV()) + } + + resp, err := c.client.Put(ctx, prefix, string(value), opts...) + if err != nil { + return err + } + + if opt.response != nil { + var r *KeyValue + if resp.PrevKv != nil { + r = &KeyValue{ + Key: resp.PrevKv.Key, + Value: value, + PrevValue: resp.PrevKv.Value, + } + } + err = opt.response(r) + if err != nil { + return err + } + } + return nil +} + +// KeyValue is the key-value pair. +type KeyValue struct { + Key []byte + Value []byte + PrevValue []byte +} + +// specialDefaultResourcePrefixes are prefixes compiled into Kubernetes. +// see k8s.io/kubernetes/pkg/kubeapiserver/default_storage_factory_builder.go +var specialDefaultResourcePrefixes = map[schema.GroupResource]string{ + {Group: "", Resource: "replicationcontrollers"}: "controllers", + {Group: "", Resource: "endpoints"}: "services/endpoints", + {Group: "", Resource: "services"}: "services/specs", + {Group: "", Resource: "nodes"}: "minions", + {Group: "extensions", Resource: "ingresses"}: "ingress", + {Group: "networking.k8s.io", Resource: "ingresses"}: "ingress", +} + +var specialDefaultMediaTypes = map[string]struct{}{ + "apiextensions.k8s.io": {}, + "apiregistration.k8s.io": {}, +} + +// PrefixFromGR returns the prefix of the given GroupResource. +func PrefixFromGR(gr schema.GroupResource) (prefix string, err error) { + groupPrefix := false + + if _, ok := specialDefaultMediaTypes[gr.Group]; ok { + groupPrefix = true + } else if !strings.Contains(gr.Group, ".") || strings.HasSuffix(gr.Group, ".k8s.io") { + // custom resources + groupPrefix = false + } else { + // builtin resource + groupPrefix = true + } + + if prefix, ok := specialDefaultResourcePrefixes[gr]; ok { + return prefix, nil + } + + if groupPrefix { + return gr.Group + "/" + gr.Resource, nil + } + + return gr.Resource, nil +} + +// MediaTypeFromGR returns the media type of the given GroupResource. +func MediaTypeFromGR(gr schema.GroupResource) (mediaType string, err error) { + mediaType = encoding.JsonMediaType + + if _, ok := specialDefaultMediaTypes[gr.Group]; ok { + return mediaType, nil + } + + if !strings.Contains(gr.Group, ".") || strings.HasSuffix(gr.Group, ".k8s.io") { + return encoding.StorageBinaryMediaType, nil + } + + return mediaType, nil +} diff --git a/pkg/ctl/command/del/del.go b/pkg/ctl/command/del/del.go new file mode 100644 index 0000000..53b53bf --- /dev/null +++ b/pkg/ctl/command/del/del.go @@ -0,0 +1,116 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package del defines a command to delete data in etcd +package del + +import ( + "context" + "fmt" + "os" + + "github.com/etcd-io/auger/pkg/client" + "github.com/etcd-io/auger/pkg/ctl/command" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type flagpole struct { + Namespace string + Output string + Prefix string +} + +// NewCommand returns a new cobra.Command for use hack the etcd data. +func NewCommand() *cobra.Command { + flags := &flagpole{} + + cmd := &cobra.Command{ + Args: cobra.RangeArgs(0, 2), + Use: "del [resource] [name]", + Short: "Deletes the resource of k8s in etcd", + RunE: func(cmd *cobra.Command, args []string) error { + etcdclient := command.MustClientFromCmd(cmd) + err := runE(cmd.Context(), etcdclient, flags, args) + + if err != nil { + return fmt.Errorf("%v: %w", args, err) + } + return nil + }, + } + + cmd.Flags().StringVarP(&flags.Output, "output", "o", "key", "output format. One of: (key, none).") + cmd.Flags().StringVarP(&flags.Namespace, "namespace", "n", "", "namespace of resource") + cmd.Flags().StringVar(&flags.Prefix, "prefix", "/registry", "prefix to prepend to the resource") + return cmd +} + +func runE(ctx context.Context, etcdclient client.Client, flags *flagpole, args []string) error { + + var targetGr schema.GroupResource + var targetName string + var targetNamespace string + if len(args) != 0 { + // TODO: Support get information from CRD and scheme.Codecs + // Support short name + // Check for namespaced + + gr := schema.ParseGroupResource(args[0]) + if gr.Empty() { + return fmt.Errorf("invalid resource %q", args[0]) + } + targetGr = gr + targetNamespace = flags.Namespace + if len(args) >= 2 { + targetName = args[1] + } + } + + var count int + var response func(kv *client.KeyValue) error + if flags.Output == "key" { + response = func(kv *client.KeyValue) error { + count++ + fmt.Fprintf(os.Stdout, "%s\n", kv.Key) + return nil + } + } + + opOpts := []client.OpOption{ + client.WithName(targetName, targetNamespace), + client.WithGR(targetGr), + } + + if response != nil { + opOpts = append(opOpts, + client.WithKeysOnly(), + client.WithResponse(response), + ) + } + + err := etcdclient.Delete(ctx, flags.Prefix, + opOpts..., + ) + if err != nil { + return err + } + + if flags.Output == "key" { + fmt.Fprintf(os.Stderr, "delete %d keys\n", count) + } + return nil +} diff --git a/pkg/ctl/command/get/get.go b/pkg/ctl/command/get/get.go new file mode 100644 index 0000000..f7bf8e2 --- /dev/null +++ b/pkg/ctl/command/get/get.go @@ -0,0 +1,199 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package get defines a command to get data in etcd +package get + +import ( + "context" + "fmt" + "os" + + "github.com/etcd-io/auger/pkg/client" + "github.com/etcd-io/auger/pkg/ctl/command" + "github.com/etcd-io/auger/pkg/encoding" + "github.com/etcd-io/auger/pkg/scheme" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type flagpole struct { + Namespace string + Output string + ChunkSize int64 + Watch bool + WatchOnly bool + Prefix string +} + +// NewCommand returns the cobra command for "get". +func NewCommand() *cobra.Command { + flags := &flagpole{} + + cmd := &cobra.Command{ + Args: cobra.RangeArgs(0, 2), + Use: "get [resource] [name]", + Short: "Gets the resource of k8s in etcd", + RunE: func(cmd *cobra.Command, args []string) error { + etcdclient := command.MustClientFromCmd(cmd) + err := runE(cmd.Context(), etcdclient, flags, args) + + if err != nil { + return fmt.Errorf("%v: %w", args, err) + } + return nil + }, + } + + cmd.Flags().StringVarP(&flags.Output, "output", "o", "yaml", "output format. One of: (json, yaml, raw, key).") + cmd.Flags().StringVarP(&flags.Namespace, "namespace", "n", "", "namespace of resource") + cmd.Flags().BoolVarP(&flags.Watch, "watch", "w", false, "after listing/getting the requested object, watch for changes") + cmd.Flags().BoolVar(&flags.WatchOnly, "watch-only", false, "watch for changes to the requested object(s), without listing/getting first") + cmd.Flags().Int64Var(&flags.ChunkSize, "chunk-size", 500, "chunk size of the list pager") + cmd.Flags().StringVar(&flags.Prefix, "prefix", "/registry", "prefix to prepend to the resource") + + return cmd +} + +func runE(ctx context.Context, etcdclient client.Client, flags *flagpole, args []string) error { + var targetGr schema.GroupResource + var targetName string + var targetNamespace string + if len(args) != 0 { + // TODO: Support get information from CRD and scheme.Codecs + // Support short name + // Check for namespaced + + gr := schema.ParseGroupResource(args[0]) + if gr.Empty() { + return fmt.Errorf("invalid resource %q", args[0]) + } + targetGr = gr + targetNamespace = flags.Namespace + if len(args) >= 2 { + targetName = args[1] + } + } + + var count int + var response func(kv *client.KeyValue) error + + switch flags.Output { + case "json": + outMediaType := encoding.JsonMediaType + response = func(kv *client.KeyValue) error { + count++ + value := kv.Value + if value == nil { + value = kv.PrevValue + } + inMediaType, _, err := encoding.DetectAndExtract(value) + if err != nil { + fmt.Fprintf(os.Stdout, "---\n# %s | raw | %v\n# %s\n", kv.Key, err, value) + return nil + } + data, _, err := encoding.Convert(scheme.Codecs, inMediaType, outMediaType, value) + if err != nil { + fmt.Fprintf(os.Stdout, "---\n# %s | raw | %v\n# %s\n", kv.Key, err, value) + } else { + fmt.Fprintf(os.Stdout, "---\n# %s | %s\n%s\n", kv.Key, inMediaType, data) + } + return nil + } + case "yaml": + outMediaType := encoding.YamlMediaType + response = func(kv *client.KeyValue) error { + count++ + value := kv.Value + if value == nil { + value = kv.PrevValue + } + inMediaType, _, err := encoding.DetectAndExtract(value) + if err != nil { + fmt.Fprintf(os.Stdout, "---\n# %s | raw | %v\n# %s\n", kv.Key, err, value) + return nil + } + data, _, err := encoding.Convert(scheme.Codecs, inMediaType, outMediaType, value) + if err != nil { + fmt.Fprintf(os.Stdout, "---\n# %s | raw | %v\n# %s\n", kv.Key, err, value) + } else { + fmt.Fprintf(os.Stdout, "---\n# %s | %s\n%s\n", kv.Key, inMediaType, data) + } + return nil + } + case "raw": + response = func(kv *client.KeyValue) error { + count++ + fmt.Fprintf(os.Stdout, "%s\n%s\n", kv.Key, kv.Value) + return nil + } + case "key": + response = func(kv *client.KeyValue) error { + count++ + fmt.Fprintf(os.Stdout, "%s\n", kv.Key) + return nil + } + default: + return fmt.Errorf("unsupported output format: %s", flags.Output) + } + + opOpts := []client.OpOption{ + client.WithName(targetName, targetNamespace), + client.WithGR(targetGr), + client.WithPageLimit(flags.ChunkSize), + client.WithResponse(response), + } + + if flags.Output == "key" { + opOpts = append(opOpts, + client.WithKeysOnly(), + ) + } + + var err error + if flags.Watch { + var rev int64 + if !flags.WatchOnly { + rev, err = etcdclient.Get(ctx, flags.Prefix, + opOpts..., + ) + if err != nil { + return err + } + } + + opOpts = append(opOpts, client.WithRevision(rev)) + + err = etcdclient.Watch(ctx, flags.Prefix, + opOpts..., + ) + if err != nil { + return err + } + } else { + _, err = etcdclient.Get(ctx, flags.Prefix, + opOpts..., + ) + if err != nil { + return err + } + + if flags.Output == "key" { + fmt.Fprintf(os.Stderr, "get %d keys\n", count) + } + } + return nil +} diff --git a/pkg/ctl/command/global.go b/pkg/ctl/command/global.go new file mode 100644 index 0000000..69f33cb --- /dev/null +++ b/pkg/ctl/command/global.go @@ -0,0 +1,376 @@ +// Copyright 2015 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package command + +import ( + "crypto/tls" + "errors" + "fmt" + "os" + "strings" + "time" + + "github.com/bgentry/speakeasy" + "github.com/etcd-io/auger/pkg/client" + "github.com/spf13/cobra" + + "go.etcd.io/etcd/client/pkg/v3/srv" + "go.etcd.io/etcd/client/pkg/v3/transport" + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/pkg/v3/cobrautl" +) + +// GlobalFlags are flags that defined globally +// and are inherited to all sub-commands. +type GlobalFlags struct { + Insecure bool + InsecureSkipVerify bool + InsecureDiscovery bool + Endpoints []string + DialTimeout time.Duration + CommandTimeOut time.Duration + KeepAliveTime time.Duration + KeepAliveTimeout time.Duration + DNSClusterServiceName string + + TLS transport.TLSInfo + + User string + Password string +} + +type secureCfg struct { + cert string + key string + cacert string + serverName string + + insecureTransport bool + insecureSkipVerify bool +} + +type authCfg struct { + username string + password string +} + +type discoveryCfg struct { + domain string + insecure bool + serviceName string +} + +type clientConfig struct { + endpoints []string + dialTimeout time.Duration + keepAliveTime time.Duration + keepAliveTimeout time.Duration + scfg *secureCfg + acfg *authCfg +} + +func clientConfigFromCmd(cmd *cobra.Command) *clientConfig { + var err error + cfg := &clientConfig{} + cfg.endpoints, err = endpointsFromCmd(cmd) + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitError, err) + } + + cfg.dialTimeout = dialTimeoutFromCmd(cmd) + cfg.keepAliveTime = keepAliveTimeFromCmd(cmd) + cfg.keepAliveTimeout = keepAliveTimeoutFromCmd(cmd) + + cfg.scfg = secureCfgFromCmd(cmd) + cfg.acfg = authCfgFromCmd(cmd) + + return cfg +} + +func MustClientFromCmd(cmd *cobra.Command) client.Client { + cfg := clientConfigFromCmd(cmd) + return cfg.mustClient() +} + +func (cc *clientConfig) mustClient() client.Client { + cfg, err := newClientCfg(cc.endpoints, cc.dialTimeout, cc.keepAliveTime, cc.keepAliveTimeout, cc.scfg, cc.acfg) + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) + } + + client, err := client.NewClient(*cfg) + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadConnection, err) + } + + return client +} + +func newClientCfg(endpoints []string, dialTimeout, keepAliveTime, keepAliveTimeout time.Duration, scfg *secureCfg, acfg *authCfg) (*clientv3.Config, error) { + // set tls if any one tls option set + var cfgtls *transport.TLSInfo + tlsinfo := transport.TLSInfo{} + if scfg.cert != "" { + tlsinfo.CertFile = scfg.cert + cfgtls = &tlsinfo + } + + if scfg.key != "" { + tlsinfo.KeyFile = scfg.key + cfgtls = &tlsinfo + } + + if scfg.cacert != "" { + tlsinfo.TrustedCAFile = scfg.cacert + cfgtls = &tlsinfo + } + + if scfg.serverName != "" { + tlsinfo.ServerName = scfg.serverName + cfgtls = &tlsinfo + } + + cfg := &clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + DialKeepAliveTime: keepAliveTime, + DialKeepAliveTimeout: keepAliveTimeout, + } + + if cfgtls != nil { + clientTLS, err := cfgtls.ClientConfig() + if err != nil { + return nil, err + } + cfg.TLS = clientTLS + } + + // if key/cert is not given but user wants secure connection, we + // should still setup an empty tls configuration for gRPC to setup + // secure connection. + if cfg.TLS == nil && !scfg.insecureTransport { + cfg.TLS = &tls.Config{} + } + + // If the user wants to skip TLS verification then we should set + // the InsecureSkipVerify flag in tls configuration. + if scfg.insecureSkipVerify && cfg.TLS != nil { + cfg.TLS.InsecureSkipVerify = true + } + + if acfg != nil { + cfg.Username = acfg.username + cfg.Password = acfg.password + } + + return cfg, nil +} + +func dialTimeoutFromCmd(cmd *cobra.Command) time.Duration { + dialTimeout, err := cmd.Flags().GetDuration("dial-timeout") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitError, err) + } + return dialTimeout +} + +func keepAliveTimeFromCmd(cmd *cobra.Command) time.Duration { + keepAliveTime, err := cmd.Flags().GetDuration("keepalive-time") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitError, err) + } + return keepAliveTime +} + +func keepAliveTimeoutFromCmd(cmd *cobra.Command) time.Duration { + keepAliveTimeout, err := cmd.Flags().GetDuration("keepalive-timeout") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitError, err) + } + return keepAliveTimeout +} + +func secureCfgFromCmd(cmd *cobra.Command) *secureCfg { + cert, key, cacert := keyAndCertFromCmd(cmd) + insecureTr := insecureTransportFromCmd(cmd) + skipVerify := insecureSkipVerifyFromCmd(cmd) + discoveryCfg := discoveryCfgFromCmd(cmd) + + if discoveryCfg.insecure { + discoveryCfg.domain = "" + } + + return &secureCfg{ + cert: cert, + key: key, + cacert: cacert, + serverName: discoveryCfg.domain, + + insecureTransport: insecureTr, + insecureSkipVerify: skipVerify, + } +} + +func insecureTransportFromCmd(cmd *cobra.Command) bool { + insecureTr, err := cmd.Flags().GetBool("insecure-transport") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitError, err) + } + return insecureTr +} + +func insecureSkipVerifyFromCmd(cmd *cobra.Command) bool { + skipVerify, err := cmd.Flags().GetBool("insecure-skip-tls-verify") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitError, err) + } + return skipVerify +} + +func keyAndCertFromCmd(cmd *cobra.Command) (cert, key, cacert string) { + var err error + if cert, err = cmd.Flags().GetString("cert"); err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) + } else if cert == "" && cmd.Flags().Changed("cert") { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, errors.New("empty string is passed to --cert option")) + } + + if key, err = cmd.Flags().GetString("key"); err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) + } else if key == "" && cmd.Flags().Changed("key") { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, errors.New("empty string is passed to --key option")) + } + + if cacert, err = cmd.Flags().GetString("cacert"); err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) + } else if cacert == "" && cmd.Flags().Changed("cacert") { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, errors.New("empty string is passed to --cacert option")) + } + + return cert, key, cacert +} + +func authCfgFromCmd(cmd *cobra.Command) *authCfg { + userFlag, err := cmd.Flags().GetString("user") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) + } + passwordFlag, err := cmd.Flags().GetString("password") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) + } + + if userFlag == "" { + return nil + } + + var cfg authCfg + + if passwordFlag == "" { + splitted := strings.SplitN(userFlag, ":", 2) + if len(splitted) < 2 { + cfg.username = userFlag + cfg.password, err = speakeasy.Ask("Password: ") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitError, err) + } + } else { + cfg.username = splitted[0] + cfg.password = splitted[1] + } + } else { + cfg.username = userFlag + cfg.password = passwordFlag + } + + return &cfg +} + +func insecureDiscoveryFromCmd(cmd *cobra.Command) bool { + discovery, err := cmd.Flags().GetBool("insecure-discovery") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitError, err) + } + return discovery +} + +func discoverySrvFromCmd(cmd *cobra.Command) string { + domainStr, err := cmd.Flags().GetString("discovery-srv") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) + } + return domainStr +} + +func discoveryDNSClusterServiceNameFromCmd(cmd *cobra.Command) string { + serviceNameStr, err := cmd.Flags().GetString("discovery-srv-name") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) + } + return serviceNameStr +} + +func discoveryCfgFromCmd(cmd *cobra.Command) *discoveryCfg { + return &discoveryCfg{ + domain: discoverySrvFromCmd(cmd), + insecure: insecureDiscoveryFromCmd(cmd), + serviceName: discoveryDNSClusterServiceNameFromCmd(cmd), + } +} + +func endpointsFromCmd(cmd *cobra.Command) ([]string, error) { + eps, err := endpointsFromFlagValue(cmd) + if err != nil { + return nil, err + } + // If domain discovery returns no endpoints, check endpoints flag + if len(eps) == 0 { + eps, err = cmd.Flags().GetStringSlice("endpoints") + if err == nil { + for i, ip := range eps { + eps[i] = strings.TrimSpace(ip) + } + } + } + return eps, err +} + +func endpointsFromFlagValue(cmd *cobra.Command) ([]string, error) { + discoveryCfg := discoveryCfgFromCmd(cmd) + + // If we still don't have domain discovery, return nothing + if discoveryCfg.domain == "" { + return []string{}, nil + } + + srvs, err := srv.GetClient("etcd-client", discoveryCfg.domain, discoveryCfg.serviceName) + if err != nil { + return nil, err + } + eps := srvs.Endpoints + if discoveryCfg.insecure { + return eps, err + } + // strip insecure connections + ret := []string{} + for _, ep := range eps { + if strings.HasPrefix(ep, "http://") { + fmt.Fprintf(os.Stderr, "ignoring discovered insecure endpoint %q\n", ep) + continue + } + ret = append(ret, ep) + } + return ret, err +} diff --git a/pkg/ctl/command/put/put.go b/pkg/ctl/command/put/put.go new file mode 100644 index 0000000..981df37 --- /dev/null +++ b/pkg/ctl/command/put/put.go @@ -0,0 +1,240 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package put defines a command to put data in etcd +package put + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "time" + + "github.com/etcd-io/auger/pkg/client" + "github.com/etcd-io/auger/pkg/ctl/command" + "github.com/etcd-io/auger/pkg/encoding" + "github.com/etcd-io/auger/pkg/scheme" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/yaml" +) + +type flagpole struct { + Namespace string + Output string + Path string + Prefix string +} + +// NewCommand returns a new cobra.Command for use hack the etcd data. +func NewCommand() *cobra.Command { + flags := &flagpole{} + + cmd := &cobra.Command{ + Args: cobra.RangeArgs(0, 2), + Use: "put [resource] [name]", + Short: "Puts the resource of k8s in etcd", + RunE: func(cmd *cobra.Command, args []string) error { + etcdclient := command.MustClientFromCmd(cmd) + err := runE(cmd.Context(), etcdclient, flags, args) + + if err != nil { + return fmt.Errorf("%v: %w", args, err) + } + return nil + }, + } + + cmd.Flags().StringVarP(&flags.Output, "output", "o", "key", "output format. One of: (key, none).") + cmd.Flags().StringVarP(&flags.Namespace, "namespace", "n", "", "namespace of resource") + cmd.Flags().StringVar(&flags.Prefix, "prefix", "/registry", "prefix to prepend to the resource") + cmd.Flags().StringVar(&flags.Path, "path", "", "path of the file") + return cmd +} + +func runE(ctx context.Context, etcdclient client.Client, flags *flagpole, args []string) error { + + var reader io.Reader + var err error + switch flags.Path { + case "-": + reader = os.Stdin + case "": + return fmt.Errorf("path is required") + default: + reader, err = os.Open(flags.Path) + if err != nil { + return err + } + } + + var wantGr *schema.GroupResource + var wantName string + if len(args) != 0 { + // TODO: Support get information from CRD and scheme.Codecs + // Support short name + // Check for namespaced + + gr := schema.ParseGroupResource(args[0]) + if gr.Empty() { + return fmt.Errorf("invalid resource %q", args[0]) + } + wantGr = &gr + if len(args) >= 2 { + wantName = args[1] + } + } + + start := time.Now() + + var count int + var response func(kv *client.KeyValue) error + if flags.Output == "key" { + //nolint:unparam + response = func(kv *client.KeyValue) error { + count++ + if kv != nil { + fmt.Fprintf(os.Stdout, "%s\n", kv.Key) + } + return nil + } + } + + err = decodeToUnstructured(reader, func(obj *unstructured.Unstructured) error { + targetName := obj.GetName() + if targetName == "" { + // There will be some unnamed hidden resources, which we should also ignore. + return nil + } + + // TODO: Use a safe way to convert GVK to GVR + // Verify that all built-in resources conform to this rule + // For custom resources try to get information from the CRD + targetGvr, _ := meta.UnsafeGuessKindToResource(obj.GroupVersionKind()) + + targetGr := targetGvr.GroupResource() + targetNamespace := obj.GetNamespace() + + if targetNamespace != "" && flags.Namespace != "" && targetNamespace != flags.Namespace { + return nil + } + + if wantGr != nil && *wantGr != targetGr { + return nil + } + + if wantName != "" && wantName != targetName { + return nil + } + + if targetName == "" { + return nil + } + + mediaType, err := client.MediaTypeFromGR(targetGr) + if err != nil { + return err + } + + t := obj.GetCreationTimestamp() + if t.IsZero() { + obj.SetCreationTimestamp(metav1.Time{Time: start}) + } + + obj.SetResourceVersion("") + obj.SetSelfLink("") + + data, err := obj.MarshalJSON() + if err != nil { + return err + } + + data, _, err = encoding.Convert(scheme.Codecs, encoding.JsonMediaType, mediaType, data) + if err != nil { + return err + } + + opOpts := []client.OpOption{ + client.WithName(targetName, targetNamespace), + client.WithGR(targetGr), + } + + if response != nil { + opOpts = append(opOpts, + client.WithResponse(response), + client.WithKeysOnly(), + ) + } + + err = etcdclient.Put(ctx, flags.Prefix, data, + opOpts..., + ) + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + + if flags.Output == "key" { + fmt.Fprintf(os.Stderr, "put %d keys\n", count) + } + return nil +} + +func decodeToUnstructured(reader io.Reader, visitFunc func(obj *unstructured.Unstructured) error) error { + d := yaml.NewYAMLToJSONDecoder(reader) + + for { + obj := &unstructured.Unstructured{} + err := d.Decode(&obj) + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return err + } + + if obj.IsList() { + err = obj.EachListItem(func(object runtime.Object) error { + obj := object.(*unstructured.Unstructured) + if len(obj.Object) == 0 { + return nil + } + return visitFunc(object.(*unstructured.Unstructured)) + }) + if err != nil { + return err + } + } else { + if len(obj.Object) == 0 { + continue + } + err = visitFunc(obj) + if err != nil { + return err + } + } + } +} diff --git a/pkg/ctl/ctl.go b/pkg/ctl/ctl.go new file mode 100644 index 0000000..92bb970 --- /dev/null +++ b/pkg/ctl/ctl.go @@ -0,0 +1,75 @@ +// Copyright 2015 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package ctlv3 contains the main entry point for the etcdctl for v3 API. +package ctl + +import ( + "time" + + "github.com/etcd-io/auger/pkg/ctl/command/del" + "github.com/etcd-io/auger/pkg/ctl/command/get" + "github.com/etcd-io/auger/pkg/ctl/command/put" + "github.com/spf13/cobra" + + "go.etcd.io/etcd/etcdctl/v3/ctlv3/command" +) + +const ( + cliName = "ctl" // TODO: + cliDescription = "A simple command line client for etcd in kubernetes." + + defaultDialTimeout = 2 * time.Second + defaultCommandTimeOut = 5 * time.Second + defaultKeepAliveTime = 2 * time.Second + defaultKeepAliveTimeOut = 6 * time.Second +) + +var ( + globalFlags = command.GlobalFlags{} +) + +var ( + RootCmd = &cobra.Command{ + Use: cliName, + Short: cliDescription, + SuggestFor: []string{cliName}, + } +) + +func init() { + RootCmd.PersistentFlags().StringSliceVar(&globalFlags.Endpoints, "endpoints", []string{"127.0.0.1:2379"}, "gRPC endpoints") + + RootCmd.PersistentFlags().DurationVar(&globalFlags.DialTimeout, "dial-timeout", defaultDialTimeout, "dial timeout for client connections") + RootCmd.PersistentFlags().DurationVar(&globalFlags.CommandTimeOut, "command-timeout", defaultCommandTimeOut, "timeout for short running command (excluding dial timeout)") + RootCmd.PersistentFlags().DurationVar(&globalFlags.KeepAliveTime, "keepalive-time", defaultKeepAliveTime, "keepalive time for client connections") + RootCmd.PersistentFlags().DurationVar(&globalFlags.KeepAliveTimeout, "keepalive-timeout", defaultKeepAliveTimeOut, "keepalive timeout for client connections") + + RootCmd.PersistentFlags().BoolVar(&globalFlags.Insecure, "insecure-transport", true, "disable transport security for client connections") + RootCmd.PersistentFlags().BoolVar(&globalFlags.InsecureDiscovery, "insecure-discovery", true, "accept insecure SRV records describing cluster endpoints") + RootCmd.PersistentFlags().BoolVar(&globalFlags.InsecureSkipVerify, "insecure-skip-tls-verify", false, "skip server certificate verification (CAUTION: this option should be enabled only for testing purposes)") + RootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CertFile, "cert", "", "identify secure client using this TLS certificate file") + RootCmd.PersistentFlags().StringVar(&globalFlags.TLS.KeyFile, "key", "", "identify secure client using this TLS key file") + RootCmd.PersistentFlags().StringVar(&globalFlags.TLS.TrustedCAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle") + RootCmd.PersistentFlags().StringVar(&globalFlags.User, "user", "", "username[:password] for authentication (prompt if password is not supplied)") + RootCmd.PersistentFlags().StringVar(&globalFlags.Password, "password", "", "password for authentication (if this option is used, --user option shouldn't include password)") + RootCmd.PersistentFlags().StringVarP(&globalFlags.TLS.ServerName, "discovery-srv", "d", "", "domain name to query for SRV records describing cluster endpoints") + RootCmd.PersistentFlags().StringVarP(&globalFlags.DNSClusterServiceName, "discovery-srv-name", "", "", "service name to query when using DNS discovery") + + RootCmd.AddCommand( + get.NewCommand(), + del.NewCommand(), + put.NewCommand(), + ) +}