diff --git a/go.mod b/go.mod
index 9d28c3a1877f6a14d7e9a82665b50e557f3d1e3e..23a612a8cfdc69901a68f53cd78cc2072a2c5d7c 100644
--- a/go.mod
+++ b/go.mod
@@ -91,6 +91,7 @@ require (
k8s.io/apiextensions-apiserver v0.17.3
k8s.io/apimachinery v0.17.3
k8s.io/apiserver v0.17.3
+ k8s.io/cli-runtime v0.17.3
k8s.io/client-go v0.17.3
k8s.io/code-generator v0.17.3
k8s.io/component-base v0.17.3
diff --git a/go.sum b/go.sum
index c549ed4b4cd4c0d97b631d1a30a469ca61e56b4e..9b515ae148474593258b02221d7233f1fb6656ab 100644
--- a/go.sum
+++ b/go.sum
@@ -275,6 +275,7 @@ github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
@@ -508,6 +509,7 @@ k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb h1:ZUNsbuPdXWrj0rZziRfCWc
k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
k8s.io/apiserver v0.0.0-20191114103151-9ca1dc586682 h1:+FvAOv/4JyYgZanQI8h+UW9FCmLzyEz7EZunuET6p5g=
k8s.io/apiserver v0.0.0-20191114103151-9ca1dc586682/go.mod h1:Idob8Va6/sMX5SmwPLsU0pdvFlkwxuJ5x+fXMG8NbKE=
+k8s.io/cli-runtime v0.17.3 h1:0ZlDdJgJBKsu77trRUynNiWsRuAvAVPBNaQfnt/1qtc=
k8s.io/cli-runtime v0.17.3/go.mod h1:X7idckYphH4SZflgNpOOViSxetiMj6xI0viMAjM81TA=
k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 h1:07mhG/2oEoo3N+sHVOo0L9PJ/qvbk3N5n2dj8IWefnQ=
k8s.io/client-go v0.0.0-20191114101535-6c5935290e33/go.mod h1:4L/zQOBkEf4pArQJ+CMk1/5xjA30B5oyWv+Bzb44DOw=
diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go
index bb5e2d840fa6e2125e104008477ef9f3a3676a04..232f23a632af2c8b4a1698c798c7b1fdeec86c4c 100644
--- a/pkg/apiserver/apiserver.go
+++ b/pkg/apiserver/apiserver.go
@@ -30,6 +30,7 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/filters"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/informers"
+ clusterkapisv1alpha1 "kubesphere.io/kubesphere/pkg/kapis/cluster/v1alpha1"
configv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/config/v1alpha2"
devopsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha2"
iamapi "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2"
@@ -139,6 +140,9 @@ func (s *APIServer) PrepareRun() error {
return nil
}
+// Install all kubesphere api groups
+// Installation happens before all informers start to cache objects, so
+// any attempt to list objects using listers will get empty results.
func (s *APIServer) installKubeSphereAPIs() {
urlruntime.Must(configv1alpha2.AddToContainer(s.container, s.Config))
urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory))
@@ -150,12 +154,26 @@ func (s *APIServer) installKubeSphereAPIs() {
urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory))
urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.InformerFactory))
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
- urlruntime.Must(iamapi.AddToContainer(s.container, im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory),
+ urlruntime.Must(clusterkapisv1alpha1.AddToContainer(s.container,
+ s.InformerFactory.KubernetesSharedInformerFactory(),
+ s.InformerFactory.KubeSphereSharedInformerFactory(),
+ s.Config.MultiClusterOptions.ProxyPublishService,
+ s.Config.MultiClusterOptions.ProxyPublishAddress,
+ s.Config.MultiClusterOptions.AgentImage))
+ urlruntime.Must(iamapi.AddToContainer(s.container,
+ im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory),
am.NewAMOperator(s.InformerFactory),
s.Config.AuthenticationOptions))
- urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient), s.Config.AuthenticationOptions))
+ urlruntime.Must(oauth.AddToContainer(s.container,
+ token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient),
+ s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
- urlruntime.Must(devopsv1alpha2.AddToContainer(s.container, s.InformerFactory.KubeSphereSharedInformerFactory(), s.DevopsClient, s.SonarClient, s.KubernetesClient.KubeSphere(), s.S3Client))
+ urlruntime.Must(devopsv1alpha2.AddToContainer(s.container,
+ s.InformerFactory.KubeSphereSharedInformerFactory(),
+ s.DevopsClient,
+ s.SonarClient,
+ s.KubernetesClient.KubeSphere(),
+ s.S3Client))
}
func (s *APIServer) Run(stopCh <-chan struct{}) (err error) {
diff --git a/pkg/kapis/cluster/v1alpha1/handler.go b/pkg/kapis/cluster/v1alpha1/handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..a0335b40b153ce84544dbe79dbd8d8d7f49d53ae
--- /dev/null
+++ b/pkg/kapis/cluster/v1alpha1/handler.go
@@ -0,0 +1,173 @@
+package v1alpha1
+
+import (
+ "bytes"
+ "fmt"
+ "github.com/emicklei/go-restful"
+ "io"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ v1 "k8s.io/client-go/listers/core/v1"
+ "kubesphere.io/kubesphere/pkg/api"
+ "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1"
+ clusterlister "kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1"
+ "strings"
+
+ "k8s.io/cli-runtime/pkg/printers"
+)
+
+const (
+ defaultAgentImage = "kubesphere/tower:v1.0"
+)
+
+type handler struct {
+ serviceLister v1.ServiceLister
+ clusterLister clusterlister.ClusterLister
+ proxyService string
+ proxyAddress string
+ agentImage string
+ yamlPrinter *printers.YAMLPrinter
+}
+
+func NewHandler(serviceLister v1.ServiceLister, clusterLister clusterlister.ClusterLister, proxyService, proxyAddress, agentImage string) *handler {
+
+ if len(agentImage) == 0 {
+ agentImage = defaultAgentImage
+ }
+
+ return &handler{
+ serviceLister: serviceLister,
+ clusterLister: clusterLister,
+ proxyService: proxyService,
+ proxyAddress: proxyAddress,
+ agentImage: agentImage,
+ yamlPrinter: &printers.YAMLPrinter{},
+ }
+}
+
+func (h *handler) GenerateAgentDeployment(request *restful.Request, response *restful.Response) {
+ clusterName := request.PathParameter("cluster")
+
+ cluster, err := h.clusterLister.Get(clusterName)
+ if err != nil {
+ if errors.IsNotFound(err) {
+ api.HandleNotFound(response, request, err)
+ return
+ } else {
+ api.HandleInternalError(response, request, err)
+ return
+ }
+ }
+
+ // use service ingress address
+ if len(h.proxyAddress) == 0 {
+ err = h.populateProxyAddress()
+ if err != nil {
+ api.HandleNotFound(response, request, err)
+ return
+ }
+ }
+
+ var buf bytes.Buffer
+
+ err = h.generateDefaultDeployment(cluster, &buf)
+ if err != nil {
+ api.HandleInternalError(response, request, err)
+ return
+ }
+
+ response.Write(buf.Bytes())
+
+}
+
+//
+func (h *handler) populateProxyAddress() error {
+ if len(h.proxyService) == 0 {
+ return fmt.Errorf("neither proxy address nor proxy service provided")
+ }
+ namespace := "kubesphere-system"
+ parts := strings.Split(h.proxyService, ".")
+ if len(parts) > 1 && len(parts[1]) != 0 {
+ namespace = parts[1]
+ }
+
+ service, err := h.serviceLister.Services(namespace).Get(parts[0])
+ if err != nil {
+ return err
+ }
+
+ if len(service.Spec.Ports) == 0 {
+ return fmt.Errorf("there are no ports in proxy service spec")
+ }
+
+ port := service.Spec.Ports[0].Port
+
+ var serviceAddress string
+ for _, ingress := range service.Status.LoadBalancer.Ingress {
+ if len(ingress.Hostname) != 0 {
+ serviceAddress = fmt.Sprintf("http://%s:%d", ingress.Hostname, port)
+ }
+
+ if len(ingress.IP) != 0 {
+ serviceAddress = fmt.Sprintf("http://%s:%d", ingress.IP, port)
+ }
+ }
+
+ if len(serviceAddress) == 0 {
+ return fmt.Errorf("service ingress is empty")
+ }
+
+ h.proxyAddress = serviceAddress
+ return nil
+}
+
+func (h *handler) generateDefaultDeployment(cluster *v1alpha1.Cluster, w io.Writer) error {
+
+ agent := appsv1.Deployment{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Deployment",
+ APIVersion: "apps/v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "cluster-agent",
+ },
+ Spec: appsv1.DeploymentSpec{
+ Selector: &metav1.LabelSelector{},
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{},
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "agent",
+ Command: []string{
+ "/agent",
+ fmt.Sprintf("--name=%s", cluster.Name),
+ fmt.Sprintf("--token=%s", cluster.Spec.Connection.Token),
+ fmt.Sprintf("--proxy-server=%s", h.proxyAddress),
+ fmt.Sprintf("--kubesphere-service=ks-apiserver.kubesphere-system.svc:80"),
+ fmt.Sprintf("--kubernetes-service=kubernetes.default.svc:443"),
+ },
+ Image: h.agentImage,
+ Resources: corev1.ResourceRequirements{
+ Limits: corev1.ResourceList{
+ corev1.ResourceCPU: resource.MustParse("1"),
+ corev1.ResourceMemory: resource.MustParse("200M"),
+ },
+ Requests: corev1.ResourceList{
+ corev1.ResourceCPU: resource.MustParse("100m"),
+ corev1.ResourceMemory: resource.MustParse("100M"),
+ },
+ },
+ },
+ },
+ ServiceAccountName: "kubesphere",
+ },
+ },
+ },
+ }
+
+ return h.yamlPrinter.PrintObj(&agent, w)
+}
diff --git a/pkg/kapis/cluster/v1alpha1/handler_test.go b/pkg/kapis/cluster/v1alpha1/handler_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..cc37e9db35f54820ebe64cc8a48b10a0e35acb29
--- /dev/null
+++ b/pkg/kapis/cluster/v1alpha1/handler_test.go
@@ -0,0 +1,141 @@
+package v1alpha1
+
+import (
+ "bytes"
+ "github.com/google/go-cmp/cmp"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/cli-runtime/pkg/printers"
+ fake2 "k8s.io/client-go/kubernetes/fake"
+ "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1"
+ "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
+ "kubesphere.io/kubesphere/pkg/informers"
+ "testing"
+)
+
+const (
+ proxyAddress = "http://139.198.121.121:8080"
+ agentImage = "kubesphere/tower:v1.0"
+ proxyService = "tower.kubesphere-system.svc"
+)
+
+var cluster = &v1alpha1.Cluster{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "gondor",
+ },
+ Spec: v1alpha1.ClusterSpec{
+ Connection: v1alpha1.Connection{
+ Type: v1alpha1.ConnectionTypeProxy,
+ Token: "randomtoken",
+ },
+ },
+}
+
+var service = &corev1.Service{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "tower",
+ Namespace: "kubesphere-system",
+ },
+ Spec: corev1.ServiceSpec{
+ Ports: []corev1.ServicePort{
+ {
+ Port: 8080,
+ Protocol: corev1.ProtocolTCP,
+ },
+ },
+ },
+ Status: corev1.ServiceStatus{
+ LoadBalancer: corev1.LoadBalancerStatus{
+ Ingress: []corev1.LoadBalancerIngress{
+ {
+ IP: "139.198.121.121",
+ Hostname: "foo.bar",
+ },
+ },
+ },
+ },
+}
+
+var expected = `apiVersion: apps/v1
+kind: Deployment
+metadata:
+ creationTimestamp: null
+ name: cluster-agent
+spec:
+ selector: {}
+ strategy: {}
+ template:
+ metadata:
+ creationTimestamp: null
+ spec:
+ containers:
+ - command:
+ - /agent
+ - --name=gondor
+ - --token=randomtoken
+ - --proxy-server=http://139.198.121.121:8080
+ - --kubesphere-service=ks-apiserver.kubesphere-system.svc:80
+ - --kubernetes-service=kubernetes.default.svc:443
+ image: kubesphere/tower:v1.0
+ name: agent
+ resources:
+ limits:
+ cpu: "1"
+ memory: 200M
+ requests:
+ cpu: 100m
+ memory: 100M
+ serviceAccountName: kubesphere
+status: {}
+`
+
+func TestGeranteAgentDeployment(t *testing.T) {
+ k8sclient := fake2.NewSimpleClientset(service)
+ ksclient := fake.NewSimpleClientset(cluster)
+
+ informersFactory := informers.NewInformerFactories(k8sclient, ksclient, nil, nil)
+
+ informersFactory.KubernetesSharedInformerFactory().Core().V1().Services().Informer().GetIndexer().Add(service)
+ informersFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Clusters().Informer().GetIndexer().Add(cluster)
+
+ h := NewHandler(informersFactory.KubernetesSharedInformerFactory().Core().V1().Services().Lister(),
+ informersFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Clusters().Lister(),
+ proxyService,
+ "",
+ agentImage)
+
+ var buf bytes.Buffer
+
+ err := h.populateProxyAddress()
+ if err != nil {
+ t.Error(err)
+ }
+
+ err = h.generateDefaultDeployment(cluster, &buf)
+ if diff := cmp.Diff(buf.String(), expected); len(diff) != 0 {
+ t.Error(diff)
+ }
+}
+
+func TestInnerGenerateAgentDeployment(t *testing.T) {
+ h := &handler{
+ proxyAddress: proxyAddress,
+ agentImage: agentImage,
+ yamlPrinter: &printers.YAMLPrinter{},
+ }
+
+ var buf bytes.Buffer
+
+ err := h.generateDefaultDeployment(cluster, &buf)
+
+ if err != nil {
+ t.Error(err)
+ }
+
+ t.Log(buf.String())
+
+ if diff := cmp.Diff(buf.String(), expected); len(diff) != 0 {
+ t.Error(diff)
+ }
+
+}
diff --git a/pkg/kapis/cluster/v1alpha1/register.go b/pkg/kapis/cluster/v1alpha1/register.go
new file mode 100644
index 0000000000000000000000000000000000000000..5012b7a446abba66e901d5950ed566832b42da7d
--- /dev/null
+++ b/pkg/kapis/cluster/v1alpha1/register.go
@@ -0,0 +1,39 @@
+package v1alpha1
+
+import (
+ "github.com/emicklei/go-restful"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ k8sinformers "k8s.io/client-go/informers"
+ "kubesphere.io/kubesphere/pkg/api"
+ "kubesphere.io/kubesphere/pkg/apiserver/runtime"
+ "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
+ "net/http"
+)
+
+const (
+ GroupName = "cluster.kubesphere.io"
+)
+
+var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
+
+func AddToContainer(container *restful.Container,
+ k8sInformers k8sinformers.SharedInformerFactory,
+ ksInformers externalversions.SharedInformerFactory,
+ proxyService string,
+ proxyAddress string,
+ agentImage string) error {
+
+ webservice := runtime.NewWebService(GroupVersion)
+ h := NewHandler(k8sInformers.Core().V1().Services().Lister(), ksInformers.Cluster().V1alpha1().Clusters().Lister(), proxyService, proxyAddress, agentImage)
+
+ // returns deployment yaml for cluster agent
+ webservice.Route(webservice.GET("/clusters/{cluster}/agent/deployment").
+ Doc("Return deployment yaml for cluster agent.").
+ Param(webservice.PathParameter("cluster", "Name of the cluster.").Required(true)).
+ To(h.GenerateAgentDeployment).
+ Returns(http.StatusOK, api.StatusOK, nil))
+
+ container.Add(webservice)
+
+ return nil
+}
diff --git a/pkg/kapis/config/v1alpha2/register.go b/pkg/kapis/config/v1alpha2/register.go
index 785d9c5a1a6ef65d306ce8f535124246bc91747f..8d49e6ead020e32e1921f296ae8b6ec6b3c3c02d 100644
--- a/pkg/kapis/config/v1alpha2/register.go
+++ b/pkg/kapis/config/v1alpha2/register.go
@@ -21,7 +21,7 @@ package v1alpha2
import (
"github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/runtime/schema"
- apiserverconfig "kubesphere.io/kubesphere/pkg/apiserver/config"
+ kubesphereconfig "kubesphere.io/kubesphere/pkg/apiserver/config"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
)
@@ -31,7 +31,7 @@ const (
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
-func AddToContainer(c *restful.Container, config *apiserverconfig.Config) error {
+func AddToContainer(c *restful.Container, config *kubesphereconfig.Config) error {
webservice := runtime.NewWebService(GroupVersion)
webservice.Route(webservice.GET("/configs/oauth").
diff --git a/pkg/simple/client/multicluster/options.go b/pkg/simple/client/multicluster/options.go
index 9d09febbc3b941841bbf170627c137e8f72bfce7..ec1605638ef6b12deefddf6d7d728828c7ad821b 100644
--- a/pkg/simple/client/multicluster/options.go
+++ b/pkg/simple/client/multicluster/options.go
@@ -6,13 +6,30 @@ type Options struct {
// Enable
Enable bool `json:"enable"`
EnableFederation bool `json:"enableFederation,omitempty"`
+
+ // ProxyPublishService is the service name of multicluster component tower.
+ // If this field provided, apiserver going to use the ingress.ip of this service.
+ // This field will be used when generating agent deployment yaml for joining clusters.
+ ProxyPublishService string `json:"proxyPublishService,omitempty"`
+
+ // ProxyPublishAddress is the public address of tower for all cluster agents.
+ // This field takes precedence over field ProxyPublishService.
+ // If both field ProxyPublishService and ProxyPublishAddress are empty, apiserver will
+ // return 404 Not Found for all cluster agent yaml requests.
+ ProxyPublishAddress string `json:"proxyPublishAddress,omitempty"`
+
+ // AgentImage is the image used when generating deployment for all cluster agents.
+ AgentImage string `json:"agentImage,omitempty"`
}
// NewOptions() returns a default nil options
func NewOptions() *Options {
return &Options{
- Enable: false,
- EnableFederation: false,
+ Enable: false,
+ EnableFederation: false,
+ ProxyPublishAddress: "",
+ ProxyPublishService: "",
+ AgentImage: "kubesphere/tower:v1.0",
}
}
@@ -23,4 +40,15 @@ func (o *Options) Validate() []error {
func (o *Options) AddFlags(fs *pflag.FlagSet, s *Options) {
fs.BoolVar(&o.Enable, "multiple-clusters", s.Enable, ""+
"This field instructs KubeSphere to enter multiple-cluster mode or not.")
+
+ fs.StringVar(&o.ProxyPublishService, "proxy-publish-service", s.ProxyPublishService, ""+
+ "Service name of tower. APIServer will use its ingress address as proxy publish address."+
+ "For example, tower.kubesphere-system.svc.")
+
+ fs.StringVar(&o.ProxyPublishAddress, "proxy-publish-address", s.ProxyPublishAddress, ""+
+ "Public address of tower, APIServer will use this field as proxy publish address. This field "+
+ "takes precedence over field proxy-publish-service. For example, 139.198.121.121:8080.")
+
+ fs.StringVar(&o.ProxyPublishAddress, "agent-image", s.AgentImage, ""+
+ "This field is used when generating deployment yaml for agent.")
}
diff --git a/vendor/github.com/liggitt/tabwriter/.travis.yml b/vendor/github.com/liggitt/tabwriter/.travis.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2768dc0727e8ddb8893f31b18747784554e30b55
--- /dev/null
+++ b/vendor/github.com/liggitt/tabwriter/.travis.yml
@@ -0,0 +1,11 @@
+language: go
+
+go:
+ - "1.8"
+ - "1.9"
+ - "1.10"
+ - "1.11"
+ - "1.12"
+ - master
+
+script: go test -v ./...
diff --git a/vendor/github.com/liggitt/tabwriter/LICENSE b/vendor/github.com/liggitt/tabwriter/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..6a66aea5eafe0ca6a688840c47219556c552488e
--- /dev/null
+++ b/vendor/github.com/liggitt/tabwriter/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/liggitt/tabwriter/README.md b/vendor/github.com/liggitt/tabwriter/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e75d35672e9c58dd2c0fed29f2cb4c2aa74b3932
--- /dev/null
+++ b/vendor/github.com/liggitt/tabwriter/README.md
@@ -0,0 +1,7 @@
+This repo is a drop-in replacement for the golang [text/tabwriter](https://golang.org/pkg/text/tabwriter/) package.
+
+It is based on that package at [cf2c2ea8](https://github.com/golang/go/tree/cf2c2ea89d09d486bb018b1817c5874388038c3a/src/text/tabwriter) and inherits its license.
+
+The following additional features are supported:
+* `RememberWidths` flag allows remembering maximum widths seen per column even after Flush() is called.
+* `RememberedWidths() []int` and `SetRememberedWidths([]int) *Writer` allows obtaining and transferring remembered column width between writers.
diff --git a/vendor/github.com/liggitt/tabwriter/tabwriter.go b/vendor/github.com/liggitt/tabwriter/tabwriter.go
new file mode 100644
index 0000000000000000000000000000000000000000..fd3431fb03dfdef34cac1ef8991e7d2abe4a73d5
--- /dev/null
+++ b/vendor/github.com/liggitt/tabwriter/tabwriter.go
@@ -0,0 +1,637 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package tabwriter implements a write filter (tabwriter.Writer) that
+// translates tabbed columns in input into properly aligned text.
+//
+// It is a drop-in replacement for the golang text/tabwriter package (https://golang.org/pkg/text/tabwriter),
+// based on that package at https://github.com/golang/go/tree/cf2c2ea89d09d486bb018b1817c5874388038c3a
+// with support for additional features.
+//
+// The package is using the Elastic Tabstops algorithm described at
+// http://nickgravgaard.com/elastictabstops/index.html.
+package tabwriter
+
+import (
+ "io"
+ "unicode/utf8"
+)
+
+// ----------------------------------------------------------------------------
+// Filter implementation
+
+// A cell represents a segment of text terminated by tabs or line breaks.
+// The text itself is stored in a separate buffer; cell only describes the
+// segment's size in bytes, its width in runes, and whether it's an htab
+// ('\t') terminated cell.
+//
+type cell struct {
+ size int // cell size in bytes
+ width int // cell width in runes
+ htab bool // true if the cell is terminated by an htab ('\t')
+}
+
+// A Writer is a filter that inserts padding around tab-delimited
+// columns in its input to align them in the output.
+//
+// The Writer treats incoming bytes as UTF-8-encoded text consisting
+// of cells terminated by horizontal ('\t') or vertical ('\v') tabs,
+// and newline ('\n') or formfeed ('\f') characters; both newline and
+// formfeed act as line breaks.
+//
+// Tab-terminated cells in contiguous lines constitute a column. The
+// Writer inserts padding as needed to make all cells in a column have
+// the same width, effectively aligning the columns. It assumes that
+// all characters have the same width, except for tabs for which a
+// tabwidth must be specified. Column cells must be tab-terminated, not
+// tab-separated: non-tab terminated trailing text at the end of a line
+// forms a cell but that cell is not part of an aligned column.
+// For instance, in this example (where | stands for a horizontal tab):
+//
+// aaaa|bbb|d
+// aa |b |dd
+// a |
+// aa |cccc|eee
+//
+// the b and c are in distinct columns (the b column is not contiguous
+// all the way). The d and e are not in a column at all (there's no
+// terminating tab, nor would the column be contiguous).
+//
+// The Writer assumes that all Unicode code points have the same width;
+// this may not be true in some fonts or if the string contains combining
+// characters.
+//
+// If DiscardEmptyColumns is set, empty columns that are terminated
+// entirely by vertical (or "soft") tabs are discarded. Columns
+// terminated by horizontal (or "hard") tabs are not affected by
+// this flag.
+//
+// If a Writer is configured to filter HTML, HTML tags and entities
+// are passed through. The widths of tags and entities are
+// assumed to be zero (tags) and one (entities) for formatting purposes.
+//
+// A segment of text may be escaped by bracketing it with Escape
+// characters. The tabwriter passes escaped text segments through
+// unchanged. In particular, it does not interpret any tabs or line
+// breaks within the segment. If the StripEscape flag is set, the
+// Escape characters are stripped from the output; otherwise they
+// are passed through as well. For the purpose of formatting, the
+// width of the escaped text is always computed excluding the Escape
+// characters.
+//
+// The formfeed character acts like a newline but it also terminates
+// all columns in the current line (effectively calling Flush). Tab-
+// terminated cells in the next line start new columns. Unless found
+// inside an HTML tag or inside an escaped text segment, formfeed
+// characters appear as newlines in the output.
+//
+// The Writer must buffer input internally, because proper spacing
+// of one line may depend on the cells in future lines. Clients must
+// call Flush when done calling Write.
+//
+type Writer struct {
+ // configuration
+ output io.Writer
+ minwidth int
+ tabwidth int
+ padding int
+ padbytes [8]byte
+ flags uint
+
+ // current state
+ buf []byte // collected text excluding tabs or line breaks
+ pos int // buffer position up to which cell.width of incomplete cell has been computed
+ cell cell // current incomplete cell; cell.width is up to buf[pos] excluding ignored sections
+ endChar byte // terminating char of escaped sequence (Escape for escapes, '>', ';' for HTML tags/entities, or 0)
+ lines [][]cell // list of lines; each line is a list of cells
+ widths []int // list of column widths in runes - re-used during formatting
+
+ maxwidths []int // list of max column widths in runes
+}
+
+// addLine adds a new line.
+// flushed is a hint indicating whether the underlying writer was just flushed.
+// If so, the previous line is not likely to be a good indicator of the new line's cells.
+func (b *Writer) addLine(flushed bool) {
+ // Grow slice instead of appending,
+ // as that gives us an opportunity
+ // to re-use an existing []cell.
+ if n := len(b.lines) + 1; n <= cap(b.lines) {
+ b.lines = b.lines[:n]
+ b.lines[n-1] = b.lines[n-1][:0]
+ } else {
+ b.lines = append(b.lines, nil)
+ }
+
+ if !flushed {
+ // The previous line is probably a good indicator
+ // of how many cells the current line will have.
+ // If the current line's capacity is smaller than that,
+ // abandon it and make a new one.
+ if n := len(b.lines); n >= 2 {
+ if prev := len(b.lines[n-2]); prev > cap(b.lines[n-1]) {
+ b.lines[n-1] = make([]cell, 0, prev)
+ }
+ }
+ }
+}
+
+// Reset the current state.
+func (b *Writer) reset() {
+ b.buf = b.buf[:0]
+ b.pos = 0
+ b.cell = cell{}
+ b.endChar = 0
+ b.lines = b.lines[0:0]
+ b.widths = b.widths[0:0]
+ b.addLine(true)
+}
+
+// Internal representation (current state):
+//
+// - all text written is appended to buf; tabs and line breaks are stripped away
+// - at any given time there is a (possibly empty) incomplete cell at the end
+// (the cell starts after a tab or line break)
+// - cell.size is the number of bytes belonging to the cell so far
+// - cell.width is text width in runes of that cell from the start of the cell to
+// position pos; html tags and entities are excluded from this width if html
+// filtering is enabled
+// - the sizes and widths of processed text are kept in the lines list
+// which contains a list of cells for each line
+// - the widths list is a temporary list with current widths used during
+// formatting; it is kept in Writer because it's re-used
+//
+// |<---------- size ---------->|
+// | |
+// |<- width ->|<- ignored ->| |
+// | | | |
+// [---processed---tab------------......]
+// ^ ^ ^
+// | | |
+// buf start of incomplete cell pos
+
+// Formatting can be controlled with these flags.
+const (
+ // Ignore html tags and treat entities (starting with '&'
+ // and ending in ';') as single characters (width = 1).
+ FilterHTML uint = 1 << iota
+
+ // Strip Escape characters bracketing escaped text segments
+ // instead of passing them through unchanged with the text.
+ StripEscape
+
+ // Force right-alignment of cell content.
+ // Default is left-alignment.
+ AlignRight
+
+ // Handle empty columns as if they were not present in
+ // the input in the first place.
+ DiscardEmptyColumns
+
+ // Always use tabs for indentation columns (i.e., padding of
+ // leading empty cells on the left) independent of padchar.
+ TabIndent
+
+ // Print a vertical bar ('|') between columns (after formatting).
+ // Discarded columns appear as zero-width columns ("||").
+ Debug
+
+ // Remember maximum widths seen per column even after Flush() is called.
+ RememberWidths
+)
+
+// A Writer must be initialized with a call to Init. The first parameter (output)
+// specifies the filter output. The remaining parameters control the formatting:
+//
+// minwidth minimal cell width including any padding
+// tabwidth width of tab characters (equivalent number of spaces)
+// padding padding added to a cell before computing its width
+// padchar ASCII char used for padding
+// if padchar == '\t', the Writer will assume that the
+// width of a '\t' in the formatted output is tabwidth,
+// and cells are left-aligned independent of align_left
+// (for correct-looking results, tabwidth must correspond
+// to the tab width in the viewer displaying the result)
+// flags formatting control
+//
+func (b *Writer) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer {
+ if minwidth < 0 || tabwidth < 0 || padding < 0 {
+ panic("negative minwidth, tabwidth, or padding")
+ }
+ b.output = output
+ b.minwidth = minwidth
+ b.tabwidth = tabwidth
+ b.padding = padding
+ for i := range b.padbytes {
+ b.padbytes[i] = padchar
+ }
+ if padchar == '\t' {
+ // tab padding enforces left-alignment
+ flags &^= AlignRight
+ }
+ b.flags = flags
+
+ b.reset()
+
+ return b
+}
+
+// debugging support (keep code around)
+func (b *Writer) dump() {
+ pos := 0
+ for i, line := range b.lines {
+ print("(", i, ") ")
+ for _, c := range line {
+ print("[", string(b.buf[pos:pos+c.size]), "]")
+ pos += c.size
+ }
+ print("\n")
+ }
+ print("\n")
+}
+
+// local error wrapper so we can distinguish errors we want to return
+// as errors from genuine panics (which we don't want to return as errors)
+type osError struct {
+ err error
+}
+
+func (b *Writer) write0(buf []byte) {
+ n, err := b.output.Write(buf)
+ if n != len(buf) && err == nil {
+ err = io.ErrShortWrite
+ }
+ if err != nil {
+ panic(osError{err})
+ }
+}
+
+func (b *Writer) writeN(src []byte, n int) {
+ for n > len(src) {
+ b.write0(src)
+ n -= len(src)
+ }
+ b.write0(src[0:n])
+}
+
+var (
+ newline = []byte{'\n'}
+ tabs = []byte("\t\t\t\t\t\t\t\t")
+)
+
+func (b *Writer) writePadding(textw, cellw int, useTabs bool) {
+ if b.padbytes[0] == '\t' || useTabs {
+ // padding is done with tabs
+ if b.tabwidth == 0 {
+ return // tabs have no width - can't do any padding
+ }
+ // make cellw the smallest multiple of b.tabwidth
+ cellw = (cellw + b.tabwidth - 1) / b.tabwidth * b.tabwidth
+ n := cellw - textw // amount of padding
+ if n < 0 {
+ panic("internal error")
+ }
+ b.writeN(tabs, (n+b.tabwidth-1)/b.tabwidth)
+ return
+ }
+
+ // padding is done with non-tab characters
+ b.writeN(b.padbytes[0:], cellw-textw)
+}
+
+var vbar = []byte{'|'}
+
+func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) {
+ pos = pos0
+ for i := line0; i < line1; i++ {
+ line := b.lines[i]
+
+ // if TabIndent is set, use tabs to pad leading empty cells
+ useTabs := b.flags&TabIndent != 0
+
+ for j, c := range line {
+ if j > 0 && b.flags&Debug != 0 {
+ // indicate column break
+ b.write0(vbar)
+ }
+
+ if c.size == 0 {
+ // empty cell
+ if j < len(b.widths) {
+ b.writePadding(c.width, b.widths[j], useTabs)
+ }
+ } else {
+ // non-empty cell
+ useTabs = false
+ if b.flags&AlignRight == 0 { // align left
+ b.write0(b.buf[pos : pos+c.size])
+ pos += c.size
+ if j < len(b.widths) {
+ b.writePadding(c.width, b.widths[j], false)
+ }
+ } else { // align right
+ if j < len(b.widths) {
+ b.writePadding(c.width, b.widths[j], false)
+ }
+ b.write0(b.buf[pos : pos+c.size])
+ pos += c.size
+ }
+ }
+ }
+
+ if i+1 == len(b.lines) {
+ // last buffered line - we don't have a newline, so just write
+ // any outstanding buffered data
+ b.write0(b.buf[pos : pos+b.cell.size])
+ pos += b.cell.size
+ } else {
+ // not the last line - write newline
+ b.write0(newline)
+ }
+ }
+ return
+}
+
+// Format the text between line0 and line1 (excluding line1); pos
+// is the buffer position corresponding to the beginning of line0.
+// Returns the buffer position corresponding to the beginning of
+// line1 and an error, if any.
+//
+func (b *Writer) format(pos0 int, line0, line1 int) (pos int) {
+ pos = pos0
+ column := len(b.widths)
+ for this := line0; this < line1; this++ {
+ line := b.lines[this]
+
+ if column >= len(line)-1 {
+ continue
+ }
+ // cell exists in this column => this line
+ // has more cells than the previous line
+ // (the last cell per line is ignored because cells are
+ // tab-terminated; the last cell per line describes the
+ // text before the newline/formfeed and does not belong
+ // to a column)
+
+ // print unprinted lines until beginning of block
+ pos = b.writeLines(pos, line0, this)
+ line0 = this
+
+ // column block begin
+ width := b.minwidth // minimal column width
+ discardable := true // true if all cells in this column are empty and "soft"
+ for ; this < line1; this++ {
+ line = b.lines[this]
+ if column >= len(line)-1 {
+ break
+ }
+ // cell exists in this column
+ c := line[column]
+ // update width
+ if w := c.width + b.padding; w > width {
+ width = w
+ }
+ // update discardable
+ if c.width > 0 || c.htab {
+ discardable = false
+ }
+ }
+ // column block end
+
+ // discard empty columns if necessary
+ if discardable && b.flags&DiscardEmptyColumns != 0 {
+ width = 0
+ }
+
+ if b.flags&RememberWidths != 0 {
+ if len(b.maxwidths) < len(b.widths) {
+ b.maxwidths = append(b.maxwidths, b.widths[len(b.maxwidths):]...)
+ }
+
+ switch {
+ case len(b.maxwidths) == len(b.widths):
+ b.maxwidths = append(b.maxwidths, width)
+ case b.maxwidths[len(b.widths)] > width:
+ width = b.maxwidths[len(b.widths)]
+ case b.maxwidths[len(b.widths)] < width:
+ b.maxwidths[len(b.widths)] = width
+ }
+ }
+
+ // format and print all columns to the right of this column
+ // (we know the widths of this column and all columns to the left)
+ b.widths = append(b.widths, width) // push width
+ pos = b.format(pos, line0, this)
+ b.widths = b.widths[0 : len(b.widths)-1] // pop width
+ line0 = this
+ }
+
+ // print unprinted lines until end
+ return b.writeLines(pos, line0, line1)
+}
+
+// Append text to current cell.
+func (b *Writer) append(text []byte) {
+ b.buf = append(b.buf, text...)
+ b.cell.size += len(text)
+}
+
+// Update the cell width.
+func (b *Writer) updateWidth() {
+ b.cell.width += utf8.RuneCount(b.buf[b.pos:])
+ b.pos = len(b.buf)
+}
+
+// To escape a text segment, bracket it with Escape characters.
+// For instance, the tab in this string "Ignore this tab: \xff\t\xff"
+// does not terminate a cell and constitutes a single character of
+// width one for formatting purposes.
+//
+// The value 0xff was chosen because it cannot appear in a valid UTF-8 sequence.
+//
+const Escape = '\xff'
+
+// Start escaped mode.
+func (b *Writer) startEscape(ch byte) {
+ switch ch {
+ case Escape:
+ b.endChar = Escape
+ case '<':
+ b.endChar = '>'
+ case '&':
+ b.endChar = ';'
+ }
+}
+
+// Terminate escaped mode. If the escaped text was an HTML tag, its width
+// is assumed to be zero for formatting purposes; if it was an HTML entity,
+// its width is assumed to be one. In all other cases, the width is the
+// unicode width of the text.
+//
+func (b *Writer) endEscape() {
+ switch b.endChar {
+ case Escape:
+ b.updateWidth()
+ if b.flags&StripEscape == 0 {
+ b.cell.width -= 2 // don't count the Escape chars
+ }
+ case '>': // tag of zero width
+ case ';':
+ b.cell.width++ // entity, count as one rune
+ }
+ b.pos = len(b.buf)
+ b.endChar = 0
+}
+
+// Terminate the current cell by adding it to the list of cells of the
+// current line. Returns the number of cells in that line.
+//
+func (b *Writer) terminateCell(htab bool) int {
+ b.cell.htab = htab
+ line := &b.lines[len(b.lines)-1]
+ *line = append(*line, b.cell)
+ b.cell = cell{}
+ return len(*line)
+}
+
+func handlePanic(err *error, op string) {
+ if e := recover(); e != nil {
+ if nerr, ok := e.(osError); ok {
+ *err = nerr.err
+ return
+ }
+ panic("tabwriter: panic during " + op)
+ }
+}
+
+// RememberedWidths returns a copy of the remembered per-column maximum widths.
+// Requires use of the RememberWidths flag, and is not threadsafe.
+func (b *Writer) RememberedWidths() []int {
+ retval := make([]int, len(b.maxwidths))
+ copy(retval, b.maxwidths)
+ return retval
+}
+
+// SetRememberedWidths sets the remembered per-column maximum widths.
+// Requires use of the RememberWidths flag, and is not threadsafe.
+func (b *Writer) SetRememberedWidths(widths []int) *Writer {
+ b.maxwidths = make([]int, len(widths))
+ copy(b.maxwidths, widths)
+ return b
+}
+
+// Flush should be called after the last call to Write to ensure
+// that any data buffered in the Writer is written to output. Any
+// incomplete escape sequence at the end is considered
+// complete for formatting purposes.
+func (b *Writer) Flush() error {
+ return b.flush()
+}
+
+func (b *Writer) flush() (err error) {
+ defer b.reset() // even in the presence of errors
+ defer handlePanic(&err, "Flush")
+
+ // add current cell if not empty
+ if b.cell.size > 0 {
+ if b.endChar != 0 {
+ // inside escape - terminate it even if incomplete
+ b.endEscape()
+ }
+ b.terminateCell(false)
+ }
+
+ // format contents of buffer
+ b.format(0, 0, len(b.lines))
+ return nil
+}
+
+var hbar = []byte("---\n")
+
+// Write writes buf to the writer b.
+// The only errors returned are ones encountered
+// while writing to the underlying output stream.
+//
+func (b *Writer) Write(buf []byte) (n int, err error) {
+ defer handlePanic(&err, "Write")
+
+ // split text into cells
+ n = 0
+ for i, ch := range buf {
+ if b.endChar == 0 {
+ // outside escape
+ switch ch {
+ case '\t', '\v', '\n', '\f':
+ // end of cell
+ b.append(buf[n:i])
+ b.updateWidth()
+ n = i + 1 // ch consumed
+ ncells := b.terminateCell(ch == '\t')
+ if ch == '\n' || ch == '\f' {
+ // terminate line
+ b.addLine(ch == '\f')
+ if ch == '\f' || ncells == 1 {
+ // A '\f' always forces a flush. Otherwise, if the previous
+ // line has only one cell which does not have an impact on
+ // the formatting of the following lines (the last cell per
+ // line is ignored by format()), thus we can flush the
+ // Writer contents.
+ if err = b.Flush(); err != nil {
+ return
+ }
+ if ch == '\f' && b.flags&Debug != 0 {
+ // indicate section break
+ b.write0(hbar)
+ }
+ }
+ }
+
+ case Escape:
+ // start of escaped sequence
+ b.append(buf[n:i])
+ b.updateWidth()
+ n = i
+ if b.flags&StripEscape != 0 {
+ n++ // strip Escape
+ }
+ b.startEscape(Escape)
+
+ case '<', '&':
+ // possibly an html tag/entity
+ if b.flags&FilterHTML != 0 {
+ // begin of tag/entity
+ b.append(buf[n:i])
+ b.updateWidth()
+ n = i
+ b.startEscape(ch)
+ }
+ }
+
+ } else {
+ // inside escape
+ if ch == b.endChar {
+ // end of tag/entity
+ j := i + 1
+ if ch == Escape && b.flags&StripEscape != 0 {
+ j = i // strip Escape
+ }
+ b.append(buf[n:j])
+ n = i + 1 // ch consumed
+ b.endEscape()
+ }
+ }
+ }
+
+ // append leftover text
+ b.append(buf[n:])
+ n = len(buf)
+ return
+}
+
+// NewWriter allocates and initializes a new tabwriter.Writer.
+// The parameters are the same as for the Init function.
+//
+func NewWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer {
+ return new(Writer).Init(output, minwidth, tabwidth, padding, padchar, flags)
+}
diff --git a/vendor/k8s.io/apimachinery/pkg/util/duration/duration.go b/vendor/k8s.io/apimachinery/pkg/util/duration/duration.go
new file mode 100644
index 0000000000000000000000000000000000000000..961ec5ed8b2db00cb0f29ae1039bc711f3948fb3
--- /dev/null
+++ b/vendor/k8s.io/apimachinery/pkg/util/duration/duration.go
@@ -0,0 +1,89 @@
+/*
+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 duration
+
+import (
+ "fmt"
+ "time"
+)
+
+// ShortHumanDuration returns a succint representation of the provided duration
+// with limited precision for consumption by humans.
+func ShortHumanDuration(d time.Duration) string {
+ // Allow deviation no more than 2 seconds(excluded) to tolerate machine time
+ // inconsistence, it can be considered as almost now.
+ if seconds := int(d.Seconds()); seconds < -1 {
+ return fmt.Sprintf("")
+ } else if seconds < 0 {
+ return fmt.Sprintf("0s")
+ } else if seconds < 60 {
+ return fmt.Sprintf("%ds", seconds)
+ } else if minutes := int(d.Minutes()); minutes < 60 {
+ return fmt.Sprintf("%dm", minutes)
+ } else if hours := int(d.Hours()); hours < 24 {
+ return fmt.Sprintf("%dh", hours)
+ } else if hours < 24*365 {
+ return fmt.Sprintf("%dd", hours/24)
+ }
+ return fmt.Sprintf("%dy", int(d.Hours()/24/365))
+}
+
+// HumanDuration returns a succint representation of the provided duration
+// with limited precision for consumption by humans. It provides ~2-3 significant
+// figures of duration.
+func HumanDuration(d time.Duration) string {
+ // Allow deviation no more than 2 seconds(excluded) to tolerate machine time
+ // inconsistence, it can be considered as almost now.
+ if seconds := int(d.Seconds()); seconds < -1 {
+ return fmt.Sprintf("")
+ } else if seconds < 0 {
+ return fmt.Sprintf("0s")
+ } else if seconds < 60*2 {
+ return fmt.Sprintf("%ds", seconds)
+ }
+ minutes := int(d / time.Minute)
+ if minutes < 10 {
+ s := int(d/time.Second) % 60
+ if s == 0 {
+ return fmt.Sprintf("%dm", minutes)
+ }
+ return fmt.Sprintf("%dm%ds", minutes, s)
+ } else if minutes < 60*3 {
+ return fmt.Sprintf("%dm", minutes)
+ }
+ hours := int(d / time.Hour)
+ if hours < 8 {
+ m := int(d/time.Minute) % 60
+ if m == 0 {
+ return fmt.Sprintf("%dh", hours)
+ }
+ return fmt.Sprintf("%dh%dm", hours, m)
+ } else if hours < 48 {
+ return fmt.Sprintf("%dh", hours)
+ } else if hours < 24*8 {
+ h := hours % 24
+ if h == 0 {
+ return fmt.Sprintf("%dd", hours/24)
+ }
+ return fmt.Sprintf("%dd%dh", hours/24, h)
+ } else if hours < 24*365*2 {
+ return fmt.Sprintf("%dd", hours/24)
+ } else if hours < 24*365*8 {
+ return fmt.Sprintf("%dy%dd", hours/24/365, (hours/24)%365)
+ }
+ return fmt.Sprintf("%dy", int(hours/24/365))
+}
diff --git a/vendor/k8s.io/cli-runtime/LICENSE b/vendor/k8s.io/cli-runtime/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/discard.go b/vendor/k8s.io/cli-runtime/pkg/printers/discard.go
new file mode 100644
index 0000000000000000000000000000000000000000..cd934976da7c977df1a7fe389738c770a00c89c7
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/discard.go
@@ -0,0 +1,30 @@
+/*
+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 printers
+
+import (
+ "io"
+
+ "k8s.io/apimachinery/pkg/runtime"
+)
+
+// NewDiscardingPrinter is a printer that discards all objects
+func NewDiscardingPrinter() ResourcePrinterFunc {
+ return ResourcePrinterFunc(func(runtime.Object, io.Writer) error {
+ return nil
+ })
+}
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/doc.go b/vendor/k8s.io/cli-runtime/pkg/printers/doc.go
new file mode 100644
index 0000000000000000000000000000000000000000..ee205371de5fd1bca791dcab6175a1a47d062d21
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/doc.go
@@ -0,0 +1,19 @@
+/*
+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 printers is helper for formatting and printing runtime objects into
+// primitives io.writer.
+package printers // import "k8s.io/cli-runtime/pkg/printers"
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/interface.go b/vendor/k8s.io/cli-runtime/pkg/printers/interface.go
new file mode 100644
index 0000000000000000000000000000000000000000..e06757f6df1e5a256de0438bb4998c595d78603f
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/interface.go
@@ -0,0 +1,54 @@
+/*
+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 printers
+
+import (
+ "io"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+// ResourcePrinterFunc is a function that can print objects
+type ResourcePrinterFunc func(runtime.Object, io.Writer) error
+
+// PrintObj implements ResourcePrinter
+func (fn ResourcePrinterFunc) PrintObj(obj runtime.Object, w io.Writer) error {
+ return fn(obj, w)
+}
+
+// ResourcePrinter is an interface that knows how to print runtime objects.
+type ResourcePrinter interface {
+ // Print receives a runtime object, formats it and prints it to a writer.
+ PrintObj(runtime.Object, io.Writer) error
+}
+
+// PrintOptions struct defines a struct for various print options
+type PrintOptions struct {
+ NoHeaders bool
+ WithNamespace bool
+ WithKind bool
+ Wide bool
+ ShowLabels bool
+ Kind schema.GroupKind
+ ColumnLabels []string
+
+ SortBy string
+
+ // indicates if it is OK to ignore missing keys for rendering an output template.
+ AllowMissingKeys bool
+}
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/json.go b/vendor/k8s.io/cli-runtime/pkg/printers/json.go
new file mode 100644
index 0000000000000000000000000000000000000000..1c35b97d7358cd236c4a081422f1f95bfe6e55bd
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/json.go
@@ -0,0 +1,142 @@
+/*
+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 printers
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "reflect"
+ "sync/atomic"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+
+ "sigs.k8s.io/yaml"
+)
+
+// JSONPrinter is an implementation of ResourcePrinter which outputs an object as JSON.
+type JSONPrinter struct{}
+
+// PrintObj is an implementation of ResourcePrinter.PrintObj which simply writes the object to the Writer.
+func (p *JSONPrinter) PrintObj(obj runtime.Object, w 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 InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
+ return fmt.Errorf(InternalObjectPrinterErr)
+ }
+
+ switch obj := obj.(type) {
+ case *metav1.WatchEvent:
+ if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj.Object.Object)).Type().PkgPath()) {
+ return fmt.Errorf(InternalObjectPrinterErr)
+ }
+ data, err := json.Marshal(obj)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(data)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write([]byte{'\n'})
+ return err
+ case *runtime.Unknown:
+ var buf bytes.Buffer
+ err := json.Indent(&buf, obj.Raw, "", " ")
+ if err != nil {
+ return err
+ }
+ buf.WriteRune('\n')
+ _, err = buf.WriteTo(w)
+ return err
+ }
+
+ if obj.GetObjectKind().GroupVersionKind().Empty() {
+ return fmt.Errorf("missing apiVersion or kind; try GetObjectKind().SetGroupVersionKind() if you know the type")
+ }
+
+ data, err := json.MarshalIndent(obj, "", " ")
+ if err != nil {
+ return err
+ }
+ data = append(data, '\n')
+ _, err = w.Write(data)
+ return err
+}
+
+// YAMLPrinter is an implementation of ResourcePrinter which outputs an object as YAML.
+// The input object is assumed to be in the internal version of an API and is converted
+// to the given version first.
+// If PrintObj() is called multiple times, objects are separated with a '---' separator.
+type YAMLPrinter struct {
+ printCount int64
+}
+
+// PrintObj prints the data as YAML.
+func (p *YAMLPrinter) PrintObj(obj runtime.Object, w 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 InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
+ return fmt.Errorf(InternalObjectPrinterErr)
+ }
+
+ count := atomic.AddInt64(&p.printCount, 1)
+ if count > 1 {
+ if _, err := w.Write([]byte("---\n")); err != nil {
+ return err
+ }
+ }
+
+ switch obj := obj.(type) {
+ case *metav1.WatchEvent:
+ if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj.Object.Object)).Type().PkgPath()) {
+ return fmt.Errorf(InternalObjectPrinterErr)
+ }
+ data, err := json.Marshal(obj)
+ if err != nil {
+ return err
+ }
+ data, err = yaml.JSONToYAML(data)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(data)
+ return err
+ case *runtime.Unknown:
+ data, err := yaml.JSONToYAML(obj.Raw)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(data)
+ return err
+ }
+
+ if obj.GetObjectKind().GroupVersionKind().Empty() {
+ return fmt.Errorf("missing apiVersion or kind; try GetObjectKind().SetGroupVersionKind() if you know the type")
+ }
+
+ output, err := yaml.Marshal(obj)
+ if err != nil {
+ return err
+ }
+ _, err = fmt.Fprint(w, string(output))
+ return err
+}
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/jsonpath.go b/vendor/k8s.io/cli-runtime/pkg/printers/jsonpath.go
new file mode 100644
index 0000000000000000000000000000000000000000..333b9c334439b650ba8486ca0a21786d703a4b48
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/jsonpath.go
@@ -0,0 +1,147 @@
+/*
+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 printers
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "reflect"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/client-go/util/jsonpath"
+)
+
+// exists returns true if it would be possible to call the index function
+// with these arguments.
+//
+// TODO: how to document this for users?
+//
+// index returns the result of indexing its first argument by the following
+// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
+// indexed item must be a map, slice, or array.
+func exists(item interface{}, indices ...interface{}) bool {
+ v := reflect.ValueOf(item)
+ for _, i := range indices {
+ index := reflect.ValueOf(i)
+ var isNil bool
+ if v, isNil = indirect(v); isNil {
+ return false
+ }
+ switch v.Kind() {
+ case reflect.Array, reflect.Slice, reflect.String:
+ var x int64
+ switch index.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ x = index.Int()
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ x = int64(index.Uint())
+ default:
+ return false
+ }
+ if x < 0 || x >= int64(v.Len()) {
+ return false
+ }
+ v = v.Index(int(x))
+ case reflect.Map:
+ if !index.IsValid() {
+ index = reflect.Zero(v.Type().Key())
+ }
+ if !index.Type().AssignableTo(v.Type().Key()) {
+ return false
+ }
+ if x := v.MapIndex(index); x.IsValid() {
+ v = x
+ } else {
+ v = reflect.Zero(v.Type().Elem())
+ }
+ default:
+ return false
+ }
+ }
+ if _, isNil := indirect(v); isNil {
+ return false
+ }
+ return true
+}
+
+// stolen from text/template
+// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
+// We indirect through pointers and empty interfaces (only) because
+// non-empty interfaces have methods we might need.
+func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
+ for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
+ if v.IsNil() {
+ return v, true
+ }
+ if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
+ break
+ }
+ }
+ return v, false
+}
+
+// JSONPathPrinter is an implementation of ResourcePrinter which formats data with jsonpath expression.
+type JSONPathPrinter struct {
+ rawTemplate string
+ *jsonpath.JSONPath
+}
+
+func NewJSONPathPrinter(tmpl string) (*JSONPathPrinter, error) {
+ j := jsonpath.New("out")
+ if err := j.Parse(tmpl); err != nil {
+ return nil, err
+ }
+ return &JSONPathPrinter{
+ rawTemplate: tmpl,
+ JSONPath: j,
+ }, nil
+}
+
+// PrintObj formats the obj with the JSONPath Template.
+func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w 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 InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
+ return fmt.Errorf(InternalObjectPrinterErr)
+ }
+
+ var queryObj interface{} = obj
+ if unstructured, ok := obj.(runtime.Unstructured); ok {
+ queryObj = unstructured.UnstructuredContent()
+ } else {
+ data, err := json.Marshal(obj)
+ if err != nil {
+ return err
+ }
+ queryObj = map[string]interface{}{}
+ if err := json.Unmarshal(data, &queryObj); err != nil {
+ return err
+ }
+ }
+
+ if err := j.JSONPath.Execute(w, queryObj); err != nil {
+ buf := bytes.NewBuffer(nil)
+ fmt.Fprintf(buf, "Error executing template: %v. Printing more information for debugging the template:\n", err)
+ fmt.Fprintf(buf, "\ttemplate was:\n\t\t%v\n", j.rawTemplate)
+ fmt.Fprintf(buf, "\tobject given to jsonpath engine was:\n\t\t%#v\n\n", queryObj)
+ return fmt.Errorf("error executing jsonpath %q: %v\n", j.rawTemplate, buf.String())
+ }
+ return nil
+}
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/name.go b/vendor/k8s.io/cli-runtime/pkg/printers/name.go
new file mode 100644
index 0000000000000000000000000000000000000000..086166af27204304262cfb0186587859912f4e8d
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/name.go
@@ -0,0 +1,130 @@
+/*
+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 printers
+
+import (
+ "fmt"
+ "io"
+ "reflect"
+ "strings"
+
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+// NamePrinter is an implementation of ResourcePrinter which outputs "resource/name" pair of an object.
+type NamePrinter struct {
+ // ShortOutput indicates whether an operation should be
+ // printed along side the "resource/name" pair for an object.
+ ShortOutput bool
+ // Operation describes the name of the action that
+ // took place on an object, to be included in the
+ // finalized "successful" message.
+ Operation string
+}
+
+// PrintObj is an implementation of ResourcePrinter.PrintObj which decodes the object
+// and print "resource/name" pair. If the object is a List, print all items in it.
+func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
+ switch castObj := obj.(type) {
+ case *metav1.WatchEvent:
+ obj = castObj.Object.Object
+ }
+
+ // we use reflect.Indirect here in order to obtain the actual value from a pointer.
+ // using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
+ // we need an actual value in order to retrieve the package path for an object.
+ if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
+ return fmt.Errorf(InternalObjectPrinterErr)
+ }
+
+ if meta.IsListType(obj) {
+ // we allow unstructured lists for now because they always contain the GVK information. We should chase down
+ // callers and stop them from passing unflattened lists
+ // TODO chase the caller that is setting this and remove it.
+ if _, ok := obj.(*unstructured.UnstructuredList); !ok {
+ return fmt.Errorf("list types are not supported by name printing: %T", obj)
+ }
+
+ items, err := meta.ExtractList(obj)
+ if err != nil {
+ return err
+ }
+ for _, obj := range items {
+ if err := p.PrintObj(obj, w); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ if obj.GetObjectKind().GroupVersionKind().Empty() {
+ return fmt.Errorf("missing apiVersion or kind; try GetObjectKind().SetGroupVersionKind() if you know the type")
+ }
+
+ name := ""
+ if acc, err := meta.Accessor(obj); err == nil {
+ if n := acc.GetName(); len(n) > 0 {
+ name = n
+ }
+ }
+
+ return printObj(w, name, p.Operation, p.ShortOutput, GetObjectGroupKind(obj))
+}
+
+func GetObjectGroupKind(obj runtime.Object) schema.GroupKind {
+ if obj == nil {
+ return schema.GroupKind{Kind: ""}
+ }
+ groupVersionKind := obj.GetObjectKind().GroupVersionKind()
+ if len(groupVersionKind.Kind) > 0 {
+ return groupVersionKind.GroupKind()
+ }
+
+ if uns, ok := obj.(*unstructured.Unstructured); ok {
+ if len(uns.GroupVersionKind().Kind) > 0 {
+ return uns.GroupVersionKind().GroupKind()
+ }
+ }
+
+ return schema.GroupKind{Kind: ""}
+}
+
+func printObj(w io.Writer, name string, operation string, shortOutput bool, groupKind schema.GroupKind) error {
+ if len(groupKind.Kind) == 0 {
+ return fmt.Errorf("missing kind for resource with name %v", name)
+ }
+
+ if len(operation) > 0 {
+ operation = " " + operation
+ }
+
+ if shortOutput {
+ operation = ""
+ }
+
+ if len(groupKind.Group) == 0 {
+ fmt.Fprintf(w, "%s/%s%s\n", strings.ToLower(groupKind.Kind), name, operation)
+ return nil
+ }
+
+ fmt.Fprintf(w, "%s.%s/%s%s\n", strings.ToLower(groupKind.Kind), groupKind.Group, name, operation)
+ return nil
+}
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/sourcechecker.go b/vendor/k8s.io/cli-runtime/pkg/printers/sourcechecker.go
new file mode 100644
index 0000000000000000000000000000000000000000..e360c8fe0bf3c00dc021413159e53466b151ab6c
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/sourcechecker.go
@@ -0,0 +1,60 @@
+/*
+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 printers
+
+import (
+ "strings"
+)
+
+var (
+ InternalObjectPrinterErr = "a versioned object must be passed to a printer"
+
+ // disallowedPackagePrefixes contains regular expression templates
+ // for object package paths that are not allowed by printers.
+ disallowedPackagePrefixes = []string{
+ "k8s.io/kubernetes/pkg/apis/",
+ }
+)
+
+var InternalObjectPreventer = &illegalPackageSourceChecker{disallowedPackagePrefixes}
+
+func IsInternalObjectError(err error) bool {
+ if err == nil {
+ return false
+ }
+
+ return err.Error() == InternalObjectPrinterErr
+}
+
+// illegalPackageSourceChecker compares a given
+// object's package path, and determines if the
+// object originates from a disallowed source.
+type illegalPackageSourceChecker struct {
+ // disallowedPrefixes is a slice of disallowed package path
+ // prefixes for a given runtime.Object that we are printing.
+ disallowedPrefixes []string
+}
+
+func (c *illegalPackageSourceChecker) IsForbidden(pkgPath string) bool {
+ for _, forbiddenPrefix := range c.disallowedPrefixes {
+ if strings.HasPrefix(pkgPath, forbiddenPrefix) || strings.Contains(pkgPath, "/vendor/"+forbiddenPrefix) {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/tableprinter.go b/vendor/k8s.io/cli-runtime/pkg/printers/tableprinter.go
new file mode 100644
index 0000000000000000000000000000000000000000..8f6f072aa2f921f0b47f6d63cab05ed7518ec4ea
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/tableprinter.go
@@ -0,0 +1,574 @@
+/*
+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 printers
+
+import (
+ "fmt"
+ "io"
+ "reflect"
+ "strings"
+ "time"
+
+ "github.com/liggitt/tabwriter"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/util/duration"
+ "k8s.io/apimachinery/pkg/watch"
+)
+
+var _ ResourcePrinter = &HumanReadablePrinter{}
+
+type printHandler struct {
+ columnDefinitions []metav1beta1.TableColumnDefinition
+ printFunc reflect.Value
+}
+
+var (
+ statusHandlerEntry = &printHandler{
+ columnDefinitions: statusColumnDefinitions,
+ printFunc: reflect.ValueOf(printStatus),
+ }
+
+ statusColumnDefinitions = []metav1beta1.TableColumnDefinition{
+ {Name: "Status", Type: "string"},
+ {Name: "Reason", Type: "string"},
+ {Name: "Message", Type: "string"},
+ }
+
+ defaultHandlerEntry = &printHandler{
+ columnDefinitions: objectMetaColumnDefinitions,
+ printFunc: reflect.ValueOf(printObjectMeta),
+ }
+
+ objectMetaColumnDefinitions = []metav1beta1.TableColumnDefinition{
+ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
+ {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
+ }
+
+ withEventTypePrefixColumns = []string{"EVENT"}
+ withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
+)
+
+// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide
+// more elegant output. It is not threadsafe, but you may call PrintObj repeatedly; headers
+// will only be printed if the object type changes. This makes it useful for printing items
+// received from watches.
+type HumanReadablePrinter struct {
+ options PrintOptions
+ lastType interface{}
+ lastColumns []metav1beta1.TableColumnDefinition
+ printedHeaders bool
+}
+
+// NewTablePrinter creates a printer suitable for calling PrintObj().
+func NewTablePrinter(options PrintOptions) ResourcePrinter {
+ printer := &HumanReadablePrinter{
+ options: options,
+ }
+ return printer
+}
+
+func printHeader(columnNames []string, w io.Writer) error {
+ if _, err := fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")); err != nil {
+ return err
+ }
+ return nil
+}
+
+// PrintObj prints the obj in a human-friendly format according to the type of the obj.
+func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
+
+ w, found := output.(*tabwriter.Writer)
+ if !found {
+ w = GetNewTabWriter(output)
+ output = w
+ defer w.Flush()
+ }
+
+ var eventType string
+ if event, isEvent := obj.(*metav1.WatchEvent); isEvent {
+ eventType = event.Type
+ obj = event.Object.Object
+ }
+
+ // Parameter "obj" is a table from server; print it.
+ // display tables following the rules of options
+ if table, ok := obj.(*metav1beta1.Table); ok {
+ // Do not print headers if this table has no column definitions, or they are the same as the last ones we printed
+ localOptions := h.options
+ if h.printedHeaders && (len(table.ColumnDefinitions) == 0 || reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns)) {
+ localOptions.NoHeaders = true
+ }
+
+ if len(table.ColumnDefinitions) == 0 {
+ // If this table has no column definitions, use the columns from the last table we printed for decoration and layout.
+ // This is done when receiving tables in watch events to save bandwidth.
+ table.ColumnDefinitions = h.lastColumns
+ } else if !reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns) {
+ // If this table has column definitions, remember them for future use.
+ h.lastColumns = table.ColumnDefinitions
+ h.printedHeaders = false
+ }
+
+ if len(table.Rows) > 0 {
+ h.printedHeaders = true
+ }
+
+ if err := decorateTable(table, localOptions); err != nil {
+ return err
+ }
+ if len(eventType) > 0 {
+ if err := addColumns(beginning, table,
+ []metav1beta1.TableColumnDefinition{{Name: "Event", Type: "string"}},
+ []cellValueFunc{func(metav1beta1.TableRow) (interface{}, error) { return formatEventType(eventType), nil }},
+ ); err != nil {
+ return err
+ }
+ }
+ return printTable(table, output, localOptions)
+ }
+
+ // Could not find print handler for "obj"; use the default or status print handler.
+ // Print with the default or status handler, and use the columns from the last time
+ var handler *printHandler
+ if _, isStatus := obj.(*metav1.Status); isStatus {
+ handler = statusHandlerEntry
+ } else {
+ handler = defaultHandlerEntry
+ }
+
+ includeHeaders := h.lastType != handler && !h.options.NoHeaders
+
+ if h.lastType != nil && h.lastType != handler && !h.options.NoHeaders {
+ fmt.Fprintln(output)
+ }
+
+ if err := printRowsForHandlerEntry(output, handler, eventType, obj, h.options, includeHeaders); err != nil {
+ return err
+ }
+ h.lastType = handler
+
+ return nil
+}
+
+// printTable prints a table to the provided output respecting the filtering rules for options
+// for wide columns and filtered rows. It filters out rows that are Completed. You should call
+// decorateTable if you receive a table from a remote server before calling printTable.
+func printTable(table *metav1beta1.Table, output io.Writer, options PrintOptions) error {
+ if !options.NoHeaders {
+ // avoid printing headers if we have no rows to display
+ if len(table.Rows) == 0 {
+ return nil
+ }
+
+ first := true
+ for _, column := range table.ColumnDefinitions {
+ if !options.Wide && column.Priority != 0 {
+ continue
+ }
+ if first {
+ first = false
+ } else {
+ fmt.Fprint(output, "\t")
+ }
+ fmt.Fprint(output, strings.ToUpper(column.Name))
+ }
+ fmt.Fprintln(output)
+ }
+ for _, row := range table.Rows {
+ first := true
+ for i, cell := range row.Cells {
+ if i >= len(table.ColumnDefinitions) {
+ // https://issue.k8s.io/66379
+ // don't panic in case of bad output from the server, with more cells than column definitions
+ break
+ }
+ column := table.ColumnDefinitions[i]
+ if !options.Wide && column.Priority != 0 {
+ continue
+ }
+ if first {
+ first = false
+ } else {
+ fmt.Fprint(output, "\t")
+ }
+ if cell != nil {
+ fmt.Fprint(output, cell)
+ }
+ }
+ fmt.Fprintln(output)
+ }
+ return nil
+}
+
+type cellValueFunc func(metav1beta1.TableRow) (interface{}, error)
+
+type columnAddPosition int
+
+const (
+ beginning columnAddPosition = 1
+ end columnAddPosition = 2
+)
+
+func addColumns(pos columnAddPosition, table *metav1beta1.Table, columns []metav1beta1.TableColumnDefinition, valueFuncs []cellValueFunc) error {
+ if len(columns) != len(valueFuncs) {
+ return fmt.Errorf("cannot prepend columns, unmatched value functions")
+ }
+ if len(columns) == 0 {
+ return nil
+ }
+
+ // Compute the new rows
+ newRows := make([][]interface{}, len(table.Rows))
+ for i := range table.Rows {
+ newCells := make([]interface{}, 0, len(columns)+len(table.Rows[i].Cells))
+
+ if pos == end {
+ // If we're appending, start with the existing cells,
+ // then add nil cells to match the number of columns
+ newCells = append(newCells, table.Rows[i].Cells...)
+ for len(newCells) < len(table.ColumnDefinitions) {
+ newCells = append(newCells, nil)
+ }
+ }
+
+ // Compute cells for new columns
+ for _, f := range valueFuncs {
+ newCell, err := f(table.Rows[i])
+ if err != nil {
+ return err
+ }
+ newCells = append(newCells, newCell)
+ }
+
+ if pos == beginning {
+ // If we're prepending, add existing cells
+ newCells = append(newCells, table.Rows[i].Cells...)
+ }
+
+ // Remember the new cells for this row
+ newRows[i] = newCells
+ }
+
+ // All cells successfully computed, now replace columns and rows
+ newColumns := make([]metav1beta1.TableColumnDefinition, 0, len(columns)+len(table.ColumnDefinitions))
+ switch pos {
+ case beginning:
+ newColumns = append(newColumns, columns...)
+ newColumns = append(newColumns, table.ColumnDefinitions...)
+ case end:
+ newColumns = append(newColumns, table.ColumnDefinitions...)
+ newColumns = append(newColumns, columns...)
+ default:
+ return fmt.Errorf("invalid column add position: %v", pos)
+ }
+ table.ColumnDefinitions = newColumns
+ for i := range table.Rows {
+ table.Rows[i].Cells = newRows[i]
+ }
+
+ return nil
+}
+
+// decorateTable takes a table and attempts to add label columns and the
+// namespace column. It will fill empty columns with nil (if the object
+// does not expose metadata). It returns an error if the table cannot
+// be decorated.
+func decorateTable(table *metav1beta1.Table, options PrintOptions) error {
+ width := len(table.ColumnDefinitions) + len(options.ColumnLabels)
+ if options.WithNamespace {
+ width++
+ }
+ if options.ShowLabels {
+ width++
+ }
+
+ columns := table.ColumnDefinitions
+
+ nameColumn := -1
+ if options.WithKind && !options.Kind.Empty() {
+ for i := range columns {
+ if columns[i].Format == "name" && columns[i].Type == "string" {
+ nameColumn = i
+ break
+ }
+ }
+ }
+
+ if width != len(table.ColumnDefinitions) {
+ columns = make([]metav1beta1.TableColumnDefinition, 0, width)
+ if options.WithNamespace {
+ columns = append(columns, metav1beta1.TableColumnDefinition{
+ Name: "Namespace",
+ Type: "string",
+ })
+ }
+ columns = append(columns, table.ColumnDefinitions...)
+ for _, label := range formatLabelHeaders(options.ColumnLabels) {
+ columns = append(columns, metav1beta1.TableColumnDefinition{
+ Name: label,
+ Type: "string",
+ })
+ }
+ if options.ShowLabels {
+ columns = append(columns, metav1beta1.TableColumnDefinition{
+ Name: "Labels",
+ Type: "string",
+ })
+ }
+ }
+
+ rows := table.Rows
+
+ includeLabels := len(options.ColumnLabels) > 0 || options.ShowLabels
+ if includeLabels || options.WithNamespace || nameColumn != -1 {
+ for i := range rows {
+ row := rows[i]
+
+ if nameColumn != -1 {
+ row.Cells[nameColumn] = fmt.Sprintf("%s/%s", strings.ToLower(options.Kind.String()), row.Cells[nameColumn])
+ }
+
+ var m metav1.Object
+ if obj := row.Object.Object; obj != nil {
+ if acc, err := meta.Accessor(obj); err == nil {
+ m = acc
+ }
+ }
+ // if we can't get an accessor, fill out the appropriate columns with empty spaces
+ if m == nil {
+ if options.WithNamespace {
+ r := make([]interface{}, 1, width)
+ row.Cells = append(r, row.Cells...)
+ }
+ for j := 0; j < width-len(row.Cells); j++ {
+ row.Cells = append(row.Cells, nil)
+ }
+ rows[i] = row
+ continue
+ }
+
+ if options.WithNamespace {
+ r := make([]interface{}, 1, width)
+ r[0] = m.GetNamespace()
+ row.Cells = append(r, row.Cells...)
+ }
+ if includeLabels {
+ row.Cells = appendLabelCells(row.Cells, m.GetLabels(), options)
+ }
+ rows[i] = row
+ }
+ }
+
+ table.ColumnDefinitions = columns
+ table.Rows = rows
+ return nil
+}
+
+// printRowsForHandlerEntry prints the incremental table output (headers if the current type is
+// different from lastType) including all the rows in the object. It returns the current type
+// or an error, if any.
+func printRowsForHandlerEntry(output io.Writer, handler *printHandler, eventType string, obj runtime.Object, options PrintOptions, includeHeaders bool) error {
+ var results []reflect.Value
+
+ args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
+ results = handler.printFunc.Call(args)
+ if !results[1].IsNil() {
+ return results[1].Interface().(error)
+ }
+
+ if includeHeaders {
+ var headers []string
+ for _, column := range handler.columnDefinitions {
+ if column.Priority != 0 && !options.Wide {
+ continue
+ }
+ headers = append(headers, strings.ToUpper(column.Name))
+ }
+ headers = append(headers, formatLabelHeaders(options.ColumnLabels)...)
+ // LABELS is always the last column.
+ headers = append(headers, formatShowLabelsHeader(options.ShowLabels)...)
+ // prepend namespace header
+ if options.WithNamespace {
+ headers = append(withNamespacePrefixColumns, headers...)
+ }
+ // prepend event type header
+ if len(eventType) > 0 {
+ headers = append(withEventTypePrefixColumns, headers...)
+ }
+ printHeader(headers, output)
+ }
+
+ if results[1].IsNil() {
+ rows := results[0].Interface().([]metav1beta1.TableRow)
+ printRows(output, eventType, rows, options)
+ return nil
+ }
+ return results[1].Interface().(error)
+}
+
+var formattedEventType = map[string]string{
+ string(watch.Added): "ADDED ",
+ string(watch.Modified): "MODIFIED",
+ string(watch.Deleted): "DELETED ",
+ string(watch.Error): "ERROR ",
+}
+
+func formatEventType(eventType string) string {
+ if formatted, ok := formattedEventType[eventType]; ok {
+ return formatted
+ }
+ return string(eventType)
+}
+
+// printRows writes the provided rows to output.
+func printRows(output io.Writer, eventType string, rows []metav1beta1.TableRow, options PrintOptions) {
+ for _, row := range rows {
+ if len(eventType) > 0 {
+ fmt.Fprint(output, formatEventType(eventType))
+ fmt.Fprint(output, "\t")
+ }
+ if options.WithNamespace {
+ if obj := row.Object.Object; obj != nil {
+ if m, err := meta.Accessor(obj); err == nil {
+ fmt.Fprint(output, m.GetNamespace())
+ }
+ }
+ fmt.Fprint(output, "\t")
+ }
+
+ for i, cell := range row.Cells {
+ if i != 0 {
+ fmt.Fprint(output, "\t")
+ } else {
+ // TODO: remove this once we drop the legacy printers
+ if options.WithKind && !options.Kind.Empty() {
+ fmt.Fprintf(output, "%s/%s", strings.ToLower(options.Kind.String()), cell)
+ continue
+ }
+ }
+ fmt.Fprint(output, cell)
+ }
+
+ hasLabels := len(options.ColumnLabels) > 0
+ if obj := row.Object.Object; obj != nil && (hasLabels || options.ShowLabels) {
+ if m, err := meta.Accessor(obj); err == nil {
+ for _, value := range labelValues(m.GetLabels(), options) {
+ output.Write([]byte("\t"))
+ output.Write([]byte(value))
+ }
+ }
+ }
+
+ output.Write([]byte("\n"))
+ }
+}
+
+func formatLabelHeaders(columnLabels []string) []string {
+ formHead := make([]string, len(columnLabels))
+ for i, l := range columnLabels {
+ p := strings.Split(l, "/")
+ formHead[i] = strings.ToUpper((p[len(p)-1]))
+ }
+ return formHead
+}
+
+// headers for --show-labels=true
+func formatShowLabelsHeader(showLabels bool) []string {
+ if showLabels {
+ return []string{"LABELS"}
+ }
+ return nil
+}
+
+// labelValues returns a slice of value columns matching the requested print options.
+func labelValues(itemLabels map[string]string, opts PrintOptions) []string {
+ var values []string
+ for _, key := range opts.ColumnLabels {
+ values = append(values, itemLabels[key])
+ }
+ if opts.ShowLabels {
+ values = append(values, labels.FormatLabels(itemLabels))
+ }
+ return values
+}
+
+// appendLabelCells returns a slice of value columns matching the requested print options.
+// Intended for use with tables.
+func appendLabelCells(values []interface{}, itemLabels map[string]string, opts PrintOptions) []interface{} {
+ for _, key := range opts.ColumnLabels {
+ values = append(values, itemLabels[key])
+ }
+ if opts.ShowLabels {
+ values = append(values, labels.FormatLabels(itemLabels))
+ }
+ return values
+}
+
+func printStatus(obj runtime.Object, options PrintOptions) ([]metav1beta1.TableRow, error) {
+ status, ok := obj.(*metav1.Status)
+ if !ok {
+ return nil, fmt.Errorf("expected *v1.Status, got %T", obj)
+ }
+ return []metav1beta1.TableRow{{
+ Object: runtime.RawExtension{Object: obj},
+ Cells: []interface{}{status.Status, status.Reason, status.Message},
+ }}, nil
+}
+
+func printObjectMeta(obj runtime.Object, options PrintOptions) ([]metav1beta1.TableRow, error) {
+ if meta.IsListType(obj) {
+ rows := make([]metav1beta1.TableRow, 0, 16)
+ err := meta.EachListItem(obj, func(obj runtime.Object) error {
+ nestedRows, err := printObjectMeta(obj, options)
+ if err != nil {
+ return err
+ }
+ rows = append(rows, nestedRows...)
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ return rows, nil
+ }
+
+ rows := make([]metav1beta1.TableRow, 0, 1)
+ m, err := meta.Accessor(obj)
+ if err != nil {
+ return nil, err
+ }
+ row := metav1beta1.TableRow{
+ Object: runtime.RawExtension{Object: obj},
+ }
+ row.Cells = append(row.Cells, m.GetName(), translateTimestampSince(m.GetCreationTimestamp()))
+ rows = append(rows, row)
+ return rows, nil
+}
+
+// translateTimestampSince returns the elapsed time since timestamp in
+// human-readable approximation.
+func translateTimestampSince(timestamp metav1.Time) string {
+ if timestamp.IsZero() {
+ return ""
+ }
+
+ return duration.HumanDuration(time.Since(timestamp.Time))
+}
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/tabwriter.go b/vendor/k8s.io/cli-runtime/pkg/printers/tabwriter.go
new file mode 100644
index 0000000000000000000000000000000000000000..21d60e1c41393622f2812fa5c16451eca25eca81
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/tabwriter.go
@@ -0,0 +1,36 @@
+/*
+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 printers
+
+import (
+ "io"
+
+ "github.com/liggitt/tabwriter"
+)
+
+const (
+ tabwriterMinWidth = 6
+ tabwriterWidth = 4
+ tabwriterPadding = 3
+ tabwriterPadChar = ' '
+ tabwriterFlags = tabwriter.RememberWidths
+)
+
+// GetNewTabWriter returns a tabwriter that translates tabbed columns in input into properly aligned text.
+func GetNewTabWriter(output io.Writer) *tabwriter.Writer {
+ return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags)
+}
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/template.go b/vendor/k8s.io/cli-runtime/pkg/printers/template.go
new file mode 100644
index 0000000000000000000000000000000000000000..ccff542262c60cb7d238c775b290f40a36128fb5
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/template.go
@@ -0,0 +1,118 @@
+/*
+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 printers
+
+import (
+ "encoding/base64"
+ "fmt"
+ "io"
+ "reflect"
+ "text/template"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/util/json"
+)
+
+// GoTemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
+type GoTemplatePrinter struct {
+ rawTemplate string
+ template *template.Template
+}
+
+func NewGoTemplatePrinter(tmpl []byte) (*GoTemplatePrinter, error) {
+ t, err := template.New("output").
+ Funcs(template.FuncMap{
+ "exists": exists,
+ "base64decode": base64decode,
+ }).
+ Parse(string(tmpl))
+ if err != nil {
+ return nil, err
+ }
+ return &GoTemplatePrinter{
+ rawTemplate: string(tmpl),
+ template: t,
+ }, nil
+}
+
+// AllowMissingKeys tells the template engine if missing keys are allowed.
+func (p *GoTemplatePrinter) AllowMissingKeys(allow bool) {
+ if allow {
+ p.template.Option("missingkey=default")
+ } else {
+ p.template.Option("missingkey=error")
+ }
+}
+
+// PrintObj formats the obj with the Go Template.
+func (p *GoTemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
+ if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
+ return fmt.Errorf(InternalObjectPrinterErr)
+ }
+
+ var data []byte
+ var err error
+ data, err = json.Marshal(obj)
+ if err != nil {
+ return err
+ }
+
+ out := map[string]interface{}{}
+ if err := json.Unmarshal(data, &out); err != nil {
+ return err
+ }
+ if err = p.safeExecute(w, out); err != nil {
+ // It is way easier to debug this stuff when it shows up in
+ // stdout instead of just stdin. So in addition to returning
+ // a nice error, also print useful stuff with the writer.
+ fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n", err)
+ fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n", p.rawTemplate)
+ fmt.Fprintf(w, "\traw data was:\n\t\t%v\n", string(data))
+ fmt.Fprintf(w, "\tobject given to template engine was:\n\t\t%+v\n\n", out)
+ return fmt.Errorf("error executing template %q: %v", p.rawTemplate, err)
+ }
+ return nil
+}
+
+// safeExecute tries to execute the template, but catches panics and returns an error
+// should the template engine panic.
+func (p *GoTemplatePrinter) safeExecute(w io.Writer, obj interface{}) error {
+ var panicErr error
+ // Sorry for the double anonymous function. There's probably a clever way
+ // to do this that has the defer'd func setting the value to be returned, but
+ // that would be even less obvious.
+ retErr := func() error {
+ defer func() {
+ if x := recover(); x != nil {
+ panicErr = fmt.Errorf("caught panic: %+v", x)
+ }
+ }()
+ return p.template.Execute(w, obj)
+ }()
+ if panicErr != nil {
+ return panicErr
+ }
+ return retErr
+}
+
+func base64decode(v string) (string, error) {
+ data, err := base64.StdEncoding.DecodeString(v)
+ if err != nil {
+ return "", fmt.Errorf("base64 decode failed: %v", err)
+ }
+ return string(data), nil
+}
diff --git a/vendor/k8s.io/cli-runtime/pkg/printers/typesetter.go b/vendor/k8s.io/cli-runtime/pkg/printers/typesetter.go
new file mode 100644
index 0000000000000000000000000000000000000000..8d2d9b56ec086bd6e429f5cce0a4f67e35d059bd
--- /dev/null
+++ b/vendor/k8s.io/cli-runtime/pkg/printers/typesetter.go
@@ -0,0 +1,95 @@
+/*
+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 printers
+
+import (
+ "fmt"
+ "io"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+// TypeSetterPrinter is an implementation of ResourcePrinter wraps another printer with types set on the objects
+type TypeSetterPrinter struct {
+ Delegate ResourcePrinter
+
+ Typer runtime.ObjectTyper
+}
+
+// NewTypeSetter constructs a wrapping printer with required params
+func NewTypeSetter(typer runtime.ObjectTyper) *TypeSetterPrinter {
+ return &TypeSetterPrinter{Typer: typer}
+}
+
+// PrintObj is an implementation of ResourcePrinter.PrintObj which sets type information on the obj for the duration
+// of printing. It is NOT threadsafe.
+func (p *TypeSetterPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
+ if obj == nil {
+ return p.Delegate.PrintObj(obj, w)
+ }
+ if !obj.GetObjectKind().GroupVersionKind().Empty() {
+ return p.Delegate.PrintObj(obj, w)
+ }
+
+ // we were empty coming in, make sure we're empty going out. This makes the call thread-unsafe
+ defer func() {
+ obj.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
+ }()
+
+ gvks, _, err := p.Typer.ObjectKinds(obj)
+ if err != nil {
+ // printers wrapped by us expect to find the type information present
+ return fmt.Errorf("missing apiVersion or kind and cannot assign it; %v", err)
+ }
+
+ for _, gvk := range gvks {
+ if len(gvk.Kind) == 0 {
+ continue
+ }
+ if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
+ continue
+ }
+ obj.GetObjectKind().SetGroupVersionKind(gvk)
+ break
+ }
+
+ return p.Delegate.PrintObj(obj, w)
+}
+
+// ToPrinter returns a printer (not threadsafe!) that has been wrapped
+func (p *TypeSetterPrinter) ToPrinter(delegate ResourcePrinter) ResourcePrinter {
+ if p == nil {
+ return delegate
+ }
+
+ p.Delegate = delegate
+ return p
+}
+
+// WrapToPrinter wraps the common ToPrinter method
+func (p *TypeSetterPrinter) WrapToPrinter(delegate ResourcePrinter, err error) (ResourcePrinter, error) {
+ if err != nil {
+ return delegate, err
+ }
+ if p == nil {
+ return delegate, nil
+ }
+
+ p.Delegate = delegate
+ return p, nil
+}
diff --git a/vendor/k8s.io/client-go/third_party/forked/golang/template/exec.go b/vendor/k8s.io/client-go/third_party/forked/golang/template/exec.go
new file mode 100644
index 0000000000000000000000000000000000000000..739fd3509ced34f05ecd7ef83cd18b67ba2ce309
--- /dev/null
+++ b/vendor/k8s.io/client-go/third_party/forked/golang/template/exec.go
@@ -0,0 +1,94 @@
+//This package is copied from Go library text/template.
+//The original private functions indirect and printableValue
+//are exported as public functions.
+package template
+
+import (
+ "fmt"
+ "reflect"
+)
+
+var Indirect = indirect
+var PrintableValue = printableValue
+
+var (
+ errorType = reflect.TypeOf((*error)(nil)).Elem()
+ fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+)
+
+// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
+// We indirect through pointers and empty interfaces (only) because
+// non-empty interfaces have methods we might need.
+func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
+ for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
+ if v.IsNil() {
+ return v, true
+ }
+ if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
+ break
+ }
+ }
+ return v, false
+}
+
+// printableValue returns the, possibly indirected, interface value inside v that
+// is best for a call to formatted printer.
+func printableValue(v reflect.Value) (interface{}, bool) {
+ if v.Kind() == reflect.Ptr {
+ v, _ = indirect(v) // fmt.Fprint handles nil.
+ }
+ if !v.IsValid() {
+ return "", true
+ }
+
+ if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
+ if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
+ v = v.Addr()
+ } else {
+ switch v.Kind() {
+ case reflect.Chan, reflect.Func:
+ return nil, false
+ }
+ }
+ }
+ return v.Interface(), true
+}
+
+// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
+func canBeNil(typ reflect.Type) bool {
+ switch typ.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+ return true
+ }
+ return false
+}
+
+// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
+// and whether the value has a meaningful truth value.
+func isTrue(val reflect.Value) (truth, ok bool) {
+ if !val.IsValid() {
+ // Something like var x interface{}, never set. It's a form of nil.
+ return false, true
+ }
+ switch val.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ truth = val.Len() > 0
+ case reflect.Bool:
+ truth = val.Bool()
+ case reflect.Complex64, reflect.Complex128:
+ truth = val.Complex() != 0
+ case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
+ truth = !val.IsNil()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ truth = val.Int() != 0
+ case reflect.Float32, reflect.Float64:
+ truth = val.Float() != 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ truth = val.Uint() != 0
+ case reflect.Struct:
+ truth = true // Struct values are always true.
+ default:
+ return
+ }
+ return truth, true
+}
diff --git a/vendor/k8s.io/client-go/third_party/forked/golang/template/funcs.go b/vendor/k8s.io/client-go/third_party/forked/golang/template/funcs.go
new file mode 100644
index 0000000000000000000000000000000000000000..27a008b0a7eb532c2655e9eff79e64a08977a3cf
--- /dev/null
+++ b/vendor/k8s.io/client-go/third_party/forked/golang/template/funcs.go
@@ -0,0 +1,599 @@
+//This package is copied from Go library text/template.
+//The original private functions eq, ge, gt, le, lt, and ne
+//are exported as public functions.
+package template
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "net/url"
+ "reflect"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+var Equal = eq
+var GreaterEqual = ge
+var Greater = gt
+var LessEqual = le
+var Less = lt
+var NotEqual = ne
+
+// FuncMap is the type of the map defining the mapping from names to functions.
+// Each function must have either a single return value, or two return values of
+// which the second has type error. In that case, if the second (error)
+// return value evaluates to non-nil during execution, execution terminates and
+// Execute returns that error.
+type FuncMap map[string]interface{}
+
+var builtins = FuncMap{
+ "and": and,
+ "call": call,
+ "html": HTMLEscaper,
+ "index": index,
+ "js": JSEscaper,
+ "len": length,
+ "not": not,
+ "or": or,
+ "print": fmt.Sprint,
+ "printf": fmt.Sprintf,
+ "println": fmt.Sprintln,
+ "urlquery": URLQueryEscaper,
+
+ // Comparisons
+ "eq": eq, // ==
+ "ge": ge, // >=
+ "gt": gt, // >
+ "le": le, // <=
+ "lt": lt, // <
+ "ne": ne, // !=
+}
+
+var builtinFuncs = createValueFuncs(builtins)
+
+// createValueFuncs turns a FuncMap into a map[string]reflect.Value
+func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
+ m := make(map[string]reflect.Value)
+ addValueFuncs(m, funcMap)
+ return m
+}
+
+// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
+func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
+ for name, fn := range in {
+ v := reflect.ValueOf(fn)
+ if v.Kind() != reflect.Func {
+ panic("value for " + name + " not a function")
+ }
+ if !goodFunc(v.Type()) {
+ panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
+ }
+ out[name] = v
+ }
+}
+
+// AddFuncs adds to values the functions in funcs. It does no checking of the input -
+// call addValueFuncs first.
+func addFuncs(out, in FuncMap) {
+ for name, fn := range in {
+ out[name] = fn
+ }
+}
+
+// goodFunc checks that the function or method has the right result signature.
+func goodFunc(typ reflect.Type) bool {
+ // We allow functions with 1 result or 2 results where the second is an error.
+ switch {
+ case typ.NumOut() == 1:
+ return true
+ case typ.NumOut() == 2 && typ.Out(1) == errorType:
+ return true
+ }
+ return false
+}
+
+// findFunction looks for a function in the template, and global map.
+func findFunction(name string) (reflect.Value, bool) {
+ if fn := builtinFuncs[name]; fn.IsValid() {
+ return fn, true
+ }
+ return reflect.Value{}, false
+}
+
+// Indexing.
+
+// index returns the result of indexing its first argument by the following
+// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
+// indexed item must be a map, slice, or array.
+func index(item interface{}, indices ...interface{}) (interface{}, error) {
+ v := reflect.ValueOf(item)
+ for _, i := range indices {
+ index := reflect.ValueOf(i)
+ var isNil bool
+ if v, isNil = indirect(v); isNil {
+ return nil, fmt.Errorf("index of nil pointer")
+ }
+ switch v.Kind() {
+ case reflect.Array, reflect.Slice, reflect.String:
+ var x int64
+ switch index.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ x = index.Int()
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ x = int64(index.Uint())
+ default:
+ return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
+ }
+ if x < 0 || x >= int64(v.Len()) {
+ return nil, fmt.Errorf("index out of range: %d", x)
+ }
+ v = v.Index(int(x))
+ case reflect.Map:
+ if !index.IsValid() {
+ index = reflect.Zero(v.Type().Key())
+ }
+ if !index.Type().AssignableTo(v.Type().Key()) {
+ return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
+ }
+ if x := v.MapIndex(index); x.IsValid() {
+ v = x
+ } else {
+ v = reflect.Zero(v.Type().Elem())
+ }
+ default:
+ return nil, fmt.Errorf("can't index item of type %s", v.Type())
+ }
+ }
+ return v.Interface(), nil
+}
+
+// Length
+
+// length returns the length of the item, with an error if it has no defined length.
+func length(item interface{}) (int, error) {
+ v, isNil := indirect(reflect.ValueOf(item))
+ if isNil {
+ return 0, fmt.Errorf("len of nil pointer")
+ }
+ switch v.Kind() {
+ case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
+ return v.Len(), nil
+ }
+ return 0, fmt.Errorf("len of type %s", v.Type())
+}
+
+// Function invocation
+
+// call returns the result of evaluating the first argument as a function.
+// The function must return 1 result, or 2 results, the second of which is an error.
+func call(fn interface{}, args ...interface{}) (interface{}, error) {
+ v := reflect.ValueOf(fn)
+ typ := v.Type()
+ if typ.Kind() != reflect.Func {
+ return nil, fmt.Errorf("non-function of type %s", typ)
+ }
+ if !goodFunc(typ) {
+ return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
+ }
+ numIn := typ.NumIn()
+ var dddType reflect.Type
+ if typ.IsVariadic() {
+ if len(args) < numIn-1 {
+ return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
+ }
+ dddType = typ.In(numIn - 1).Elem()
+ } else {
+ if len(args) != numIn {
+ return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
+ }
+ }
+ argv := make([]reflect.Value, len(args))
+ for i, arg := range args {
+ value := reflect.ValueOf(arg)
+ // Compute the expected type. Clumsy because of variadics.
+ var argType reflect.Type
+ if !typ.IsVariadic() || i < numIn-1 {
+ argType = typ.In(i)
+ } else {
+ argType = dddType
+ }
+ if !value.IsValid() && canBeNil(argType) {
+ value = reflect.Zero(argType)
+ }
+ if !value.Type().AssignableTo(argType) {
+ return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
+ }
+ argv[i] = value
+ }
+ result := v.Call(argv)
+ if len(result) == 2 && !result[1].IsNil() {
+ return result[0].Interface(), result[1].Interface().(error)
+ }
+ return result[0].Interface(), nil
+}
+
+// Boolean logic.
+
+func truth(a interface{}) bool {
+ t, _ := isTrue(reflect.ValueOf(a))
+ return t
+}
+
+// and computes the Boolean AND of its arguments, returning
+// the first false argument it encounters, or the last argument.
+func and(arg0 interface{}, args ...interface{}) interface{} {
+ if !truth(arg0) {
+ return arg0
+ }
+ for i := range args {
+ arg0 = args[i]
+ if !truth(arg0) {
+ break
+ }
+ }
+ return arg0
+}
+
+// or computes the Boolean OR of its arguments, returning
+// the first true argument it encounters, or the last argument.
+func or(arg0 interface{}, args ...interface{}) interface{} {
+ if truth(arg0) {
+ return arg0
+ }
+ for i := range args {
+ arg0 = args[i]
+ if truth(arg0) {
+ break
+ }
+ }
+ return arg0
+}
+
+// not returns the Boolean negation of its argument.
+func not(arg interface{}) (truth bool) {
+ truth, _ = isTrue(reflect.ValueOf(arg))
+ return !truth
+}
+
+// Comparison.
+
+// TODO: Perhaps allow comparison between signed and unsigned integers.
+
+var (
+ errBadComparisonType = errors.New("invalid type for comparison")
+ errBadComparison = errors.New("incompatible types for comparison")
+ errNoComparison = errors.New("missing argument for comparison")
+)
+
+type kind int
+
+const (
+ invalidKind kind = iota
+ boolKind
+ complexKind
+ intKind
+ floatKind
+ integerKind
+ stringKind
+ uintKind
+)
+
+func basicKind(v reflect.Value) (kind, error) {
+ switch v.Kind() {
+ case reflect.Bool:
+ return boolKind, nil
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return intKind, nil
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return uintKind, nil
+ case reflect.Float32, reflect.Float64:
+ return floatKind, nil
+ case reflect.Complex64, reflect.Complex128:
+ return complexKind, nil
+ case reflect.String:
+ return stringKind, nil
+ }
+ return invalidKind, errBadComparisonType
+}
+
+// eq evaluates the comparison a == b || a == c || ...
+func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
+ v1 := reflect.ValueOf(arg1)
+ k1, err := basicKind(v1)
+ if err != nil {
+ return false, err
+ }
+ if len(arg2) == 0 {
+ return false, errNoComparison
+ }
+ for _, arg := range arg2 {
+ v2 := reflect.ValueOf(arg)
+ k2, err := basicKind(v2)
+ if err != nil {
+ return false, err
+ }
+ truth := false
+ if k1 != k2 {
+ // Special case: Can compare integer values regardless of type's sign.
+ switch {
+ case k1 == intKind && k2 == uintKind:
+ truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
+ case k1 == uintKind && k2 == intKind:
+ truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
+ default:
+ return false, errBadComparison
+ }
+ } else {
+ switch k1 {
+ case boolKind:
+ truth = v1.Bool() == v2.Bool()
+ case complexKind:
+ truth = v1.Complex() == v2.Complex()
+ case floatKind:
+ truth = v1.Float() == v2.Float()
+ case intKind:
+ truth = v1.Int() == v2.Int()
+ case stringKind:
+ truth = v1.String() == v2.String()
+ case uintKind:
+ truth = v1.Uint() == v2.Uint()
+ default:
+ panic("invalid kind")
+ }
+ }
+ if truth {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+// ne evaluates the comparison a != b.
+func ne(arg1, arg2 interface{}) (bool, error) {
+ // != is the inverse of ==.
+ equal, err := eq(arg1, arg2)
+ return !equal, err
+}
+
+// lt evaluates the comparison a < b.
+func lt(arg1, arg2 interface{}) (bool, error) {
+ v1 := reflect.ValueOf(arg1)
+ k1, err := basicKind(v1)
+ if err != nil {
+ return false, err
+ }
+ v2 := reflect.ValueOf(arg2)
+ k2, err := basicKind(v2)
+ if err != nil {
+ return false, err
+ }
+ truth := false
+ if k1 != k2 {
+ // Special case: Can compare integer values regardless of type's sign.
+ switch {
+ case k1 == intKind && k2 == uintKind:
+ truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
+ case k1 == uintKind && k2 == intKind:
+ truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
+ default:
+ return false, errBadComparison
+ }
+ } else {
+ switch k1 {
+ case boolKind, complexKind:
+ return false, errBadComparisonType
+ case floatKind:
+ truth = v1.Float() < v2.Float()
+ case intKind:
+ truth = v1.Int() < v2.Int()
+ case stringKind:
+ truth = v1.String() < v2.String()
+ case uintKind:
+ truth = v1.Uint() < v2.Uint()
+ default:
+ panic("invalid kind")
+ }
+ }
+ return truth, nil
+}
+
+// le evaluates the comparison <= b.
+func le(arg1, arg2 interface{}) (bool, error) {
+ // <= is < or ==.
+ lessThan, err := lt(arg1, arg2)
+ if lessThan || err != nil {
+ return lessThan, err
+ }
+ return eq(arg1, arg2)
+}
+
+// gt evaluates the comparison a > b.
+func gt(arg1, arg2 interface{}) (bool, error) {
+ // > is the inverse of <=.
+ lessOrEqual, err := le(arg1, arg2)
+ if err != nil {
+ return false, err
+ }
+ return !lessOrEqual, nil
+}
+
+// ge evaluates the comparison a >= b.
+func ge(arg1, arg2 interface{}) (bool, error) {
+ // >= is the inverse of <.
+ lessThan, err := lt(arg1, arg2)
+ if err != nil {
+ return false, err
+ }
+ return !lessThan, nil
+}
+
+// HTML escaping.
+
+var (
+ htmlQuot = []byte(""") // shorter than """
+ htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
+ htmlAmp = []byte("&")
+ htmlLt = []byte("<")
+ htmlGt = []byte(">")
+)
+
+// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
+func HTMLEscape(w io.Writer, b []byte) {
+ last := 0
+ for i, c := range b {
+ var html []byte
+ switch c {
+ case '"':
+ html = htmlQuot
+ case '\'':
+ html = htmlApos
+ case '&':
+ html = htmlAmp
+ case '<':
+ html = htmlLt
+ case '>':
+ html = htmlGt
+ default:
+ continue
+ }
+ w.Write(b[last:i])
+ w.Write(html)
+ last = i + 1
+ }
+ w.Write(b[last:])
+}
+
+// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
+func HTMLEscapeString(s string) string {
+ // Avoid allocation if we can.
+ if strings.IndexAny(s, `'"&<>`) < 0 {
+ return s
+ }
+ var b bytes.Buffer
+ HTMLEscape(&b, []byte(s))
+ return b.String()
+}
+
+// HTMLEscaper returns the escaped HTML equivalent of the textual
+// representation of its arguments.
+func HTMLEscaper(args ...interface{}) string {
+ return HTMLEscapeString(evalArgs(args))
+}
+
+// JavaScript escaping.
+
+var (
+ jsLowUni = []byte(`\u00`)
+ hex = []byte("0123456789ABCDEF")
+
+ jsBackslash = []byte(`\\`)
+ jsApos = []byte(`\'`)
+ jsQuot = []byte(`\"`)
+ jsLt = []byte(`\x3C`)
+ jsGt = []byte(`\x3E`)
+)
+
+// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
+func JSEscape(w io.Writer, b []byte) {
+ last := 0
+ for i := 0; i < len(b); i++ {
+ c := b[i]
+
+ if !jsIsSpecial(rune(c)) {
+ // fast path: nothing to do
+ continue
+ }
+ w.Write(b[last:i])
+
+ if c < utf8.RuneSelf {
+ // Quotes, slashes and angle brackets get quoted.
+ // Control characters get written as \u00XX.
+ switch c {
+ case '\\':
+ w.Write(jsBackslash)
+ case '\'':
+ w.Write(jsApos)
+ case '"':
+ w.Write(jsQuot)
+ case '<':
+ w.Write(jsLt)
+ case '>':
+ w.Write(jsGt)
+ default:
+ w.Write(jsLowUni)
+ t, b := c>>4, c&0x0f
+ w.Write(hex[t : t+1])
+ w.Write(hex[b : b+1])
+ }
+ } else {
+ // Unicode rune.
+ r, size := utf8.DecodeRune(b[i:])
+ if unicode.IsPrint(r) {
+ w.Write(b[i : i+size])
+ } else {
+ fmt.Fprintf(w, "\\u%04X", r)
+ }
+ i += size - 1
+ }
+ last = i + 1
+ }
+ w.Write(b[last:])
+}
+
+// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
+func JSEscapeString(s string) string {
+ // Avoid allocation if we can.
+ if strings.IndexFunc(s, jsIsSpecial) < 0 {
+ return s
+ }
+ var b bytes.Buffer
+ JSEscape(&b, []byte(s))
+ return b.String()
+}
+
+func jsIsSpecial(r rune) bool {
+ switch r {
+ case '\\', '\'', '"', '<', '>':
+ return true
+ }
+ return r < ' ' || utf8.RuneSelf <= r
+}
+
+// JSEscaper returns the escaped JavaScript equivalent of the textual
+// representation of its arguments.
+func JSEscaper(args ...interface{}) string {
+ return JSEscapeString(evalArgs(args))
+}
+
+// URLQueryEscaper returns the escaped value of the textual representation of
+// its arguments in a form suitable for embedding in a URL query.
+func URLQueryEscaper(args ...interface{}) string {
+ return url.QueryEscape(evalArgs(args))
+}
+
+// evalArgs formats the list of arguments into a string. It is therefore equivalent to
+// fmt.Sprint(args...)
+// except that each argument is indirected (if a pointer), as required,
+// using the same rules as the default string evaluation during template
+// execution.
+func evalArgs(args []interface{}) string {
+ ok := false
+ var s string
+ // Fast path for simple common case.
+ if len(args) == 1 {
+ s, ok = args[0].(string)
+ }
+ if !ok {
+ for i, arg := range args {
+ a, ok := printableValue(reflect.ValueOf(arg))
+ if ok {
+ args[i] = a
+ } // else left fmt do its thing
+ }
+ s = fmt.Sprint(args...)
+ }
+ return s
+}
diff --git a/vendor/k8s.io/client-go/util/jsonpath/doc.go b/vendor/k8s.io/client-go/util/jsonpath/doc.go
new file mode 100644
index 0000000000000000000000000000000000000000..0effb15c4117ba14049d090f337ae3bc3779820e
--- /dev/null
+++ b/vendor/k8s.io/client-go/util/jsonpath/doc.go
@@ -0,0 +1,20 @@
+/*
+Copyright 2015 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 jsonpath is a template engine using jsonpath syntax,
+// which can be seen at http://goessner.net/articles/JsonPath/.
+// In addition, it has {range} {end} function to iterate list and slice.
+package jsonpath // import "k8s.io/client-go/util/jsonpath"
diff --git a/vendor/k8s.io/client-go/util/jsonpath/jsonpath.go b/vendor/k8s.io/client-go/util/jsonpath/jsonpath.go
new file mode 100644
index 0000000000000000000000000000000000000000..78b6b678f7f3c6bd5599c539606212405706e02f
--- /dev/null
+++ b/vendor/k8s.io/client-go/util/jsonpath/jsonpath.go
@@ -0,0 +1,525 @@
+/*
+Copyright 2015 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 jsonpath
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "reflect"
+ "strings"
+
+ "k8s.io/client-go/third_party/forked/golang/template"
+)
+
+type JSONPath struct {
+ name string
+ parser *Parser
+ stack [][]reflect.Value // push and pop values in different scopes
+ cur []reflect.Value // current scope values
+ beginRange int
+ inRange int
+ endRange int
+
+ allowMissingKeys bool
+}
+
+// New creates a new JSONPath with the given name.
+func New(name string) *JSONPath {
+ return &JSONPath{
+ name: name,
+ beginRange: 0,
+ inRange: 0,
+ endRange: 0,
+ }
+}
+
+// AllowMissingKeys allows a caller to specify whether they want an error if a field or map key
+// cannot be located, or simply an empty result. The receiver is returned for chaining.
+func (j *JSONPath) AllowMissingKeys(allow bool) *JSONPath {
+ j.allowMissingKeys = allow
+ return j
+}
+
+// Parse parses the given template and returns an error.
+func (j *JSONPath) Parse(text string) error {
+ var err error
+ j.parser, err = Parse(j.name, text)
+ return err
+}
+
+// Execute bounds data into template and writes the result.
+func (j *JSONPath) Execute(wr io.Writer, data interface{}) error {
+ fullResults, err := j.FindResults(data)
+ if err != nil {
+ return err
+ }
+ for ix := range fullResults {
+ if err := j.PrintResults(wr, fullResults[ix]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) {
+ if j.parser == nil {
+ return nil, fmt.Errorf("%s is an incomplete jsonpath template", j.name)
+ }
+
+ j.cur = []reflect.Value{reflect.ValueOf(data)}
+ nodes := j.parser.Root.Nodes
+ fullResult := [][]reflect.Value{}
+ for i := 0; i < len(nodes); i++ {
+ node := nodes[i]
+ results, err := j.walk(j.cur, node)
+ if err != nil {
+ return nil, err
+ }
+
+ // encounter an end node, break the current block
+ if j.endRange > 0 && j.endRange <= j.inRange {
+ j.endRange--
+ break
+ }
+ // encounter a range node, start a range loop
+ if j.beginRange > 0 {
+ j.beginRange--
+ j.inRange++
+ for k, value := range results {
+ j.parser.Root.Nodes = nodes[i+1:]
+ if k == len(results)-1 {
+ j.inRange--
+ }
+ nextResults, err := j.FindResults(value.Interface())
+ if err != nil {
+ return nil, err
+ }
+ fullResult = append(fullResult, nextResults...)
+ }
+ break
+ }
+ fullResult = append(fullResult, results)
+ }
+ return fullResult, nil
+}
+
+// PrintResults writes the results into writer
+func (j *JSONPath) PrintResults(wr io.Writer, results []reflect.Value) error {
+ for i, r := range results {
+ text, err := j.evalToText(r)
+ if err != nil {
+ return err
+ }
+ if i != len(results)-1 {
+ text = append(text, ' ')
+ }
+ if _, err = wr.Write(text); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// walk visits tree rooted at the given node in DFS order
+func (j *JSONPath) walk(value []reflect.Value, node Node) ([]reflect.Value, error) {
+ switch node := node.(type) {
+ case *ListNode:
+ return j.evalList(value, node)
+ case *TextNode:
+ return []reflect.Value{reflect.ValueOf(node.Text)}, nil
+ case *FieldNode:
+ return j.evalField(value, node)
+ case *ArrayNode:
+ return j.evalArray(value, node)
+ case *FilterNode:
+ return j.evalFilter(value, node)
+ case *IntNode:
+ return j.evalInt(value, node)
+ case *BoolNode:
+ return j.evalBool(value, node)
+ case *FloatNode:
+ return j.evalFloat(value, node)
+ case *WildcardNode:
+ return j.evalWildcard(value, node)
+ case *RecursiveNode:
+ return j.evalRecursive(value, node)
+ case *UnionNode:
+ return j.evalUnion(value, node)
+ case *IdentifierNode:
+ return j.evalIdentifier(value, node)
+ default:
+ return value, fmt.Errorf("unexpected Node %v", node)
+ }
+}
+
+// evalInt evaluates IntNode
+func (j *JSONPath) evalInt(input []reflect.Value, node *IntNode) ([]reflect.Value, error) {
+ result := make([]reflect.Value, len(input))
+ for i := range input {
+ result[i] = reflect.ValueOf(node.Value)
+ }
+ return result, nil
+}
+
+// evalFloat evaluates FloatNode
+func (j *JSONPath) evalFloat(input []reflect.Value, node *FloatNode) ([]reflect.Value, error) {
+ result := make([]reflect.Value, len(input))
+ for i := range input {
+ result[i] = reflect.ValueOf(node.Value)
+ }
+ return result, nil
+}
+
+// evalBool evaluates BoolNode
+func (j *JSONPath) evalBool(input []reflect.Value, node *BoolNode) ([]reflect.Value, error) {
+ result := make([]reflect.Value, len(input))
+ for i := range input {
+ result[i] = reflect.ValueOf(node.Value)
+ }
+ return result, nil
+}
+
+// evalList evaluates ListNode
+func (j *JSONPath) evalList(value []reflect.Value, node *ListNode) ([]reflect.Value, error) {
+ var err error
+ curValue := value
+ for _, node := range node.Nodes {
+ curValue, err = j.walk(curValue, node)
+ if err != nil {
+ return curValue, err
+ }
+ }
+ return curValue, nil
+}
+
+// evalIdentifier evaluates IdentifierNode
+func (j *JSONPath) evalIdentifier(input []reflect.Value, node *IdentifierNode) ([]reflect.Value, error) {
+ results := []reflect.Value{}
+ switch node.Name {
+ case "range":
+ j.stack = append(j.stack, j.cur)
+ j.beginRange++
+ results = input
+ case "end":
+ if j.endRange < j.inRange { // inside a loop, break the current block
+ j.endRange++
+ break
+ }
+ // the loop is about to end, pop value and continue the following execution
+ if len(j.stack) > 0 {
+ j.cur, j.stack = j.stack[len(j.stack)-1], j.stack[:len(j.stack)-1]
+ } else {
+ return results, fmt.Errorf("not in range, nothing to end")
+ }
+ default:
+ return input, fmt.Errorf("unrecognized identifier %v", node.Name)
+ }
+ return results, nil
+}
+
+// evalArray evaluates ArrayNode
+func (j *JSONPath) evalArray(input []reflect.Value, node *ArrayNode) ([]reflect.Value, error) {
+ result := []reflect.Value{}
+ for _, value := range input {
+
+ value, isNil := template.Indirect(value)
+ if isNil {
+ continue
+ }
+ if value.Kind() != reflect.Array && value.Kind() != reflect.Slice {
+ return input, fmt.Errorf("%v is not array or slice", value.Type())
+ }
+ params := node.Params
+ if !params[0].Known {
+ params[0].Value = 0
+ }
+ if params[0].Value < 0 {
+ params[0].Value += value.Len()
+ }
+ if !params[1].Known {
+ params[1].Value = value.Len()
+ }
+
+ if params[1].Value < 0 || (params[1].Value == 0 && params[1].Derived) {
+ params[1].Value += value.Len()
+ }
+ sliceLength := value.Len()
+ if params[1].Value != params[0].Value { // if you're requesting zero elements, allow it through.
+ if params[0].Value >= sliceLength || params[0].Value < 0 {
+ return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[0].Value, sliceLength)
+ }
+ if params[1].Value > sliceLength || params[1].Value < 0 {
+ return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[1].Value-1, sliceLength)
+ }
+ if params[0].Value > params[1].Value {
+ return input, fmt.Errorf("starting index %d is greater than ending index %d", params[0].Value, params[1].Value)
+ }
+ } else {
+ return result, nil
+ }
+
+ value = value.Slice(params[0].Value, params[1].Value)
+
+ step := 1
+ if params[2].Known {
+ if params[2].Value <= 0 {
+ return input, fmt.Errorf("step must be > 0")
+ }
+ step = params[2].Value
+ }
+ for i := 0; i < value.Len(); i += step {
+ result = append(result, value.Index(i))
+ }
+ }
+ return result, nil
+}
+
+// evalUnion evaluates UnionNode
+func (j *JSONPath) evalUnion(input []reflect.Value, node *UnionNode) ([]reflect.Value, error) {
+ result := []reflect.Value{}
+ for _, listNode := range node.Nodes {
+ temp, err := j.evalList(input, listNode)
+ if err != nil {
+ return input, err
+ }
+ result = append(result, temp...)
+ }
+ return result, nil
+}
+
+func (j *JSONPath) findFieldInValue(value *reflect.Value, node *FieldNode) (reflect.Value, error) {
+ t := value.Type()
+ var inlineValue *reflect.Value
+ for ix := 0; ix < t.NumField(); ix++ {
+ f := t.Field(ix)
+ jsonTag := f.Tag.Get("json")
+ parts := strings.Split(jsonTag, ",")
+ if len(parts) == 0 {
+ continue
+ }
+ if parts[0] == node.Value {
+ return value.Field(ix), nil
+ }
+ if len(parts[0]) == 0 {
+ val := value.Field(ix)
+ inlineValue = &val
+ }
+ }
+ if inlineValue != nil {
+ if inlineValue.Kind() == reflect.Struct {
+ // handle 'inline'
+ match, err := j.findFieldInValue(inlineValue, node)
+ if err != nil {
+ return reflect.Value{}, err
+ }
+ if match.IsValid() {
+ return match, nil
+ }
+ }
+ }
+ return value.FieldByName(node.Value), nil
+}
+
+// evalField evaluates field of struct or key of map.
+func (j *JSONPath) evalField(input []reflect.Value, node *FieldNode) ([]reflect.Value, error) {
+ results := []reflect.Value{}
+ // If there's no input, there's no output
+ if len(input) == 0 {
+ return results, nil
+ }
+ for _, value := range input {
+ var result reflect.Value
+ value, isNil := template.Indirect(value)
+ if isNil {
+ continue
+ }
+
+ if value.Kind() == reflect.Struct {
+ var err error
+ if result, err = j.findFieldInValue(&value, node); err != nil {
+ return nil, err
+ }
+ } else if value.Kind() == reflect.Map {
+ mapKeyType := value.Type().Key()
+ nodeValue := reflect.ValueOf(node.Value)
+ // node value type must be convertible to map key type
+ if !nodeValue.Type().ConvertibleTo(mapKeyType) {
+ return results, fmt.Errorf("%s is not convertible to %s", nodeValue, mapKeyType)
+ }
+ result = value.MapIndex(nodeValue.Convert(mapKeyType))
+ }
+ if result.IsValid() {
+ results = append(results, result)
+ }
+ }
+ if len(results) == 0 {
+ if j.allowMissingKeys {
+ return results, nil
+ }
+ return results, fmt.Errorf("%s is not found", node.Value)
+ }
+ return results, nil
+}
+
+// evalWildcard extracts all contents of the given value
+func (j *JSONPath) evalWildcard(input []reflect.Value, node *WildcardNode) ([]reflect.Value, error) {
+ results := []reflect.Value{}
+ for _, value := range input {
+ value, isNil := template.Indirect(value)
+ if isNil {
+ continue
+ }
+
+ kind := value.Kind()
+ if kind == reflect.Struct {
+ for i := 0; i < value.NumField(); i++ {
+ results = append(results, value.Field(i))
+ }
+ } else if kind == reflect.Map {
+ for _, key := range value.MapKeys() {
+ results = append(results, value.MapIndex(key))
+ }
+ } else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String {
+ for i := 0; i < value.Len(); i++ {
+ results = append(results, value.Index(i))
+ }
+ }
+ }
+ return results, nil
+}
+
+// evalRecursive visits the given value recursively and pushes all of them to result
+func (j *JSONPath) evalRecursive(input []reflect.Value, node *RecursiveNode) ([]reflect.Value, error) {
+ result := []reflect.Value{}
+ for _, value := range input {
+ results := []reflect.Value{}
+ value, isNil := template.Indirect(value)
+ if isNil {
+ continue
+ }
+
+ kind := value.Kind()
+ if kind == reflect.Struct {
+ for i := 0; i < value.NumField(); i++ {
+ results = append(results, value.Field(i))
+ }
+ } else if kind == reflect.Map {
+ for _, key := range value.MapKeys() {
+ results = append(results, value.MapIndex(key))
+ }
+ } else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String {
+ for i := 0; i < value.Len(); i++ {
+ results = append(results, value.Index(i))
+ }
+ }
+ if len(results) != 0 {
+ result = append(result, value)
+ output, err := j.evalRecursive(results, node)
+ if err != nil {
+ return result, err
+ }
+ result = append(result, output...)
+ }
+ }
+ return result, nil
+}
+
+// evalFilter filters array according to FilterNode
+func (j *JSONPath) evalFilter(input []reflect.Value, node *FilterNode) ([]reflect.Value, error) {
+ results := []reflect.Value{}
+ for _, value := range input {
+ value, _ = template.Indirect(value)
+
+ if value.Kind() != reflect.Array && value.Kind() != reflect.Slice {
+ return input, fmt.Errorf("%v is not array or slice and cannot be filtered", value)
+ }
+ for i := 0; i < value.Len(); i++ {
+ temp := []reflect.Value{value.Index(i)}
+ lefts, err := j.evalList(temp, node.Left)
+
+ //case exists
+ if node.Operator == "exists" {
+ if len(lefts) > 0 {
+ results = append(results, value.Index(i))
+ }
+ continue
+ }
+
+ if err != nil {
+ return input, err
+ }
+
+ var left, right interface{}
+ switch {
+ case len(lefts) == 0:
+ continue
+ case len(lefts) > 1:
+ return input, fmt.Errorf("can only compare one element at a time")
+ }
+ left = lefts[0].Interface()
+
+ rights, err := j.evalList(temp, node.Right)
+ if err != nil {
+ return input, err
+ }
+ switch {
+ case len(rights) == 0:
+ continue
+ case len(rights) > 1:
+ return input, fmt.Errorf("can only compare one element at a time")
+ }
+ right = rights[0].Interface()
+
+ pass := false
+ switch node.Operator {
+ case "<":
+ pass, err = template.Less(left, right)
+ case ">":
+ pass, err = template.Greater(left, right)
+ case "==":
+ pass, err = template.Equal(left, right)
+ case "!=":
+ pass, err = template.NotEqual(left, right)
+ case "<=":
+ pass, err = template.LessEqual(left, right)
+ case ">=":
+ pass, err = template.GreaterEqual(left, right)
+ default:
+ return results, fmt.Errorf("unrecognized filter operator %s", node.Operator)
+ }
+ if err != nil {
+ return results, err
+ }
+ if pass {
+ results = append(results, value.Index(i))
+ }
+ }
+ }
+ return results, nil
+}
+
+// evalToText translates reflect value to corresponding text
+func (j *JSONPath) evalToText(v reflect.Value) ([]byte, error) {
+ iface, ok := template.PrintableValue(v)
+ if !ok {
+ return nil, fmt.Errorf("can't print type %s", v.Type())
+ }
+ var buffer bytes.Buffer
+ fmt.Fprint(&buffer, iface)
+ return buffer.Bytes(), nil
+}
diff --git a/vendor/k8s.io/client-go/util/jsonpath/node.go b/vendor/k8s.io/client-go/util/jsonpath/node.go
new file mode 100644
index 0000000000000000000000000000000000000000..83abe8b03773ff0b07b6225665cae973dd612309
--- /dev/null
+++ b/vendor/k8s.io/client-go/util/jsonpath/node.go
@@ -0,0 +1,256 @@
+/*
+Copyright 2015 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 jsonpath
+
+import "fmt"
+
+// NodeType identifies the type of a parse tree node.
+type NodeType int
+
+// Type returns itself and provides an easy default implementation
+func (t NodeType) Type() NodeType {
+ return t
+}
+
+func (t NodeType) String() string {
+ return NodeTypeName[t]
+}
+
+const (
+ NodeText NodeType = iota
+ NodeArray
+ NodeList
+ NodeField
+ NodeIdentifier
+ NodeFilter
+ NodeInt
+ NodeFloat
+ NodeWildcard
+ NodeRecursive
+ NodeUnion
+ NodeBool
+)
+
+var NodeTypeName = map[NodeType]string{
+ NodeText: "NodeText",
+ NodeArray: "NodeArray",
+ NodeList: "NodeList",
+ NodeField: "NodeField",
+ NodeIdentifier: "NodeIdentifier",
+ NodeFilter: "NodeFilter",
+ NodeInt: "NodeInt",
+ NodeFloat: "NodeFloat",
+ NodeWildcard: "NodeWildcard",
+ NodeRecursive: "NodeRecursive",
+ NodeUnion: "NodeUnion",
+ NodeBool: "NodeBool",
+}
+
+type Node interface {
+ Type() NodeType
+ String() string
+}
+
+// ListNode holds a sequence of nodes.
+type ListNode struct {
+ NodeType
+ Nodes []Node // The element nodes in lexical order.
+}
+
+func newList() *ListNode {
+ return &ListNode{NodeType: NodeList}
+}
+
+func (l *ListNode) append(n Node) {
+ l.Nodes = append(l.Nodes, n)
+}
+
+func (l *ListNode) String() string {
+ return l.Type().String()
+}
+
+// TextNode holds plain text.
+type TextNode struct {
+ NodeType
+ Text string // The text; may span newlines.
+}
+
+func newText(text string) *TextNode {
+ return &TextNode{NodeType: NodeText, Text: text}
+}
+
+func (t *TextNode) String() string {
+ return fmt.Sprintf("%s: %s", t.Type(), t.Text)
+}
+
+// FieldNode holds field of struct
+type FieldNode struct {
+ NodeType
+ Value string
+}
+
+func newField(value string) *FieldNode {
+ return &FieldNode{NodeType: NodeField, Value: value}
+}
+
+func (f *FieldNode) String() string {
+ return fmt.Sprintf("%s: %s", f.Type(), f.Value)
+}
+
+// IdentifierNode holds an identifier
+type IdentifierNode struct {
+ NodeType
+ Name string
+}
+
+func newIdentifier(value string) *IdentifierNode {
+ return &IdentifierNode{
+ NodeType: NodeIdentifier,
+ Name: value,
+ }
+}
+
+func (f *IdentifierNode) String() string {
+ return fmt.Sprintf("%s: %s", f.Type(), f.Name)
+}
+
+// ParamsEntry holds param information for ArrayNode
+type ParamsEntry struct {
+ Value int
+ Known bool // whether the value is known when parse it
+ Derived bool
+}
+
+// ArrayNode holds start, end, step information for array index selection
+type ArrayNode struct {
+ NodeType
+ Params [3]ParamsEntry // start, end, step
+}
+
+func newArray(params [3]ParamsEntry) *ArrayNode {
+ return &ArrayNode{
+ NodeType: NodeArray,
+ Params: params,
+ }
+}
+
+func (a *ArrayNode) String() string {
+ return fmt.Sprintf("%s: %v", a.Type(), a.Params)
+}
+
+// FilterNode holds operand and operator information for filter
+type FilterNode struct {
+ NodeType
+ Left *ListNode
+ Right *ListNode
+ Operator string
+}
+
+func newFilter(left, right *ListNode, operator string) *FilterNode {
+ return &FilterNode{
+ NodeType: NodeFilter,
+ Left: left,
+ Right: right,
+ Operator: operator,
+ }
+}
+
+func (f *FilterNode) String() string {
+ return fmt.Sprintf("%s: %s %s %s", f.Type(), f.Left, f.Operator, f.Right)
+}
+
+// IntNode holds integer value
+type IntNode struct {
+ NodeType
+ Value int
+}
+
+func newInt(num int) *IntNode {
+ return &IntNode{NodeType: NodeInt, Value: num}
+}
+
+func (i *IntNode) String() string {
+ return fmt.Sprintf("%s: %d", i.Type(), i.Value)
+}
+
+// FloatNode holds float value
+type FloatNode struct {
+ NodeType
+ Value float64
+}
+
+func newFloat(num float64) *FloatNode {
+ return &FloatNode{NodeType: NodeFloat, Value: num}
+}
+
+func (i *FloatNode) String() string {
+ return fmt.Sprintf("%s: %f", i.Type(), i.Value)
+}
+
+// WildcardNode means a wildcard
+type WildcardNode struct {
+ NodeType
+}
+
+func newWildcard() *WildcardNode {
+ return &WildcardNode{NodeType: NodeWildcard}
+}
+
+func (i *WildcardNode) String() string {
+ return i.Type().String()
+}
+
+// RecursiveNode means a recursive descent operator
+type RecursiveNode struct {
+ NodeType
+}
+
+func newRecursive() *RecursiveNode {
+ return &RecursiveNode{NodeType: NodeRecursive}
+}
+
+func (r *RecursiveNode) String() string {
+ return r.Type().String()
+}
+
+// UnionNode is union of ListNode
+type UnionNode struct {
+ NodeType
+ Nodes []*ListNode
+}
+
+func newUnion(nodes []*ListNode) *UnionNode {
+ return &UnionNode{NodeType: NodeUnion, Nodes: nodes}
+}
+
+func (u *UnionNode) String() string {
+ return u.Type().String()
+}
+
+// BoolNode holds bool value
+type BoolNode struct {
+ NodeType
+ Value bool
+}
+
+func newBool(value bool) *BoolNode {
+ return &BoolNode{NodeType: NodeBool, Value: value}
+}
+
+func (b *BoolNode) String() string {
+ return fmt.Sprintf("%s: %t", b.Type(), b.Value)
+}
diff --git a/vendor/k8s.io/client-go/util/jsonpath/parser.go b/vendor/k8s.io/client-go/util/jsonpath/parser.go
new file mode 100644
index 0000000000000000000000000000000000000000..e1aab6804f81fd6079f501225ad9422a981e3477
--- /dev/null
+++ b/vendor/k8s.io/client-go/util/jsonpath/parser.go
@@ -0,0 +1,524 @@
+/*
+Copyright 2015 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 jsonpath
+
+import (
+ "errors"
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+const eof = -1
+
+const (
+ leftDelim = "{"
+ rightDelim = "}"
+)
+
+type Parser struct {
+ Name string
+ Root *ListNode
+ input string
+ pos int
+ start int
+ width int
+}
+
+var (
+ ErrSyntax = errors.New("invalid syntax")
+ dictKeyRex = regexp.MustCompile(`^'([^']*)'$`)
+ sliceOperatorRex = regexp.MustCompile(`^(-?[\d]*)(:-?[\d]*)?(:-?[\d]*)?$`)
+)
+
+// Parse parsed the given text and return a node Parser.
+// If an error is encountered, parsing stops and an empty
+// Parser is returned with the error
+func Parse(name, text string) (*Parser, error) {
+ p := NewParser(name)
+ err := p.Parse(text)
+ if err != nil {
+ p = nil
+ }
+ return p, err
+}
+
+func NewParser(name string) *Parser {
+ return &Parser{
+ Name: name,
+ }
+}
+
+// parseAction parsed the expression inside delimiter
+func parseAction(name, text string) (*Parser, error) {
+ p, err := Parse(name, fmt.Sprintf("%s%s%s", leftDelim, text, rightDelim))
+ // when error happens, p will be nil, so we need to return here
+ if err != nil {
+ return p, err
+ }
+ p.Root = p.Root.Nodes[0].(*ListNode)
+ return p, nil
+}
+
+func (p *Parser) Parse(text string) error {
+ p.input = text
+ p.Root = newList()
+ p.pos = 0
+ return p.parseText(p.Root)
+}
+
+// consumeText return the parsed text since last cosumeText
+func (p *Parser) consumeText() string {
+ value := p.input[p.start:p.pos]
+ p.start = p.pos
+ return value
+}
+
+// next returns the next rune in the input.
+func (p *Parser) next() rune {
+ if p.pos >= len(p.input) {
+ p.width = 0
+ return eof
+ }
+ r, w := utf8.DecodeRuneInString(p.input[p.pos:])
+ p.width = w
+ p.pos += p.width
+ return r
+}
+
+// peek returns but does not consume the next rune in the input.
+func (p *Parser) peek() rune {
+ r := p.next()
+ p.backup()
+ return r
+}
+
+// backup steps back one rune. Can only be called once per call of next.
+func (p *Parser) backup() {
+ p.pos -= p.width
+}
+
+func (p *Parser) parseText(cur *ListNode) error {
+ for {
+ if strings.HasPrefix(p.input[p.pos:], leftDelim) {
+ if p.pos > p.start {
+ cur.append(newText(p.consumeText()))
+ }
+ return p.parseLeftDelim(cur)
+ }
+ if p.next() == eof {
+ break
+ }
+ }
+ // Correctly reached EOF.
+ if p.pos > p.start {
+ cur.append(newText(p.consumeText()))
+ }
+ return nil
+}
+
+// parseLeftDelim scans the left delimiter, which is known to be present.
+func (p *Parser) parseLeftDelim(cur *ListNode) error {
+ p.pos += len(leftDelim)
+ p.consumeText()
+ newNode := newList()
+ cur.append(newNode)
+ cur = newNode
+ return p.parseInsideAction(cur)
+}
+
+func (p *Parser) parseInsideAction(cur *ListNode) error {
+ prefixMap := map[string]func(*ListNode) error{
+ rightDelim: p.parseRightDelim,
+ "[?(": p.parseFilter,
+ "..": p.parseRecursive,
+ }
+ for prefix, parseFunc := range prefixMap {
+ if strings.HasPrefix(p.input[p.pos:], prefix) {
+ return parseFunc(cur)
+ }
+ }
+
+ switch r := p.next(); {
+ case r == eof || isEndOfLine(r):
+ return fmt.Errorf("unclosed action")
+ case r == ' ':
+ p.consumeText()
+ case r == '@' || r == '$': //the current object, just pass it
+ p.consumeText()
+ case r == '[':
+ return p.parseArray(cur)
+ case r == '"' || r == '\'':
+ return p.parseQuote(cur, r)
+ case r == '.':
+ return p.parseField(cur)
+ case r == '+' || r == '-' || unicode.IsDigit(r):
+ p.backup()
+ return p.parseNumber(cur)
+ case isAlphaNumeric(r):
+ p.backup()
+ return p.parseIdentifier(cur)
+ default:
+ return fmt.Errorf("unrecognized character in action: %#U", r)
+ }
+ return p.parseInsideAction(cur)
+}
+
+// parseRightDelim scans the right delimiter, which is known to be present.
+func (p *Parser) parseRightDelim(cur *ListNode) error {
+ p.pos += len(rightDelim)
+ p.consumeText()
+ return p.parseText(p.Root)
+}
+
+// parseIdentifier scans build-in keywords, like "range" "end"
+func (p *Parser) parseIdentifier(cur *ListNode) error {
+ var r rune
+ for {
+ r = p.next()
+ if isTerminator(r) {
+ p.backup()
+ break
+ }
+ }
+ value := p.consumeText()
+
+ if isBool(value) {
+ v, err := strconv.ParseBool(value)
+ if err != nil {
+ return fmt.Errorf("can not parse bool '%s': %s", value, err.Error())
+ }
+
+ cur.append(newBool(v))
+ } else {
+ cur.append(newIdentifier(value))
+ }
+
+ return p.parseInsideAction(cur)
+}
+
+// parseRecursive scans the recursive desent operator ..
+func (p *Parser) parseRecursive(cur *ListNode) error {
+ p.pos += len("..")
+ p.consumeText()
+ cur.append(newRecursive())
+ if r := p.peek(); isAlphaNumeric(r) {
+ return p.parseField(cur)
+ }
+ return p.parseInsideAction(cur)
+}
+
+// parseNumber scans number
+func (p *Parser) parseNumber(cur *ListNode) error {
+ r := p.peek()
+ if r == '+' || r == '-' {
+ p.next()
+ }
+ for {
+ r = p.next()
+ if r != '.' && !unicode.IsDigit(r) {
+ p.backup()
+ break
+ }
+ }
+ value := p.consumeText()
+ i, err := strconv.Atoi(value)
+ if err == nil {
+ cur.append(newInt(i))
+ return p.parseInsideAction(cur)
+ }
+ d, err := strconv.ParseFloat(value, 64)
+ if err == nil {
+ cur.append(newFloat(d))
+ return p.parseInsideAction(cur)
+ }
+ return fmt.Errorf("cannot parse number %s", value)
+}
+
+// parseArray scans array index selection
+func (p *Parser) parseArray(cur *ListNode) error {
+Loop:
+ for {
+ switch p.next() {
+ case eof, '\n':
+ return fmt.Errorf("unterminated array")
+ case ']':
+ break Loop
+ }
+ }
+ text := p.consumeText()
+ text = text[1 : len(text)-1]
+ if text == "*" {
+ text = ":"
+ }
+
+ //union operator
+ strs := strings.Split(text, ",")
+ if len(strs) > 1 {
+ union := []*ListNode{}
+ for _, str := range strs {
+ parser, err := parseAction("union", fmt.Sprintf("[%s]", strings.Trim(str, " ")))
+ if err != nil {
+ return err
+ }
+ union = append(union, parser.Root)
+ }
+ cur.append(newUnion(union))
+ return p.parseInsideAction(cur)
+ }
+
+ // dict key
+ value := dictKeyRex.FindStringSubmatch(text)
+ if value != nil {
+ parser, err := parseAction("arraydict", fmt.Sprintf(".%s", value[1]))
+ if err != nil {
+ return err
+ }
+ for _, node := range parser.Root.Nodes {
+ cur.append(node)
+ }
+ return p.parseInsideAction(cur)
+ }
+
+ //slice operator
+ value = sliceOperatorRex.FindStringSubmatch(text)
+ if value == nil {
+ return fmt.Errorf("invalid array index %s", text)
+ }
+ value = value[1:]
+ params := [3]ParamsEntry{}
+ for i := 0; i < 3; i++ {
+ if value[i] != "" {
+ if i > 0 {
+ value[i] = value[i][1:]
+ }
+ if i > 0 && value[i] == "" {
+ params[i].Known = false
+ } else {
+ var err error
+ params[i].Known = true
+ params[i].Value, err = strconv.Atoi(value[i])
+ if err != nil {
+ return fmt.Errorf("array index %s is not a number", value[i])
+ }
+ }
+ } else {
+ if i == 1 {
+ params[i].Known = true
+ params[i].Value = params[0].Value + 1
+ params[i].Derived = true
+ } else {
+ params[i].Known = false
+ params[i].Value = 0
+ }
+ }
+ }
+ cur.append(newArray(params))
+ return p.parseInsideAction(cur)
+}
+
+// parseFilter scans filter inside array selection
+func (p *Parser) parseFilter(cur *ListNode) error {
+ p.pos += len("[?(")
+ p.consumeText()
+ begin := false
+ end := false
+ var pair rune
+
+Loop:
+ for {
+ r := p.next()
+ switch r {
+ case eof, '\n':
+ return fmt.Errorf("unterminated filter")
+ case '"', '\'':
+ if begin == false {
+ //save the paired rune
+ begin = true
+ pair = r
+ continue
+ }
+ //only add when met paired rune
+ if p.input[p.pos-2] != '\\' && r == pair {
+ end = true
+ }
+ case ')':
+ //in rightParser below quotes only appear zero or once
+ //and must be paired at the beginning and end
+ if begin == end {
+ break Loop
+ }
+ }
+ }
+ if p.next() != ']' {
+ return fmt.Errorf("unclosed array expect ]")
+ }
+ reg := regexp.MustCompile(`^([^!<>=]+)([!<>=]+)(.+?)$`)
+ text := p.consumeText()
+ text = text[:len(text)-2]
+ value := reg.FindStringSubmatch(text)
+ if value == nil {
+ parser, err := parseAction("text", text)
+ if err != nil {
+ return err
+ }
+ cur.append(newFilter(parser.Root, newList(), "exists"))
+ } else {
+ leftParser, err := parseAction("left", value[1])
+ if err != nil {
+ return err
+ }
+ rightParser, err := parseAction("right", value[3])
+ if err != nil {
+ return err
+ }
+ cur.append(newFilter(leftParser.Root, rightParser.Root, value[2]))
+ }
+ return p.parseInsideAction(cur)
+}
+
+// parseQuote unquotes string inside double or single quote
+func (p *Parser) parseQuote(cur *ListNode, end rune) error {
+Loop:
+ for {
+ switch p.next() {
+ case eof, '\n':
+ return fmt.Errorf("unterminated quoted string")
+ case end:
+ //if it's not escape break the Loop
+ if p.input[p.pos-2] != '\\' {
+ break Loop
+ }
+ }
+ }
+ value := p.consumeText()
+ s, err := UnquoteExtend(value)
+ if err != nil {
+ return fmt.Errorf("unquote string %s error %v", value, err)
+ }
+ cur.append(newText(s))
+ return p.parseInsideAction(cur)
+}
+
+// parseField scans a field until a terminator
+func (p *Parser) parseField(cur *ListNode) error {
+ p.consumeText()
+ for p.advance() {
+ }
+ value := p.consumeText()
+ if value == "*" {
+ cur.append(newWildcard())
+ } else {
+ cur.append(newField(strings.Replace(value, "\\", "", -1)))
+ }
+ return p.parseInsideAction(cur)
+}
+
+// advance scans until next non-escaped terminator
+func (p *Parser) advance() bool {
+ r := p.next()
+ if r == '\\' {
+ p.next()
+ } else if isTerminator(r) {
+ p.backup()
+ return false
+ }
+ return true
+}
+
+// isTerminator reports whether the input is at valid termination character to appear after an identifier.
+func isTerminator(r rune) bool {
+ if isSpace(r) || isEndOfLine(r) {
+ return true
+ }
+ switch r {
+ case eof, '.', ',', '[', ']', '$', '@', '{', '}':
+ return true
+ }
+ return false
+}
+
+// isSpace reports whether r is a space character.
+func isSpace(r rune) bool {
+ return r == ' ' || r == '\t'
+}
+
+// isEndOfLine reports whether r is an end-of-line character.
+func isEndOfLine(r rune) bool {
+ return r == '\r' || r == '\n'
+}
+
+// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
+func isAlphaNumeric(r rune) bool {
+ return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
+}
+
+// isBool reports whether s is a boolean value.
+func isBool(s string) bool {
+ return s == "true" || s == "false"
+}
+
+//UnquoteExtend is almost same as strconv.Unquote(), but it support parse single quotes as a string
+func UnquoteExtend(s string) (string, error) {
+ n := len(s)
+ if n < 2 {
+ return "", ErrSyntax
+ }
+ quote := s[0]
+ if quote != s[n-1] {
+ return "", ErrSyntax
+ }
+ s = s[1 : n-1]
+
+ if quote != '"' && quote != '\'' {
+ return "", ErrSyntax
+ }
+
+ // Is it trivial? Avoid allocation.
+ if !contains(s, '\\') && !contains(s, quote) {
+ return s, nil
+ }
+
+ var runeTmp [utf8.UTFMax]byte
+ buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
+ for len(s) > 0 {
+ c, multibyte, ss, err := strconv.UnquoteChar(s, quote)
+ if err != nil {
+ return "", err
+ }
+ s = ss
+ if c < utf8.RuneSelf || !multibyte {
+ buf = append(buf, byte(c))
+ } else {
+ n := utf8.EncodeRune(runeTmp[:], c)
+ buf = append(buf, runeTmp[:n]...)
+ }
+ }
+ return string(buf), nil
+}
+
+func contains(s string, c byte) bool {
+ for i := 0; i < len(s); i++ {
+ if s[i] == c {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 528a487600b041de2e0896dabd9e18f0799a0f94..323c9eddb3f13fdc5a308b88c7220fd81681b91a 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -366,6 +366,8 @@ github.com/kubernetes-sigs/application/pkg/finalizer
github.com/kubernetes-sigs/application/pkg/resource
# github.com/kubesphere/sonargo v0.0.2 => github.com/kubesphere/sonargo v0.0.2
github.com/kubesphere/sonargo/sonar
+# github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de => github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
+github.com/liggitt/tabwriter
# github.com/magiconair/properties v1.8.0 => github.com/magiconair/properties v1.8.0
github.com/magiconair/properties
# github.com/mailru/easyjson v0.7.0 => github.com/mailru/easyjson v0.7.0
@@ -921,6 +923,7 @@ k8s.io/apimachinery/pkg/types
k8s.io/apimachinery/pkg/util/cache
k8s.io/apimachinery/pkg/util/clock
k8s.io/apimachinery/pkg/util/diff
+k8s.io/apimachinery/pkg/util/duration
k8s.io/apimachinery/pkg/util/errors
k8s.io/apimachinery/pkg/util/framer
k8s.io/apimachinery/pkg/util/httpstream
@@ -1051,6 +1054,8 @@ k8s.io/apiserver/plugin/pkg/audit/truncate
k8s.io/apiserver/plugin/pkg/audit/webhook
k8s.io/apiserver/plugin/pkg/authenticator/token/webhook
k8s.io/apiserver/plugin/pkg/authorizer/webhook
+# k8s.io/cli-runtime v0.17.3 => k8s.io/cli-runtime v0.17.3
+k8s.io/cli-runtime/pkg/printers
# k8s.io/client-go v0.17.3 => k8s.io/client-go v0.0.0-20191114101535-6c5935290e33
k8s.io/client-go/discovery
k8s.io/client-go/discovery/fake
@@ -1231,6 +1236,7 @@ k8s.io/client-go/rest
k8s.io/client-go/rest/watch
k8s.io/client-go/restmapper
k8s.io/client-go/testing
+k8s.io/client-go/third_party/forked/golang/template
k8s.io/client-go/tools/auth
k8s.io/client-go/tools/cache
k8s.io/client-go/tools/clientcmd
@@ -1252,6 +1258,7 @@ k8s.io/client-go/util/connrotation
k8s.io/client-go/util/exec
k8s.io/client-go/util/flowcontrol
k8s.io/client-go/util/homedir
+k8s.io/client-go/util/jsonpath
k8s.io/client-go/util/keyutil
k8s.io/client-go/util/retry
k8s.io/client-go/util/workqueue