From ce1ecd68c6342f260d618b04f50294e7119c3c3d Mon Sep 17 00:00:00 2001 From: Liang Deng <283304489@qq.com> Date: Wed, 20 Sep 2023 19:27:14 +0800 Subject: [PATCH 1/3] feat: add command-line tool admiralctl and implement the migrate subcommand Signed-off-by: Liang Deng <283304489@qq.com> Signed-off-by: Liang Deng <283304489@qq.com> Signed-off-by: Liang Deng <283304489@qq.com> Signed-off-by: Liang Deng <283304489@qq.com> Signed-off-by: Liang Deng <283304489@qq.com> --- cmd/admiralctl/admiralctl.go | 31 + go.mod | 22 +- go.sum | 30 +- pkg/admiralctl/admiralctl.go | 95 +++ pkg/admiralctl/federalize/federalize.go | 667 +++++++++++++++++++ pkg/admiralctl/options/options.go | 56 ++ pkg/admiralctl/util/factory.go | 105 +++ pkg/admiralctl/util/restmapper/restmapper.go | 48 ++ 8 files changed, 1051 insertions(+), 3 deletions(-) create mode 100755 cmd/admiralctl/admiralctl.go create mode 100644 pkg/admiralctl/admiralctl.go create mode 100644 pkg/admiralctl/federalize/federalize.go create mode 100644 pkg/admiralctl/options/options.go create mode 100644 pkg/admiralctl/util/factory.go create mode 100644 pkg/admiralctl/util/restmapper/restmapper.go diff --git a/cmd/admiralctl/admiralctl.go b/cmd/admiralctl/admiralctl.go new file mode 100755 index 00000000..3a7ef668 --- /dev/null +++ b/cmd/admiralctl/admiralctl.go @@ -0,0 +1,31 @@ +/* +Copyright 2023 The KubeAdmiral 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 ( + "k8s.io/component-base/cli" + "k8s.io/kubectl/pkg/cmd/util" + + "github.com/kubewharf/kubeadmiral/pkg/admiralctl" +) + +func main() { + cmd := admiralctl.NewDefaultAdmiralctlCommand() + if err := cli.RunNoErrOutput(cmd); err != nil { + util.CheckErr(err) + } +} diff --git a/go.mod b/go.mod index d6204f1f..a243c467 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/onsi/gomega v1.27.8 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.14.0 + github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 golang.org/x/sync v0.2.0 @@ -21,11 +22,13 @@ require ( k8s.io/apiextensions-apiserver v0.26.6 k8s.io/apimachinery v0.27.1 k8s.io/apiserver v0.27.1 + k8s.io/cli-runtime v0.26.6 k8s.io/client-go v0.27.1 k8s.io/component-base v0.27.1 k8s.io/klog/v2 v2.90.1 k8s.io/kube-openapi v0.0.0-20230426210814-b0c0aaee3cc0 k8s.io/metrics v0.27.1 + k8s.io/kubectl v0.26.6 k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 sigs.k8s.io/controller-runtime v0.14.1 sigs.k8s.io/custom-metrics-apiserver v1.27.0 @@ -34,8 +37,10 @@ require ( require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/alessio/shellescape v1.4.1 // indirect github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -44,10 +49,13 @@ require ( 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.4.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/go-errors/errors v1.0.1 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -56,10 +64,12 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/cel-go v0.12.6 // indirect + github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect @@ -67,20 +77,28 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml v1.9.4 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - github.com/spf13/cobra v1.6.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xlab/treeprint v1.1.0 // indirect + go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect go.etcd.io/etcd/api/v3 v3.5.7 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect @@ -116,6 +134,8 @@ require ( k8s.io/kms v0.27.1 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.12.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 0f44eab9..1040bfe0 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= @@ -87,6 +89,8 @@ github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV 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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -271,6 +275,12 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= 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= @@ -278,6 +288,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN 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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -293,6 +305,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 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= @@ -327,7 +341,9 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 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= @@ -335,8 +351,9 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= @@ -519,6 +536,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -545,6 +563,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -581,6 +600,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -736,6 +756,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/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= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= 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= @@ -778,6 +800,10 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kind v0.17.0 h1:CScmGz/wX66puA06Gj8OZb76Wmk7JIjgWf5JDvY7msM= sigs.k8s.io/kind v0.17.0/go.mod h1:Qqp8AiwOlMZmJWs37Hgs31xcbiYXjtXlRBSftcnZXQk= +sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= +sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= +sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= +sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/pkg/admiralctl/admiralctl.go b/pkg/admiralctl/admiralctl.go new file mode 100644 index 00000000..056d83ed --- /dev/null +++ b/pkg/admiralctl/admiralctl.go @@ -0,0 +1,95 @@ +/* +Copyright 2023 The KubeAdmiral 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 admiralctl + +import ( + "flag" + "fmt" + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/federalize" + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/options" + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "k8s.io/cli-runtime/pkg/genericclioptions" + apiserverflag "k8s.io/component-base/cli/flag" + "k8s.io/klog/v2" + "k8s.io/kubectl/pkg/util/templates" + "os" +) + +var ( + cliName = "admiralctl" + rootCmdShort = "%s controls the Kubernetes cluster federation manager." + // defaultConfigFlags It composes the set of values necessary for obtaining a REST client config with default values set. + defaultConfigFlags = genericclioptions.NewConfigFlags(true). + WithDeprecatedPasswordFlag().WithDiscoveryBurst(300).WithDiscoveryQPS(50.0) +) + +// NewDefaultAdmiralctlCommand creates the `admiralctl` command. +func NewDefaultAdmiralctlCommand() *cobra.Command { + rootCmd := &cobra.Command{ + Use: cliName, + Short: fmt.Sprintf(rootCmdShort, cliName), + RunE: runHelp, + } + + // Init log flags + klog.InitFlags(flag.CommandLine) + + // Add the command line flags from other dependencies (e.g., klog), but do not + // warn if they contain underscores. + pflag.CommandLine.SetNormalizeFunc(apiserverflag.WordSepNormalizeFunc) + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + flags := rootCmd.PersistentFlags() + flags.AddFlagSet(pflag.CommandLine) + addKubeConfigFlags(flags) + + // From this point and forward we get warnings on flags that contain "_" separators + // when adding them with hyphen instead of the original name. + rootCmd.SetGlobalNormalizationFunc(apiserverflag.WarnWordSepNormalizeFunc) + + // Prevent klog errors about logging before parsing. + _ = flag.CommandLine.Parse(nil) + f := util.NewFactory(defaultConfigFlags) + ioStreams := genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} + groups := templates.CommandGroups{ + { + Message: "Resource Management Commands:", + Commands: []*cobra.Command{ + federalize.NewCmdFederalize(f, cliName), + }, + }, + } + groups.Add(rootCmd) + filters := []string{"options"} + rootCmd.AddCommand(options.NewCmdOptions(cliName, ioStreams.Out)) + templates.ActsAsRootCommand(rootCmd, filters, groups...) + + return rootCmd +} + +// addKubeConfigFlags adds flags to the specified FlagSet. +func addKubeConfigFlags(flags *pflag.FlagSet) { + flags.StringVar(defaultConfigFlags.KubeConfig, "kubeconfig", *defaultConfigFlags.KubeConfig, + "Path to the kubeconfig file to use for CLI requests.") + flags.StringVar(defaultConfigFlags.Context, "kubeadmiral-context", *defaultConfigFlags.Context, + "The name of the kubeconfig context to use") +} + +func runHelp(cmd *cobra.Command, _ []string) error { + return cmd.Help() +} diff --git a/pkg/admiralctl/federalize/federalize.go b/pkg/admiralctl/federalize/federalize.go new file mode 100644 index 00000000..33ec8a86 --- /dev/null +++ b/pkg/admiralctl/federalize/federalize.go @@ -0,0 +1,667 @@ +/* +Copyright 2023 The KubeAdmiral 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 federalize + +import ( + "context" + "fmt" + "github.com/kubewharf/kubeadmiral/pkg/util/naming" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "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/schema" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util" + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util/restmapper" + fedcorev1a1 "github.com/kubewharf/kubeadmiral/pkg/apis/core/v1alpha1" + fedclient "github.com/kubewharf/kubeadmiral/pkg/client/clientset/versioned" + "github.com/kubewharf/kubeadmiral/pkg/client/generic/scheme" + "github.com/kubewharf/kubeadmiral/pkg/controllers/common" + "github.com/kubewharf/kubeadmiral/pkg/controllers/scheduler" + "github.com/kubewharf/kubeadmiral/pkg/util/adoption" +) + +var ( + federalizeLongDesc = templates.LongDesc(` + Federalize resources from target cluster to Kubeadmiral control plane. + The target cluster needs to have joined the federation. + + If the resource already exists in Kubeadmiral control plane, + you need to edit PropagationPolicy and OverridePolicy to propagate it. + `) + + federalizeExample = templates.Examples(` + # Federalize deployment(default/busybox) from cluster1 to Kubeadmiral + %[1]s federalize deployment busybox -n default -C cluster1 + + # Federalize deployment(default/busybox) with gvk from cluster1 to Kubeadmiral + %[1]s federalize deployment.v1.apps busybox -n default -C cluster1 + + # Support to use '--dry-run' to print resource and policy that need to be deployed + %[1]s federalize deployment busybox -n default -C cluster1 --dry-run + + # Support to use '--policy-name' to specify a policy that already exists + %[1]s federalize deployment busybox -n default -C cluster1 --auto-create-policy=false --policy-name= + + # Federalize secret(default/default-secret) from cluster1 to Kubeadmiral + %[1]s federalize secret default-secret -n default -C cluster1 + + # Support to use '--cluster-kubeconfig' to specify the kubeconfig of member cluster + %[1]s federalize deployment busybox -n default -C cluster1 --cluster-kubeconfig= + + # Support to use '--cluster-kubeconfig' and '--cluster-context' to specify the kubeconfig and context of member cluster + %[1]s federalize deployment busybox -n default -C cluster1 --cluster-kubeconfig= --cluster-context=`) +) + +// CommandFederalizeOption holds all command options for federalize +type CommandFederalizeOption struct { + // AutoCreatePolicy determines whether a PropagationPolicy + // or ClusterPropagationPolicy should be created automatically. + AutoCreatePolicy bool + + // Cluster is the name of target cluster + Cluster string + + // ClusterContext is context name of target cluster in kubeconfig. + ClusterContext string + + // ClusterKubeConfig is the target cluster's kubeconfig path. + ClusterKubeConfig string + + // DryRun run the command in dry-run mode. + DryRun bool + + // Namespace is the namespace of target resource + Namespace string + + // OutputFormat determines the output format, json or yaml. + OutputFormat string + + // PolicyName is the name of the PropagationPolicy or ClusterPropagationPolicy. + // If AutoCreatePolicy is false, it needs to be specified as an existing policy. + PolicyName string + + // SchedulingMode determines the mode used by the scheduler when scheduling federated objects. + SchedulingMode string + + JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags + Printer func(*meta.RESTMapping, *bool, bool, bool) (printers.ResourcePrinterFunc, error) + + DynamicClientset dynamic.Interface + FedClientset *fedclient.Clientset + + controlPlaneRestConfig *rest.Config + resourceName string + resourceKind string + resourceObj *unstructured.Unstructured + resource.FilenameOptions + mapper meta.RESTMapper + gvr schema.GroupVersionResource + gvk schema.GroupVersionKind +} + +// AddFlags adds flags for a specified FlagSet. +func (o *CommandFederalizeOption) AddFlags(flags *pflag.FlagSet) { + flags.BoolVar(&o.AutoCreatePolicy, "auto-create-policy", true, + "Automatically create a PropagationPolicy for namespace-scoped resources or "+ + "create a ClusterPropagationPolicy for cluster-scoped resources.") + flags.StringVarP(&o.Cluster, "cluster", "C", "", "The name of target cluster (eg. -C=member1).") + flags.StringVar(&o.ClusterContext, "cluster-context", "", "Context name of target cluster in kubeconfig.") + flags.StringVar(&o.ClusterKubeConfig, "cluster-kubeconfig", "", "Path of the target cluster's kubeconfig.") + flags.BoolVar(&o.DryRun, "dry-run", false, "Run the command in dry-run mode.") + flags.StringVarP(&o.Namespace, "namespace", "n", o.Namespace, "The namespace of the resource to be federalized.") + flags.StringVarP(&o.OutputFormat, "output", "o", "yaml", "Output format: yaml or json.") + flags.StringVar(&o.PolicyName, "policy-name", "", + "The name of the PropagationPolicy or ClusterPropagationPolicy that is automatically created after migration. "+ + "If not specified, the policy name will be the resource name with resource group and kind.") + flags.StringVar(&o.SchedulingMode, "scheduling-mode", string(fedcorev1a1.SchedulingModeDuplicate), + "determines the mode used by the scheduler when scheduling federated objects, One of: Duplicate or Divide, default is Duplicate.") +} + +// NewCmdFederalize creates the `federalize` command +func NewCmdFederalize(f util.Factory, parentCommand string) *cobra.Command { + opts := CommandFederalizeOption{ + JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(), + } + + cmd := &cobra.Command{ + Use: "federalize -n -C ", + Short: "federalize resource from target clusters to Kubeadmiral control plane", + Long: federalizeLongDesc, + Example: fmt.Sprintf(federalizeExample, parentCommand), + SilenceUsage: true, + DisableFlagsInUseLine: true, + RunE: func(cmd *cobra.Command, args []string) error { + if err := opts.Preflight(f, args); err != nil { + return err + } + if err := opts.Federalize(); err != nil { + return err + } + return nil + }, + } + + flag := cmd.Flags() + opts.AddFlags(flag) + + return cmd +} + +// Obj cluster info +type Obj struct { + Cluster string + Info *resource.Info +} + +// Preflight validate the option in advance and set option value. +func (o *CommandFederalizeOption) Preflight(f util.Factory, args []string) error { + var err error + + if len(args) != 2 { + return fmt.Errorf("command line input format error") + } + + if o.Cluster == "" { + return fmt.Errorf("the cluster name cannot be empty") + } + + if o.OutputFormat != "" && o.OutputFormat != "yaml" && o.OutputFormat != "json" { + return fmt.Errorf("output format is only one of json and yaml") + } + + if !o.AutoCreatePolicy && o.PolicyName == "" { + return fmt.Errorf("if AutoCreatePolicy is false, you need to specify an existing policy") + } + + o.resourceKind = args[0] + o.resourceName = args[1] + + o.Printer = func(mapping *meta.RESTMapping, outputObjects *bool, withNamespace bool, + withKind bool) (printers.ResourcePrinterFunc, error) { + printer, err := o.JSONYamlPrintFlags.ToPrinter(o.OutputFormat) + if err != nil { + return nil, err + } + + printer, err = printers.NewTypeSetter(scheme.Scheme).WrapToPrinter(printer, nil) + if err != nil { + return nil, err + } + + return printer.PrintObj, nil + } + + if o.Namespace == "" { + o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return fmt.Errorf("failed to get namespace from Factory. error: %w", err) + } + } + + if len(o.ClusterContext) == 0 { + o.ClusterContext = o.Cluster + } + + o.controlPlaneRestConfig, err = f.ToRESTConfig() + if err != nil { + return fmt.Errorf("failed to get control plane rest config. err: %w", err) + } + + o.DynamicClientset = dynamic.NewForConfigOrDie(o.controlPlaneRestConfig) + o.FedClientset = fedclient.NewForConfigOrDie(o.controlPlaneRestConfig) + + memberClusterFactory, err := o.createMemberClusterFactory(f) + if err != nil { + return err + } + + objInfo, err := o.getObjInfo(memberClusterFactory, o.Cluster, args) + if err != nil { + return fmt.Errorf("failed to get resource in cluster(%s). err: %w", o.Cluster, err) + } + + obj := objInfo.Info.Object.(*unstructured.Unstructured) + + // Resources in deletion do not support federalization + if obj.GetDeletionTimestamp() != nil { + return fmt.Errorf("this resource is in the deleted state and cannot be federalized") + } + + o.resourceObj = obj + o.gvk = obj.GetObjectKind().GroupVersionKind() + o.mapper, err = restmapper.GetRESTMapper(o.controlPlaneRestConfig) + if err != nil { + return fmt.Errorf("failed to create restmapper: %w", err) + } + + o.gvr, err = restmapper.ConvertGVKToGVR(o.mapper, o.gvk) + if err != nil { + return fmt.Errorf("failed to get gvr from %q: %w", o.gvk, err) + } + + return nil +} + +// getObjInfo get obj info in member cluster +func (o *CommandFederalizeOption) getObjInfo(f cmdutil.Factory, cluster string, args []string) (*Obj, error) { + r := f.NewBuilder(). + Unstructured(). + NamespaceParam(o.Namespace). + FilenameParam(false, &o.FilenameOptions). + RequestChunksOf(500). + ResourceTypeOrNameArgs(true, args...). + ContinueOnError(). + Latest(). + Flatten(). + Do() + + r.IgnoreErrors(apierrors.IsNotFound) + + infos, err := r.Infos() + if err != nil { + return nil, err + } + + if len(infos) == 0 { + return nil, fmt.Errorf("resource %s(%s) don't exist in cluster(%s)", o.resourceKind, o.resourceName, o.Cluster) + } + + obj := &Obj{ + Cluster: cluster, + Info: infos[0], + } + + return obj, nil +} + +// createMemberClusterFactory create member cluster factory base on whether kubeconfig is specified +func (o *CommandFederalizeOption) createMemberClusterFactory(f util.Factory) (cmdutil.Factory, error) { + var memberClusterFactory cmdutil.Factory + var err error + if o.ClusterKubeConfig != "" { + memberClusterFactory, err = util.NewClusterFactoryByKubeConfig(o.ClusterKubeConfig, o.ClusterContext) + if err != nil { + return nil, err + } + } else { + memberClusterFactory, err = f.NewClusterFactoryByClusterName(o.Cluster) + if err != nil { + return nil, err + } + } + return memberClusterFactory, nil +} + +// Federalize federalize resource from target cluster +func (o *CommandFederalizeOption) Federalize() error { + if err := templateForResource(o.resourceObj); err != nil { + return fmt.Errorf("failed to convert resource %q(%s/%s) to template: %w", o.gvr, o.Namespace, o.resourceName, err) + } + + // if dry running, just print resourceObj and policy. + if o.DryRun { + err := o.printResourceObjectAndPolicy(o.resourceObj) + return err + } + + policyName := o.PolicyName + var err error + if len(o.resourceObj.GetNamespace()) == 0 { + if o.AutoCreatePolicy { + policyName, err = o.createClusterPropagationPolicy() + if err != nil { + return err + } + } + + _, err := o.DynamicClientset.Resource(o.gvr).Get(context.TODO(), o.resourceName, metav1.GetOptions{}) + if err == nil { + fmt.Printf("Resource %q(%s) already exist in Kubeadmiral control plane.", o.gvr, o.resourceName) + return nil + } + + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get resource %q(%s) in control plane: %w", o.gvr, o.resourceName, err) + } + + bindPolicyToResource(o.resourceObj, policyName) + setConflictResolutionAnnotation(o.resourceObj) + + _, err = o.DynamicClientset.Resource(o.gvr).Create(context.TODO(), o.resourceObj, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create resource %q(%s) in control plane: %w", o.gvr, o.resourceName, err) + } + + fmt.Printf("Resource %q(%s) is federalized successfully\n", o.gvr, o.resourceName) + } else { + if o.AutoCreatePolicy { + policyName, err = o.createPropagationPolicy() + if err != nil { + return err + } + } + + _, err := o.DynamicClientset.Resource(o.gvr).Namespace(o.Namespace).Get(context.TODO(), o.resourceName, metav1.GetOptions{}) + if err == nil { + fmt.Printf("Resource %q(%s/%s) already exist in Kubeadmiral control plane.", o.gvr, o.Namespace, o.resourceName) + return nil + } + + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get resource %q(%s/%s) in control plane: %w", o.gvr, o.Namespace, o.resourceName, err) + } + + bindPolicyToResource(o.resourceObj, policyName) + setConflictResolutionAnnotation(o.resourceObj) + + _, err = o.DynamicClientset.Resource(o.gvr).Namespace(o.Namespace).Create(context.TODO(), o.resourceObj, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create resource %q(%s/%s) in control plane: %w", o.gvr, o.Namespace, o.resourceName, err) + } + + fmt.Printf("Resource %q(%s/%s) is federalized successfully\n", o.gvr, o.Namespace, o.resourceName) + } + + return nil +} + +// bindPolicyToResource bind the policy to the resource as a label +func bindPolicyToResource(obj *unstructured.Unstructured, policyName string) { + labels := obj.DeepCopy().GetLabels() + if labels == nil { + labels = map[string]string{} + } + if _, exist := labels[scheduler.PropagationPolicyNameLabel]; !exist { + labels[scheduler.PropagationPolicyNameLabel] = policyName + } + obj.SetLabels(labels) +} + +// setConflictResolutionAnnotation set ConflictResolution "adopt" to resource +// because the resources that need to be federalized already exist in the cluster +func setConflictResolutionAnnotation(obj *unstructured.Unstructured) { + annotations := obj.DeepCopy().GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + if _, exist := annotations[adoption.ConflictResolutionAnnotation]; !exist { + annotations[adoption.ConflictResolutionAnnotation] = string(adoption.ConflictResolutionAdopt) + } + obj.SetAnnotations(annotations) +} + +// printResourceObjectAndPolicy print the converted resource and federalized policy +func (o *CommandFederalizeOption) printResourceObjectAndPolicy(resourceObj *unstructured.Unstructured) error { + printer, err := o.Printer(nil, nil, false, false) + if err != nil { + return fmt.Errorf("failed to initialize k8s printer. err: %w", err) + } + + if err = printer.PrintObj(resourceObj, os.Stdout); err != nil { + return fmt.Errorf("failed to print the resource template. err: %w", err) + } + if o.AutoCreatePolicy { + var policyName string + if o.PolicyName == "" { + policyName = naming.GenerateFederatedObjectName(o.resourceName, o.gvk.String()) + } else { + policyName = o.PolicyName + } + + if len(resourceObj.GetNamespace()) == 0 { + policy := constructPropagationPolicy(policyName, "", o.Cluster, o.SchedulingMode) + if cpp, ok := policy.(*fedcorev1a1.ClusterPropagationPolicy); ok { + if err = printer.PrintObj(cpp, os.Stdout); err != nil { + return fmt.Errorf("failed to print the ClusterPropagationPolicy. err: %w", err) + } + } else { + return fmt.Errorf("failed to construct the ClusterPropagationPolicy") + } + } else { + policy := constructPropagationPolicy(policyName, o.Namespace, o.Cluster, o.SchedulingMode) + if pp, ok := policy.(*fedcorev1a1.PropagationPolicy); ok { + if err = printer.PrintObj(pp, os.Stdout); err != nil { + return fmt.Errorf("failed to print the PropagationPolicy. err: %w", err) + } + } else { + return fmt.Errorf("failed to construct the PropagationPolicy") + } + } + } else { + // The user specifies a policy that already exists + if len(resourceObj.GetNamespace()) == 0 { + policy, err := o.FedClientset.CoreV1alpha1().ClusterPropagationPolicies().Get(context.TODO(), o.PolicyName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get the ClusterPropagationPolicy %s. err: %w", o.PolicyName, err) + } + if err = printer.PrintObj(policy, os.Stdout); err != nil { + return fmt.Errorf("failed to print the ClusterPropagationPolicy. err: %w", err) + } + } else { + policy, err := o.FedClientset.CoreV1alpha1().PropagationPolicies(o.Namespace).Get(context.TODO(), o.PolicyName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get the PropagationPolicy %s in namespace %s. err: %w", o.PolicyName, o.Namespace, err) + } + if err = printer.PrintObj(policy, os.Stdout); err != nil { + return fmt.Errorf("failed to print the PropagationPolicy %s in namespace %s. err: %w", o.PolicyName, o.Namespace, err) + } + } + } + + return nil +} + +// createPropagationPolicy create PropagationPolicy in Kubeadmiral control plane +func (o *CommandFederalizeOption) createPropagationPolicy() (string, error) { + var policyName string + if o.PolicyName == "" { + policyName = naming.GenerateFederatedObjectName(o.resourceName, strings.ToLower(fmt.Sprintf("%s-%s", o.gvk.Group, o.gvk.Kind))) + } else { + policyName = o.PolicyName + } + + _, err := o.FedClientset.CoreV1alpha1().PropagationPolicies(o.Namespace).Get(context.TODO(), policyName, metav1.GetOptions{}) + if err != nil && apierrors.IsNotFound(err) { + policy := constructPropagationPolicy(policyName, o.Namespace, o.Cluster, o.SchedulingMode) + if pp, ok := policy.(*fedcorev1a1.PropagationPolicy); ok { + _, err = o.FedClientset.CoreV1alpha1().PropagationPolicies(o.Namespace).Create(context.TODO(), pp, metav1.CreateOptions{}) + if err != nil { + return "", err + } + } else { + err = fmt.Errorf("failed to construct the PropagationPolicy") + } + + return policyName, err + } + if err != nil { + return "", fmt.Errorf("failed to get PropagationPolicy(%s/%s) in control plane: %w", o.Namespace, policyName, err) + } + + // PropagationPolicy already exists, not to create it + return "", fmt.Errorf("the PropagationPolicy(%s/%s) already exist, please edit it to propagate resource", o.Namespace, policyName) +} + +// createClusterPropagationPolicy create ClusterPropagationPolicy in Kubeadmiral control plane +func (o *CommandFederalizeOption) createClusterPropagationPolicy() (string, error) { + var policyName string + if o.PolicyName == "" { + policyName = naming.GenerateFederatedObjectName(o.resourceName, o.gvk.String()) + } else { + policyName = o.PolicyName + } + + _, err := o.FedClientset.CoreV1alpha1().ClusterPropagationPolicies().Get(context.TODO(), policyName, metav1.GetOptions{}) + if err != nil && apierrors.IsNotFound(err) { + policy := constructPropagationPolicy(policyName, "", o.Cluster, o.SchedulingMode) + if cpp, ok := policy.(*fedcorev1a1.ClusterPropagationPolicy); ok { + _, err = o.FedClientset.CoreV1alpha1().ClusterPropagationPolicies().Create(context.TODO(), cpp, metav1.CreateOptions{}) + } else { + err = fmt.Errorf("failed to construct the ClusterPropagationPolicy") + } + + return policyName, err + } + if err != nil { + return "", fmt.Errorf("failed to get ClusterPropagationPolicy(%s) in control plane: %w", policyName, err) + } + + // ClusterPropagationPolicy already exists, not to create it + return "", fmt.Errorf("the ClusterPropagationPolicy(%s) already exist, please edit it to propagate resource", policyName) +} + +// templateForResource convert resource to template +func templateForResource(resource *unstructured.Unstructured) error { + resource.SetSelfLink("") + resource.SetUID("") + resource.SetResourceVersion("") + resource.SetCreationTimestamp(metav1.Time{}) + resource.SetOwnerReferences(nil) + resource.SetFinalizers(nil) + resource.SetManagedFields(nil) + resource.SetDeletionGracePeriodSeconds(nil) + resource.SetGeneration(0) + unstructured.RemoveNestedField(resource.Object, common.StatusField) + + if resource.GetKind() == common.ServiceKind { + clusterIP, exist, _ := unstructured.NestedString(resource.Object, "spec", "clusterIP") + if exist && clusterIP != corev1.ClusterIPNone { + unstructured.RemoveNestedField(resource.Object, "spec", "clusterIP") + unstructured.RemoveNestedField(resource.Object, "spec", "clusterIPs") + } + } + + if resource.GetKind() == common.JobKind { + manualSelector, exists, _ := unstructured.NestedBool(resource.Object, "spec", "manualSelector") + if !exists || exists && !manualSelector { + if err := removeGenerateSelectorOfJob(resource); err != nil { + return err + } + } + } + + if resource.GetKind() == common.ServiceAccountKind { + secrets, exist, _ := unstructured.NestedSlice(resource.Object, "secrets") + if exist && len(secrets) > 0 { + tokenPrefix := fmt.Sprintf("%s-token-", resource.GetName()) + for idx := 0; idx < len(secrets); idx++ { + if strings.HasPrefix(secrets[idx].(map[string]interface{})["name"].(string), tokenPrefix) { + secrets = append(secrets[:idx], secrets[idx+1:]...) + } + } + _ = unstructured.SetNestedSlice(resource.Object, secrets, "secrets") + } + } + return nil +} + +// getLabelValue get the label value by labelKey. +func getLabelValue(labels map[string]string, labelKey string) string { + if labels == nil { + return "" + } + + return labels[labelKey] +} + +// removeGenerateSelectorOfJob remove some unwanted information that is not needed during +// the job federalization process +func removeGenerateSelectorOfJob(resource *unstructured.Unstructured) error { + matchLabels, exist, err := unstructured.NestedStringMap(resource.Object, "spec", "selector", "matchLabels") + if err != nil { + return err + } + if exist { + if getLabelValue(matchLabels, "controller-uid") != "" { + delete(matchLabels, "controller-uid") + } + err = unstructured.SetNestedStringMap(resource.Object, matchLabels, "spec", "selector", "matchLabels") + if err != nil { + return err + } + } + + labels, exist, err := unstructured.NestedStringMap(resource.Object, "spec", "template", "metadata", "labels") + if err != nil { + return err + } + if exist { + if getLabelValue(labels, "controller-uid") != "" { + delete(labels, "controller-uid") + } + + if getLabelValue(labels, "job-name") != "" { + delete(labels, "job-name") + } + + err = unstructured.SetNestedStringMap(resource.Object, labels, "spec", "template", "metadata", "labels") + if err != nil { + return err + } + } + return nil +} + +// constructPropagationPolicy construct PropagationPolicy or ClusterPropagationPolicy +func constructPropagationPolicy(name, namespace, cluster, schedulingMode string) interface{} { + objMeta := metav1.ObjectMeta{Name: name} + + var clusterScoped bool + if len(namespace) == 0 { + clusterScoped = true + } + + if !clusterScoped { + objMeta.Namespace = namespace + } + + spec := fedcorev1a1.PropagationPolicySpec{ + SchedulingMode: fedcorev1a1.SchedulingMode(schedulingMode), + Placements: []fedcorev1a1.DesiredPlacement{ + { + Cluster: cluster, + }, + }, + } + + if clusterScoped { + return &fedcorev1a1.ClusterPropagationPolicy{ + ObjectMeta: objMeta, + Spec: spec, + } + } + + return &fedcorev1a1.PropagationPolicy{ + ObjectMeta: objMeta, + Spec: spec, + } +} diff --git a/pkg/admiralctl/options/options.go b/pkg/admiralctl/options/options.go new file mode 100644 index 00000000..503b2c27 --- /dev/null +++ b/pkg/admiralctl/options/options.go @@ -0,0 +1,56 @@ +/* +Copyright 2023 The KubeAdmiral 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 options + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + "k8s.io/kubectl/pkg/util/templates" +) + +var optionsExample = templates.Examples(` + # Print flags inherited by all commands + %[1]s options`) + +// NewCmdOptions implements the options command +func NewCmdOptions(parentCommand string, out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "options", + Short: "Print the list of flags inherited by all commands", + Long: "Print the list of flags inherited by all commands", + Example: fmt.Sprintf(optionsExample, parentCommand), + RunE: func(cmd *cobra.Command, args []string) error { + if err := cmd.Usage(); err != nil { + return err + } + return nil + }, + } + + // The `options` command needs write its output to the `out` stream + // (typically stdout). Without calling SetOutput here, the Usage() + // function call will fall back to stderr. + // + // See https://github.com/kubernetes/kubernetes/pull/46394 for details. + cmd.SetOut(out) + cmd.SetErr(out) + + templates.UseOptionsTemplates(cmd) + return cmd +} diff --git a/pkg/admiralctl/util/factory.go b/pkg/admiralctl/util/factory.go new file mode 100644 index 00000000..898d44f3 --- /dev/null +++ b/pkg/admiralctl/util/factory.go @@ -0,0 +1,105 @@ +/* +Copyright 2023 The KubeAdmiral 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 util + +import ( + "context" + fedclient "github.com/kubewharf/kubeadmiral/pkg/client/clientset/versioned" + "github.com/kubewharf/kubeadmiral/pkg/controllers/common" + clusterutil "github.com/kubewharf/kubeadmiral/pkg/util/cluster" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/kubernetes" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +type Factory interface { + cmdutil.Factory + + NewClusterFactoryByClusterName(clusterName string) (cmdutil.Factory, error) +} + +var _ Factory = &factoryImpl{} + +// factoryImpl is the implementation of Factory +type factoryImpl struct { + cmdutil.Factory + + // kubeConfigFlags holds all the flags specified by user. + // These flags will be inherited by the member cluster's client. + kubeConfigFlags *genericclioptions.ConfigFlags +} + +// NewClusterFactoryByClusterName create a new ClusterFactory by ClusterName +func (f *factoryImpl) NewClusterFactoryByClusterName(clusterName string) (cmdutil.Factory, error) { + restConfig, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + + fedClientset, err := fedclient.NewForConfig(restConfig) + if err != nil { + return nil, err + } + + cluster, err := fedClientset.CoreV1alpha1().FederatedClusters().Get( + context.TODO(), + clusterName, + metav1.GetOptions{}, + ) + if err != nil { + return nil, err + } + + kubeClientset, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return nil, err + } + + config, err := clusterutil.BuildClusterConfig(cluster, kubeClientset, restConfig, common.DefaultFedSystemNamespace) + if err != nil { + return nil, err + } + + kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() + kubeConfigFlags.APIServer = &config.Host + kubeConfigFlags.BearerToken = &config.BearerToken + kubeConfigFlags.KeyFile = &config.KeyFile + kubeConfigFlags.CAFile = &config.TLSClientConfig.CAFile + kubeConfigFlags.CertFile = &config.TLSClientConfig.CertFile + kubeConfigFlags.Insecure = &config.Insecure + + return cmdutil.NewFactory(kubeConfigFlags), nil +} + +// NewFactory creates a new factory +func NewFactory(kubeConfigFlags *genericclioptions.ConfigFlags) Factory { + matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags) + f := &factoryImpl{ + kubeConfigFlags: kubeConfigFlags, + Factory: cmdutil.NewFactory(matchVersionKubeConfigFlags), + } + return f +} + +// NewClusterFactoryByKubeConfig create a new ClusterFactory by KubeConfig +func NewClusterFactoryByKubeConfig(clusterKubeConfig, clusterContext string) (cmdutil.Factory, error) { + configFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() + configFlags.KubeConfig = &clusterKubeConfig + configFlags.Context = &clusterContext + return cmdutil.NewFactory(configFlags), nil +} diff --git a/pkg/admiralctl/util/restmapper/restmapper.go b/pkg/admiralctl/util/restmapper/restmapper.go new file mode 100644 index 00000000..c6a09499 --- /dev/null +++ b/pkg/admiralctl/util/restmapper/restmapper.go @@ -0,0 +1,48 @@ +/* +Copyright 2023 The KubeAdmiral 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 restmapper + +import ( + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" +) + +func GetRESTMapper(restConfig *rest.Config) (meta.RESTMapper, error) { + dc, err := discovery.NewDiscoveryClientForConfig(restConfig) + if err != nil { + panic(err.Error()) + } + + grl, err := restmapper.GetAPIGroupResources(dc) + if err != nil { + panic(err.Error()) + } + + return restmapper.NewDiscoveryRESTMapper(grl), nil +} + +// ConvertGVKToGVR convert GVK to GVR +func ConvertGVKToGVR(restMapper meta.RESTMapper, gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) { + restMapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return schema.GroupVersionResource{}, err + } + return restMapping.Resource, nil +} From 48ffcf76f83d81d41d0bef385b2180a7f372ea1a Mon Sep 17 00:00:00 2001 From: miraclejzd <1061954832@qq.com> Date: Wed, 3 Jan 2024 10:15:33 +0000 Subject: [PATCH 2/3] add feat: admiralctl join&unjoin --- go.mod | 21 +- go.sum | 38 +- pkg/admiralctl/admiralctl.go | 175 +++---- pkg/admiralctl/federalize/federalize.go | 667 ------------------------ pkg/admiralctl/join/join.go | 304 +++++++++++ pkg/admiralctl/join/join_test.go | 126 +++++ pkg/admiralctl/options/options.go | 56 -- pkg/admiralctl/unjoin/unjoin.go | 144 +++++ pkg/admiralctl/unjoin/unjoin_test.go | 79 +++ pkg/admiralctl/util/factory.go | 195 ++++--- 10 files changed, 866 insertions(+), 939 deletions(-) delete mode 100644 pkg/admiralctl/federalize/federalize.go create mode 100644 pkg/admiralctl/join/join.go create mode 100644 pkg/admiralctl/join/join_test.go delete mode 100644 pkg/admiralctl/options/options.go create mode 100644 pkg/admiralctl/unjoin/unjoin.go create mode 100644 pkg/admiralctl/unjoin/unjoin_test.go diff --git a/go.mod b/go.mod index a243c467..74ec6896 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/onsi/gomega v1.27.8 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.14.0 - github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 golang.org/x/sync v0.2.0 @@ -22,13 +21,11 @@ require ( k8s.io/apiextensions-apiserver v0.26.6 k8s.io/apimachinery v0.27.1 k8s.io/apiserver v0.27.1 - k8s.io/cli-runtime v0.26.6 k8s.io/client-go v0.27.1 k8s.io/component-base v0.27.1 k8s.io/klog/v2 v2.90.1 k8s.io/kube-openapi v0.0.0-20230426210814-b0c0aaee3cc0 k8s.io/metrics v0.27.1 - k8s.io/kubectl v0.26.6 k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 sigs.k8s.io/controller-runtime v0.14.1 sigs.k8s.io/custom-metrics-apiserver v1.27.0 @@ -39,23 +36,23 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect - github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/alessio/shellescape v1.4.1 // indirect github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.4.0 // indirect - github.com/chai2010/gettext-go v1.0.2 // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/go-errors/errors v1.0.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -63,14 +60,15 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/cel-go v0.12.6 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/cel-go v0.12.6 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/imdario/mergo v0.3.13 // indirect @@ -97,9 +95,9 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/xlab/treeprint v1.1.0 // indirect - go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect + github.com/spf13/cobra v1.6.1 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/xlab/treeprint v1.1.0 // indirect go.etcd.io/etcd/api/v3 v3.5.7 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect go.etcd.io/etcd/client/v3 v3.5.7 // indirect @@ -113,6 +111,7 @@ require ( go.opentelemetry.io/otel/sdk v1.10.0 // indirect go.opentelemetry.io/otel/trace v1.10.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect + go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect @@ -131,7 +130,9 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/cli-runtime v0.26.6 // indirect k8s.io/kms v0.27.1 // indirect + k8s.io/kubectl v0.26.6 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.12.1 // indirect diff --git a/go.sum b/go.sum index 1040bfe0..040eda70 100644 --- a/go.sum +++ b/go.sum @@ -42,9 +42,13 @@ github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 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= @@ -56,6 +60,7 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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= @@ -71,6 +76,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/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/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -89,7 +96,6 @@ github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV 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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -98,6 +104,7 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 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/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -106,11 +113,14 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/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.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= @@ -118,6 +128,8 @@ github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNy github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -185,6 +197,7 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= @@ -218,12 +231,17 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI= github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -268,6 +286,8 @@ 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/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -343,7 +363,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 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= @@ -351,8 +370,8 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -378,6 +397,8 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -419,6 +440,8 @@ go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/A go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= @@ -757,7 +780,6 @@ gopkg.in/yaml.v3 v3.0.0/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= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= 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= @@ -773,6 +795,8 @@ k8s.io/apimachinery v0.26.6 h1:OT04J9US8G+AqfqvcJZZ8s3WUQkWbc3t6ePPWieDN6I= k8s.io/apimachinery v0.26.6/go.mod h1:qYzLkrQ9lhrZRh0jNKo2cfvf/R1/kQONnSiyB7NUJU0= k8s.io/apiserver v0.26.6 h1:gM6Ai7L4Kv+4iYeJhEd8VgP8KrdMjJNGrH9iEcnqB4c= k8s.io/apiserver v0.26.6/go.mod h1:Lfs3EMXwKERf8PUa3a+jpb23lJAFtJPDj/xKDxYktFo= +k8s.io/cli-runtime v0.26.6 h1:535Ult64Zp1583D1mewL1LdThwAcuYc4MwM69l3p2VU= +k8s.io/cli-runtime v0.26.6/go.mod h1:m/5RF2eCPVh0fhdBXgSJIyTiZuPOG0BOvkIVZtc1NqE= k8s.io/client-go v0.26.6 h1:CtC0wOxkAwjYyG2URGzdEKo0nLILopSDYn5AmzOkdi4= k8s.io/client-go v0.26.6/go.mod h1:HDjbQGY7XzFYFUWOPAfAsIYhvFXyc9l6Ne0pO0bOQ7o= k8s.io/component-base v0.26.6 h1:/Tm16Z8l/ruLFcw1XbFKTRSuxD6gQULQxxYgmar8PI0= @@ -783,6 +807,8 @@ k8s.io/kms v0.26.6 h1:cDT0gJJcDzLoV7sdZoWR5nUxlHpQI7+AWoeJyhkdtbg= k8s.io/kms v0.26.6/go.mod h1:AYuV9ZebRhr6cb1eT9L6kZVxvgIUxmE1Fe6kPhqYvuc= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kubectl v0.26.6 h1:8w/13HZ+kb7tKFoZ55Ci96L3RvjTFFuLPBEYYSOP0rA= +k8s.io/kubectl v0.26.6/go.mod h1:q9wFF+QoE0tOQnJvPbxCXnjKuot/0v/eFXNBjheEsgY= k8s.io/metrics v0.26.6 h1:gfSNDEYws2A3d1DRsHYXyAKpxE93Fn9aLUKpQI7GoEI= k8s.io/metrics v0.26.6/go.mod h1:EvxYwtVrZ7GzZ34kNjivvSCnZXdLM/uV8leXG3OIJ3U= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= @@ -807,4 +833,4 @@ sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= \ No newline at end of file diff --git a/pkg/admiralctl/admiralctl.go b/pkg/admiralctl/admiralctl.go index 056d83ed..f4f6b07d 100644 --- a/pkg/admiralctl/admiralctl.go +++ b/pkg/admiralctl/admiralctl.go @@ -1,95 +1,80 @@ -/* -Copyright 2023 The KubeAdmiral 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 admiralctl - -import ( - "flag" - "fmt" - "github.com/kubewharf/kubeadmiral/pkg/admiralctl/federalize" - "github.com/kubewharf/kubeadmiral/pkg/admiralctl/options" - "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "k8s.io/cli-runtime/pkg/genericclioptions" - apiserverflag "k8s.io/component-base/cli/flag" - "k8s.io/klog/v2" - "k8s.io/kubectl/pkg/util/templates" - "os" -) - -var ( - cliName = "admiralctl" - rootCmdShort = "%s controls the Kubernetes cluster federation manager." - // defaultConfigFlags It composes the set of values necessary for obtaining a REST client config with default values set. - defaultConfigFlags = genericclioptions.NewConfigFlags(true). - WithDeprecatedPasswordFlag().WithDiscoveryBurst(300).WithDiscoveryQPS(50.0) -) - -// NewDefaultAdmiralctlCommand creates the `admiralctl` command. -func NewDefaultAdmiralctlCommand() *cobra.Command { - rootCmd := &cobra.Command{ - Use: cliName, - Short: fmt.Sprintf(rootCmdShort, cliName), - RunE: runHelp, - } - - // Init log flags - klog.InitFlags(flag.CommandLine) - - // Add the command line flags from other dependencies (e.g., klog), but do not - // warn if they contain underscores. - pflag.CommandLine.SetNormalizeFunc(apiserverflag.WordSepNormalizeFunc) - pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - flags := rootCmd.PersistentFlags() - flags.AddFlagSet(pflag.CommandLine) - addKubeConfigFlags(flags) - - // From this point and forward we get warnings on flags that contain "_" separators - // when adding them with hyphen instead of the original name. - rootCmd.SetGlobalNormalizationFunc(apiserverflag.WarnWordSepNormalizeFunc) - - // Prevent klog errors about logging before parsing. - _ = flag.CommandLine.Parse(nil) - f := util.NewFactory(defaultConfigFlags) - ioStreams := genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} - groups := templates.CommandGroups{ - { - Message: "Resource Management Commands:", - Commands: []*cobra.Command{ - federalize.NewCmdFederalize(f, cliName), - }, - }, - } - groups.Add(rootCmd) - filters := []string{"options"} - rootCmd.AddCommand(options.NewCmdOptions(cliName, ioStreams.Out)) - templates.ActsAsRootCommand(rootCmd, filters, groups...) - - return rootCmd -} - -// addKubeConfigFlags adds flags to the specified FlagSet. -func addKubeConfigFlags(flags *pflag.FlagSet) { - flags.StringVar(defaultConfigFlags.KubeConfig, "kubeconfig", *defaultConfigFlags.KubeConfig, - "Path to the kubeconfig file to use for CLI requests.") - flags.StringVar(defaultConfigFlags.Context, "kubeadmiral-context", *defaultConfigFlags.Context, - "The name of the kubeconfig context to use") -} - -func runHelp(cmd *cobra.Command, _ []string) error { - return cmd.Help() -} +package admiralctl + +import ( + "flag" + "fmt" + + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/join" + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/unjoin" + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "k8s.io/cli-runtime/pkg/genericclioptions" + apiserverflag "k8s.io/component-base/cli/flag" + "k8s.io/klog/v2" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + cliName = "admiralctl" + rootCmdShort = "%s controls the Kubernetes cluster federation manager." + // defaultConfigFlags It composes the set of values necessary for obtaining a REST client config with default values set. + defaultConfigFlags = genericclioptions.NewConfigFlags(true). + WithDeprecatedPasswordFlag().WithDiscoveryBurst(300).WithDiscoveryQPS(50.0) +) + +// NewDefaultAdmiralctlCommand creates the `admiralctl` command. +func NewDefaultAdmiralctlCommand() *cobra.Command { + rootCmd := &cobra.Command{ + Use: cliName, + Short: fmt.Sprintf(rootCmdShort, cliName), + RunE: runHelp, + } + + // Init log flags + klog.InitFlags(flag.CommandLine) + + // Add the command line flags from other dependencies (e.g., klog), but do not + // warn if they contain underscores. + pflag.CommandLine.SetNormalizeFunc(apiserverflag.WordSepNormalizeFunc) + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + flags := rootCmd.PersistentFlags() + flags.AddFlagSet(pflag.CommandLine) + addKubeConfigFlags(flags) + + // From this point and forward we get warnings on flags that contain "_" separators + // when adding them with hyphen instead of the original name. + rootCmd.SetGlobalNormalizationFunc(apiserverflag.WarnWordSepNormalizeFunc) + + // Prevent klog errors about logging before parsing. + _ = flag.CommandLine.Parse(nil) + f := util.NewFactory(defaultConfigFlags) + + // ioStreams := genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} + groups := templates.CommandGroups{ + { + Message: "Resource Management Commands:", + Commands: []*cobra.Command{ + join.NewCmdJoin(f, cliName), + unjoin.NewCmdJoin(f, cliName), + }, + }, + } + groups.Add(rootCmd) + filters := []string{""} + templates.ActsAsRootCommand(rootCmd, filters, groups...) + + return rootCmd +} + +// addKubeConfigFlags adds flags to the specified FlagSet. +func addKubeConfigFlags(flags *pflag.FlagSet) { + flags.StringVar(defaultConfigFlags.KubeConfig, "kubeconfig", *defaultConfigFlags.KubeConfig, + "Path to the kubeconfig file to use for CLI requests.") + flags.StringVar(defaultConfigFlags.Context, "kubeadmiral-context", *defaultConfigFlags.Context, + "The name of the kubeconfig context to use") +} + +func runHelp(cmd *cobra.Command, _ []string) error { + return cmd.Help() +} diff --git a/pkg/admiralctl/federalize/federalize.go b/pkg/admiralctl/federalize/federalize.go deleted file mode 100644 index 33ec8a86..00000000 --- a/pkg/admiralctl/federalize/federalize.go +++ /dev/null @@ -1,667 +0,0 @@ -/* -Copyright 2023 The KubeAdmiral 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 federalize - -import ( - "context" - "fmt" - "github.com/kubewharf/kubeadmiral/pkg/util/naming" - "os" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "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/schema" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/cli-runtime/pkg/printers" - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util" - "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util/restmapper" - fedcorev1a1 "github.com/kubewharf/kubeadmiral/pkg/apis/core/v1alpha1" - fedclient "github.com/kubewharf/kubeadmiral/pkg/client/clientset/versioned" - "github.com/kubewharf/kubeadmiral/pkg/client/generic/scheme" - "github.com/kubewharf/kubeadmiral/pkg/controllers/common" - "github.com/kubewharf/kubeadmiral/pkg/controllers/scheduler" - "github.com/kubewharf/kubeadmiral/pkg/util/adoption" -) - -var ( - federalizeLongDesc = templates.LongDesc(` - Federalize resources from target cluster to Kubeadmiral control plane. - The target cluster needs to have joined the federation. - - If the resource already exists in Kubeadmiral control plane, - you need to edit PropagationPolicy and OverridePolicy to propagate it. - `) - - federalizeExample = templates.Examples(` - # Federalize deployment(default/busybox) from cluster1 to Kubeadmiral - %[1]s federalize deployment busybox -n default -C cluster1 - - # Federalize deployment(default/busybox) with gvk from cluster1 to Kubeadmiral - %[1]s federalize deployment.v1.apps busybox -n default -C cluster1 - - # Support to use '--dry-run' to print resource and policy that need to be deployed - %[1]s federalize deployment busybox -n default -C cluster1 --dry-run - - # Support to use '--policy-name' to specify a policy that already exists - %[1]s federalize deployment busybox -n default -C cluster1 --auto-create-policy=false --policy-name= - - # Federalize secret(default/default-secret) from cluster1 to Kubeadmiral - %[1]s federalize secret default-secret -n default -C cluster1 - - # Support to use '--cluster-kubeconfig' to specify the kubeconfig of member cluster - %[1]s federalize deployment busybox -n default -C cluster1 --cluster-kubeconfig= - - # Support to use '--cluster-kubeconfig' and '--cluster-context' to specify the kubeconfig and context of member cluster - %[1]s federalize deployment busybox -n default -C cluster1 --cluster-kubeconfig= --cluster-context=`) -) - -// CommandFederalizeOption holds all command options for federalize -type CommandFederalizeOption struct { - // AutoCreatePolicy determines whether a PropagationPolicy - // or ClusterPropagationPolicy should be created automatically. - AutoCreatePolicy bool - - // Cluster is the name of target cluster - Cluster string - - // ClusterContext is context name of target cluster in kubeconfig. - ClusterContext string - - // ClusterKubeConfig is the target cluster's kubeconfig path. - ClusterKubeConfig string - - // DryRun run the command in dry-run mode. - DryRun bool - - // Namespace is the namespace of target resource - Namespace string - - // OutputFormat determines the output format, json or yaml. - OutputFormat string - - // PolicyName is the name of the PropagationPolicy or ClusterPropagationPolicy. - // If AutoCreatePolicy is false, it needs to be specified as an existing policy. - PolicyName string - - // SchedulingMode determines the mode used by the scheduler when scheduling federated objects. - SchedulingMode string - - JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags - Printer func(*meta.RESTMapping, *bool, bool, bool) (printers.ResourcePrinterFunc, error) - - DynamicClientset dynamic.Interface - FedClientset *fedclient.Clientset - - controlPlaneRestConfig *rest.Config - resourceName string - resourceKind string - resourceObj *unstructured.Unstructured - resource.FilenameOptions - mapper meta.RESTMapper - gvr schema.GroupVersionResource - gvk schema.GroupVersionKind -} - -// AddFlags adds flags for a specified FlagSet. -func (o *CommandFederalizeOption) AddFlags(flags *pflag.FlagSet) { - flags.BoolVar(&o.AutoCreatePolicy, "auto-create-policy", true, - "Automatically create a PropagationPolicy for namespace-scoped resources or "+ - "create a ClusterPropagationPolicy for cluster-scoped resources.") - flags.StringVarP(&o.Cluster, "cluster", "C", "", "The name of target cluster (eg. -C=member1).") - flags.StringVar(&o.ClusterContext, "cluster-context", "", "Context name of target cluster in kubeconfig.") - flags.StringVar(&o.ClusterKubeConfig, "cluster-kubeconfig", "", "Path of the target cluster's kubeconfig.") - flags.BoolVar(&o.DryRun, "dry-run", false, "Run the command in dry-run mode.") - flags.StringVarP(&o.Namespace, "namespace", "n", o.Namespace, "The namespace of the resource to be federalized.") - flags.StringVarP(&o.OutputFormat, "output", "o", "yaml", "Output format: yaml or json.") - flags.StringVar(&o.PolicyName, "policy-name", "", - "The name of the PropagationPolicy or ClusterPropagationPolicy that is automatically created after migration. "+ - "If not specified, the policy name will be the resource name with resource group and kind.") - flags.StringVar(&o.SchedulingMode, "scheduling-mode", string(fedcorev1a1.SchedulingModeDuplicate), - "determines the mode used by the scheduler when scheduling federated objects, One of: Duplicate or Divide, default is Duplicate.") -} - -// NewCmdFederalize creates the `federalize` command -func NewCmdFederalize(f util.Factory, parentCommand string) *cobra.Command { - opts := CommandFederalizeOption{ - JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(), - } - - cmd := &cobra.Command{ - Use: "federalize -n -C ", - Short: "federalize resource from target clusters to Kubeadmiral control plane", - Long: federalizeLongDesc, - Example: fmt.Sprintf(federalizeExample, parentCommand), - SilenceUsage: true, - DisableFlagsInUseLine: true, - RunE: func(cmd *cobra.Command, args []string) error { - if err := opts.Preflight(f, args); err != nil { - return err - } - if err := opts.Federalize(); err != nil { - return err - } - return nil - }, - } - - flag := cmd.Flags() - opts.AddFlags(flag) - - return cmd -} - -// Obj cluster info -type Obj struct { - Cluster string - Info *resource.Info -} - -// Preflight validate the option in advance and set option value. -func (o *CommandFederalizeOption) Preflight(f util.Factory, args []string) error { - var err error - - if len(args) != 2 { - return fmt.Errorf("command line input format error") - } - - if o.Cluster == "" { - return fmt.Errorf("the cluster name cannot be empty") - } - - if o.OutputFormat != "" && o.OutputFormat != "yaml" && o.OutputFormat != "json" { - return fmt.Errorf("output format is only one of json and yaml") - } - - if !o.AutoCreatePolicy && o.PolicyName == "" { - return fmt.Errorf("if AutoCreatePolicy is false, you need to specify an existing policy") - } - - o.resourceKind = args[0] - o.resourceName = args[1] - - o.Printer = func(mapping *meta.RESTMapping, outputObjects *bool, withNamespace bool, - withKind bool) (printers.ResourcePrinterFunc, error) { - printer, err := o.JSONYamlPrintFlags.ToPrinter(o.OutputFormat) - if err != nil { - return nil, err - } - - printer, err = printers.NewTypeSetter(scheme.Scheme).WrapToPrinter(printer, nil) - if err != nil { - return nil, err - } - - return printer.PrintObj, nil - } - - if o.Namespace == "" { - o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace() - if err != nil { - return fmt.Errorf("failed to get namespace from Factory. error: %w", err) - } - } - - if len(o.ClusterContext) == 0 { - o.ClusterContext = o.Cluster - } - - o.controlPlaneRestConfig, err = f.ToRESTConfig() - if err != nil { - return fmt.Errorf("failed to get control plane rest config. err: %w", err) - } - - o.DynamicClientset = dynamic.NewForConfigOrDie(o.controlPlaneRestConfig) - o.FedClientset = fedclient.NewForConfigOrDie(o.controlPlaneRestConfig) - - memberClusterFactory, err := o.createMemberClusterFactory(f) - if err != nil { - return err - } - - objInfo, err := o.getObjInfo(memberClusterFactory, o.Cluster, args) - if err != nil { - return fmt.Errorf("failed to get resource in cluster(%s). err: %w", o.Cluster, err) - } - - obj := objInfo.Info.Object.(*unstructured.Unstructured) - - // Resources in deletion do not support federalization - if obj.GetDeletionTimestamp() != nil { - return fmt.Errorf("this resource is in the deleted state and cannot be federalized") - } - - o.resourceObj = obj - o.gvk = obj.GetObjectKind().GroupVersionKind() - o.mapper, err = restmapper.GetRESTMapper(o.controlPlaneRestConfig) - if err != nil { - return fmt.Errorf("failed to create restmapper: %w", err) - } - - o.gvr, err = restmapper.ConvertGVKToGVR(o.mapper, o.gvk) - if err != nil { - return fmt.Errorf("failed to get gvr from %q: %w", o.gvk, err) - } - - return nil -} - -// getObjInfo get obj info in member cluster -func (o *CommandFederalizeOption) getObjInfo(f cmdutil.Factory, cluster string, args []string) (*Obj, error) { - r := f.NewBuilder(). - Unstructured(). - NamespaceParam(o.Namespace). - FilenameParam(false, &o.FilenameOptions). - RequestChunksOf(500). - ResourceTypeOrNameArgs(true, args...). - ContinueOnError(). - Latest(). - Flatten(). - Do() - - r.IgnoreErrors(apierrors.IsNotFound) - - infos, err := r.Infos() - if err != nil { - return nil, err - } - - if len(infos) == 0 { - return nil, fmt.Errorf("resource %s(%s) don't exist in cluster(%s)", o.resourceKind, o.resourceName, o.Cluster) - } - - obj := &Obj{ - Cluster: cluster, - Info: infos[0], - } - - return obj, nil -} - -// createMemberClusterFactory create member cluster factory base on whether kubeconfig is specified -func (o *CommandFederalizeOption) createMemberClusterFactory(f util.Factory) (cmdutil.Factory, error) { - var memberClusterFactory cmdutil.Factory - var err error - if o.ClusterKubeConfig != "" { - memberClusterFactory, err = util.NewClusterFactoryByKubeConfig(o.ClusterKubeConfig, o.ClusterContext) - if err != nil { - return nil, err - } - } else { - memberClusterFactory, err = f.NewClusterFactoryByClusterName(o.Cluster) - if err != nil { - return nil, err - } - } - return memberClusterFactory, nil -} - -// Federalize federalize resource from target cluster -func (o *CommandFederalizeOption) Federalize() error { - if err := templateForResource(o.resourceObj); err != nil { - return fmt.Errorf("failed to convert resource %q(%s/%s) to template: %w", o.gvr, o.Namespace, o.resourceName, err) - } - - // if dry running, just print resourceObj and policy. - if o.DryRun { - err := o.printResourceObjectAndPolicy(o.resourceObj) - return err - } - - policyName := o.PolicyName - var err error - if len(o.resourceObj.GetNamespace()) == 0 { - if o.AutoCreatePolicy { - policyName, err = o.createClusterPropagationPolicy() - if err != nil { - return err - } - } - - _, err := o.DynamicClientset.Resource(o.gvr).Get(context.TODO(), o.resourceName, metav1.GetOptions{}) - if err == nil { - fmt.Printf("Resource %q(%s) already exist in Kubeadmiral control plane.", o.gvr, o.resourceName) - return nil - } - - if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to get resource %q(%s) in control plane: %w", o.gvr, o.resourceName, err) - } - - bindPolicyToResource(o.resourceObj, policyName) - setConflictResolutionAnnotation(o.resourceObj) - - _, err = o.DynamicClientset.Resource(o.gvr).Create(context.TODO(), o.resourceObj, metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("failed to create resource %q(%s) in control plane: %w", o.gvr, o.resourceName, err) - } - - fmt.Printf("Resource %q(%s) is federalized successfully\n", o.gvr, o.resourceName) - } else { - if o.AutoCreatePolicy { - policyName, err = o.createPropagationPolicy() - if err != nil { - return err - } - } - - _, err := o.DynamicClientset.Resource(o.gvr).Namespace(o.Namespace).Get(context.TODO(), o.resourceName, metav1.GetOptions{}) - if err == nil { - fmt.Printf("Resource %q(%s/%s) already exist in Kubeadmiral control plane.", o.gvr, o.Namespace, o.resourceName) - return nil - } - - if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to get resource %q(%s/%s) in control plane: %w", o.gvr, o.Namespace, o.resourceName, err) - } - - bindPolicyToResource(o.resourceObj, policyName) - setConflictResolutionAnnotation(o.resourceObj) - - _, err = o.DynamicClientset.Resource(o.gvr).Namespace(o.Namespace).Create(context.TODO(), o.resourceObj, metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("failed to create resource %q(%s/%s) in control plane: %w", o.gvr, o.Namespace, o.resourceName, err) - } - - fmt.Printf("Resource %q(%s/%s) is federalized successfully\n", o.gvr, o.Namespace, o.resourceName) - } - - return nil -} - -// bindPolicyToResource bind the policy to the resource as a label -func bindPolicyToResource(obj *unstructured.Unstructured, policyName string) { - labels := obj.DeepCopy().GetLabels() - if labels == nil { - labels = map[string]string{} - } - if _, exist := labels[scheduler.PropagationPolicyNameLabel]; !exist { - labels[scheduler.PropagationPolicyNameLabel] = policyName - } - obj.SetLabels(labels) -} - -// setConflictResolutionAnnotation set ConflictResolution "adopt" to resource -// because the resources that need to be federalized already exist in the cluster -func setConflictResolutionAnnotation(obj *unstructured.Unstructured) { - annotations := obj.DeepCopy().GetAnnotations() - if annotations == nil { - annotations = map[string]string{} - } - if _, exist := annotations[adoption.ConflictResolutionAnnotation]; !exist { - annotations[adoption.ConflictResolutionAnnotation] = string(adoption.ConflictResolutionAdopt) - } - obj.SetAnnotations(annotations) -} - -// printResourceObjectAndPolicy print the converted resource and federalized policy -func (o *CommandFederalizeOption) printResourceObjectAndPolicy(resourceObj *unstructured.Unstructured) error { - printer, err := o.Printer(nil, nil, false, false) - if err != nil { - return fmt.Errorf("failed to initialize k8s printer. err: %w", err) - } - - if err = printer.PrintObj(resourceObj, os.Stdout); err != nil { - return fmt.Errorf("failed to print the resource template. err: %w", err) - } - if o.AutoCreatePolicy { - var policyName string - if o.PolicyName == "" { - policyName = naming.GenerateFederatedObjectName(o.resourceName, o.gvk.String()) - } else { - policyName = o.PolicyName - } - - if len(resourceObj.GetNamespace()) == 0 { - policy := constructPropagationPolicy(policyName, "", o.Cluster, o.SchedulingMode) - if cpp, ok := policy.(*fedcorev1a1.ClusterPropagationPolicy); ok { - if err = printer.PrintObj(cpp, os.Stdout); err != nil { - return fmt.Errorf("failed to print the ClusterPropagationPolicy. err: %w", err) - } - } else { - return fmt.Errorf("failed to construct the ClusterPropagationPolicy") - } - } else { - policy := constructPropagationPolicy(policyName, o.Namespace, o.Cluster, o.SchedulingMode) - if pp, ok := policy.(*fedcorev1a1.PropagationPolicy); ok { - if err = printer.PrintObj(pp, os.Stdout); err != nil { - return fmt.Errorf("failed to print the PropagationPolicy. err: %w", err) - } - } else { - return fmt.Errorf("failed to construct the PropagationPolicy") - } - } - } else { - // The user specifies a policy that already exists - if len(resourceObj.GetNamespace()) == 0 { - policy, err := o.FedClientset.CoreV1alpha1().ClusterPropagationPolicies().Get(context.TODO(), o.PolicyName, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("failed to get the ClusterPropagationPolicy %s. err: %w", o.PolicyName, err) - } - if err = printer.PrintObj(policy, os.Stdout); err != nil { - return fmt.Errorf("failed to print the ClusterPropagationPolicy. err: %w", err) - } - } else { - policy, err := o.FedClientset.CoreV1alpha1().PropagationPolicies(o.Namespace).Get(context.TODO(), o.PolicyName, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("failed to get the PropagationPolicy %s in namespace %s. err: %w", o.PolicyName, o.Namespace, err) - } - if err = printer.PrintObj(policy, os.Stdout); err != nil { - return fmt.Errorf("failed to print the PropagationPolicy %s in namespace %s. err: %w", o.PolicyName, o.Namespace, err) - } - } - } - - return nil -} - -// createPropagationPolicy create PropagationPolicy in Kubeadmiral control plane -func (o *CommandFederalizeOption) createPropagationPolicy() (string, error) { - var policyName string - if o.PolicyName == "" { - policyName = naming.GenerateFederatedObjectName(o.resourceName, strings.ToLower(fmt.Sprintf("%s-%s", o.gvk.Group, o.gvk.Kind))) - } else { - policyName = o.PolicyName - } - - _, err := o.FedClientset.CoreV1alpha1().PropagationPolicies(o.Namespace).Get(context.TODO(), policyName, metav1.GetOptions{}) - if err != nil && apierrors.IsNotFound(err) { - policy := constructPropagationPolicy(policyName, o.Namespace, o.Cluster, o.SchedulingMode) - if pp, ok := policy.(*fedcorev1a1.PropagationPolicy); ok { - _, err = o.FedClientset.CoreV1alpha1().PropagationPolicies(o.Namespace).Create(context.TODO(), pp, metav1.CreateOptions{}) - if err != nil { - return "", err - } - } else { - err = fmt.Errorf("failed to construct the PropagationPolicy") - } - - return policyName, err - } - if err != nil { - return "", fmt.Errorf("failed to get PropagationPolicy(%s/%s) in control plane: %w", o.Namespace, policyName, err) - } - - // PropagationPolicy already exists, not to create it - return "", fmt.Errorf("the PropagationPolicy(%s/%s) already exist, please edit it to propagate resource", o.Namespace, policyName) -} - -// createClusterPropagationPolicy create ClusterPropagationPolicy in Kubeadmiral control plane -func (o *CommandFederalizeOption) createClusterPropagationPolicy() (string, error) { - var policyName string - if o.PolicyName == "" { - policyName = naming.GenerateFederatedObjectName(o.resourceName, o.gvk.String()) - } else { - policyName = o.PolicyName - } - - _, err := o.FedClientset.CoreV1alpha1().ClusterPropagationPolicies().Get(context.TODO(), policyName, metav1.GetOptions{}) - if err != nil && apierrors.IsNotFound(err) { - policy := constructPropagationPolicy(policyName, "", o.Cluster, o.SchedulingMode) - if cpp, ok := policy.(*fedcorev1a1.ClusterPropagationPolicy); ok { - _, err = o.FedClientset.CoreV1alpha1().ClusterPropagationPolicies().Create(context.TODO(), cpp, metav1.CreateOptions{}) - } else { - err = fmt.Errorf("failed to construct the ClusterPropagationPolicy") - } - - return policyName, err - } - if err != nil { - return "", fmt.Errorf("failed to get ClusterPropagationPolicy(%s) in control plane: %w", policyName, err) - } - - // ClusterPropagationPolicy already exists, not to create it - return "", fmt.Errorf("the ClusterPropagationPolicy(%s) already exist, please edit it to propagate resource", policyName) -} - -// templateForResource convert resource to template -func templateForResource(resource *unstructured.Unstructured) error { - resource.SetSelfLink("") - resource.SetUID("") - resource.SetResourceVersion("") - resource.SetCreationTimestamp(metav1.Time{}) - resource.SetOwnerReferences(nil) - resource.SetFinalizers(nil) - resource.SetManagedFields(nil) - resource.SetDeletionGracePeriodSeconds(nil) - resource.SetGeneration(0) - unstructured.RemoveNestedField(resource.Object, common.StatusField) - - if resource.GetKind() == common.ServiceKind { - clusterIP, exist, _ := unstructured.NestedString(resource.Object, "spec", "clusterIP") - if exist && clusterIP != corev1.ClusterIPNone { - unstructured.RemoveNestedField(resource.Object, "spec", "clusterIP") - unstructured.RemoveNestedField(resource.Object, "spec", "clusterIPs") - } - } - - if resource.GetKind() == common.JobKind { - manualSelector, exists, _ := unstructured.NestedBool(resource.Object, "spec", "manualSelector") - if !exists || exists && !manualSelector { - if err := removeGenerateSelectorOfJob(resource); err != nil { - return err - } - } - } - - if resource.GetKind() == common.ServiceAccountKind { - secrets, exist, _ := unstructured.NestedSlice(resource.Object, "secrets") - if exist && len(secrets) > 0 { - tokenPrefix := fmt.Sprintf("%s-token-", resource.GetName()) - for idx := 0; idx < len(secrets); idx++ { - if strings.HasPrefix(secrets[idx].(map[string]interface{})["name"].(string), tokenPrefix) { - secrets = append(secrets[:idx], secrets[idx+1:]...) - } - } - _ = unstructured.SetNestedSlice(resource.Object, secrets, "secrets") - } - } - return nil -} - -// getLabelValue get the label value by labelKey. -func getLabelValue(labels map[string]string, labelKey string) string { - if labels == nil { - return "" - } - - return labels[labelKey] -} - -// removeGenerateSelectorOfJob remove some unwanted information that is not needed during -// the job federalization process -func removeGenerateSelectorOfJob(resource *unstructured.Unstructured) error { - matchLabels, exist, err := unstructured.NestedStringMap(resource.Object, "spec", "selector", "matchLabels") - if err != nil { - return err - } - if exist { - if getLabelValue(matchLabels, "controller-uid") != "" { - delete(matchLabels, "controller-uid") - } - err = unstructured.SetNestedStringMap(resource.Object, matchLabels, "spec", "selector", "matchLabels") - if err != nil { - return err - } - } - - labels, exist, err := unstructured.NestedStringMap(resource.Object, "spec", "template", "metadata", "labels") - if err != nil { - return err - } - if exist { - if getLabelValue(labels, "controller-uid") != "" { - delete(labels, "controller-uid") - } - - if getLabelValue(labels, "job-name") != "" { - delete(labels, "job-name") - } - - err = unstructured.SetNestedStringMap(resource.Object, labels, "spec", "template", "metadata", "labels") - if err != nil { - return err - } - } - return nil -} - -// constructPropagationPolicy construct PropagationPolicy or ClusterPropagationPolicy -func constructPropagationPolicy(name, namespace, cluster, schedulingMode string) interface{} { - objMeta := metav1.ObjectMeta{Name: name} - - var clusterScoped bool - if len(namespace) == 0 { - clusterScoped = true - } - - if !clusterScoped { - objMeta.Namespace = namespace - } - - spec := fedcorev1a1.PropagationPolicySpec{ - SchedulingMode: fedcorev1a1.SchedulingMode(schedulingMode), - Placements: []fedcorev1a1.DesiredPlacement{ - { - Cluster: cluster, - }, - }, - } - - if clusterScoped { - return &fedcorev1a1.ClusterPropagationPolicy{ - ObjectMeta: objMeta, - Spec: spec, - } - } - - return &fedcorev1a1.PropagationPolicy{ - ObjectMeta: objMeta, - Spec: spec, - } -} diff --git a/pkg/admiralctl/join/join.go b/pkg/admiralctl/join/join.go new file mode 100644 index 00000000..1a7ea31f --- /dev/null +++ b/pkg/admiralctl/join/join.go @@ -0,0 +1,304 @@ +package join + +import ( + "context" + "fmt" + + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util" + fedcorev1a1 "github.com/kubewharf/kubeadmiral/pkg/apis/core/v1alpha1" + fedclient "github.com/kubewharf/kubeadmiral/pkg/client/clientset/versioned" + "github.com/kubewharf/kubeadmiral/pkg/controllers/common" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" + applymetav1 "k8s.io/client-go/applyconfigurations/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + joinLongDesc = templates.LongDesc(` + Join a member cluster to Kubeadmiral control plane. + + If the control plane of the member cluster is not ready or has already joined, this command will do nothing. + `) + + joinExample = templates.Examples(` + # Join cluster1 to Kubeadmiral by kubeconfig + %[1]s join cluster1 --cluster-kubeconfig= + + # Support to use '--api-endpoint' to overwrite the member cluster's api-endpoint + %[1]s join cluster1 --cluster-kubeconfig= --api-endpoint= + + # Support to use '--use-service-account' to determine whether create a new ServiceAccount when join + %[1]s join cluster1 --cluster-kubeconfig= --use-service-account=false + + # Support to use '--cluster-context' to specify the context of member cluster + %[1]s join cluster1 --cluster-kubeconfig= --cluster-context= + + `) +) + +// CommandJoinOption holds all command options for join +type CommandJoinOption struct { + // Cluster is the name of member cluster + Cluster string + + // Namespace is the kube-admiral-system namespace, corresponding to common.DefaultFedSystemNamespace + Namespace string + + // ClusterContext is context name of member cluster in kubeconfig + ClusterContext string + + // ClusterKubeConfig is the member cluster's kubeconfig path + ClusterKubeConfig string + + // Host is the api-endpoint of the member cluster + Host string + + // CAData is the CA of the member cluster + CAData []byte + + // CertData is the cert of the member cluster + CertData []byte + + // KeyData is the cert-key of the member cluster + KeyData []byte + + // BearerToken is the the client authentication specified by user + BearerToken string + + // UseServiceAccount is a flag to choose whether create a new ServiceAccount when join, corresponding to FederatedCluster.spec.useServiceAccount + UseServiceAccount bool + + ClusterK8sClientSet *kubernetes.Clientset + FedK8sClientSet *kubernetes.Clientset + FedClientSet *fedclient.Clientset +} + +// AddFlags adds command line flags to the given cobra.Command. +func (o *CommandJoinOption) AddFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + flags.StringVar(&o.ClusterKubeConfig, "cluster-kubeconfig", "", "Path of the member cluster's kubeconfig.") + cmd.MarkFlagRequired("cluster-kubeconfig") + flags.StringVar(&o.ClusterContext, "cluster-context", "", "Context name of member cluster in kubeconfig.") + flags.StringVar(&o.Host, "api-endpoint", "", "api-endpoint of the member cluster.") + flags.BoolVar(&o.UseServiceAccount, "use-service-account", true, + "Whether creates a new ServiceAccount when join, corresponding to FederatedCluster.spec.useServiceAccount. "+ + "If you set 'false', BearerToken should be in the kubeconfig.") +} + +func NewCmdJoin(f util.Factory, parentCommand string) *cobra.Command { + o := CommandJoinOption{} + + cmd := &cobra.Command{ + Use: "join --cluster-kubeconfig ", + Short: "join clusters to Kubeadmiral control plane", + Long: joinLongDesc, + Example: fmt.Sprintf(joinExample, parentCommand), + SilenceUsage: true, + DisableFlagsInUseLine: true, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.ToOptions(f, args); err != nil { + return err + } + if err := o.Validate(); err != nil { + return err + } + if err := o.Join(); err != nil { + return err + } + return nil + }, + } + + o.AddFlags(cmd) + + return cmd +} + +// ToOptions converts from CLI inputs to runtime options +func (o *CommandJoinOption) ToOptions(f util.Factory, args []string) error { + var err error = nil + + if len(args) != 1 { + return fmt.Errorf("command line input format error") + } + + o.Cluster = args[0] + + o.Namespace = common.DefaultFedSystemNamespace + + clusterFactory, err := util.NewClusterFactoryByKubeConfig(o.ClusterKubeConfig, o.ClusterContext) + if err != nil { + return err + } + + clusterRESTConfig, err := clusterFactory.ToRESTConfig() + if err != nil { + return err + } + o.ClusterK8sClientSet = kubernetes.NewForConfigOrDie(clusterRESTConfig) + + fedRESTConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.FedK8sClientSet = kubernetes.NewForConfigOrDie(fedRESTConfig) + o.FedClientSet = fedclient.NewForConfigOrDie(fedRESTConfig) + + if len(o.Host) == 0 { + o.Host = clusterRESTConfig.Host + } + o.CAData = clusterRESTConfig.CAData + o.CertData = clusterRESTConfig.CertData + o.KeyData = clusterRESTConfig.KeyData + o.BearerToken = clusterRESTConfig.BearerToken + + return nil +} + +// Validate verifies whether the options are valid and whether the joining is valid. +func (o *CommandJoinOption) Validate() error { + if !o.UseServiceAccount { + if len(o.BearerToken) == 0 { + return fmt.Errorf("if --use-service-account sets false, BearerToken should be in the kubeconfig") + } + } else if len(o.CertData) == 0 || len(o.KeyData) == 0 { + return fmt.Errorf("if --use-service-account sets true, certificate and key should be in the kubeconfig") + } + + if err := o.checkClusterReady(); err != nil { + return err + } + + if err := o.checkClusterJoined(); err != nil { + return err + } + + return nil +} + +// check whether the member cluster's control plane nodes are ready +func (o *CommandJoinOption) checkClusterReady() error { + nodes, err := o.ClusterK8sClientSet.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + + for _, node := range nodes.Items { + if isControlPlane(&node) { + for _, condition := range node.Status.Conditions { + if condition.Type == corev1.NodeReady && condition.Status != corev1.ConditionTrue { + return fmt.Errorf("control plane %s is not ready", node.Name) + } + } + } + } + + return nil +} + +func isControlPlane(node *corev1.Node) bool { + labels := node.Labels + if labels != nil { + _, hasControlPlaneLabel := labels["node-role.kubernetes.io/control-plane"] + return hasControlPlaneLabel + } + return false +} + +// check whether the member cluster has already joined a kubeadmiral federation +func (o *CommandJoinOption) checkClusterJoined() error { + namespace, err := o.ClusterK8sClientSet.CoreV1().Namespaces().Get(context.TODO(), o.Namespace, metav1.GetOptions{}) + if err == nil { + for key, value := range namespace.GetAnnotations() { + if key == "kubeadmiral.io/federated-cluster-uid" { + fedClusters, err := o.FedClientSet.CoreV1alpha1().FederatedClusters().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + + for _, fedCluster := range fedClusters.Items { + if fedCluster.UID == types.UID(value) { + return fmt.Errorf("the cluster has already joined your kubeadmiral federation (federated-cluster-uid: %v)", value) + } + } + return fmt.Errorf("the cluster has joined another kubeadmiral federation (federated-cluster-uid: %v)", value) + } + } + } + + return nil +} + +func (o *CommandJoinOption) Join() error { + if err := o.applySecret(); err != nil { + return err + } + + if err := o.createFederatedCluster(); err != nil { + return err + } + + fmt.Printf("Cluster: %s joined\n", o.Cluster) + return nil +} + +// apply the Secret +func (o *CommandJoinOption) applySecret() error { + kindString := "Secret" + APIVersionString := "v1" + secret := &applycorev1.SecretApplyConfiguration{ + TypeMetaApplyConfiguration: applymetav1.TypeMetaApplyConfiguration{ + Kind: &kindString, + APIVersion: &APIVersionString, + }, + ObjectMetaApplyConfiguration: &applymetav1.ObjectMetaApplyConfiguration{ + Name: &o.Cluster, + Namespace: &o.Namespace, + }, + Data: map[string][]byte{ + common.ClusterCertificateAuthorityKey: o.CAData, + common.ClusterClientCertificateKey: o.CertData, + common.ClusterClientKeyKey: o.KeyData, + common.ClusterBootstrapTokenKey: []byte(o.BearerToken), + }, + } + + _, err := o.FedK8sClientSet.CoreV1().Secrets(o.Namespace).Apply(context.TODO(), secret, metav1.ApplyOptions{FieldManager: "kubectl-client-side-apply"}) + if err != nil { + return err + } + + fmt.Printf("Secret: %s/%s created\n", o.Namespace, o.Cluster) + return nil +} + +// create the FederatedCluster +func (o *CommandJoinOption) createFederatedCluster() error { + federatedCluster := &fedcorev1a1.FederatedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: o.Cluster, + Namespace: o.Namespace, + }, + Spec: fedcorev1a1.FederatedClusterSpec{ + APIEndpoint: o.Host, + UseServiceAccountToken: o.UseServiceAccount, + SecretRef: fedcorev1a1.LocalSecretReference{ + Name: o.Cluster, + }, + }, + } + + _, err := o.FedClientSet.CoreV1alpha1().FederatedClusters().Create(context.TODO(), federatedCluster, metav1.CreateOptions{}) + if err != nil { + return err + } + + fmt.Printf("FederatedCluster: %s created\n", o.Cluster) + return nil +} diff --git a/pkg/admiralctl/join/join_test.go b/pkg/admiralctl/join/join_test.go new file mode 100644 index 00000000..a7ea2c25 --- /dev/null +++ b/pkg/admiralctl/join/join_test.go @@ -0,0 +1,126 @@ +package join + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/cli-runtime/pkg/genericclioptions" + + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util" + utilpointer "k8s.io/utils/pointer" +) + +func TestCommandJoinOption_ToOptions(t *testing.T) { + testCases := []struct { + name string + option CommandJoinOption + args []string + expectedErr error + }{ + // error test + { + name: "no fcluster name", + option: CommandJoinOption{ + ClusterKubeConfig: "/home/jzd/.kube/config", + ClusterContext: "", + UseServiceAccount: true, + }, + args: []string{}, + expectedErr: fmt.Errorf("command line input format error"), + }, + } + + defaultConfigFlags := genericclioptions.NewConfigFlags(true).WithDiscoveryBurst(300).WithDiscoveryQPS(50.0) + defaultConfigFlags.KubeConfig = utilpointer.String("/home/jzd/.kube/kubeadmiral/kubeadmiral.config") + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert := assert.New(t) + err := testCase.option.ToOptions(util.NewFactory(defaultConfigFlags), testCase.args) + assert.Equal(testCase.expectedErr, err) + }) + } +} + +func TestCommandJoinOption_Validate(t *testing.T) { + testCases := []struct { + name string + option CommandJoinOption + args []string + expectedErr error + }{ + // normal test + { + name: "normal test, UseServiceAccount=true", + option: CommandJoinOption{ + ClusterKubeConfig: "/home/jzd/.kube/config", + ClusterContext: "", + UseServiceAccount: true, + }, + args: []string{"cluster"}, + expectedErr: nil, + }, + { + name: "normal test, UseServiceAccount=false", + option: CommandJoinOption{ + ClusterKubeConfig: "/home/jzd/.kube/config", + ClusterContext: "token-context", + UseServiceAccount: false, + }, + args: []string{"cluster"}, + expectedErr: nil, + }, + // error test + { + name: "UseServiceAccount=false, but no BearerToken", + option: CommandJoinOption{ + ClusterKubeConfig: "/home/jzd/.kube/config", + ClusterContext: "", + UseServiceAccount: false, + }, + args: []string{"cluster"}, + expectedErr: fmt.Errorf("if --use-service-account sets false, BearerToken should be in the kubeconfig"), + }, + { + name: "UseServiceAccount=true, but no cert and key", + option: CommandJoinOption{ + ClusterKubeConfig: "/home/jzd/.kube/config", + ClusterContext: "no-cert-context", + UseServiceAccount: true, + }, + args: []string{"cluster"}, + expectedErr: fmt.Errorf("if --use-service-account sets true, certificate and key should be in the kubeconfig"), + }, + { + name: "cluster's control plane not ready", + option: CommandJoinOption{ + ClusterKubeConfig: "/home/jzd/.kube/kubeadmiral/member-test-notready.config", + ClusterContext: "", + UseServiceAccount: true, + }, + args: []string{"cluster"}, + expectedErr: fmt.Errorf("control plane %s is not ready", "kubeadmiral-member-test-notready-control-plane"), + }, + { + name: "cluster already joined the federation", + option: CommandJoinOption{ + ClusterKubeConfig: "/home/jzd/.kube/kubeadmiral/member-1.config", + ClusterContext: "", + UseServiceAccount: true, + }, + args: []string{"cluster"}, + expectedErr: fmt.Errorf("the cluster has already joined your kubeadmiral federation (federated-cluster-uid: %v)", "e9e8be6f-5d61-4f97-bc67-f1fadf17542a"), + }, + } + + defaultConfigFlags := genericclioptions.NewConfigFlags(true).WithDiscoveryBurst(300).WithDiscoveryQPS(50.0) + defaultConfigFlags.KubeConfig = utilpointer.String("/home/jzd/.kube/kubeadmiral/kubeadmiral.config") + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert := assert.New(t) + _ = testCase.option.ToOptions(util.NewFactory(defaultConfigFlags), testCase.args) + err := testCase.option.Validate() + assert.Equal(testCase.expectedErr, err) + }) + } +} diff --git a/pkg/admiralctl/options/options.go b/pkg/admiralctl/options/options.go deleted file mode 100644 index 503b2c27..00000000 --- a/pkg/admiralctl/options/options.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2023 The KubeAdmiral 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 options - -import ( - "fmt" - "io" - - "github.com/spf13/cobra" - "k8s.io/kubectl/pkg/util/templates" -) - -var optionsExample = templates.Examples(` - # Print flags inherited by all commands - %[1]s options`) - -// NewCmdOptions implements the options command -func NewCmdOptions(parentCommand string, out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "options", - Short: "Print the list of flags inherited by all commands", - Long: "Print the list of flags inherited by all commands", - Example: fmt.Sprintf(optionsExample, parentCommand), - RunE: func(cmd *cobra.Command, args []string) error { - if err := cmd.Usage(); err != nil { - return err - } - return nil - }, - } - - // The `options` command needs write its output to the `out` stream - // (typically stdout). Without calling SetOutput here, the Usage() - // function call will fall back to stderr. - // - // See https://github.com/kubernetes/kubernetes/pull/46394 for details. - cmd.SetOut(out) - cmd.SetErr(out) - - templates.UseOptionsTemplates(cmd) - return cmd -} diff --git a/pkg/admiralctl/unjoin/unjoin.go b/pkg/admiralctl/unjoin/unjoin.go new file mode 100644 index 00000000..2be35b64 --- /dev/null +++ b/pkg/admiralctl/unjoin/unjoin.go @@ -0,0 +1,144 @@ +package unjoin + +import ( + "context" + "fmt" + + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util" + fedclient "github.com/kubewharf/kubeadmiral/pkg/client/clientset/versioned" + "github.com/kubewharf/kubeadmiral/pkg/controllers/common" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + unjoinLongDesc = templates.LongDesc(` + Unjoin a FederatedCluster from Kubeadmiral federation. + + If the the federated cluster has not joined the federation, this command will do nothing. + `) + + unjoinExample = templates.Examples(` + # Unjoin cluster1 from Kubeadmiral federation + %[1]s unjoin cluster1 + `) +) + +// CommandUnjoinOption holds all command options for unjoin +type CommandUnjoinOption struct { + // Cluster is the name of member cluster + Cluster string + + // Namespace is the kube-admiral-system namespace, corresponding to common.DefaultFedSystemNamespace + Namespace string + + FedK8sClientSet *kubernetes.Clientset + FedClientSet *fedclient.Clientset +} + +func NewCmdJoin(f util.Factory, parentCommand string) *cobra.Command { + o := CommandUnjoinOption{} + + cmd := &cobra.Command{ + Use: "unjoin ", + Short: "unjoin the FederatedCluster from Kubeadmiral federation", + Long: unjoinLongDesc, + Example: fmt.Sprintf(unjoinExample, parentCommand), + SilenceUsage: true, + DisableFlagsInUseLine: true, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.ToOptions(f, args); err != nil { + return err + } + if err := o.Validate(); err != nil { + return err + } + if err := o.Unjoin(); err != nil { + return err + } + return nil + }, + } + + return cmd +} + +// ToOptions converts from CLI inputs to runtime options +func (o *CommandUnjoinOption) ToOptions(f util.Factory, args []string) error { + if len(args) != 1 { + return fmt.Errorf("command line input format error") + } + + o.Cluster = args[0] + + o.Namespace = common.DefaultFedSystemNamespace + + fedRESTConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.FedK8sClientSet = kubernetes.NewForConfigOrDie(fedRESTConfig) + o.FedClientSet = fedclient.NewForConfigOrDie(fedRESTConfig) + + return nil +} + +// Validate verifies whether the options are valid and whether the unjoining is valid. +func (o *CommandUnjoinOption) Validate() error { + if err := o.checkClusterJoined(); err != nil { + return err + } + + if err := o.checkSecretExists(); err != nil { + return err + } + + return nil +} + +// check whether the cluster has joined kubeadmiral federation +func (o *CommandUnjoinOption) checkClusterJoined() error { + fedClusters, err := o.FedClientSet.CoreV1alpha1().FederatedClusters().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + + for _, fedCluster := range fedClusters.Items { + if fedCluster.Name == o.Cluster { + return nil + } + } + + return fmt.Errorf("cluster: %s has not joined kubeadmiral federation", o.Cluster) +} + +// check whether the secret exists +func (o *CommandUnjoinOption) checkSecretExists() error { + _, err := o.FedK8sClientSet.CoreV1().Secrets(o.Namespace).Get(context.TODO(), o.Cluster, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("secret: %s/%s does not exist", o.Namespace, o.Cluster) + } + + return nil +} + +func (o *CommandUnjoinOption) Unjoin() error { + if err := o.deleteFederatedCluster(); err != nil { + return err + } + + fmt.Printf("Cluster: %s unjoined\n", o.Cluster) + return nil +} + +// delete the FederatedCluster +func (o *CommandUnjoinOption) deleteFederatedCluster() error { + if err := o.FedClientSet.CoreV1alpha1().FederatedClusters().Delete(context.TODO(), o.Cluster, metav1.DeleteOptions{}); err != nil { + return err + } + + fmt.Printf("FederatedCluster: %s deleted\n", o.Cluster) + return nil +} diff --git a/pkg/admiralctl/unjoin/unjoin_test.go b/pkg/admiralctl/unjoin/unjoin_test.go new file mode 100644 index 00000000..107c8d0b --- /dev/null +++ b/pkg/admiralctl/unjoin/unjoin_test.go @@ -0,0 +1,79 @@ +package unjoin + +import ( + "fmt" + "testing" + + "github.com/kubewharf/kubeadmiral/pkg/admiralctl/util" + "github.com/stretchr/testify/assert" + "k8s.io/cli-runtime/pkg/genericclioptions" + utilpointer "k8s.io/utils/pointer" +) + +func TestCommandJoinOption_ToOptions(t *testing.T) { + testCases := []struct { + name string + option CommandUnjoinOption + args []string + expectedErr error + }{ + // error test + { + name: "no fcluster name", + option: CommandUnjoinOption{}, + args: []string{}, + expectedErr: fmt.Errorf("command line input format error"), + }, + } + + defaultConfigFlags := genericclioptions.NewConfigFlags(true).WithDiscoveryBurst(300).WithDiscoveryQPS(50.0) + defaultConfigFlags.KubeConfig = utilpointer.String("/home/jzd/.kube/kubeadmiral/kubeadmiral.config") + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert := assert.New(t) + err := testCase.option.ToOptions(util.NewFactory(defaultConfigFlags), testCase.args) + assert.Equal(testCase.expectedErr, err) + }) + } +} + +func TestCommandJoinOption_Validate(t *testing.T) { + testCases := []struct { + name string + option CommandUnjoinOption + args []string + expectedErr error + }{ + // normal test + { + name: "normal test", + option: CommandUnjoinOption{}, + args: []string{"cluster-test-healthy"}, + expectedErr: nil, + }, + // error test + { + name: "cluster has not joined kubeadmiral federation", + option: CommandUnjoinOption{}, + args: []string{"member-test-notjoined"}, + expectedErr: fmt.Errorf("cluster: %s has not joined kubeadmiral federation", "member-test-notjoined"), + }, + { + name: "cluster has joined, but secret does not exist", + option: CommandUnjoinOption{}, + args: []string{"member-test-no-secret"}, + expectedErr: fmt.Errorf("secret: %s/%s does not exist", "kube-admiral-system", "member-test-no-secret"), + }, + } + + defaultConfigFlags := genericclioptions.NewConfigFlags(true).WithDiscoveryBurst(300).WithDiscoveryQPS(50.0) + defaultConfigFlags.KubeConfig = utilpointer.String("/home/jzd/.kube/kubeadmiral/kubeadmiral.config") + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert := assert.New(t) + _ = testCase.option.ToOptions(util.NewFactory(defaultConfigFlags), testCase.args) + err := testCase.option.Validate() + assert.Equal(testCase.expectedErr, err) + }) + } +} diff --git a/pkg/admiralctl/util/factory.go b/pkg/admiralctl/util/factory.go index 898d44f3..7f4a4f07 100644 --- a/pkg/admiralctl/util/factory.go +++ b/pkg/admiralctl/util/factory.go @@ -1,105 +1,90 @@ -/* -Copyright 2023 The KubeAdmiral 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 util - -import ( - "context" - fedclient "github.com/kubewharf/kubeadmiral/pkg/client/clientset/versioned" - "github.com/kubewharf/kubeadmiral/pkg/controllers/common" - clusterutil "github.com/kubewharf/kubeadmiral/pkg/util/cluster" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/kubernetes" - cmdutil "k8s.io/kubectl/pkg/cmd/util" -) - -type Factory interface { - cmdutil.Factory - - NewClusterFactoryByClusterName(clusterName string) (cmdutil.Factory, error) -} - -var _ Factory = &factoryImpl{} - -// factoryImpl is the implementation of Factory -type factoryImpl struct { - cmdutil.Factory - - // kubeConfigFlags holds all the flags specified by user. - // These flags will be inherited by the member cluster's client. - kubeConfigFlags *genericclioptions.ConfigFlags -} - -// NewClusterFactoryByClusterName create a new ClusterFactory by ClusterName -func (f *factoryImpl) NewClusterFactoryByClusterName(clusterName string) (cmdutil.Factory, error) { - restConfig, err := f.ToRESTConfig() - if err != nil { - return nil, err - } - - fedClientset, err := fedclient.NewForConfig(restConfig) - if err != nil { - return nil, err - } - - cluster, err := fedClientset.CoreV1alpha1().FederatedClusters().Get( - context.TODO(), - clusterName, - metav1.GetOptions{}, - ) - if err != nil { - return nil, err - } - - kubeClientset, err := kubernetes.NewForConfig(restConfig) - if err != nil { - return nil, err - } - - config, err := clusterutil.BuildClusterConfig(cluster, kubeClientset, restConfig, common.DefaultFedSystemNamespace) - if err != nil { - return nil, err - } - - kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() - kubeConfigFlags.APIServer = &config.Host - kubeConfigFlags.BearerToken = &config.BearerToken - kubeConfigFlags.KeyFile = &config.KeyFile - kubeConfigFlags.CAFile = &config.TLSClientConfig.CAFile - kubeConfigFlags.CertFile = &config.TLSClientConfig.CertFile - kubeConfigFlags.Insecure = &config.Insecure - - return cmdutil.NewFactory(kubeConfigFlags), nil -} - -// NewFactory creates a new factory -func NewFactory(kubeConfigFlags *genericclioptions.ConfigFlags) Factory { - matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags) - f := &factoryImpl{ - kubeConfigFlags: kubeConfigFlags, - Factory: cmdutil.NewFactory(matchVersionKubeConfigFlags), - } - return f -} - -// NewClusterFactoryByKubeConfig create a new ClusterFactory by KubeConfig -func NewClusterFactoryByKubeConfig(clusterKubeConfig, clusterContext string) (cmdutil.Factory, error) { - configFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() - configFlags.KubeConfig = &clusterKubeConfig - configFlags.Context = &clusterContext - return cmdutil.NewFactory(configFlags), nil -} +package util + +import ( + "context" + + fedclient "github.com/kubewharf/kubeadmiral/pkg/client/clientset/versioned" + "github.com/kubewharf/kubeadmiral/pkg/controllers/common" + clusterutil "github.com/kubewharf/kubeadmiral/pkg/util/cluster" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/kubernetes" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +type Factory interface { + cmdutil.Factory + + NewClusterFactoryByClusterName(clusterName string) (cmdutil.Factory, error) +} + +var _ Factory = &factoryImpl{} + +// factoryImpl is the implementation of Factory +type factoryImpl struct { + cmdutil.Factory + + // kubeConfigFlags holds all the flags specified by user. + // These flags will be inherited by the member cluster's client. + kubeConfigFlags *genericclioptions.ConfigFlags +} + +// NewClusterFactoryByClusterName create a new ClusterFactory by ClusterName +func (f *factoryImpl) NewClusterFactoryByClusterName(clusterName string) (cmdutil.Factory, error) { + restConfig, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + + fedClientset, err := fedclient.NewForConfig(restConfig) + if err != nil { + return nil, err + } + + cluster, err := fedClientset.CoreV1alpha1().FederatedClusters().Get( + context.TODO(), + clusterName, + metav1.GetOptions{}, + ) + if err != nil { + return nil, err + } + + kubeClientset, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return nil, err + } + + config, err := clusterutil.BuildClusterConfig(cluster, kubeClientset, restConfig, common.DefaultFedSystemNamespace) + if err != nil { + return nil, err + } + + kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() + kubeConfigFlags.APIServer = &config.Host + kubeConfigFlags.BearerToken = &config.BearerToken + kubeConfigFlags.KeyFile = &config.KeyFile + kubeConfigFlags.CAFile = &config.TLSClientConfig.CAFile + kubeConfigFlags.CertFile = &config.TLSClientConfig.CertFile + kubeConfigFlags.Insecure = &config.Insecure + + return cmdutil.NewFactory(kubeConfigFlags), nil +} + +// NewFactory creates a new factory +func NewFactory(kubeConfigFlags *genericclioptions.ConfigFlags) Factory { + matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags) + f := &factoryImpl{ + kubeConfigFlags: kubeConfigFlags, + Factory: cmdutil.NewFactory(matchVersionKubeConfigFlags), + } + return f +} + +// NewClusterFactoryByKubeConfig create a new ClusterFactory by KubeConfig +func NewClusterFactoryByKubeConfig(clusterKubeConfig, clusterContext string) (cmdutil.Factory, error) { + configFlags := genericclioptions.NewConfigFlags(true) + configFlags.KubeConfig = &clusterKubeConfig + configFlags.Context = &clusterContext + return cmdutil.NewFactory(configFlags), nil +} From 95798f05020bf55578b62fc39938b96dc45fa11e Mon Sep 17 00:00:00 2001 From: miraclejzd <1061954832@qq.com> Date: Wed, 3 Jan 2024 10:20:46 +0000 Subject: [PATCH 3/3] Remove unrelated dirs. Retain the minimum executive unit. --- pkg/admiralctl/util/restmapper/restmapper.go | 48 -------------------- 1 file changed, 48 deletions(-) delete mode 100644 pkg/admiralctl/util/restmapper/restmapper.go diff --git a/pkg/admiralctl/util/restmapper/restmapper.go b/pkg/admiralctl/util/restmapper/restmapper.go deleted file mode 100644 index c6a09499..00000000 --- a/pkg/admiralctl/util/restmapper/restmapper.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2023 The KubeAdmiral 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 restmapper - -import ( - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" - "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" -) - -func GetRESTMapper(restConfig *rest.Config) (meta.RESTMapper, error) { - dc, err := discovery.NewDiscoveryClientForConfig(restConfig) - if err != nil { - panic(err.Error()) - } - - grl, err := restmapper.GetAPIGroupResources(dc) - if err != nil { - panic(err.Error()) - } - - return restmapper.NewDiscoveryRESTMapper(grl), nil -} - -// ConvertGVKToGVR convert GVK to GVR -func ConvertGVKToGVR(restMapper meta.RESTMapper, gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) { - restMapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) - if err != nil { - return schema.GroupVersionResource{}, err - } - return restMapping.Resource, nil -}