diff options
| author | KubeEdge Bot <48982446+kubeedge-bot@users.noreply.github.com> | 2024-04-23 16:42:01 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-23 16:42:01 +0800 |
| commit | 8defa9d91ffe1744feaffc60fe7647c55e12608a (patch) | |
| tree | 482de9c92700cde3e803270b280e15156b71cc97 | |
| parent | Merge pull request #5513 from micplus/feat/module-restart (diff) | |
| parent | When the edge node goes offline, add pod restart and status query functions f... (diff) | |
| download | kubeedge-8defa9d91ffe1744feaffc60fe7647c55e12608a.tar.gz | |
Merge pull request #5504 from luomengY/kectl
When the edge node goes offline, add pod restart and status query functions for the edge node
29 files changed, 3059 insertions, 1 deletions
diff --git a/LICENSES/vendor/github.com/fvbommel/sortorder/LICENSE b/LICENSES/vendor/github.com/fvbommel/sortorder/LICENSE new file mode 100644 index 000000000..4eadd661c --- /dev/null +++ b/LICENSES/vendor/github.com/fvbommel/sortorder/LICENSE @@ -0,0 +1,21 @@ += vendor/github.com/fvbommel/sortorder licensed under: = + +The MIT License (MIT) +Copyright (c) 2015 Frits van Bommel +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + += vendor/github.com/fvbommel/sortorder/LICENSE 9f7e1d7e8f527330ebb5f4c32e0f3e40 @@ -68,6 +68,7 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/component-helpers v0.0.0 + k8s.io/kubectl v0.28.6 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) @@ -127,6 +128,7 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.0.5 // indirect github.com/go-logr/logr v1.2.4 // indirect @@ -270,7 +272,6 @@ require ( k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect k8s.io/kms v0.28.6 //indirect k8s.io/kube-scheduler v0.28.6 // indirect - k8s.io/kubectl v0.28.6 // indirect k8s.io/legacy-cloud-providers v0.0.0 // indirect k8s.io/pod-security-admission v0.0.0 // indirect k8s.io/system-validators v1.8.0 // indirect @@ -938,6 +938,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= +github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= diff --git a/keadm/cmd/keadm/app/cmd/cmd_others.go b/keadm/cmd/keadm/app/cmd/cmd_others.go index 30004e279..ed3bf3b80 100644 --- a/keadm/cmd/keadm/app/cmd/cmd_others.go +++ b/keadm/cmd/keadm/app/cmd/cmd_others.go @@ -23,6 +23,7 @@ import ( "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/beta" "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/cloud" + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/ctl" "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/debug" "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/deprecated" "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/edge" @@ -91,5 +92,7 @@ func NewKubeedgeCommand() *cobra.Command { cmds.AddCommand(edge.NewEdgeRollback()) + cmds.AddCommand(ctl.NewCtl()) + return cmds } diff --git a/keadm/cmd/keadm/app/cmd/common/constant.go b/keadm/cmd/keadm/app/cmd/common/constant.go index b7fdc7012..05c07b55e 100644 --- a/keadm/cmd/keadm/app/cmd/common/constant.go +++ b/keadm/cmd/keadm/app/cmd/common/constant.go @@ -280,3 +280,10 @@ var ( // make it an var so it can be changed to adapt to windows(In rare cases, user name is Administrator) DefaultKubeConfig = "/root/.kube/config" ) + +const ( + FlagNameNamespace = "namespace" + FlagNameAllNamespaces = "all-namespaces" + FlagNameOutput = "output" + FlagNameLabelSelector = "selector" +) diff --git a/keadm/cmd/keadm/app/cmd/ctl/ctl.go b/keadm/cmd/keadm/app/cmd/ctl/ctl.go new file mode 100644 index 000000000..06cdc8f74 --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/ctl/ctl.go @@ -0,0 +1,41 @@ +/* +Copyright 2024 The KubeEdge 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 ctl + +import ( + "github.com/spf13/cobra" + + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/ctl/get" + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/ctl/restart" +) + +var ( + ctlShortDescription = `Commands operating on the data plane at edge` +) + +// NewCtl returns KubeEdge edge pod command. +func NewCtl() *cobra.Command { + cmd := &cobra.Command{ + Use: "ctl", + Short: ctlShortDescription, + Long: ctlShortDescription, + } + + cmd.AddCommand(get.NewEdgeGet()) + cmd.AddCommand(restart.NewEdgeRestart()) + return cmd +} diff --git a/keadm/cmd/keadm/app/cmd/ctl/get/get.go b/keadm/cmd/keadm/app/cmd/ctl/get/get.go new file mode 100644 index 000000000..7408f1941 --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/ctl/get/get.go @@ -0,0 +1,35 @@ +/* +Copyright 2024 The KubeEdge Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import "github.com/spf13/cobra" + +var ( + edgeGetShortDescription = `Get resources in edge node` +) + +// NewEdgeGet returns KubeEdge edge resources get command. +func NewEdgeGet() *cobra.Command { + cmd := &cobra.Command{ + Use: "get", + Short: edgeGetShortDescription, + Long: edgeGetShortDescription, + } + + cmd.AddCommand(NewEdgePodGet()) + return cmd +} diff --git a/keadm/cmd/keadm/app/cmd/ctl/get/pod.go b/keadm/cmd/keadm/app/cmd/ctl/get/pod.go new file mode 100644 index 000000000..d06974eb2 --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/ctl/get/pod.go @@ -0,0 +1,181 @@ +/* +Copyright 2024 The KubeEdge Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import ( + "context" + "fmt" + "os" + + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kubectl/pkg/cmd/get" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_v1_api "k8s.io/kubernetes/pkg/apis/core/v1" + k8sprinters "k8s.io/kubernetes/pkg/printers" + printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" + "k8s.io/kubernetes/pkg/printers/storage" + + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/common" + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/ctl/restful" + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/util" +) + +var ( + edgePodGetShortDescription = `Get pods in edge node` +) + +type PodGetOptions struct { + Namespace string + LabelSelector string + AllNamespaces bool + Output string + PrintFlags *get.PrintFlags +} + +// NewEdgePodGet returns KubeEdge edge pod command. +func NewEdgePodGet() *cobra.Command { + podGetOptions := NewGetOpts() + cmd := &cobra.Command{ + Use: "pod", + Short: edgePodGetShortDescription, + Long: edgePodGetShortDescription, + RunE: func(cmd *cobra.Command, args []string) error { + cmdutil.CheckErr(podGetOptions.getPods(args)) + return nil + }, + } + AddGetPodFlags(cmd, podGetOptions) + return cmd +} + +func (o *PodGetOptions) getPods(args []string) error { + config, err := util.ParseEdgecoreConfig(common.EdgecoreConfigPath) + if err != nil { + return fmt.Errorf("get edge config failed with err:%v", err) + } + nodeName := config.Modules.Edged.HostnameOverride + + var podListFilter *api.PodList + if len(args) > 0 { + podListFilter = &api.PodList{ + Items: make([]api.Pod, 0, len(args)), + } + var podRequest *restful.PodRequest + for _, podName := range args { + podRequest = &restful.PodRequest{ + Namespace: o.Namespace, + PodName: podName, + } + pod, err := podRequest.GetPod() + if err != nil { + fmt.Println(err.Error()) + continue + } + + if pod.Spec.NodeName == nodeName { + var apiPod api.Pod + if err := k8s_v1_api.Convert_v1_Pod_To_core_Pod(pod, &apiPod, nil); err != nil { + fmt.Printf("failed to covert pod with err:%v\n", err) + continue + } + podListFilter.Items = append(podListFilter.Items, apiPod) + } else { + fmt.Printf("can't to query pod: \"%s\" for node: \"%s\"\n", pod.Name, pod.Spec.NodeName) + } + } + } else { + podRequest := &restful.PodRequest{ + Namespace: o.Namespace, + AllNamespaces: o.AllNamespaces, + LabelSelector: o.LabelSelector, + } + podList, err := podRequest.GetPods() + if err != nil { + return err + } + + podListFilter = &api.PodList{ + Items: make([]api.Pod, 0, len(podList.Items)), + } + + for _, pod := range podList.Items { + if pod.Spec.NodeName == nodeName { + var apiPod api.Pod + if err := k8s_v1_api.Convert_v1_Pod_To_core_Pod(&pod, &apiPod, nil); err != nil { + return err + } + podListFilter.Items = append(podListFilter.Items, apiPod) + } + } + } + + if len(podListFilter.Items) == 0 { + if len(args) > 0 { + return nil + } + fmt.Printf("No resources found in %s namespace.\n", o.Namespace) + return nil + } + + table, err := ConvertDataToTable(podListFilter) + if err != nil { + return err + } + + if o.AllNamespaces { + if err := o.PrintFlags.EnsureWithNamespace(); err != nil { + return err + } + } + + printer, err := o.PrintFlags.ToPrinter() + if err := printer.PrintObj(table, os.Stdout); err != nil { + return err + } + return nil +} + +func NewGetOpts() *PodGetOptions { + podGetOptions := &PodGetOptions{} + podGetOptions.Namespace = "default" + podGetOptions.PrintFlags = get.NewGetPrintFlags() + podGetOptions.PrintFlags.OutputFormat = &podGetOptions.Output + return podGetOptions +} + +func AddGetPodFlags(cmd *cobra.Command, getOptions *PodGetOptions) { + cmd.Flags().StringVarP(&getOptions.Namespace, common.FlagNameNamespace, "n", getOptions.Namespace, + "Specify a namespace") + cmd.Flags().StringVarP(&getOptions.LabelSelector, common.FlagNameLabelSelector, "l", getOptions.LabelSelector, + "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + + cmd.Flags().StringVarP(&getOptions.Output, common.FlagNameOutput, "o", getOptions.Output, + "Output format. One of: (json, yaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file, custom-columns, custom-columns-file, wide)") + + cmd.Flags().BoolVarP(&getOptions.AllNamespaces, common.FlagNameAllNamespaces, "A", getOptions.AllNamespaces, + "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace") +} + +func ConvertDataToTable(obj runtime.Object) (runtime.Object, error) { + to := metav1.TableOptions{} + tc := storage.TableConvertor{TableGenerator: k8sprinters.NewTableGenerator().With(printersinternal.AddHandlers)} + + return tc.ConvertToTable(context.TODO(), obj, &to) +} diff --git a/keadm/cmd/keadm/app/cmd/ctl/restart/pod.go b/keadm/cmd/keadm/app/cmd/ctl/restart/pod.go new file mode 100644 index 000000000..cc77150d9 --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/ctl/restart/pod.go @@ -0,0 +1,121 @@ +/* +Copyright 2024 The KubeEdge 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 restart + +import ( + "context" + "fmt" + "time" + + "github.com/spf13/cobra" + oteltrace "go.opentelemetry.io/otel/trace" + runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubernetes/pkg/kubelet/cri/remote" + + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/common" + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/ctl/restful" + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/util" +) + +type PodRestartOptions struct { + Namespace string +} + +var ( + edgePodRestartShortDescription = `Restart pods in edge node` +) + +// NewEdgePodRestart returns KubeEdge delete edge pod command. +func NewEdgePodRestart() *cobra.Command { + deleteOpts := NewRestartPodOpts() + cmd := &cobra.Command{ + Use: "pod", + Short: edgePodRestartShortDescription, + Long: edgePodRestartShortDescription, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) <= 0 { + return fmt.Errorf("no pod specified for reboot") + } + cmdutil.CheckErr(deleteOpts.restartPod(args)) + return nil + }, + } + AddRestartPodFlags(cmd, deleteOpts) + return cmd +} + +func NewRestartPodOpts() *PodRestartOptions { + podDeleteOptions := &PodRestartOptions{} + podDeleteOptions.Namespace = "default" + return podDeleteOptions +} + +func AddRestartPodFlags(cmd *cobra.Command, RestartPodOptions *PodRestartOptions) { + cmd.Flags().StringVarP(&RestartPodOptions.Namespace, common.FlagNameNamespace, "n", RestartPodOptions.Namespace, + "Specify a namespace") +} + +func (o *PodRestartOptions) restartPod(podNames []string) error { + for _, podName := range podNames { + podRequest := &restful.PodRequest{ + Namespace: o.Namespace, + PodName: podName, + } + pod, err := podRequest.GetPod() + if err != nil { + fmt.Println(err.Error()) + continue + } + config, err := util.ParseEdgecoreConfig(common.EdgecoreConfigPath) + if err != nil { + fmt.Printf("get edge config failed with err:%v\n", err) + continue + } + nodeName := config.Modules.Edged.HostnameOverride + if nodeName != pod.Spec.NodeName { + fmt.Printf("can't to restart pod: \"%s\" for node: \"%s\"\n", pod.Name, pod.Spec.NodeName) + continue + } + endpoint := config.Modules.Edged.TailoredKubeletConfig.ContainerRuntimeEndpoint + remoteRuntimeService, err := remote.NewRemoteRuntimeService(endpoint, time.Second*10, oteltrace.NewNoopTracerProvider()) + + var labelSelector = map[string]string{ + "io.kubernetes.pod.name": pod.Name, + "io.kubernetes.pod.namespace": pod.Namespace, + } + + filter := &runtimeapi.ContainerFilter{ + LabelSelector: labelSelector, + } + containers, err := remoteRuntimeService.ListContainers(context.TODO(), filter) + if err != nil { + return err + } + + for _, container := range containers { + containerID := container.Id + err := remoteRuntimeService.StopContainer(context.TODO(), containerID, 3) + if err != nil { + fmt.Printf("stop containerID:%s with err:%v\n", containerID, err) + } else { + fmt.Println(containerID) + } + } + } + return nil +} diff --git a/keadm/cmd/keadm/app/cmd/ctl/restart/restart.go b/keadm/cmd/keadm/app/cmd/ctl/restart/restart.go new file mode 100644 index 000000000..6e0df456c --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/ctl/restart/restart.go @@ -0,0 +1,34 @@ +/* +Copyright 2024 The KubeEdge 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 restart + +import "github.com/spf13/cobra" + +var ( + edgeRestartShortDescription = `Restart resources in edge node` +) + +// NewEdgeRestart returns KubeEdge restart edge resources command. +func NewEdgeRestart() *cobra.Command { + cmd := &cobra.Command{ + Use: "restart", + Short: edgeRestartShortDescription, + Long: edgeRestartShortDescription, + } + cmd.AddCommand(NewEdgePodRestart()) + return cmd +} diff --git a/keadm/cmd/keadm/app/cmd/ctl/restful/pod.go b/keadm/cmd/keadm/app/cmd/ctl/restful/pod.go new file mode 100644 index 000000000..6b9eabda1 --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/ctl/restful/pod.go @@ -0,0 +1,73 @@ +/* +Copyright 2024 The KubeEdge 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 restful + +import ( + "net/url" + + corev1 "k8s.io/api/core/v1" +) + +type PodRequest struct { + Namespace string + LabelSelector string + AllNamespaces bool + PodName string +} + +func (podRequest *PodRequest) GetPod() (*corev1.Pod, error) { + request := Request{ + Method: "GET", + Path: "/" + CoreAPIPrefix + "/" + CoreAPIGroupVersion.Version + + "/namespaces/" + podRequest.Namespace + "/pods/" + podRequest.PodName, + } + + pod, err := request.ResponseToPod() + if err != nil { + return nil, err + } + return pod, nil +} + +func (podRequest *PodRequest) GetPods() (*corev1.PodList, error) { + var request Request + if podRequest.AllNamespaces { + request = Request{ + Method: "GET", + Path: "/" + CoreAPIPrefix + "/" + CoreAPIGroupVersion.Version + "/pods", + } + } else { + request = Request{ + Method: "GET", + Path: "/" + CoreAPIPrefix + "/" + CoreAPIGroupVersion.Version + + "/namespaces/" + podRequest.Namespace + "/pods", + } + } + + if podRequest.LabelSelector != "" { + values := url.Values{} + values.Set("labelSelector", podRequest.LabelSelector) + queryParams := values.Encode() + request.Path += "?" + queryParams + } + + podList, err := request.ResponseToPodList() + if err != nil { + return nil, err + } + return podList, nil +} diff --git a/keadm/cmd/keadm/app/cmd/ctl/restful/request.go b/keadm/cmd/keadm/app/cmd/ctl/restful/request.go new file mode 100644 index 000000000..0e0f2c73b --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/ctl/restful/request.go @@ -0,0 +1,151 @@ +/* +Copyright 2024 The KubeEdge 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 restful + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/common" + util2 "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/ctl/util" + keadutil "github.com/kubeedge/kubeedge/keadm/cmd/keadm/app/cmd/util" +) + +var ( + CoreAPIPrefix = "api" + CoreAPIGroupVersion = schema.GroupVersion{Group: "", Version: "v1"} + Prefix = "apis" +) + +type Request struct { + Method string + Path string + Body io.Reader +} + +func (req *Request) RestfulRequest() (*http.Response, error) { + var client http.Client + config, err := keadutil.ParseEdgecoreConfig(common.EdgecoreConfigPath) + if err != nil { + return nil, fmt.Errorf("get edge config failed with err:%v", err) + } + if config.Modules.MetaManager.MetaServer.Enable { + url := config.Modules.MetaManager.MetaServer.Server + ok, requireAuthorization := config.FeatureGates["requireAuthorization"] + if ok && requireAuthorization { + serverCrt := config.Modules.MetaManager.MetaServer.TLSCertFile + serverKey := config.Modules.MetaManager.MetaServer.TLSPrivateKeyFile + cert, err := tls.LoadX509KeyPair(serverCrt, serverKey) + if err != nil { + return nil, fmt.Errorf("failed to load server certificate and private key with err:%v", err) + } + + tlsCaFile := config.Modules.MetaManager.MetaServer.TLSCaFile + caCert, err := os.ReadFile(tlsCaFile) + if err != nil { + return nil, fmt.Errorf("failed to load tlsCaFile with err:%v", err) + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + //ClientAuth: tls.RequireAndVerifyClientCert, + RootCAs: caCertPool, + } + + url = "https://" + url + client = http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + } + } else { + url = "http://" + url + client = http.Client{} + } + + request, err := http.NewRequest(req.Method, url+req.Path, req.Body) + if err != nil { + return nil, fmt.Errorf("restful format failed with err:%v", err) + } + response, err := client.Do(request) + if err != nil { + return nil, fmt.Errorf("restful failed with err:%v", err) + } + + return response, nil + } + return nil, fmt.Errorf("metaserver don't open") +} + +func (req *Request) ResponseToPodList() (*corev1.PodList, error) { + response, err := req.RestfulRequest() + if err != nil { + return nil, err + } + + bodyBytes, err := io.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("read response's body failed with err:%v", err) + } + + if response.StatusCode != http.StatusOK { + return nil, util2.GetErrMessage(bodyBytes) + } + + var podList *corev1.PodList + err = json.Unmarshal(bodyBytes, &podList) + if err != nil { + return nil, fmt.Errorf("parsing response's body failed with err:%v", err) + } + + return podList, err +} + +func (req *Request) ResponseToPod() (*corev1.Pod, error) { + response, err := req.RestfulRequest() + if err != nil { + return nil, err + } + + bodyBytes, err := io.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("read response's body failed with err:%v", err) + } + + if response.StatusCode != http.StatusOK { + return nil, util2.GetErrMessage(bodyBytes) + } + + var pod *corev1.Pod + err = json.Unmarshal(bodyBytes, &pod) + if err != nil { + return nil, fmt.Errorf("parsing response's body failed with err:%v", err) + } + + return pod, err +} diff --git a/keadm/cmd/keadm/app/cmd/ctl/util/pod.go b/keadm/cmd/keadm/app/cmd/ctl/util/pod.go new file mode 100644 index 000000000..015a8ca6b --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/ctl/util/pod.go @@ -0,0 +1,34 @@ +/* +Copyright 2024 The KubeEdge 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 ( + "encoding/json" + "errors" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func GetErrMessage(bodyBytes []byte) error { + var status metav1.Status + err := json.Unmarshal(bodyBytes, &status) + if err != nil { + return fmt.Errorf("parsing response's body failed with err:%v", err) + } + return errors.New(status.Message) +} diff --git a/vendor/github.com/fvbommel/sortorder/.gitignore b/vendor/github.com/fvbommel/sortorder/.gitignore new file mode 100644 index 000000000..c021733e2 --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/.gitignore @@ -0,0 +1,19 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so +# Folders +_obj +_test +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* +_testmain.go +*.exe +*.test +*.prof diff --git a/vendor/github.com/fvbommel/sortorder/LICENSE b/vendor/github.com/fvbommel/sortorder/LICENSE new file mode 100644 index 000000000..5c695fb59 --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/LICENSE @@ -0,0 +1,17 @@ +The MIT License (MIT) +Copyright (c) 2015 Frits van Bommel +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/fvbommel/sortorder/README.md b/vendor/github.com/fvbommel/sortorder/README.md new file mode 100644 index 000000000..06779c885 --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/README.md @@ -0,0 +1,9 @@ +# sortorder [](https://pkg.go.dev/github.com/fvbommel/sortorder) + + import "github.com/fvbommel/sortorder" + +Sort orders and comparison functions. + +Case-insensitive sort orders are in the `casefolded` sub-package +because it pulls in the Unicode tables in the standard library, +which can add significantly to the size of binaries. diff --git a/vendor/github.com/fvbommel/sortorder/doc.go b/vendor/github.com/fvbommel/sortorder/doc.go new file mode 100644 index 000000000..a7dd9585d --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/doc.go @@ -0,0 +1,5 @@ +// Package sortorder implements sort orders and comparison functions. +// +// Currently, it only implements so-called "natural order", where integers +// embedded in strings are compared by value. +package sortorder // import "github.com/fvbommel/sortorder" diff --git a/vendor/github.com/fvbommel/sortorder/natsort.go b/vendor/github.com/fvbommel/sortorder/natsort.go new file mode 100644 index 000000000..e4f15110b --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/natsort.go @@ -0,0 +1,76 @@ +package sortorder + +// Natural implements sort.Interface to sort strings in natural order. This +// means that e.g. "abc2" < "abc12". +// +// Non-digit sequences and numbers are compared separately. The former are +// compared bytewise, while digits are compared numerically (except that +// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") +// +// Limitation: only ASCII digits (0-9) are considered. +type Natural []string + +func (n Natural) Len() int { return len(n) } +func (n Natural) Swap(i, j int) { n[i], n[j] = n[j], n[i] } +func (n Natural) Less(i, j int) bool { return NaturalLess(n[i], n[j]) } + +func isDigit(b byte) bool { return '0' <= b && b <= '9' } + +// NaturalLess compares two strings using natural ordering. This means that e.g. +// "abc2" < "abc12". +// +// Non-digit sequences and numbers are compared separately. The former are +// compared bytewise, while digits are compared numerically (except that +// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") +// +// Limitation: only ASCII digits (0-9) are considered. +func NaturalLess(str1, str2 string) bool { + idx1, idx2 := 0, 0 + for idx1 < len(str1) && idx2 < len(str2) { + c1, c2 := str1[idx1], str2[idx2] + dig1, dig2 := isDigit(c1), isDigit(c2) + switch { + case dig1 != dig2: // Digits before other characters. + return dig1 // True if LHS is a digit, false if the RHS is one. + case !dig1: // && !dig2, because dig1 == dig2 + // UTF-8 compares bytewise-lexicographically, no need to decode + // codepoints. + if c1 != c2 { + return c1 < c2 + } + idx1++ + idx2++ + default: // Digits + // Eat zeros. + for ; idx1 < len(str1) && str1[idx1] == '0'; idx1++ { + } + for ; idx2 < len(str2) && str2[idx2] == '0'; idx2++ { + } + // Eat all digits. + nonZero1, nonZero2 := idx1, idx2 + for ; idx1 < len(str1) && isDigit(str1[idx1]); idx1++ { + } + for ; idx2 < len(str2) && isDigit(str2[idx2]); idx2++ { + } + // If lengths of numbers with non-zero prefix differ, the shorter + // one is less. + if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 { + return len1 < len2 + } + // If they're equally long, string comparison is correct. + if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 { + return nr1 < nr2 + } + // Otherwise, the one with less zeros is less. + // Because everything up to the number is equal, comparing the index + // after the zeros is sufficient. + if nonZero1 != nonZero2 { + return nonZero1 < nonZero2 + } + } + // They're identical so far, so continue comparing. + } + // So far they are identical. At least one is ended. If the other continues, + // it sorts last. + return len(str1) < len(str2) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn.go b/vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn.go new file mode 100644 index 000000000..38024cfa5 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn.go @@ -0,0 +1,262 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import ( + "bufio" + "bytes" + "fmt" + "io" + "reflect" + "regexp" + "strings" + + "github.com/liggitt/tabwriter" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/client-go/util/jsonpath" +) + +var jsonRegexp = regexp.MustCompile(`^\{\.?([^{}]+)\}$|^\.?([^{}]+)$`) + +// RelaxedJSONPathExpression attempts to be flexible with JSONPath expressions, it accepts: +// - metadata.name (no leading '.' or curly braces '{...}' +// - {metadata.name} (no leading '.') +// - .metadata.name (no curly braces '{...}') +// - {.metadata.name} (complete expression) +// +// And transforms them all into a valid jsonpath expression: +// +// {.metadata.name} +func RelaxedJSONPathExpression(pathExpression string) (string, error) { + if len(pathExpression) == 0 { + return pathExpression, nil + } + submatches := jsonRegexp.FindStringSubmatch(pathExpression) + if submatches == nil { + return "", fmt.Errorf("unexpected path string, expected a 'name1.name2' or '.name1.name2' or '{name1.name2}' or '{.name1.name2}'") + } + if len(submatches) != 3 { + return "", fmt.Errorf("unexpected submatch list: %v", submatches) + } + var fieldSpec string + if len(submatches[1]) != 0 { + fieldSpec = submatches[1] + } else { + fieldSpec = submatches[2] + } + return fmt.Sprintf("{.%s}", fieldSpec), nil +} + +// NewCustomColumnsPrinterFromSpec creates a custom columns printer from a comma separated list of <header>:<jsonpath-field-spec> pairs. +// e.g. NAME:metadata.name,API_VERSION:apiVersion creates a printer that prints: +// +// NAME API_VERSION +// foo bar +func NewCustomColumnsPrinterFromSpec(spec string, decoder runtime.Decoder, noHeaders bool) (*CustomColumnsPrinter, error) { + if len(spec) == 0 { + return nil, fmt.Errorf("custom-columns format specified but no custom columns given") + } + parts := strings.Split(spec, ",") + columns := make([]Column, len(parts)) + for ix := range parts { + colSpec := strings.SplitN(parts[ix], ":", 2) + if len(colSpec) != 2 { + return nil, fmt.Errorf("unexpected custom-columns spec: %s, expected <header>:<json-path-expr>", parts[ix]) + } + spec, err := RelaxedJSONPathExpression(colSpec[1]) + if err != nil { + return nil, err + } + columns[ix] = Column{Header: colSpec[0], FieldSpec: spec} + } + return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: noHeaders}, nil +} + +func splitOnWhitespace(line string) []string { + lineScanner := bufio.NewScanner(bytes.NewBufferString(line)) + lineScanner.Split(bufio.ScanWords) + result := []string{} + for lineScanner.Scan() { + result = append(result, lineScanner.Text()) + } + return result +} + +// NewCustomColumnsPrinterFromTemplate creates a custom columns printer from a template stream. The template is expected +// to consist of two lines, whitespace separated. The first line is the header line, the second line is the jsonpath field spec +// For example, the template below: +// NAME API_VERSION +// {metadata.name} {apiVersion} +func NewCustomColumnsPrinterFromTemplate(templateReader io.Reader, decoder runtime.Decoder) (*CustomColumnsPrinter, error) { + scanner := bufio.NewScanner(templateReader) + if !scanner.Scan() { + return nil, fmt.Errorf("invalid template, missing header line. Expected format is one line of space separated headers, one line of space separated column specs.") + } + headers := splitOnWhitespace(scanner.Text()) + + if !scanner.Scan() { + return nil, fmt.Errorf("invalid template, missing spec line. Expected format is one line of space separated headers, one line of space separated column specs.") + } + specs := splitOnWhitespace(scanner.Text()) + + if len(headers) != len(specs) { + return nil, fmt.Errorf("number of headers (%d) and field specifications (%d) don't match", len(headers), len(specs)) + } + + columns := make([]Column, len(headers)) + for ix := range headers { + spec, err := RelaxedJSONPathExpression(specs[ix]) + if err != nil { + return nil, err + } + columns[ix] = Column{ + Header: headers[ix], + FieldSpec: spec, + } + } + return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: false}, nil +} + +// Column represents a user specified column +type Column struct { + // The header to print above the column, general style is ALL_CAPS + Header string + // The pointer to the field in the object to print in JSONPath form + // e.g. {.ObjectMeta.Name}, see pkg/util/jsonpath for more details. + FieldSpec string +} + +// CustomColumnPrinter is a printer that knows how to print arbitrary columns +// of data from templates specified in the `Columns` array +type CustomColumnsPrinter struct { + Columns []Column + Decoder runtime.Decoder + NoHeaders bool + // lastType records type of resource printed last so that we don't repeat + // header while printing same type of resources. + lastType reflect.Type +} + +func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error { + // we use reflect.Indirect here in order to obtain the actual value from a pointer. + // we need an actual value in order to retrieve the package path for an object. + // using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers. + if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) { + return fmt.Errorf(printers.InternalObjectPrinterErr) + } + + if _, found := out.(*tabwriter.Writer); !found { + w := printers.GetNewTabWriter(out) + out = w + defer w.Flush() + } + + t := reflect.TypeOf(obj) + if !s.NoHeaders && t != s.lastType { + headers := make([]string, len(s.Columns)) + for ix := range s.Columns { + headers[ix] = s.Columns[ix].Header + } + fmt.Fprintln(out, strings.Join(headers, "\t")) + s.lastType = t + } + parsers := make([]*jsonpath.JSONPath, len(s.Columns)) + for ix := range s.Columns { + parsers[ix] = jsonpath.New(fmt.Sprintf("column%d", ix)).AllowMissingKeys(true) + if err := parsers[ix].Parse(s.Columns[ix].FieldSpec); err != nil { + return err + } + } + + if meta.IsListType(obj) { + objs, err := meta.ExtractList(obj) + if err != nil { + return err + } + for ix := range objs { + if err := s.printOneObject(objs[ix], parsers, out); err != nil { + return err + } + } + } else { + if err := s.printOneObject(obj, parsers, out); err != nil { + return err + } + } + return nil +} + +func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jsonpath.JSONPath, out io.Writer) error { + columns := make([]string, len(parsers)) + switch u := obj.(type) { + case *metav1.WatchEvent: + if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(u.Object.Object)).Type().PkgPath()) { + return fmt.Errorf(printers.InternalObjectPrinterErr) + } + unstructuredObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(u.Object.Object) + if err != nil { + return err + } + obj = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "type": u.Type, + "object": unstructuredObject, + }, + } + + case *runtime.Unknown: + if len(u.Raw) > 0 { + var err error + if obj, err = runtime.Decode(s.Decoder, u.Raw); err != nil { + return fmt.Errorf("can't decode object for printing: %v (%s)", err, u.Raw) + } + } + } + + for ix := range parsers { + parser := parsers[ix] + + var values [][]reflect.Value + var err error + if unstructured, ok := obj.(runtime.Unstructured); ok { + values, err = parser.FindResults(unstructured.UnstructuredContent()) + } else { + values, err = parser.FindResults(reflect.ValueOf(obj).Elem().Interface()) + } + + if err != nil { + return err + } + valueStrings := []string{} + if len(values) == 0 || len(values[0]) == 0 { + valueStrings = append(valueStrings, "<none>") + } + for arrIx := range values { + for valIx := range values[arrIx] { + valueStrings = append(valueStrings, printers.EscapeTerminal(fmt.Sprint(values[arrIx][valIx].Interface()))) + } + } + columns[ix] = strings.Join(valueStrings, ",") + } + fmt.Fprintln(out, strings.Join(columns, "\t")) + return nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn_flags.go b/vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn_flags.go new file mode 100644 index 000000000..efd7fcf75 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn_flags.go @@ -0,0 +1,113 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import ( + "fmt" + "os" + "sort" + "strings" + + "github.com/spf13/cobra" + + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/kubectl/pkg/scheme" +) + +var columnsFormats = map[string]bool{ + "custom-columns-file": true, + "custom-columns": true, +} + +// CustomColumnsPrintFlags provides default flags necessary for printing +// custom resource columns from an inline-template or file. +type CustomColumnsPrintFlags struct { + NoHeaders bool + TemplateArgument string +} + +func (f *CustomColumnsPrintFlags) AllowedFormats() []string { + formats := make([]string, 0, len(columnsFormats)) + for format := range columnsFormats { + formats = append(formats, format) + } + sort.Strings(formats) + return formats +} + +// ToPrinter receives an templateFormat and returns a printer capable of +// handling custom-column printing. +// Returns false if the specified templateFormat does not match a supported format. +// Supported format types can be found in pkg/printers/printers.go +func (f *CustomColumnsPrintFlags) ToPrinter(templateFormat string) (printers.ResourcePrinter, error) { + if len(templateFormat) == 0 { + return nil, genericclioptions.NoCompatiblePrinterError{} + } + + templateValue := "" + + if len(f.TemplateArgument) == 0 { + for format := range columnsFormats { + format = format + "=" + if strings.HasPrefix(templateFormat, format) { + templateValue = templateFormat[len(format):] + templateFormat = format[:len(format)-1] + break + } + } + } else { + templateValue = f.TemplateArgument + } + + if _, supportedFormat := columnsFormats[templateFormat]; !supportedFormat { + return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()} + } + + if len(templateValue) == 0 { + return nil, fmt.Errorf("custom-columns format specified but no custom columns given") + } + + // UniversalDecoder call must specify parameter versions; otherwise it will decode to internal versions. + decoder := scheme.Codecs.UniversalDecoder(scheme.Scheme.PrioritizedVersionsAllGroups()...) + + if templateFormat == "custom-columns-file" { + file, err := os.Open(templateValue) + if err != nil { + return nil, fmt.Errorf("error reading template %s, %v\n", templateValue, err) + } + defer file.Close() + p, err := NewCustomColumnsPrinterFromTemplate(file, decoder) + return p, err + } + + return NewCustomColumnsPrinterFromSpec(templateValue, decoder, f.NoHeaders) +} + +// AddFlags receives a *cobra.Command reference and binds +// flags related to custom-columns printing +func (f *CustomColumnsPrintFlags) AddFlags(c *cobra.Command) {} + +// NewCustomColumnsPrintFlags returns flags associated with +// custom-column printing, with default values set. +// NoHeaders and TemplateArgument should be set by callers. +func NewCustomColumnsPrintFlags() *CustomColumnsPrintFlags { + return &CustomColumnsPrintFlags{ + NoHeaders: false, + TemplateArgument: "", + } +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/get/get.go b/vendor/k8s.io/kubectl/pkg/cmd/get/get.go new file mode 100644 index 000000000..f16586994 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/get/get.go @@ -0,0 +1,821 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/url" + "strings" + + "github.com/spf13/cobra" + + 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" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/cli-runtime/pkg/resource" + kubernetesscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + watchtools "k8s.io/client-go/tools/watch" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/rawhttp" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/interrupt" + "k8s.io/kubectl/pkg/util/slice" + "k8s.io/kubectl/pkg/util/templates" + utilpointer "k8s.io/utils/pointer" +) + +// GetOptions contains the input to the get command. +type GetOptions struct { + PrintFlags *PrintFlags + ToPrinter func(*meta.RESTMapping, *bool, bool, bool) (printers.ResourcePrinterFunc, error) + IsHumanReadablePrinter bool + + CmdParent string + + resource.FilenameOptions + + Raw string + Watch bool + WatchOnly bool + ChunkSize int64 + + OutputWatchEvents bool + + LabelSelector string + FieldSelector string + AllNamespaces bool + Namespace string + ExplicitNamespace bool + Subresource string + SortBy string + + ServerPrint bool + + NoHeaders bool + IgnoreNotFound bool + + genericiooptions.IOStreams +} + +var ( + getLong = templates.LongDesc(i18n.T(` + Display one or many resources. + + Prints a table of the most important information about the specified resources. + You can filter the list using a label selector and the --selector flag. If the + desired resource type is namespaced you will only see results in your current + namespace unless you pass --all-namespaces. + + By specifying the output as 'template' and providing a Go template as the value + of the --template flag, you can filter the attributes of the fetched resources.`)) + + getExample = templates.Examples(i18n.T(` + # List all pods in ps output format + kubectl get pods + + # List all pods in ps output format with more information (such as node name) + kubectl get pods -o wide + + # List a single replication controller with specified NAME in ps output format + kubectl get replicationcontroller web + + # List deployments in JSON output format, in the "v1" version of the "apps" API group + kubectl get deployments.v1.apps -o json + + # List a single pod in JSON output format + kubectl get -o json pod web-pod-13je7 + + # List a pod identified by type and name specified in "pod.yaml" in JSON output format + kubectl get -f pod.yaml -o json + + # List resources from a directory with kustomization.yaml - e.g. dir/kustomization.yaml + kubectl get -k dir/ + + # Return only the phase value of the specified pod + kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}} + + # List resource information in custom columns + kubectl get pod test-pod -o custom-columns=CONTAINER:.spec.containers[0].name,IMAGE:.spec.containers[0].image + + # List all replication controllers and services together in ps output format + kubectl get rc,services + + # List one or more resources by their type and names + kubectl get rc/web service/frontend pods/web-pod-13je7 + + # List the 'status' subresource for a single pod + kubectl get pod web-pod-13je7 --subresource status`)) +) + +const ( + useServerPrintColumns = "server-print" +) + +var supportedSubresources = []string{"status", "scale"} + +// NewGetOptions returns a GetOptions with default chunk size 500. +func NewGetOptions(parent string, streams genericiooptions.IOStreams) *GetOptions { + return &GetOptions{ + PrintFlags: NewGetPrintFlags(), + CmdParent: parent, + + IOStreams: streams, + ChunkSize: cmdutil.DefaultChunkSize, + ServerPrint: true, + } +} + +// NewCmdGet creates a command object for the generic "get" action, which +// retrieves one or more resources from a server. +func NewCmdGet(parent string, f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := NewGetOptions(parent, streams) + + cmd := &cobra.Command{ + Use: fmt.Sprintf("get [(-o|--output=)%s] (TYPE[.VERSION][.GROUP] [NAME | -l label] | TYPE[.VERSION][.GROUP]/NAME ...) [flags]", strings.Join(o.PrintFlags.AllowedFormats(), "|")), + DisableFlagsInUseLine: true, + Short: i18n.T("Display one or many resources"), + Long: getLong + "\n\n" + cmdutil.SuggestAPIResources(parent), + Example: getExample, + // ValidArgsFunction is set when this function is called so that we have access to the util package + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run(f, args)) + }, + SuggestFor: []string{"list", "ps"}, + } + + o.PrintFlags.AddFlags(cmd) + + cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to request from the server. Uses the transport specified by the kubeconfig file.") + cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes.") + cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.") + cmd.Flags().BoolVar(&o.OutputWatchEvents, "output-watch-events", o.OutputWatchEvents, "Output watch event objects when --watch or --watch-only is used. Existing objects are output as initial ADDED events.") + cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.") + cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.") + cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") + addServerPrintColumnFlags(cmd, o) + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to get from a server.") + cmdutil.AddChunkSizeFlag(cmd, &o.ChunkSize) + cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector) + cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, gets the subresource of the requested object.", supportedSubresources...) + return cmd +} + +// Complete takes the command arguments and factory and infers any remaining options. +func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(o.Raw) > 0 { + if len(args) > 0 { + return fmt.Errorf("arguments may not be passed when --raw is specified") + } + return nil + } + + var err error + o.Namespace, o.ExplicitNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + if o.AllNamespaces { + o.ExplicitNamespace = false + } + + if o.PrintFlags.HumanReadableFlags.SortBy != nil { + o.SortBy = *o.PrintFlags.HumanReadableFlags.SortBy + } + + o.NoHeaders = cmdutil.GetFlagBool(cmd, "no-headers") + + // TODO (soltysh): currently we don't support custom columns + // with server side print. So in these cases force the old behavior. + outputOption := cmd.Flags().Lookup("output").Value.String() + if strings.Contains(outputOption, "custom-columns") || outputOption == "yaml" || strings.Contains(outputOption, "json") { + o.ServerPrint = false + } + + templateArg := "" + if o.PrintFlags.TemplateFlags != nil && o.PrintFlags.TemplateFlags.TemplateArgument != nil { + templateArg = *o.PrintFlags.TemplateFlags.TemplateArgument + } + + // human readable printers have special conversion rules, so we determine if we're using one. + if (len(*o.PrintFlags.OutputFormat) == 0 && len(templateArg) == 0) || *o.PrintFlags.OutputFormat == "wide" { + o.IsHumanReadablePrinter = true + } + + o.ToPrinter = func(mapping *meta.RESTMapping, outputObjects *bool, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) { + // make a new copy of current flags / opts before mutating + printFlags := o.PrintFlags.Copy() + + if mapping != nil { + printFlags.SetKind(mapping.GroupVersionKind.GroupKind()) + } + if withNamespace { + printFlags.EnsureWithNamespace() + } + if withKind { + printFlags.EnsureWithKind() + } + + printer, err := printFlags.ToPrinter() + if err != nil { + return nil, err + } + printer, err = printers.NewTypeSetter(scheme.Scheme).WrapToPrinter(printer, nil) + if err != nil { + return nil, err + } + + if len(o.SortBy) > 0 { + printer = &SortingPrinter{Delegate: printer, SortField: o.SortBy} + } + if outputObjects != nil { + printer = &skipPrinter{delegate: printer, output: outputObjects} + } + if o.ServerPrint { + printer = &TablePrinter{Delegate: printer} + } + return printer.PrintObj, nil + } + + switch { + case o.Watch || o.WatchOnly: + if len(o.SortBy) > 0 { + fmt.Fprintf(o.IOStreams.ErrOut, "warning: --watch or --watch-only requested, --sort-by will be ignored\n") + } + default: + if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { + fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.SuggestAPIResources(o.CmdParent)) + fullCmdName := cmd.Parent().CommandPath() + usageString := "Required resource not specified." + if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") { + usageString = fmt.Sprintf("%s\nUse \"%s explain <resource>\" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName) + } + + return cmdutil.UsageErrorf(cmd, usageString) + } + } + + return nil +} + +// Validate checks the set of flags provided by the user. +func (o *GetOptions) Validate() error { + if len(o.Raw) > 0 { + if o.Watch || o.WatchOnly || len(o.LabelSelector) > 0 { + return fmt.Errorf("--raw may not be specified with other flags that filter the server request or alter the output") + } + if o.PrintFlags.OutputFormat != nil && len(*o.PrintFlags.OutputFormat) > 0 { + return fmt.Errorf("--raw and --output are mutually exclusive") + } + if _, err := url.ParseRequestURI(o.Raw); err != nil { + return fmt.Errorf("--raw must be a valid URL path: %v", err) + } + } + if o.PrintFlags.HumanReadableFlags.ShowLabels != nil && *o.PrintFlags.HumanReadableFlags.ShowLabels && o.PrintFlags.OutputFormat != nil { + outputOption := *o.PrintFlags.OutputFormat + if outputOption != "" && outputOption != "wide" { + return fmt.Errorf("--show-labels option cannot be used with %s printer", outputOption) + } + } + if o.OutputWatchEvents && !(o.Watch || o.WatchOnly) { + return fmt.Errorf("--output-watch-events option can only be used with --watch or --watch-only") + } + if len(o.Subresource) > 0 && !slice.ContainsString(supportedSubresources, o.Subresource, nil) { + return fmt.Errorf("invalid subresource value: %q. Must be one of %v", o.Subresource, supportedSubresources) + } + return nil +} + +// OriginalPositioner and NopPositioner is required for swap/sort operations of data in table format +type OriginalPositioner interface { + OriginalPosition(int) int +} + +// NopPositioner and OriginalPositioner is required for swap/sort operations of data in table format +type NopPositioner struct{} + +// OriginalPosition returns the original position from NopPositioner object +func (t *NopPositioner) OriginalPosition(ix int) int { + return ix +} + +// RuntimeSorter holds the required objects to perform sorting of runtime objects +type RuntimeSorter struct { + field string + decoder runtime.Decoder + objects []runtime.Object + positioner OriginalPositioner +} + +// Sort performs the sorting of runtime objects +func (r *RuntimeSorter) Sort() error { + // a list is only considered "sorted" if there are 0 or 1 items in it + // AND (if 1 item) the item is not a Table object + if len(r.objects) == 0 { + return nil + } + if len(r.objects) == 1 { + _, isTable := r.objects[0].(*metav1.Table) + if !isTable { + return nil + } + } + + includesTable := false + includesRuntimeObjs := false + + for _, obj := range r.objects { + switch t := obj.(type) { + case *metav1.Table: + includesTable = true + + if sorter, err := NewTableSorter(t, r.field); err != nil { + return err + } else if err := sorter.Sort(); err != nil { + return err + } + default: + includesRuntimeObjs = true + } + } + + // we use a NopPositioner when dealing with Table objects + // because the objects themselves are not swapped, but rather + // the rows in each object are swapped / sorted. + r.positioner = &NopPositioner{} + + if includesRuntimeObjs && includesTable { + return fmt.Errorf("sorting is not supported on mixed Table and non-Table object lists") + } + if includesTable { + return nil + } + + // if not dealing with a Table response from the server, assume + // all objects are runtime.Object as usual, and sort using old method. + var err error + if r.positioner, err = SortObjects(r.decoder, r.objects, r.field); err != nil { + return err + } + return nil +} + +// OriginalPosition returns the original position of a runtime object +func (r *RuntimeSorter) OriginalPosition(ix int) int { + if r.positioner == nil { + return 0 + } + return r.positioner.OriginalPosition(ix) +} + +// WithDecoder allows custom decoder to be set for testing +func (r *RuntimeSorter) WithDecoder(decoder runtime.Decoder) *RuntimeSorter { + r.decoder = decoder + return r +} + +// NewRuntimeSorter returns a new instance of RuntimeSorter +func NewRuntimeSorter(objects []runtime.Object, sortBy string) *RuntimeSorter { + parsedField, err := RelaxedJSONPathExpression(sortBy) + if err != nil { + parsedField = sortBy + } + + return &RuntimeSorter{ + field: parsedField, + decoder: kubernetesscheme.Codecs.UniversalDecoder(), + objects: objects, + } +} + +func (o *GetOptions) transformRequests(req *rest.Request) { + if !o.ServerPrint || !o.IsHumanReadablePrinter { + return + } + + req.SetHeader("Accept", strings.Join([]string{ + fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName), + fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName), + "application/json", + }, ",")) + + // if sorting, ensure we receive the full object in order to introspect its fields via jsonpath + if len(o.SortBy) > 0 { + req.Param("includeObject", "Object") + } +} + +// Run performs the get operation. +// TODO: remove the need to pass these arguments, like other commands. +func (o *GetOptions) Run(f cmdutil.Factory, args []string) error { + if len(o.Raw) > 0 { + restClient, err := f.RESTClient() + if err != nil { + return err + } + return rawhttp.RawGet(restClient, o.IOStreams, o.Raw) + } + if o.Watch || o.WatchOnly { + return o.watch(f, args) + } + + chunkSize := o.ChunkSize + if len(o.SortBy) > 0 { + // TODO(juanvallejo): in the future, we could have the client use chunking + // to gather all results, then sort them all at the end to reduce server load. + chunkSize = 0 + } + + r := f.NewBuilder(). + Unstructured(). + NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces). + FilenameParam(o.ExplicitNamespace, &o.FilenameOptions). + LabelSelectorParam(o.LabelSelector). + FieldSelectorParam(o.FieldSelector). + Subresource(o.Subresource). + RequestChunksOf(chunkSize). + ResourceTypeOrNameArgs(true, args...). + ContinueOnError(). + Latest(). + Flatten(). + TransformRequests(o.transformRequests). + Do() + + if o.IgnoreNotFound { + r.IgnoreErrors(apierrors.IsNotFound) + } + if err := r.Err(); err != nil { + return err + } + + if !o.IsHumanReadablePrinter { + return o.printGeneric(r) + } + + allErrs := []error{} + errs := sets.NewString() + infos, err := r.Infos() + if err != nil { + allErrs = append(allErrs, err) + } + printWithKind := multipleGVKsRequested(infos) + + objs := make([]runtime.Object, len(infos)) + for ix := range infos { + objs[ix] = infos[ix].Object + } + + var positioner OriginalPositioner + if len(o.SortBy) > 0 { + sorter := NewRuntimeSorter(objs, o.SortBy) + if err := sorter.Sort(); err != nil { + return err + } + positioner = sorter + } + + var printer printers.ResourcePrinter + var lastMapping *meta.RESTMapping + + // track if we write any output + trackingWriter := &trackingWriterWrapper{Delegate: o.Out} + // output an empty line separating output + separatorWriter := &separatorWriterWrapper{Delegate: trackingWriter} + + w := printers.GetNewTabWriter(separatorWriter) + allResourcesNamespaced := !o.AllNamespaces + for ix := range objs { + var mapping *meta.RESTMapping + var info *resource.Info + if positioner != nil { + info = infos[positioner.OriginalPosition(ix)] + mapping = info.Mapping + } else { + info = infos[ix] + mapping = info.Mapping + } + + allResourcesNamespaced = allResourcesNamespaced && info.Namespaced() + printWithNamespace := o.AllNamespaces + + if mapping != nil && mapping.Scope.Name() == meta.RESTScopeNameRoot { + printWithNamespace = false + } + + if shouldGetNewPrinterForMapping(printer, lastMapping, mapping) { + w.Flush() + w.SetRememberedWidths(nil) + + // add linebreaks between resource groups (if there is more than one) + // when it satisfies all following 3 conditions: + // 1) it's not the first resource group + // 2) it has row header + // 3) we've written output since the last time we started a new set of headers + if lastMapping != nil && !o.NoHeaders && trackingWriter.Written > 0 { + separatorWriter.SetReady(true) + } + + printer, err = o.ToPrinter(mapping, nil, printWithNamespace, printWithKind) + if err != nil { + if !errs.Has(err.Error()) { + errs.Insert(err.Error()) + allErrs = append(allErrs, err) + } + continue + } + + lastMapping = mapping + } + + printer.PrintObj(info.Object, w) + } + w.Flush() + if trackingWriter.Written == 0 && !o.IgnoreNotFound && len(allErrs) == 0 { + // if we wrote no output, and had no errors, and are not ignoring NotFound, be sure we output something + if allResourcesNamespaced { + fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace) + } else { + fmt.Fprintln(o.ErrOut, "No resources found") + } + } + return utilerrors.NewAggregate(allErrs) +} + +type trackingWriterWrapper struct { + Delegate io.Writer + Written int +} + +func (t *trackingWriterWrapper) Write(p []byte) (n int, err error) { + t.Written += len(p) + return t.Delegate.Write(p) +} + +type separatorWriterWrapper struct { + Delegate io.Writer + Ready bool +} + +func (s *separatorWriterWrapper) Write(p []byte) (n int, err error) { + // If we're about to write non-empty bytes and `s` is ready, + // we prepend an empty line to `p` and reset `s.Read`. + if len(p) != 0 && s.Ready { + fmt.Fprintln(s.Delegate) + s.Ready = false + } + return s.Delegate.Write(p) +} + +func (s *separatorWriterWrapper) SetReady(state bool) { + s.Ready = state +} + +// watch starts a client-side watch of one or more resources. +// TODO: remove the need for arguments here. +func (o *GetOptions) watch(f cmdutil.Factory, args []string) error { + r := f.NewBuilder(). + Unstructured(). + NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces). + FilenameParam(o.ExplicitNamespace, &o.FilenameOptions). + LabelSelectorParam(o.LabelSelector). + FieldSelectorParam(o.FieldSelector). + RequestChunksOf(o.ChunkSize). + ResourceTypeOrNameArgs(true, args...). + SingleResourceType(). + Latest(). + TransformRequests(o.transformRequests). + Do() + if err := r.Err(); err != nil { + return err + } + infos, err := r.Infos() + if err != nil { + return err + } + if multipleGVKsRequested(infos) { + return i18n.Errorf("watch is only supported on individual resources and resource collections - more than 1 resource was found") + } + + info := infos[0] + mapping := info.ResourceMapping() + outputObjects := utilpointer.BoolPtr(!o.WatchOnly) + printer, err := o.ToPrinter(mapping, outputObjects, o.AllNamespaces, false) + if err != nil { + return err + } + obj, err := r.Object() + if err != nil { + return err + } + + // watching from resourceVersion 0, starts the watch at ~now and + // will return an initial watch event. Starting form ~now, rather + // the rv of the object will insure that we start the watch from + // inside the watch window, which the rv of the object might not be. + rv := "0" + isList := meta.IsListType(obj) + if isList { + // the resourceVersion of list objects is ~now but won't return + // an initial watch event + rv, err = meta.NewAccessor().ResourceVersion(obj) + if err != nil { + return err + } + } + + writer := printers.GetNewTabWriter(o.Out) + + // print the current object + var objsToPrint []runtime.Object + if isList { + objsToPrint, _ = meta.ExtractList(obj) + } else { + objsToPrint = append(objsToPrint, obj) + } + for _, objToPrint := range objsToPrint { + if o.OutputWatchEvents { + objToPrint = &metav1.WatchEvent{Type: string(watch.Added), Object: runtime.RawExtension{Object: objToPrint}} + } + if err := printer.PrintObj(objToPrint, writer); err != nil { + return fmt.Errorf("unable to output the provided object: %v", err) + } + } + writer.Flush() + if isList { + // we can start outputting objects now, watches started from lists don't emit synthetic added events + *outputObjects = true + } else { + // suppress output, since watches started for individual items emit a synthetic ADDED event first + *outputObjects = false + } + + // print watched changes + w, err := r.Watch(rv) + if err != nil { + return err + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + intr := interrupt.New(nil, cancel) + intr.Run(func() error { + _, err := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) { + objToPrint := e.Object + if o.OutputWatchEvents { + objToPrint = &metav1.WatchEvent{Type: string(e.Type), Object: runtime.RawExtension{Object: objToPrint}} + } + if err := printer.PrintObj(objToPrint, writer); err != nil { + return false, err + } + writer.Flush() + // after processing at least one event, start outputting objects + *outputObjects = true + return false, nil + }) + return err + }) + return nil +} + +func (o *GetOptions) printGeneric(r *resource.Result) error { + // we flattened the data from the builder, so we have individual items, but now we'd like to either: + // 1. if there is more than one item, combine them all into a single list + // 2. if there is a single item and that item is a list, leave it as its specific list + // 3. if there is a single item and it is not a list, leave it as a single item + var errs []error + singleItemImplied := false + infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos() + if err != nil { + if singleItemImplied { + return err + } + errs = append(errs, err) + } + + if len(infos) == 0 && o.IgnoreNotFound { + return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) + } + + printer, err := o.ToPrinter(nil, nil, false, false) + if err != nil { + return err + } + + var obj runtime.Object + if !singleItemImplied || len(infos) != 1 { + // we have zero or multple items, so coerce all items into a list. + // we don't want an *unstructured.Unstructured list yet, as we + // may be dealing with non-unstructured objects. Compose all items + // into an corev1.List, and then decode using an unstructured scheme. + list := corev1.List{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{}, + } + for _, info := range infos { + list.Items = append(list.Items, runtime.RawExtension{Object: info.Object}) + } + + listData, err := json.Marshal(list) + if err != nil { + return err + } + + converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, listData) + if err != nil { + return err + } + + obj = converted + } else { + obj = infos[0].Object + } + + isList := meta.IsListType(obj) + if isList { + items, err := meta.ExtractList(obj) + if err != nil { + return err + } + + // take the items and create a new list for display + list := &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + "metadata": map[string]interface{}{}, + }, + } + if listMeta, err := meta.ListAccessor(obj); err == nil { + list.Object["metadata"] = map[string]interface{}{ + "resourceVersion": listMeta.GetResourceVersion(), + } + } + + for _, item := range items { + list.Items = append(list.Items, *item.(*unstructured.Unstructured)) + } + if err := printer.PrintObj(list, o.Out); err != nil { + errs = append(errs, err) + } + return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) + } + + if printErr := printer.PrintObj(obj, o.Out); printErr != nil { + errs = append(errs, printErr) + } + + return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) +} + +func addServerPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) { + cmd.Flags().BoolVar(&opt.ServerPrint, useServerPrintColumns, opt.ServerPrint, "If true, have the server return the appropriate table output. Supports extension APIs and CRDs.") +} + +func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping, mapping *meta.RESTMapping) bool { + return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource +} + +func multipleGVKsRequested(infos []*resource.Info) bool { + if len(infos) < 2 { + return false + } + gvk := infos[0].Mapping.GroupVersionKind + for _, info := range infos { + if info.Mapping.GroupVersionKind != gvk { + return true + } + } + return false +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/get/get_flags.go b/vendor/k8s.io/kubectl/pkg/cmd/get/get_flags.go new file mode 100644 index 000000000..e5eb98b21 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/get/get_flags.go @@ -0,0 +1,170 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/kubectl/pkg/cmd/util" +) + +// PrintFlags composes common printer flag structs +// used in the Get command. +type PrintFlags struct { + JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags + NamePrintFlags *genericclioptions.NamePrintFlags + CustomColumnsFlags *CustomColumnsPrintFlags + HumanReadableFlags *HumanPrintFlags + TemplateFlags *genericclioptions.KubeTemplatePrintFlags + + NoHeaders *bool + OutputFormat *string +} + +// SetKind sets the Kind option of humanreadable flags +func (f *PrintFlags) SetKind(kind schema.GroupKind) { + f.HumanReadableFlags.SetKind(kind) +} + +// EnsureWithNamespace ensures that humanreadable flags return +// a printer capable of printing with a "namespace" column. +func (f *PrintFlags) EnsureWithNamespace() error { + return f.HumanReadableFlags.EnsureWithNamespace() +} + +// EnsureWithKind ensures that humanreadable flags return +// a printer capable of including resource kinds. +func (f *PrintFlags) EnsureWithKind() error { + return f.HumanReadableFlags.EnsureWithKind() +} + +// Copy returns a copy of PrintFlags for mutation +func (f *PrintFlags) Copy() PrintFlags { + printFlags := *f + return printFlags +} + +// AllowedFormats is the list of formats in which data can be displayed +func (f *PrintFlags) AllowedFormats() []string { + formats := f.JSONYamlPrintFlags.AllowedFormats() + formats = append(formats, f.NamePrintFlags.AllowedFormats()...) + formats = append(formats, f.TemplateFlags.AllowedFormats()...) + formats = append(formats, f.CustomColumnsFlags.AllowedFormats()...) + formats = append(formats, f.HumanReadableFlags.AllowedFormats()...) + return formats +} + +// ToPrinter attempts to find a composed set of PrintFlags suitable for +// returning a printer based on current flag values. +func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) { + outputFormat := "" + if f.OutputFormat != nil { + outputFormat = *f.OutputFormat + } + + noHeaders := false + if f.NoHeaders != nil { + noHeaders = *f.NoHeaders + } + f.HumanReadableFlags.NoHeaders = noHeaders + f.CustomColumnsFlags.NoHeaders = noHeaders + + // for "get.go" we want to support a --template argument given, even when no --output format is provided + if f.TemplateFlags.TemplateArgument != nil && len(*f.TemplateFlags.TemplateArgument) > 0 && len(outputFormat) == 0 { + outputFormat = "go-template" + } + + if p, err := f.TemplateFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) { + return p, err + } + + if f.TemplateFlags.TemplateArgument != nil { + f.CustomColumnsFlags.TemplateArgument = *f.TemplateFlags.TemplateArgument + } + + if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) { + return p, err + } + + if p, err := f.HumanReadableFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) { + return p, err + } + + if p, err := f.CustomColumnsFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) { + return p, err + } + + if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) { + return p, err + } + + return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()} +} + +// AddFlags receives a *cobra.Command reference and binds +// flags related to humanreadable and template printing. +func (f *PrintFlags) AddFlags(cmd *cobra.Command) { + f.JSONYamlPrintFlags.AddFlags(cmd) + f.NamePrintFlags.AddFlags(cmd) + f.TemplateFlags.AddFlags(cmd) + f.HumanReadableFlags.AddFlags(cmd) + f.CustomColumnsFlags.AddFlags(cmd) + + if f.OutputFormat != nil { + cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, fmt.Sprintf(`Output format. One of: (%s). See custom columns [https://kubernetes.io/docs/reference/kubectl/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [https://kubernetes.io/docs/reference/kubectl/jsonpath/].`, strings.Join(f.AllowedFormats(), ", "))) + util.CheckErr(cmd.RegisterFlagCompletionFunc( + "output", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var comps []string + for _, format := range f.AllowedFormats() { + if strings.HasPrefix(format, toComplete) { + comps = append(comps, format) + } + } + return comps, cobra.ShellCompDirectiveNoFileComp + }, + )) + } + if f.NoHeaders != nil { + cmd.Flags().BoolVar(f.NoHeaders, "no-headers", *f.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).") + } +} + +// NewGetPrintFlags returns flags associated with humanreadable, +// template, and "name" printing, with default values set. +func NewGetPrintFlags() *PrintFlags { + outputFormat := "" + noHeaders := false + + return &PrintFlags{ + OutputFormat: &outputFormat, + NoHeaders: &noHeaders, + + JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(), + NamePrintFlags: genericclioptions.NewNamePrintFlags(""), + TemplateFlags: genericclioptions.NewKubeTemplatePrintFlags(), + + HumanReadableFlags: NewHumanPrintFlags(), + CustomColumnsFlags: NewCustomColumnsPrintFlags(), + } +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/get/humanreadable_flags.go b/vendor/k8s.io/kubectl/pkg/cmd/get/humanreadable_flags.go new file mode 100644 index 000000000..5fcf34d8c --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/get/humanreadable_flags.go @@ -0,0 +1,138 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import ( + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/printers" +) + +// HumanPrintFlags provides default flags necessary for printing. +// Given the following flag values, a printer can be requested that knows +// how to handle printing based on these values. +type HumanPrintFlags struct { + ShowKind *bool + ShowLabels *bool + SortBy *string + ColumnLabels *[]string + + // get.go-specific values + NoHeaders bool + + Kind schema.GroupKind + WithNamespace bool +} + +// SetKind sets the Kind option +func (f *HumanPrintFlags) SetKind(kind schema.GroupKind) { + f.Kind = kind +} + +// EnsureWithKind sets the "Showkind" humanreadable option to true. +func (f *HumanPrintFlags) EnsureWithKind() error { + showKind := true + f.ShowKind = &showKind + return nil +} + +// EnsureWithNamespace sets the "WithNamespace" humanreadable option to true. +func (f *HumanPrintFlags) EnsureWithNamespace() error { + f.WithNamespace = true + return nil +} + +// AllowedFormats returns more customized formating options +func (f *HumanPrintFlags) AllowedFormats() []string { + return []string{"wide"} +} + +// ToPrinter receives an outputFormat and returns a printer capable of +// handling human-readable output. +func (f *HumanPrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) { + if len(outputFormat) > 0 && outputFormat != "wide" { + return nil, genericclioptions.NoCompatiblePrinterError{Options: f, AllowedFormats: f.AllowedFormats()} + } + + showKind := false + if f.ShowKind != nil { + showKind = *f.ShowKind + } + + showLabels := false + if f.ShowLabels != nil { + showLabels = *f.ShowLabels + } + + columnLabels := []string{} + if f.ColumnLabels != nil { + columnLabels = *f.ColumnLabels + } + + p := printers.NewTablePrinter(printers.PrintOptions{ + Kind: f.Kind, + WithKind: showKind, + NoHeaders: f.NoHeaders, + Wide: outputFormat == "wide", + WithNamespace: f.WithNamespace, + ColumnLabels: columnLabels, + ShowLabels: showLabels, + }) + + // TODO(juanvallejo): handle sorting here + + return p, nil +} + +// AddFlags receives a *cobra.Command reference and binds +// flags related to human-readable printing to it +func (f *HumanPrintFlags) AddFlags(c *cobra.Command) { + if f.ShowLabels != nil { + c.Flags().BoolVar(f.ShowLabels, "show-labels", *f.ShowLabels, "When printing, show all labels as the last column (default hide labels column)") + } + if f.SortBy != nil { + c.Flags().StringVar(f.SortBy, "sort-by", *f.SortBy, "If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.") + } + if f.ColumnLabels != nil { + c.Flags().StringSliceVarP(f.ColumnLabels, "label-columns", "L", *f.ColumnLabels, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag options like -L label1 -L label2...") + } + if f.ShowKind != nil { + c.Flags().BoolVar(f.ShowKind, "show-kind", *f.ShowKind, "If present, list the resource type for the requested object(s).") + } +} + +// NewHumanPrintFlags returns flags associated with +// human-readable printing, with default values set. +func NewHumanPrintFlags() *HumanPrintFlags { + showLabels := false + sortBy := "" + showKind := false + columnLabels := []string{} + + return &HumanPrintFlags{ + NoHeaders: false, + WithNamespace: false, + ColumnLabels: &columnLabels, + + Kind: schema.GroupKind{}, + ShowLabels: &showLabels, + SortBy: &sortBy, + ShowKind: &showKind, + } +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/get/skip_printer.go b/vendor/k8s.io/kubectl/pkg/cmd/get/skip_printer.go new file mode 100644 index 000000000..95a804315 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/get/skip_printer.go @@ -0,0 +1,48 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import ( + "io" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/printers" +) + +// skipPrinter allows conditionally suppressing object output via the output field. +// table objects are suppressed by setting their Rows to nil (allowing column definitions to propagate to the delegate). +// non-table objects are suppressed by not calling the delegate at all. +type skipPrinter struct { + delegate printers.ResourcePrinter + output *bool +} + +func (p *skipPrinter) PrintObj(obj runtime.Object, writer io.Writer) error { + if *p.output { + return p.delegate.PrintObj(obj, writer) + } + + table, isTable := obj.(*metav1.Table) + if !isTable { + return nil + } + + table = table.DeepCopy() + table.Rows = nil + return p.delegate.PrintObj(table, writer) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/get/sorter.go b/vendor/k8s.io/kubectl/pkg/cmd/get/sorter.go new file mode 100644 index 000000000..9f9e2b804 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/get/sorter.go @@ -0,0 +1,424 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import ( + "fmt" + "io" + "reflect" + "sort" + + "k8s.io/klog/v2" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/client-go/util/jsonpath" + "k8s.io/utils/integer" + + "github.com/fvbommel/sortorder" +) + +// SortingPrinter sorts list types before delegating to another printer. +// Non-list types are simply passed through +type SortingPrinter struct { + SortField string + Delegate printers.ResourcePrinter + Decoder runtime.Decoder +} + +func (s *SortingPrinter) PrintObj(obj runtime.Object, out io.Writer) error { + if table, isTable := obj.(*metav1.Table); isTable && len(table.Rows) > 1 { + parsedField, err := RelaxedJSONPathExpression(s.SortField) + if err != nil { + parsedField = s.SortField + } + + if sorter, err := NewTableSorter(table, parsedField); err != nil { + return err + } else if err := sorter.Sort(); err != nil { + return err + } + return s.Delegate.PrintObj(table, out) + } + + if meta.IsListType(obj) { + if err := s.sortObj(obj); err != nil { + return err + } + return s.Delegate.PrintObj(obj, out) + } + + return s.Delegate.PrintObj(obj, out) +} + +func (s *SortingPrinter) sortObj(obj runtime.Object) error { + objs, err := meta.ExtractList(obj) + if err != nil { + return err + } + if len(objs) == 0 { + return nil + } + + sorter, err := SortObjects(s.Decoder, objs, s.SortField) + if err != nil { + return err + } + + switch list := obj.(type) { + case *corev1.List: + outputList := make([]runtime.RawExtension, len(objs)) + for ix := range objs { + outputList[ix] = list.Items[sorter.OriginalPosition(ix)] + } + list.Items = outputList + return nil + } + return meta.SetList(obj, objs) +} + +// SortObjects sorts the runtime.Object based on fieldInput and returns RuntimeSort that implements +// the golang sort interface +func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput string) (*RuntimeSort, error) { + for ix := range objs { + item := objs[ix] + switch u := item.(type) { + case *runtime.Unknown: + var err error + // decode runtime.Unknown to runtime.Unstructured for sorting. + // we don't actually want the internal versions of known types. + if objs[ix], _, err = decoder.Decode(u.Raw, nil, &unstructured.Unstructured{}); err != nil { + return nil, err + } + } + } + + field, err := RelaxedJSONPathExpression(fieldInput) + if err != nil { + return nil, err + } + + parser := jsonpath.New("sorting").AllowMissingKeys(true) + if err := parser.Parse(field); err != nil { + return nil, err + } + + // We don't do any model validation here, so we traverse all objects to be sorted + // and, if the field is valid to at least one of them, we consider it to be a + // valid field; otherwise error out. + // Note that this requires empty fields to be considered later, when sorting. + var fieldFoundOnce bool + for _, obj := range objs { + values, err := findJSONPathResults(parser, obj) + if err != nil { + return nil, err + } + if len(values) > 0 && len(values[0]) > 0 { + fieldFoundOnce = true + break + } + } + if !fieldFoundOnce { + return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field) + } + + sorter := NewRuntimeSort(field, objs) + sort.Sort(sorter) + return sorter, nil +} + +// RuntimeSort is an implementation of the golang sort interface that knows how to sort +// lists of runtime.Object +type RuntimeSort struct { + field string + objs []runtime.Object + origPosition []int +} + +// NewRuntimeSort creates a new RuntimeSort struct that implements golang sort interface +func NewRuntimeSort(field string, objs []runtime.Object) *RuntimeSort { + sorter := &RuntimeSort{field: field, objs: objs, origPosition: make([]int, len(objs))} + for ix := range objs { + sorter.origPosition[ix] = ix + } + return sorter +} + +func (r *RuntimeSort) Len() int { + return len(r.objs) +} + +func (r *RuntimeSort) Swap(i, j int) { + r.objs[i], r.objs[j] = r.objs[j], r.objs[i] + r.origPosition[i], r.origPosition[j] = r.origPosition[j], r.origPosition[i] +} + +func isLess(i, j reflect.Value) (bool, error) { + switch i.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return i.Int() < j.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return i.Uint() < j.Uint(), nil + case reflect.Float32, reflect.Float64: + return i.Float() < j.Float(), nil + case reflect.String: + return sortorder.NaturalLess(i.String(), j.String()), nil + case reflect.Pointer: + return isLess(i.Elem(), j.Elem()) + case reflect.Struct: + // sort metav1.Time + in := i.Interface() + if t, ok := in.(metav1.Time); ok { + time := j.Interface().(metav1.Time) + return t.Before(&time), nil + } + // sort resource.Quantity + if iQuantity, ok := in.(resource.Quantity); ok { + jQuantity := j.Interface().(resource.Quantity) + return iQuantity.Cmp(jQuantity) < 0, nil + } + // fallback to the fields comparison + for idx := 0; idx < i.NumField(); idx++ { + less, err := isLess(i.Field(idx), j.Field(idx)) + if err != nil || !less { + return less, err + } + } + return true, nil + case reflect.Array, reflect.Slice: + // note: the length of i and j may be different + for idx := 0; idx < integer.IntMin(i.Len(), j.Len()); idx++ { + less, err := isLess(i.Index(idx), j.Index(idx)) + if err != nil || !less { + return less, err + } + } + return true, nil + case reflect.Interface: + if i.IsNil() && j.IsNil() { + return false, nil + } else if i.IsNil() { + return true, nil + } else if j.IsNil() { + return false, nil + } + switch itype := i.Interface().(type) { + case uint8: + if jtype, ok := j.Interface().(uint8); ok { + return itype < jtype, nil + } + case uint16: + if jtype, ok := j.Interface().(uint16); ok { + return itype < jtype, nil + } + case uint32: + if jtype, ok := j.Interface().(uint32); ok { + return itype < jtype, nil + } + case uint64: + if jtype, ok := j.Interface().(uint64); ok { + return itype < jtype, nil + } + case int8: + if jtype, ok := j.Interface().(int8); ok { + return itype < jtype, nil + } + case int16: + if jtype, ok := j.Interface().(int16); ok { + return itype < jtype, nil + } + case int32: + if jtype, ok := j.Interface().(int32); ok { + return itype < jtype, nil + } + case int64: + if jtype, ok := j.Interface().(int64); ok { + return itype < jtype, nil + } + case uint: + if jtype, ok := j.Interface().(uint); ok { + return itype < jtype, nil + } + case int: + if jtype, ok := j.Interface().(int); ok { + return itype < jtype, nil + } + case float32: + if jtype, ok := j.Interface().(float32); ok { + return itype < jtype, nil + } + case float64: + if jtype, ok := j.Interface().(float64); ok { + return itype < jtype, nil + } + case string: + if jtype, ok := j.Interface().(string); ok { + // check if it's a Quantity + itypeQuantity, err := resource.ParseQuantity(itype) + if err != nil { + return sortorder.NaturalLess(itype, jtype), nil + } + jtypeQuantity, err := resource.ParseQuantity(jtype) + if err != nil { + return sortorder.NaturalLess(itype, jtype), nil + } + // Both strings are quantity + return itypeQuantity.Cmp(jtypeQuantity) < 0, nil + } + default: + return false, fmt.Errorf("unsortable type: %T", itype) + } + return false, fmt.Errorf("unsortable interface: %v", i.Kind()) + + default: + return false, fmt.Errorf("unsortable type: %v", i.Kind()) + } +} + +func (r *RuntimeSort) Less(i, j int) bool { + iObj := r.objs[i] + jObj := r.objs[j] + + var iValues [][]reflect.Value + var jValues [][]reflect.Value + var err error + + parser := jsonpath.New("sorting").AllowMissingKeys(true) + err = parser.Parse(r.field) + if err != nil { + panic(err) + } + + iValues, err = findJSONPathResults(parser, iObj) + if err != nil { + klog.Fatalf("Failed to get i values for %#v using %s (%#v)", iObj, r.field, err) + } + + jValues, err = findJSONPathResults(parser, jObj) + if err != nil { + klog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err) + } + + if len(iValues) == 0 || len(iValues[0]) == 0 { + return true + } + if len(jValues) == 0 || len(jValues[0]) == 0 { + return false + } + iField := iValues[0][0] + jField := jValues[0][0] + + less, err := isLess(iField, jField) + if err != nil { + klog.Exitf("Field %s in %T is an unsortable type: %s, err: %v", r.field, iObj, iField.Kind().String(), err) + } + return less +} + +// OriginalPosition returns the starting (original) position of a particular index. +// e.g. If OriginalPosition(0) returns 5 than the +// item currently at position 0 was at position 5 in the original unsorted array. +func (r *RuntimeSort) OriginalPosition(ix int) int { + if ix < 0 || ix > len(r.origPosition) { + return -1 + } + return r.origPosition[ix] +} + +type TableSorter struct { + field string + obj *metav1.Table + parsedRows [][][]reflect.Value +} + +func (t *TableSorter) Len() int { + return len(t.obj.Rows) +} + +func (t *TableSorter) Swap(i, j int) { + t.obj.Rows[i], t.obj.Rows[j] = t.obj.Rows[j], t.obj.Rows[i] + t.parsedRows[i], t.parsedRows[j] = t.parsedRows[j], t.parsedRows[i] +} + +func (t *TableSorter) Less(i, j int) bool { + iValues := t.parsedRows[i] + jValues := t.parsedRows[j] + + if len(iValues) == 0 || len(iValues[0]) == 0 { + return true + } + if len(jValues) == 0 || len(jValues[0]) == 0 { + return false + } + + iField := iValues[0][0] + jField := jValues[0][0] + + less, err := isLess(iField, jField) + if err != nil { + klog.Exitf("Field %s in %T is an unsortable type: %s, err: %v", t.field, t.parsedRows, iField.Kind().String(), err) + } + return less +} + +func (t *TableSorter) Sort() error { + sort.Sort(t) + return nil +} + +func NewTableSorter(table *metav1.Table, field string) (*TableSorter, error) { + var parsedRows [][][]reflect.Value + + parser := jsonpath.New("sorting").AllowMissingKeys(true) + err := parser.Parse(field) + if err != nil { + return nil, fmt.Errorf("sorting error: %v", err) + } + + fieldFoundOnce := false + for i := range table.Rows { + parsedRow, err := findJSONPathResults(parser, table.Rows[i].Object.Object) + if err != nil { + return nil, fmt.Errorf("Failed to get values for %#v using %s (%#v)", parsedRow, field, err) + } + parsedRows = append(parsedRows, parsedRow) + if len(parsedRow) > 0 && len(parsedRow[0]) > 0 { + fieldFoundOnce = true + } + } + + if len(table.Rows) > 0 && !fieldFoundOnce { + return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field) + } + + return &TableSorter{ + obj: table, + field: field, + parsedRows: parsedRows, + }, nil +} +func findJSONPathResults(parser *jsonpath.JSONPath, from runtime.Object) ([][]reflect.Value, error) { + if unstructuredObj, ok := from.(*unstructured.Unstructured); ok { + return parser.FindResults(unstructuredObj.Object) + } + return parser.FindResults(reflect.ValueOf(from).Elem().Interface()) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/get/table_printer.go b/vendor/k8s.io/kubectl/pkg/cmd/get/table_printer.go new file mode 100644 index 000000000..920722737 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/get/table_printer.go @@ -0,0 +1,94 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package get + +import ( + "fmt" + "io" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/klog/v2" +) + +// TablePrinter decodes table objects into typed objects before delegating to another printer. +// Non-table types are simply passed through +type TablePrinter struct { + Delegate printers.ResourcePrinter +} + +func (t *TablePrinter) PrintObj(obj runtime.Object, writer io.Writer) error { + table, err := decodeIntoTable(obj) + if err == nil { + return t.Delegate.PrintObj(table, writer) + } + // if we are unable to decode server response into a v1beta1.Table, + // fallback to client-side printing with whatever info the server returned. + klog.V(2).Infof("Unable to decode server response into a Table. Falling back to hardcoded types: %v", err) + return t.Delegate.PrintObj(obj, writer) +} + +var recognizedTableVersions = map[schema.GroupVersionKind]bool{ + metav1beta1.SchemeGroupVersion.WithKind("Table"): true, + metav1.SchemeGroupVersion.WithKind("Table"): true, +} + +// assert the types are identical, since we're decoding both types into a metav1.Table +var _ metav1.Table = metav1beta1.Table{} +var _ metav1beta1.Table = metav1.Table{} + +func decodeIntoTable(obj runtime.Object) (runtime.Object, error) { + event, isEvent := obj.(*metav1.WatchEvent) + if isEvent { + obj = event.Object.Object + } + + if !recognizedTableVersions[obj.GetObjectKind().GroupVersionKind()] { + return nil, fmt.Errorf("attempt to decode non-Table object") + } + + unstr, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("attempt to decode non-Unstructured object") + } + table := &metav1.Table{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, table); err != nil { + return nil, err + } + + for i := range table.Rows { + row := &table.Rows[i] + if row.Object.Raw == nil || row.Object.Object != nil { + continue + } + converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, row.Object.Raw) + if err != nil { + return nil, err + } + row.Object.Object = converted + } + + if isEvent { + event.Object.Object = table + return event, nil + } + return table, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/rawhttp/raw.go b/vendor/k8s.io/kubectl/pkg/rawhttp/raw.go new file mode 100644 index 000000000..57d73cde0 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/rawhttp/raw.go @@ -0,0 +1,95 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rawhttp + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/client-go/rest" +) + +// RawPost uses the REST client to POST content +func RawPost(restClient *rest.RESTClient, streams genericiooptions.IOStreams, url, filename string) error { + return raw(restClient, streams, url, filename, "POST") +} + +// RawPut uses the REST client to PUT content +func RawPut(restClient *rest.RESTClient, streams genericiooptions.IOStreams, url, filename string) error { + return raw(restClient, streams, url, filename, "PUT") +} + +// RawGet uses the REST client to GET content +func RawGet(restClient *rest.RESTClient, streams genericiooptions.IOStreams, url string) error { + return raw(restClient, streams, url, "", "GET") +} + +// RawDelete uses the REST client to DELETE content +func RawDelete(restClient *rest.RESTClient, streams genericiooptions.IOStreams, url, filename string) error { + return raw(restClient, streams, url, filename, "DELETE") +} + +// raw makes a simple HTTP request to the provided path on the server using the default credentials. +func raw(restClient *rest.RESTClient, streams genericiooptions.IOStreams, url, filename, requestType string) error { + var data io.Reader + switch { + case len(filename) == 0: + data = bytes.NewBuffer([]byte{}) + + case filename == "-": + data = streams.In + + default: + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + data = f + } + + var request *rest.Request + switch requestType { + case "GET": + request = restClient.Get().RequestURI(url) + case "PUT": + request = restClient.Put().RequestURI(url).Body(data) + case "POST": + request = restClient.Post().RequestURI(url).Body(data) + case "DELETE": + request = restClient.Delete().RequestURI(url).Body(data) + + default: + return fmt.Errorf("unknown requestType: %q", requestType) + } + + stream, err := request.Stream(context.TODO()) + if err != nil { + return err + } + defer stream.Close() + + _, err = io.Copy(streams.Out, stream) + if err != nil && err != io.EOF { + return err + } + return nil +} diff --git a/vendor/k8s.io/kubectl/pkg/util/slice/slice.go b/vendor/k8s.io/kubectl/pkg/util/slice/slice.go new file mode 100644 index 000000000..d02bb3458 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/slice/slice.go @@ -0,0 +1,57 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package slice + +import ( + "sort" +) + +// SortInts64 sorts []int64 in increasing order +func SortInts64(a []int64) { sort.Slice(a, func(i, j int) bool { return a[i] < a[j] }) } + +// ContainsString checks if a given slice of strings contains the provided string. +// If a modifier func is provided, it is called with the slice item before the comparation. +func ContainsString(slice []string, s string, modifier func(s string) string) bool { + for _, item := range slice { + if item == s { + return true + } + if modifier != nil && modifier(item) == s { + return true + } + } + return false +} + +// ToSet returns a single slice containing the unique values from one or more slices. The order of the items in the +// result is not guaranteed. +func ToSet[T comparable](slices ...[]T) []T { + if len(slices) == 0 { + return nil + } + m := map[T]struct{}{} + for _, slice := range slices { + for _, value := range slice { + m[value] = struct{}{} + } + } + result := []T{} + for k := range m { + result = append(result, k) + } + return result +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b0b614d7a..dc6bd9a40 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -301,6 +301,9 @@ github.com/felixge/httpsnoop # github.com/fsnotify/fsnotify v1.6.0 ## explicit; go 1.16 github.com/fsnotify/fsnotify +# github.com/fvbommel/sortorder v1.1.0 +## explicit; go 1.13 +github.com/fvbommel/sortorder # github.com/go-errors/errors v1.4.2 ## explicit; go 1.14 github.com/go-errors/errors @@ -2171,13 +2174,16 @@ k8s.io/kube-scheduler/config/v1beta3 k8s.io/kube-scheduler/extender/v1 # k8s.io/kubectl v0.28.6 => github.com/kubeedge/kubernetes/staging/src/k8s.io/kubectl v1.28.6-kubeedge1 ## explicit; go 1.20 +k8s.io/kubectl/pkg/cmd/get k8s.io/kubectl/pkg/cmd/util +k8s.io/kubectl/pkg/rawhttp k8s.io/kubectl/pkg/scale k8s.io/kubectl/pkg/scheme k8s.io/kubectl/pkg/util/i18n k8s.io/kubectl/pkg/util/interrupt k8s.io/kubectl/pkg/util/openapi k8s.io/kubectl/pkg/util/podutils +k8s.io/kubectl/pkg/util/slice k8s.io/kubectl/pkg/util/templates k8s.io/kubectl/pkg/util/term k8s.io/kubectl/pkg/validation |
