mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
vendor files
This commit is contained in:
97
vendor/k8s.io/kubernetes/pkg/kubectl/resource/BUILD
generated
vendored
Normal file
97
vendor/k8s.io/kubernetes/pkg/kubectl/resource/BUILD
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"builder.go",
|
||||
"doc.go",
|
||||
"helper.go",
|
||||
"interfaces.go",
|
||||
"mapper.go",
|
||||
"result.go",
|
||||
"selector.go",
|
||||
"visitor.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubectl/resource",
|
||||
visibility = [
|
||||
"//build/visible_to:pkg_kubectl_resource_CONSUMERS",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/kubectl/categories:go_default_library",
|
||||
"//pkg/kubectl/validation:go_default_library",
|
||||
"//vendor/golang.org/x/text/encoding/unicode:go_default_library",
|
||||
"//vendor/golang.org/x/text/transform:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"builder_test.go",
|
||||
"helper_test.go",
|
||||
"visitor_test.go",
|
||||
],
|
||||
data = [
|
||||
"//examples:config",
|
||||
"//test/fixtures",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubectl/resource",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/kubectl/categories:go_default_library",
|
||||
"//pkg/kubectl/scheme:go_default_library",
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = [
|
||||
"//build/visible_to:pkg_kubectl_resource_CONSUMERS",
|
||||
],
|
||||
)
|
1018
vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder.go
generated
vendored
Normal file
1018
vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1387
vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder_test.go
generated
vendored
Normal file
1387
vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24
vendor/k8s.io/kubernetes/pkg/kubectl/resource/doc.go
generated
vendored
Normal file
24
vendor/k8s.io/kubernetes/pkg/kubectl/resource/doc.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package resource assists clients in dealing with RESTful objects that match the
|
||||
// Kubernetes API conventions. The Helper object provides simple CRUD operations
|
||||
// on resources. The Visitor interface makes it easy to deal with multiple resources
|
||||
// in bulk for retrieval and operation. The Builder object simplifies converting
|
||||
// standard command line arguments and parameters into a Visitor that can iterate
|
||||
// over all of the identified resources, whether on the server or on the local
|
||||
// filesystem.
|
||||
package resource // import "k8s.io/kubernetes/pkg/kubectl/resource"
|
174
vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper.go
generated
vendored
Normal file
174
vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
// Helper provides methods for retrieving or mutating a RESTful
|
||||
// resource.
|
||||
type Helper struct {
|
||||
// The name of this resource as the server would recognize it
|
||||
Resource string
|
||||
// A RESTClient capable of mutating this resource.
|
||||
RESTClient RESTClient
|
||||
// An interface for reading or writing the resource version of this
|
||||
// type.
|
||||
Versioner runtime.ResourceVersioner
|
||||
// True if the resource type is scoped to namespaces
|
||||
NamespaceScoped bool
|
||||
}
|
||||
|
||||
// NewHelper creates a Helper from a ResourceMapping
|
||||
func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
|
||||
return &Helper{
|
||||
Resource: mapping.Resource,
|
||||
RESTClient: client,
|
||||
Versioner: mapping.MetadataAccessor,
|
||||
NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Helper) Get(namespace, name string, export bool) (runtime.Object, error) {
|
||||
req := m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name)
|
||||
if export {
|
||||
// TODO: I should be part of GetOptions
|
||||
req.Param("export", strconv.FormatBool(export))
|
||||
}
|
||||
return req.Do().Get()
|
||||
}
|
||||
|
||||
func (m *Helper) List(namespace, apiVersion string, export bool, options *metav1.ListOptions) (runtime.Object, error) {
|
||||
req := m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
VersionedParams(options, metav1.ParameterCodec)
|
||||
if export {
|
||||
// TODO: I should be part of ListOptions
|
||||
req.Param("export", strconv.FormatBool(export))
|
||||
}
|
||||
return req.Do().Get()
|
||||
}
|
||||
|
||||
func (m *Helper) Watch(namespace, apiVersion string, options *metav1.ListOptions) (watch.Interface, error) {
|
||||
options.Watch = true
|
||||
return m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
VersionedParams(options, metav1.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) {
|
||||
return m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
VersionedParams(&metav1.ListOptions{
|
||||
ResourceVersion: resourceVersion,
|
||||
Watch: true,
|
||||
FieldSelector: fields.OneTermEqualSelector("metadata.name", name).String(),
|
||||
}, metav1.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
func (m *Helper) Delete(namespace, name string) error {
|
||||
return m.DeleteWithOptions(namespace, name, nil)
|
||||
}
|
||||
|
||||
func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.DeleteOptions) error {
|
||||
return m.RESTClient.Delete().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
func (m *Helper) Create(namespace string, modify bool, obj runtime.Object) (runtime.Object, error) {
|
||||
if modify {
|
||||
// Attempt to version the object based on client logic.
|
||||
version, err := m.Versioner.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
// We don't know how to clear the version on this object, so send it to the server as is
|
||||
return m.createResource(m.RESTClient, m.Resource, namespace, obj)
|
||||
}
|
||||
if version != "" {
|
||||
if err := m.Versioner.SetResourceVersion(obj, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m.createResource(m.RESTClient, m.Resource, namespace, obj)
|
||||
}
|
||||
|
||||
func (m *Helper) createResource(c RESTClient, resource, namespace string, obj runtime.Object) (runtime.Object, error) {
|
||||
return c.Post().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Body(obj).Do().Get()
|
||||
}
|
||||
func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte) (runtime.Object, error) {
|
||||
return m.RESTClient.Patch(pt).
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Get()
|
||||
}
|
||||
|
||||
func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Object) (runtime.Object, error) {
|
||||
c := m.RESTClient
|
||||
|
||||
// Attempt to version the object based on client logic.
|
||||
version, err := m.Versioner.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
// We don't know how to version this object, so send it to the server as is
|
||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||
}
|
||||
if version == "" && overwrite {
|
||||
// Retrieve the current version of the object to overwrite the server object
|
||||
serverObj, err := c.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).Name(name).Do().Get()
|
||||
if err != nil {
|
||||
// The object does not exist, but we want it to be created
|
||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||
}
|
||||
serverVersion, err := m.Versioner.ResourceVersion(serverObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := m.Versioner.SetResourceVersion(obj, serverVersion); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||
}
|
||||
|
||||
func (m *Helper) replaceResource(c RESTClient, resource, namespace, name string, obj runtime.Object) (runtime.Object, error) {
|
||||
return c.Put().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Name(name).Body(obj).Do().Get()
|
||||
}
|
596
vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper_test.go
generated
vendored
Normal file
596
vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper_test.go
generated
vendored
Normal file
@ -0,0 +1,596 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
)
|
||||
|
||||
func objBody(obj runtime.Object) io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(corev1Codec, obj))))
|
||||
}
|
||||
|
||||
func header() http.Header {
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", runtime.ContentTypeJSON)
|
||||
return header
|
||||
}
|
||||
|
||||
// splitPath returns the segments for a URL path.
|
||||
func splitPath(path string) []string {
|
||||
path = strings.Trim(path, "/")
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, "/")
|
||||
}
|
||||
|
||||
// V1DeepEqualSafePodSpec returns a PodSpec which is ready to be used with apiequality.Semantic.DeepEqual
|
||||
func V1DeepEqualSafePodSpec() corev1.PodSpec {
|
||||
grace := int64(30)
|
||||
return corev1.PodSpec{
|
||||
RestartPolicy: corev1.RestartPolicyAlways,
|
||||
DNSPolicy: corev1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
SecurityContext: &corev1.PodSecurityContext{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
|
||||
},
|
||||
Req: func(req *http.Request) bool {
|
||||
if req.Method != "DELETE" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if len(parts) < 3 {
|
||||
t.Errorf("expected URL path to have 3 parts: %s", req.URL.Path)
|
||||
return false
|
||||
}
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
if parts[2] != "foo" {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
err := modifier.Delete("bar", "foo")
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("unexpected error: %t %v", test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("unexpected request: %#v", client.Req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperCreate(t *testing.T) {
|
||||
expectPost := func(req *http.Request) bool {
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
Modify bool
|
||||
Object runtime.Object
|
||||
|
||||
ExpectObject runtime.Object
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
|
||||
},
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
Req: expectPost,
|
||||
},
|
||||
{
|
||||
Modify: false,
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
|
||||
Req: expectPost,
|
||||
},
|
||||
{
|
||||
Modify: true,
|
||||
Object: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||
Spec: V1DeepEqualSafePodSpec(),
|
||||
},
|
||||
ExpectObject: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: V1DeepEqualSafePodSpec(),
|
||||
},
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
|
||||
Req: expectPost,
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
GroupVersion: corev1GV,
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
Versioner: metaAccessor,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
_, err := modifier.Create("bar", test.Modify, test.Object)
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
||||
}
|
||||
body, err := ioutil.ReadAll(client.Req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: unexpected error: %#v", i, err)
|
||||
}
|
||||
t.Logf("got body: %s", string(body))
|
||||
expect := []byte{}
|
||||
if test.ExpectObject != nil {
|
||||
expect = []byte(runtime.EncodeOrDie(corev1Codec, test.ExpectObject))
|
||||
}
|
||||
if !reflect.DeepEqual(expect, body) {
|
||||
t.Errorf("%d: unexpected body: %s (expected %s)", i, string(body), string(expect))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperGet(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&corev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"}}),
|
||||
},
|
||||
Req: func(req *http.Request) bool {
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
if parts[2] != "foo" {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
GroupVersion: corev1GV,
|
||||
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
obj, err := modifier.Get("bar", "foo", false)
|
||||
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("unexpected error: %d %t %v", i, test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if obj.(*corev1.Pod).Name != "foo" {
|
||||
t.Errorf("unexpected object: %#v", obj)
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("unexpected request: %#v", client.Req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperList(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&corev1.PodList{
|
||||
Items: []corev1.Pod{{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
Req: func(req *http.Request) bool {
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Path != "/namespaces/bar" {
|
||||
t.Errorf("url doesn't contain name: %#v", req.URL)
|
||||
return false
|
||||
}
|
||||
if req.URL.Query().Get(metav1.LabelSelectorQueryParam(corev1GV.String())) != labels.SelectorFromSet(labels.Set{"foo": "baz"}).String() {
|
||||
t.Errorf("url doesn't contain query parameters: %#v", req.URL)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
GroupVersion: corev1GV,
|
||||
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
obj, err := modifier.List("bar", corev1GV.String(), false, &metav1.ListOptions{LabelSelector: "foo=baz"})
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("unexpected error: %t %v", test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if obj.(*corev1.PodList).Items[0].Name != "foo" {
|
||||
t.Errorf("unexpected object: %#v", obj)
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("unexpected request: %#v", client.Req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperListSelectorCombination(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
Err bool
|
||||
ErrMsg string
|
||||
FieldSelector string
|
||||
LabelSelector string
|
||||
}{
|
||||
{
|
||||
Name: "No selector",
|
||||
Err: false,
|
||||
},
|
||||
{
|
||||
Name: "Only Label Selector",
|
||||
Err: false,
|
||||
LabelSelector: "foo=baz",
|
||||
},
|
||||
{
|
||||
Name: "Only Field Selector",
|
||||
Err: false,
|
||||
FieldSelector: "xyz=zyx",
|
||||
},
|
||||
{
|
||||
Name: "Both Label and Field Selector",
|
||||
Err: false,
|
||||
LabelSelector: "foo=baz",
|
||||
FieldSelector: "xyz=zyx",
|
||||
},
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&corev1.PodList{
|
||||
Items: []corev1.Pod{{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
client := &fake.RESTClient{
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
Resp: resp,
|
||||
Err: nil,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, err := modifier.List("bar",
|
||||
corev1GV.String(),
|
||||
false,
|
||||
&metav1.ListOptions{LabelSelector: test.LabelSelector, FieldSelector: test.FieldSelector})
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Errorf("%q expected error: %q", test.Name, test.ErrMsg)
|
||||
}
|
||||
if err != nil && err.Error() != test.ErrMsg {
|
||||
t.Errorf("%q expected error: %q", test.Name, test.ErrMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperReplace(t *testing.T) {
|
||||
expectPut := func(path string, req *http.Request) bool {
|
||||
if req.Method != "PUT" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Path != path {
|
||||
t.Errorf("unexpected url: %v", req.URL)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Resp *http.Response
|
||||
HTTPClient *http.Client
|
||||
HttpErr error
|
||||
Overwrite bool
|
||||
Object runtime.Object
|
||||
Namespace string
|
||||
NamespaceScoped bool
|
||||
|
||||
ExpectPath string
|
||||
ExpectObject runtime.Object
|
||||
Err bool
|
||||
Req func(string, *http.Request) bool
|
||||
}{
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
ExpectPath: "/namespaces/bar/foo",
|
||||
ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
|
||||
},
|
||||
Req: expectPut,
|
||||
},
|
||||
// namespace scoped resource
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: V1DeepEqualSafePodSpec(),
|
||||
},
|
||||
ExpectPath: "/namespaces/bar/foo",
|
||||
ExpectObject: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||
Spec: V1DeepEqualSafePodSpec(),
|
||||
},
|
||||
Overwrite: true,
|
||||
HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "PUT" {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
|
||||
}),
|
||||
Req: expectPut,
|
||||
},
|
||||
// cluster scoped resource
|
||||
{
|
||||
Object: &corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
ExpectObject: &corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||
},
|
||||
Overwrite: true,
|
||||
ExpectPath: "/foo",
|
||||
HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "PUT" {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
|
||||
}),
|
||||
Req: expectPut,
|
||||
},
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
ExpectPath: "/namespaces/bar/foo",
|
||||
ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
|
||||
Req: expectPut,
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
GroupVersion: corev1GV,
|
||||
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
|
||||
Client: test.HTTPClient,
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
Versioner: metaAccessor,
|
||||
NamespaceScoped: test.NamespaceScoped,
|
||||
}
|
||||
_, err := modifier.Replace(test.Namespace, "foo", test.Overwrite, test.Object)
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if test.Req != nil && !test.Req(test.ExpectPath, client.Req) {
|
||||
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
||||
}
|
||||
body, err := ioutil.ReadAll(client.Req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: unexpected error: %#v", i, err)
|
||||
}
|
||||
expect := []byte{}
|
||||
if test.ExpectObject != nil {
|
||||
expect = []byte(runtime.EncodeOrDie(corev1Codec, test.ExpectObject))
|
||||
}
|
||||
if !reflect.DeepEqual(expect, body) {
|
||||
t.Errorf("%d: unexpected body: %s", i, string(body))
|
||||
}
|
||||
}
|
||||
}
|
84
vendor/k8s.io/kubernetes/pkg/kubectl/resource/interfaces.go
generated
vendored
Normal file
84
vendor/k8s.io/kubernetes/pkg/kubectl/resource/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
client "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// RESTClient is a client helper for dealing with RESTful resources
|
||||
// in a generic way.
|
||||
type RESTClient interface {
|
||||
Get() *client.Request
|
||||
Post() *client.Request
|
||||
Patch(types.PatchType) *client.Request
|
||||
Delete() *client.Request
|
||||
Put() *client.Request
|
||||
}
|
||||
|
||||
// ClientMapper abstracts retrieving a Client for mapped objects.
|
||||
type ClientMapper interface {
|
||||
ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error)
|
||||
}
|
||||
|
||||
// ClientMapperFunc implements ClientMapper for a function
|
||||
type ClientMapperFunc func(mapping *meta.RESTMapping) (RESTClient, error)
|
||||
|
||||
// ClientForMapping implements ClientMapper
|
||||
func (f ClientMapperFunc) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) {
|
||||
return f(mapping)
|
||||
}
|
||||
|
||||
// RequestTransform is a function that is given a chance to modify the outgoing request.
|
||||
type RequestTransform func(*client.Request)
|
||||
|
||||
// NewClientWithOptions wraps the provided RESTClient and invokes each transform on each
|
||||
// newly created request.
|
||||
func NewClientWithOptions(c RESTClient, transforms ...RequestTransform) RESTClient {
|
||||
return &clientOptions{c: c, transforms: transforms}
|
||||
}
|
||||
|
||||
type clientOptions struct {
|
||||
c RESTClient
|
||||
transforms []RequestTransform
|
||||
}
|
||||
|
||||
func (c *clientOptions) modify(req *client.Request) *client.Request {
|
||||
for _, transform := range c.transforms {
|
||||
transform(req)
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *clientOptions) Get() *client.Request {
|
||||
return c.modify(c.c.Get())
|
||||
}
|
||||
|
||||
func (c *clientOptions) Post() *client.Request {
|
||||
return c.modify(c.c.Post())
|
||||
}
|
||||
func (c *clientOptions) Patch(t types.PatchType) *client.Request {
|
||||
return c.modify(c.c.Patch(t))
|
||||
}
|
||||
func (c *clientOptions) Delete() *client.Request {
|
||||
return c.modify(c.c.Delete())
|
||||
}
|
||||
func (c *clientOptions) Put() *client.Request {
|
||||
return c.modify(c.c.Put())
|
||||
}
|
237
vendor/k8s.io/kubernetes/pkg/kubectl/resource/mapper.go
generated
vendored
Normal file
237
vendor/k8s.io/kubernetes/pkg/kubectl/resource/mapper.go
generated
vendored
Normal file
@ -0,0 +1,237 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// Mapper is a convenience struct for holding references to the interfaces
|
||||
// needed to create Info for arbitrary objects.
|
||||
type Mapper struct {
|
||||
runtime.ObjectTyper
|
||||
meta.RESTMapper
|
||||
ClientMapper
|
||||
runtime.Decoder
|
||||
}
|
||||
|
||||
// AcceptUnrecognizedObjects will return a mapper that will tolerate objects
|
||||
// that are not recognized by the RESTMapper, returning mappings that can
|
||||
// perform minimal transformation. Allows working in disconnected mode, or with
|
||||
// objects that the server does not recognize. Returned resource.Info objects
|
||||
// may have empty resource fields and nil clients.
|
||||
func (m *Mapper) AcceptUnrecognizedObjects() *Mapper {
|
||||
copied := *m
|
||||
copied.RESTMapper = NewRelaxedRESTMapper(m.RESTMapper)
|
||||
copied.ClientMapper = NewRelaxedClientMapper(m.ClientMapper)
|
||||
return &copied
|
||||
}
|
||||
|
||||
// InfoForData creates an Info object for the given data. An error is returned
|
||||
// if any of the decoding or client lookup steps fail. Name and namespace will be
|
||||
// set into Info if the mapping's MetadataAccessor can retrieve them.
|
||||
func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
|
||||
obj, gvk, err := m.Decode(data, nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode %q: %v", source, err)
|
||||
}
|
||||
|
||||
mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
|
||||
}
|
||||
|
||||
client, err := m.ClientForMapping(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err)
|
||||
}
|
||||
|
||||
name, _ := mapping.MetadataAccessor.Name(obj)
|
||||
namespace, _ := mapping.MetadataAccessor.Namespace(obj)
|
||||
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
|
||||
return &Info{
|
||||
Client: client,
|
||||
Mapping: mapping,
|
||||
|
||||
Source: source,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
ResourceVersion: resourceVersion,
|
||||
|
||||
Object: obj,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InfoForObject creates an Info object for the given Object. An error is returned
|
||||
// if the object cannot be introspected. Name and namespace will be set into Info
|
||||
// if the mapping's MetadataAccessor can retrieve them.
|
||||
func (m *Mapper) InfoForObject(obj runtime.Object, preferredGVKs []schema.GroupVersionKind) (*Info, error) {
|
||||
groupVersionKinds, _, err := m.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get type info from the object %q: %v", reflect.TypeOf(obj), err)
|
||||
}
|
||||
|
||||
groupVersionKind := groupVersionKinds[0]
|
||||
if len(groupVersionKinds) > 1 && len(preferredGVKs) > 0 {
|
||||
groupVersionKind = preferredObjectKind(groupVersionKinds, preferredGVKs)
|
||||
}
|
||||
|
||||
mapping, err := m.RESTMapping(groupVersionKind.GroupKind(), groupVersionKind.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to recognize %v: %v", groupVersionKind, err)
|
||||
}
|
||||
|
||||
client, err := m.ClientForMapping(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err)
|
||||
}
|
||||
name, _ := mapping.MetadataAccessor.Name(obj)
|
||||
namespace, _ := mapping.MetadataAccessor.Namespace(obj)
|
||||
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
return &Info{
|
||||
Client: client,
|
||||
Mapping: mapping,
|
||||
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
ResourceVersion: resourceVersion,
|
||||
|
||||
Object: obj,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// preferredObjectKind picks the possibility that most closely matches the priority list in this order:
|
||||
// GroupVersionKind matches (exact match)
|
||||
// GroupKind matches
|
||||
// Group matches
|
||||
func preferredObjectKind(possibilities []schema.GroupVersionKind, preferences []schema.GroupVersionKind) schema.GroupVersionKind {
|
||||
// Exact match
|
||||
for _, priority := range preferences {
|
||||
for _, possibility := range possibilities {
|
||||
if possibility == priority {
|
||||
return possibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GroupKind match
|
||||
for _, priority := range preferences {
|
||||
for _, possibility := range possibilities {
|
||||
if possibility.GroupKind() == priority.GroupKind() {
|
||||
return possibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group match
|
||||
for _, priority := range preferences {
|
||||
for _, possibility := range possibilities {
|
||||
if possibility.Group == priority.Group {
|
||||
return possibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just pick the first
|
||||
return possibilities[0]
|
||||
}
|
||||
|
||||
// DisabledClientForMapping allows callers to avoid allowing remote calls when handling
|
||||
// resources.
|
||||
type DisabledClientForMapping struct {
|
||||
ClientMapper
|
||||
}
|
||||
|
||||
func (f DisabledClientForMapping) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NewRelaxedClientMapper will return a nil mapping if the object is not a recognized resource.
|
||||
func NewRelaxedClientMapper(mapper ClientMapper) ClientMapper {
|
||||
return relaxedClientMapper{mapper}
|
||||
}
|
||||
|
||||
type relaxedClientMapper struct {
|
||||
ClientMapper
|
||||
}
|
||||
|
||||
func (f relaxedClientMapper) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) {
|
||||
if len(mapping.Resource) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return f.ClientMapper.ClientForMapping(mapping)
|
||||
}
|
||||
|
||||
// NewRelaxedRESTMapper returns a RESTMapper that will tolerate mappings that don't exist in provided
|
||||
// RESTMapper, returning a mapping that is a best effort against the current server. This allows objects
|
||||
// that the server does not recognize to still be loaded.
|
||||
func NewRelaxedRESTMapper(mapper meta.RESTMapper) meta.RESTMapper {
|
||||
return relaxedMapper{mapper}
|
||||
}
|
||||
|
||||
type relaxedMapper struct {
|
||||
meta.RESTMapper
|
||||
}
|
||||
|
||||
func (m relaxedMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
|
||||
mapping, err := m.RESTMapper.RESTMapping(gk, versions...)
|
||||
if err != nil && meta.IsNoMatchError(err) && len(versions) > 0 {
|
||||
return &meta.RESTMapping{
|
||||
GroupVersionKind: gk.WithVersion(versions[0]),
|
||||
MetadataAccessor: meta.NewAccessor(),
|
||||
Scope: meta.RESTScopeRoot,
|
||||
ObjectConvertor: identityConvertor{},
|
||||
}, nil
|
||||
}
|
||||
return mapping, err
|
||||
}
|
||||
func (m relaxedMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
|
||||
mappings, err := m.RESTMapper.RESTMappings(gk, versions...)
|
||||
if err != nil && meta.IsNoMatchError(err) && len(versions) > 0 {
|
||||
return []*meta.RESTMapping{
|
||||
{
|
||||
GroupVersionKind: gk.WithVersion(versions[0]),
|
||||
MetadataAccessor: meta.NewAccessor(),
|
||||
Scope: meta.RESTScopeRoot,
|
||||
ObjectConvertor: identityConvertor{},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return mappings, err
|
||||
}
|
||||
|
||||
type identityConvertor struct{}
|
||||
|
||||
var _ runtime.ObjectConvertor = identityConvertor{}
|
||||
|
||||
func (c identityConvertor) Convert(in interface{}, out interface{}, context interface{}) error {
|
||||
return fmt.Errorf("unable to convert objects across pointers")
|
||||
}
|
||||
|
||||
func (c identityConvertor) ConvertToVersion(in runtime.Object, gv runtime.GroupVersioner) (out runtime.Object, err error) {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (c identityConvertor) ConvertFieldLabel(version string, kind string, label string, value string) (string, string, error) {
|
||||
return "", "", fmt.Errorf("unable to convert field labels")
|
||||
}
|
241
vendor/k8s.io/kubernetes/pkg/kubectl/resource/result.go
generated
vendored
Normal file
241
vendor/k8s.io/kubernetes/pkg/kubectl/resource/result.go
generated
vendored
Normal file
@ -0,0 +1,241 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
// ErrMatchFunc can be used to filter errors that may not be true failures.
|
||||
type ErrMatchFunc func(error) bool
|
||||
|
||||
// Result contains helper methods for dealing with the outcome of a Builder.
|
||||
type Result struct {
|
||||
err error
|
||||
visitor Visitor
|
||||
|
||||
sources []Visitor
|
||||
singleItemImplied bool
|
||||
targetsSingleItems bool
|
||||
|
||||
mapper *Mapper
|
||||
ignoreErrors []utilerrors.Matcher
|
||||
|
||||
// populated by a call to Infos
|
||||
info []*Info
|
||||
}
|
||||
|
||||
// withError allows a fluent style for internal result code.
|
||||
func (r *Result) withError(err error) *Result {
|
||||
r.err = err
|
||||
return r
|
||||
}
|
||||
|
||||
// TargetsSingleItems returns true if any of the builder arguments pointed
|
||||
// to non-list calls (if the user explicitly asked for any object by name).
|
||||
// This includes directories, streams, URLs, and resource name tuples.
|
||||
func (r *Result) TargetsSingleItems() bool {
|
||||
return r.targetsSingleItems
|
||||
}
|
||||
|
||||
// IgnoreErrors will filter errors that occur when by visiting the result
|
||||
// (but not errors that occur by creating the result in the first place),
|
||||
// eliminating any that match fns. This is best used in combination with
|
||||
// Builder.ContinueOnError(), where the visitors accumulate errors and return
|
||||
// them after visiting as a slice of errors. If no errors remain after
|
||||
// filtering, the various visitor methods on Result will return nil for
|
||||
// err.
|
||||
func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result {
|
||||
for _, fn := range fns {
|
||||
r.ignoreErrors = append(r.ignoreErrors, utilerrors.Matcher(fn))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Mapper returns a copy of the builder's mapper.
|
||||
func (r *Result) Mapper() *Mapper {
|
||||
return r.mapper
|
||||
}
|
||||
|
||||
// Err returns one or more errors (via a util.ErrorList) that occurred prior
|
||||
// to visiting the elements in the visitor. To see all errors including those
|
||||
// that occur during visitation, invoke Infos().
|
||||
func (r *Result) Err() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
// Visit implements the Visitor interface on the items described in the Builder.
|
||||
// Note that some visitor sources are not traversable more than once, or may
|
||||
// return different results. If you wish to operate on the same set of resources
|
||||
// multiple times, use the Infos() method.
|
||||
func (r *Result) Visit(fn VisitorFunc) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
err := r.visitor.Visit(fn)
|
||||
return utilerrors.FilterOut(err, r.ignoreErrors...)
|
||||
}
|
||||
|
||||
// IntoSingleItemImplied sets the provided boolean pointer to true if the Builder input
|
||||
// implies a single item, or multiple.
|
||||
func (r *Result) IntoSingleItemImplied(b *bool) *Result {
|
||||
*b = r.singleItemImplied
|
||||
return r
|
||||
}
|
||||
|
||||
// Infos returns an array of all of the resource infos retrieved via traversal.
|
||||
// Will attempt to traverse the entire set of visitors only once, and will return
|
||||
// a cached list on subsequent calls.
|
||||
func (r *Result) Infos() ([]*Info, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.info != nil {
|
||||
return r.info, nil
|
||||
}
|
||||
|
||||
infos := []*Info{}
|
||||
err := r.visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
infos = append(infos, info)
|
||||
return nil
|
||||
})
|
||||
err = utilerrors.FilterOut(err, r.ignoreErrors...)
|
||||
|
||||
r.info, r.err = infos, err
|
||||
return infos, err
|
||||
}
|
||||
|
||||
// Object returns a single object representing the output of a single visit to all
|
||||
// found resources. If the Builder was a singular context (expected to return a
|
||||
// single resource by user input) and only a single resource was found, the resource
|
||||
// will be returned as is. Otherwise, the returned resources will be part of an
|
||||
// v1.List. The ResourceVersion of the v1.List will be set only if it is identical
|
||||
// across all infos returned.
|
||||
func (r *Result) Object() (runtime.Object, error) {
|
||||
infos, err := r.Infos()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
versions := sets.String{}
|
||||
objects := []runtime.Object{}
|
||||
for _, info := range infos {
|
||||
if info.Object != nil {
|
||||
objects = append(objects, info.Object)
|
||||
versions.Insert(info.ResourceVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if len(objects) == 1 {
|
||||
if r.singleItemImplied {
|
||||
return objects[0], nil
|
||||
}
|
||||
// if the item is a list already, don't create another list
|
||||
if meta.IsListType(objects[0]) {
|
||||
return objects[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
version := ""
|
||||
if len(versions) == 1 {
|
||||
version = versions.List()[0]
|
||||
}
|
||||
|
||||
return toV1List(objects, version), err
|
||||
}
|
||||
|
||||
// Compile time check to enforce that list implements the necessary interface
|
||||
var _ metav1.ListInterface = &v1.List{}
|
||||
var _ metav1.ListMetaAccessor = &v1.List{}
|
||||
|
||||
// toV1List takes a slice of Objects + their version, and returns
|
||||
// a v1.List Object containing the objects in the Items field
|
||||
func toV1List(objects []runtime.Object, version string) runtime.Object {
|
||||
raw := []runtime.RawExtension{}
|
||||
for _, o := range objects {
|
||||
raw = append(raw, runtime.RawExtension{Object: o})
|
||||
}
|
||||
return &v1.List{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: version,
|
||||
},
|
||||
Items: raw,
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceMapping returns a single meta.RESTMapping representing the
|
||||
// resources located by the builder, or an error if more than one
|
||||
// mapping was found.
|
||||
func (r *Result) ResourceMapping() (*meta.RESTMapping, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
mappings := map[string]*meta.RESTMapping{}
|
||||
for i := range r.sources {
|
||||
m, ok := r.sources[i].(ResourceMapping)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("a resource mapping could not be loaded from %v", reflect.TypeOf(r.sources[i]))
|
||||
}
|
||||
mapping := m.ResourceMapping()
|
||||
mappings[mapping.Resource] = mapping
|
||||
}
|
||||
if len(mappings) != 1 {
|
||||
return nil, fmt.Errorf("expected only a single resource type")
|
||||
}
|
||||
for _, mapping := range mappings {
|
||||
return mapping, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Watch retrieves changes that occur on the server to the specified resource.
|
||||
// It currently supports watching a single source - if the resource source
|
||||
// (selectors or pure types) can be watched, they will be, otherwise the list
|
||||
// will be visited (equivalent to the Infos() call) and if there is a single
|
||||
// resource present, it will be watched, otherwise an error will be returned.
|
||||
func (r *Result) Watch(resourceVersion string) (watch.Interface, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if len(r.sources) != 1 {
|
||||
return nil, fmt.Errorf("you may only watch a single resource or type of resource at a time")
|
||||
}
|
||||
w, ok := r.sources[0].(Watchable)
|
||||
if !ok {
|
||||
info, err := r.Infos()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(info) != 1 {
|
||||
return nil, fmt.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(info))
|
||||
}
|
||||
return info[0].Watch(resourceVersion)
|
||||
}
|
||||
return w.Watch(resourceVersion)
|
||||
}
|
122
vendor/k8s.io/kubernetes/pkg/kubectl/resource/selector.go
generated
vendored
Normal file
122
vendor/k8s.io/kubernetes/pkg/kubectl/resource/selector.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
// Selector is a Visitor for resources that match a label selector.
|
||||
type Selector struct {
|
||||
Client RESTClient
|
||||
Mapping *meta.RESTMapping
|
||||
Namespace string
|
||||
LabelSelector string
|
||||
FieldSelector string
|
||||
Export bool
|
||||
IncludeUninitialized bool
|
||||
LimitChunks int64
|
||||
}
|
||||
|
||||
// NewSelector creates a resource selector which hides details of getting items by their label selector.
|
||||
func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace, labelSelector, fieldSelector string, export, includeUninitialized bool, limitChunks int64) *Selector {
|
||||
return &Selector{
|
||||
Client: client,
|
||||
Mapping: mapping,
|
||||
Namespace: namespace,
|
||||
LabelSelector: labelSelector,
|
||||
FieldSelector: fieldSelector,
|
||||
Export: export,
|
||||
IncludeUninitialized: includeUninitialized,
|
||||
LimitChunks: limitChunks,
|
||||
}
|
||||
}
|
||||
|
||||
// Visit implements Visitor and uses request chunking by default.
|
||||
func (r *Selector) Visit(fn VisitorFunc) error {
|
||||
var continueToken string
|
||||
for {
|
||||
list, err := NewHelper(r.Client, r.Mapping).List(
|
||||
r.Namespace,
|
||||
r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
|
||||
r.Export,
|
||||
&metav1.ListOptions{
|
||||
LabelSelector: r.LabelSelector,
|
||||
FieldSelector: r.FieldSelector,
|
||||
IncludeUninitialized: r.IncludeUninitialized,
|
||||
Limit: r.LimitChunks,
|
||||
Continue: continueToken,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.IsResourceExpired(err) {
|
||||
return err
|
||||
}
|
||||
if errors.IsBadRequest(err) || errors.IsNotFound(err) {
|
||||
if se, ok := err.(*errors.StatusError); ok {
|
||||
// modify the message without hiding this is an API error
|
||||
if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 {
|
||||
se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", r.Mapping.Resource, se.ErrStatus.Message)
|
||||
} else {
|
||||
se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, se.ErrStatus.Message)
|
||||
}
|
||||
return se
|
||||
}
|
||||
if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 {
|
||||
return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err)
|
||||
}
|
||||
return fmt.Errorf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
accessor := r.Mapping.MetadataAccessor
|
||||
resourceVersion, _ := accessor.ResourceVersion(list)
|
||||
nextContinueToken, _ := accessor.Continue(list)
|
||||
info := &Info{
|
||||
Client: r.Client,
|
||||
Mapping: r.Mapping,
|
||||
|
||||
Namespace: r.Namespace,
|
||||
ResourceVersion: resourceVersion,
|
||||
|
||||
Object: list,
|
||||
}
|
||||
|
||||
if err := fn(info, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(nextContinueToken) == 0 {
|
||||
return nil
|
||||
}
|
||||
continueToken = nextContinueToken
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) {
|
||||
return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
|
||||
&metav1.ListOptions{ResourceVersion: resourceVersion, LabelSelector: r.LabelSelector, FieldSelector: r.FieldSelector})
|
||||
}
|
||||
|
||||
// ResourceMapping returns the mapping for this resource and implements ResourceMapping
|
||||
func (r *Selector) ResourceMapping() *meta.RESTMapping {
|
||||
return r.Mapping
|
||||
}
|
763
vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor.go
generated
vendored
Normal file
763
vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor.go
generated
vendored
Normal file
@ -0,0 +1,763 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/kubectl/validation"
|
||||
)
|
||||
|
||||
const (
|
||||
constSTDINstr string = "STDIN"
|
||||
stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false"
|
||||
)
|
||||
|
||||
// Visitor lets clients walk a list of resources.
|
||||
type Visitor interface {
|
||||
Visit(VisitorFunc) error
|
||||
}
|
||||
|
||||
// VisitorFunc implements the Visitor interface for a matching function.
|
||||
// If there was a problem walking a list of resources, the incoming error
|
||||
// will describe the problem and the function can decide how to handle that error.
|
||||
// A nil returned indicates to accept an error to continue loops even when errors happen.
|
||||
// This is useful for ignoring certain kinds of errors or aggregating errors in some way.
|
||||
type VisitorFunc func(*Info, error) error
|
||||
|
||||
// Watchable describes a resource that can be watched for changes that occur on the server,
|
||||
// beginning after the provided resource version.
|
||||
type Watchable interface {
|
||||
Watch(resourceVersion string) (watch.Interface, error)
|
||||
}
|
||||
|
||||
// ResourceMapping allows an object to return the resource mapping associated with
|
||||
// the resource or resources it represents.
|
||||
type ResourceMapping interface {
|
||||
ResourceMapping() *meta.RESTMapping
|
||||
}
|
||||
|
||||
// Info contains temporary info to execute a REST call, or show the results
|
||||
// of an already completed REST call.
|
||||
type Info struct {
|
||||
Client RESTClient
|
||||
// Mapping may be nil if the object has no available metadata, but is still parseable
|
||||
// from disk.
|
||||
Mapping *meta.RESTMapping
|
||||
// Namespace will be set if the object is namespaced and has a specified value.
|
||||
Namespace string
|
||||
Name string
|
||||
|
||||
// Optional, Source is the filename or URL to template file (.json or .yaml),
|
||||
// or stdin to use to handle the resource
|
||||
Source string
|
||||
// Optional, this is the most recent value returned by the server if available. It will
|
||||
// typically be in unstructured or internal forms, depending on how the Builder was
|
||||
// defined. If retrieved from the server, the Builder expects the mapping client to
|
||||
// decide the final form. Use the AsVersioned, AsUnstructured, and AsInternal helpers
|
||||
// to alter the object versions.
|
||||
Object runtime.Object
|
||||
// Optional, this is the most recent resource version the server knows about for
|
||||
// this type of resource. It may not match the resource version of the object,
|
||||
// but if set it should be equal to or newer than the resource version of the
|
||||
// object (however the server defines resource version).
|
||||
ResourceVersion string
|
||||
// Optional, should this resource be exported, stripped of cluster-specific and instance specific fields
|
||||
Export bool
|
||||
}
|
||||
|
||||
// Visit implements Visitor
|
||||
func (i *Info) Visit(fn VisitorFunc) error {
|
||||
return fn(i, nil)
|
||||
}
|
||||
|
||||
// Get retrieves the object from the Namespace and Name fields
|
||||
func (i *Info) Get() (err error) {
|
||||
obj, err := NewHelper(i.Client, i.Mapping).Get(i.Namespace, i.Name, i.Export)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) && len(i.Namespace) > 0 && i.Namespace != metav1.NamespaceDefault && i.Namespace != metav1.NamespaceAll {
|
||||
err2 := i.Client.Get().AbsPath("api", "v1", "namespaces", i.Namespace).Do().Error()
|
||||
if err2 != nil && errors.IsNotFound(err2) {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
i.Object = obj
|
||||
i.ResourceVersion, _ = i.Mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refresh updates the object with another object. If ignoreError is set
|
||||
// the Object will be updated even if name, namespace, or resourceVersion
|
||||
// attributes cannot be loaded from the object.
|
||||
func (i *Info) Refresh(obj runtime.Object, ignoreError bool) error {
|
||||
name, err := i.Mapping.MetadataAccessor.Name(obj)
|
||||
if err != nil {
|
||||
if !ignoreError {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i.Name = name
|
||||
}
|
||||
namespace, err := i.Mapping.MetadataAccessor.Namespace(obj)
|
||||
if err != nil {
|
||||
if !ignoreError {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i.Namespace = namespace
|
||||
}
|
||||
version, err := i.Mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
if !ignoreError {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i.ResourceVersion = version
|
||||
}
|
||||
i.Object = obj
|
||||
return nil
|
||||
}
|
||||
|
||||
// Namespaced returns true if the object belongs to a namespace
|
||||
func (i *Info) Namespaced() bool {
|
||||
return i.Mapping != nil && i.Mapping.Scope.Name() == meta.RESTScopeNameNamespace
|
||||
}
|
||||
|
||||
// Watch returns server changes to this object after it was retrieved.
|
||||
func (i *Info) Watch(resourceVersion string) (watch.Interface, error) {
|
||||
return NewHelper(i.Client, i.Mapping).WatchSingle(i.Namespace, i.Name, resourceVersion)
|
||||
}
|
||||
|
||||
// ResourceMapping returns the mapping for this resource and implements ResourceMapping
|
||||
func (i *Info) ResourceMapping() *meta.RESTMapping {
|
||||
return i.Mapping
|
||||
}
|
||||
|
||||
// Internal attempts to convert the provided object to an internal type or returns an error.
|
||||
func (i *Info) Internal() (runtime.Object, error) {
|
||||
return i.Mapping.ConvertToVersion(i.Object, i.Mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion())
|
||||
}
|
||||
|
||||
// AsInternal returns the object in internal form if possible, or i.Object if it cannot be
|
||||
// converted.
|
||||
func (i *Info) AsInternal() runtime.Object {
|
||||
if obj, err := i.Internal(); err == nil {
|
||||
return obj
|
||||
}
|
||||
return i.Object
|
||||
}
|
||||
|
||||
// Versioned returns the object as a Go type in the mapping's version or returns an error.
|
||||
func (i *Info) Versioned() (runtime.Object, error) {
|
||||
return i.Mapping.ConvertToVersion(i.Object, i.Mapping.GroupVersionKind.GroupVersion())
|
||||
}
|
||||
|
||||
// AsVersioned returns the object as a Go object in the external form if possible (matching the
|
||||
// group version kind of the mapping, or i.Object if it cannot be converted.
|
||||
func (i *Info) AsVersioned() runtime.Object {
|
||||
if obj, err := i.Versioned(); err == nil {
|
||||
return obj
|
||||
}
|
||||
return i.Object
|
||||
}
|
||||
|
||||
// Unstructured returns the current object in unstructured form (as a runtime.Unstructured)
|
||||
func (i *Info) Unstructured() (runtime.Unstructured, error) {
|
||||
switch t := i.Object.(type) {
|
||||
case runtime.Unstructured:
|
||||
return t, nil
|
||||
case *runtime.Unknown:
|
||||
gvk := i.Mapping.GroupVersionKind
|
||||
out, _, err := unstructured.UnstructuredJSONScheme.Decode(t.Raw, &gvk, nil)
|
||||
return out.(runtime.Unstructured), err
|
||||
default:
|
||||
out := &unstructured.Unstructured{}
|
||||
if err := i.Mapping.Convert(i.Object, out, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
|
||||
// AsUnstructured returns the object as a Go object in external form as a runtime.Unstructured
|
||||
// (map of JSON equivalent values) or as i.Object if it cannot be converted.
|
||||
func (i *Info) AsUnstructured() runtime.Object {
|
||||
if out, err := i.Unstructured(); err == nil {
|
||||
return out
|
||||
}
|
||||
return i.Object
|
||||
}
|
||||
|
||||
// VisitorList implements Visit for the sub visitors it contains. The first error
|
||||
// returned from a child Visitor will terminate iteration.
|
||||
type VisitorList []Visitor
|
||||
|
||||
// Visit implements Visitor
|
||||
func (l VisitorList) Visit(fn VisitorFunc) error {
|
||||
for i := range l {
|
||||
if err := l[i].Visit(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EagerVisitorList implements Visit for the sub visitors it contains. All errors
|
||||
// will be captured and returned at the end of iteration.
|
||||
type EagerVisitorList []Visitor
|
||||
|
||||
// Visit implements Visitor, and gathers errors that occur during processing until
|
||||
// all sub visitors have been visited.
|
||||
func (l EagerVisitorList) Visit(fn VisitorFunc) error {
|
||||
errs := []error(nil)
|
||||
for i := range l {
|
||||
if err := l[i].Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
if err := fn(info, nil); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func ValidateSchema(data []byte, schema validation.Schema) error {
|
||||
if schema == nil {
|
||||
return nil
|
||||
}
|
||||
if err := schema.ValidateBytes(data); err != nil {
|
||||
return fmt.Errorf("error validating data: %v; %s", err, stopValidateMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// URLVisitor downloads the contents of a URL, and if successful, returns
|
||||
// an info object representing the downloaded object.
|
||||
type URLVisitor struct {
|
||||
URL *url.URL
|
||||
*StreamVisitor
|
||||
HttpAttemptCount int
|
||||
}
|
||||
|
||||
func (v *URLVisitor) Visit(fn VisitorFunc) error {
|
||||
body, err := readHttpWithRetries(httpgetImpl, time.Second, v.URL.String(), v.HttpAttemptCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer body.Close()
|
||||
v.StreamVisitor.Reader = body
|
||||
return v.StreamVisitor.Visit(fn)
|
||||
}
|
||||
|
||||
// readHttpWithRetries tries to http.Get the v.URL retries times before giving up.
|
||||
func readHttpWithRetries(get httpget, duration time.Duration, u string, attempts int) (io.ReadCloser, error) {
|
||||
var err error
|
||||
var body io.ReadCloser
|
||||
if attempts <= 0 {
|
||||
return nil, fmt.Errorf("http attempts must be greater than 0, was %d", attempts)
|
||||
}
|
||||
for i := 0; i < attempts; i++ {
|
||||
var statusCode int
|
||||
var status string
|
||||
if i > 0 {
|
||||
time.Sleep(duration)
|
||||
}
|
||||
|
||||
// Try to get the URL
|
||||
statusCode, status, body, err = get(u)
|
||||
|
||||
// Retry Errors
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Error - Set the error condition from the StatusCode
|
||||
if statusCode != http.StatusOK {
|
||||
err = fmt.Errorf("unable to read URL %q, server reported %s, status code=%d", u, status, statusCode)
|
||||
}
|
||||
|
||||
if statusCode >= 500 && statusCode < 600 {
|
||||
// Retry 500's
|
||||
continue
|
||||
} else {
|
||||
// Don't retry other StatusCodes
|
||||
break
|
||||
}
|
||||
}
|
||||
return body, err
|
||||
}
|
||||
|
||||
// httpget Defines function to retrieve a url and return the results. Exists for unit test stubbing.
|
||||
type httpget func(url string) (int, string, io.ReadCloser, error)
|
||||
|
||||
// httpgetImpl Implements a function to retrieve a url and return the results.
|
||||
func httpgetImpl(url string) (int, string, io.ReadCloser, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return 0, "", nil, err
|
||||
}
|
||||
return resp.StatusCode, resp.Status, resp.Body, nil
|
||||
}
|
||||
|
||||
// DecoratedVisitor will invoke the decorators in order prior to invoking the visitor function
|
||||
// passed to Visit. An error will terminate the visit.
|
||||
type DecoratedVisitor struct {
|
||||
visitor Visitor
|
||||
decorators []VisitorFunc
|
||||
}
|
||||
|
||||
// NewDecoratedVisitor will create a visitor that invokes the provided visitor functions before
|
||||
// the user supplied visitor function is invoked, giving them the opportunity to mutate the Info
|
||||
// object or terminate early with an error.
|
||||
func NewDecoratedVisitor(v Visitor, fn ...VisitorFunc) Visitor {
|
||||
if len(fn) == 0 {
|
||||
return v
|
||||
}
|
||||
return DecoratedVisitor{v, fn}
|
||||
}
|
||||
|
||||
// Visit implements Visitor
|
||||
func (v DecoratedVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range v.decorators {
|
||||
if err := v.decorators[i](info, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fn(info, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// ContinueOnErrorVisitor visits each item and, if an error occurs on
|
||||
// any individual item, returns an aggregate error after all items
|
||||
// are visited.
|
||||
type ContinueOnErrorVisitor struct {
|
||||
Visitor
|
||||
}
|
||||
|
||||
// Visit returns nil if no error occurs during traversal, a regular
|
||||
// error if one occurs, or if multiple errors occur, an aggregate
|
||||
// error. If the provided visitor fails on any individual item it
|
||||
// will not prevent the remaining items from being visited. An error
|
||||
// returned by the visitor directly may still result in some items
|
||||
// not being visited.
|
||||
func (v ContinueOnErrorVisitor) Visit(fn VisitorFunc) error {
|
||||
errs := []error{}
|
||||
err := v.Visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
if err := fn(info, nil); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if len(errs) == 1 {
|
||||
return errs[0]
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// FlattenListVisitor flattens any objects that runtime.ExtractList recognizes as a list
|
||||
// - has an "Items" public field that is a slice of runtime.Objects or objects satisfying
|
||||
// that interface - into multiple Infos. An error on any sub item (for instance, if a List
|
||||
// contains an object that does not have a registered client or resource) will terminate
|
||||
// the visit.
|
||||
// TODO: allow errors to be aggregated?
|
||||
type FlattenListVisitor struct {
|
||||
Visitor
|
||||
*Mapper
|
||||
}
|
||||
|
||||
// NewFlattenListVisitor creates a visitor that will expand list style runtime.Objects
|
||||
// into individual items and then visit them individually.
|
||||
func NewFlattenListVisitor(v Visitor, mapper *Mapper) Visitor {
|
||||
return FlattenListVisitor{v, mapper}
|
||||
}
|
||||
|
||||
func (v FlattenListVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.Visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Object == nil {
|
||||
return fn(info, nil)
|
||||
}
|
||||
items, err := meta.ExtractList(info.Object)
|
||||
if err != nil {
|
||||
return fn(info, nil)
|
||||
}
|
||||
if errs := runtime.DecodeList(items, v.Mapper.Decoder); len(errs) > 0 {
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// If we have a GroupVersionKind on the list, prioritize that when asking for info on the objects contained in the list
|
||||
var preferredGVKs []schema.GroupVersionKind
|
||||
if info.Mapping != nil && !info.Mapping.GroupVersionKind.Empty() {
|
||||
preferredGVKs = append(preferredGVKs, info.Mapping.GroupVersionKind)
|
||||
}
|
||||
|
||||
for i := range items {
|
||||
item, err := v.InfoForObject(items[i], preferredGVKs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(info.ResourceVersion) != 0 {
|
||||
item.ResourceVersion = info.ResourceVersion
|
||||
}
|
||||
if err := fn(item, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func ignoreFile(path string, extensions []string) bool {
|
||||
if len(extensions) == 0 {
|
||||
return false
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
for _, s := range extensions {
|
||||
if s == ext {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FileVisitorForSTDIN return a special FileVisitor just for STDIN
|
||||
func FileVisitorForSTDIN(mapper *Mapper, schema validation.Schema) Visitor {
|
||||
return &FileVisitor{
|
||||
Path: constSTDINstr,
|
||||
StreamVisitor: NewStreamVisitor(nil, mapper, constSTDINstr, schema),
|
||||
}
|
||||
}
|
||||
|
||||
// ExpandPathsToFileVisitors will return a slice of FileVisitors that will handle files from the provided path.
|
||||
// After FileVisitors open the files, they will pass an io.Reader to a StreamVisitor to do the reading. (stdin
|
||||
// is also taken care of). Paths argument also accepts a single file, and will return a single visitor
|
||||
func ExpandPathsToFileVisitors(mapper *Mapper, paths string, recursive bool, extensions []string, schema validation.Schema) ([]Visitor, error) {
|
||||
var visitors []Visitor
|
||||
err := filepath.Walk(paths, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
if path != paths && !recursive {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Don't check extension if the filepath was passed explicitly
|
||||
if path != paths && ignoreFile(path, extensions) {
|
||||
return nil
|
||||
}
|
||||
|
||||
visitor := &FileVisitor{
|
||||
Path: path,
|
||||
StreamVisitor: NewStreamVisitor(nil, mapper, path, schema),
|
||||
}
|
||||
|
||||
visitors = append(visitors, visitor)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return visitors, nil
|
||||
}
|
||||
|
||||
// FileVisitor is wrapping around a StreamVisitor, to handle open/close files
|
||||
type FileVisitor struct {
|
||||
Path string
|
||||
*StreamVisitor
|
||||
}
|
||||
|
||||
// Visit in a FileVisitor is just taking care of opening/closing files
|
||||
func (v *FileVisitor) Visit(fn VisitorFunc) error {
|
||||
var f *os.File
|
||||
if v.Path == constSTDINstr {
|
||||
f = os.Stdin
|
||||
} else {
|
||||
var err error
|
||||
f, err = os.Open(v.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
// TODO: Consider adding a flag to force to UTF16, apparently some
|
||||
// Windows tools don't write the BOM
|
||||
utf16bom := unicode.BOMOverride(unicode.UTF8.NewDecoder())
|
||||
v.StreamVisitor.Reader = transform.NewReader(f, utf16bom)
|
||||
|
||||
return v.StreamVisitor.Visit(fn)
|
||||
}
|
||||
|
||||
// StreamVisitor reads objects from an io.Reader and walks them. A stream visitor can only be
|
||||
// visited once.
|
||||
// TODO: depends on objects being in JSON format before being passed to decode - need to implement
|
||||
// a stream decoder method on runtime.Codec to properly handle this.
|
||||
type StreamVisitor struct {
|
||||
io.Reader
|
||||
*Mapper
|
||||
|
||||
Source string
|
||||
Schema validation.Schema
|
||||
}
|
||||
|
||||
// NewStreamVisitor is a helper function that is useful when we want to change the fields of the struct but keep calls the same.
|
||||
func NewStreamVisitor(r io.Reader, mapper *Mapper, source string, schema validation.Schema) *StreamVisitor {
|
||||
return &StreamVisitor{
|
||||
Reader: r,
|
||||
Mapper: mapper,
|
||||
Source: source,
|
||||
Schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
// Visit implements Visitor over a stream. StreamVisitor is able to distinct multiple resources in one stream.
|
||||
func (v *StreamVisitor) Visit(fn VisitorFunc) error {
|
||||
d := yaml.NewYAMLOrJSONDecoder(v.Reader, 4096)
|
||||
for {
|
||||
ext := runtime.RawExtension{}
|
||||
if err := d.Decode(&ext); err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
// TODO: This needs to be able to handle object in other encodings and schemas.
|
||||
ext.Raw = bytes.TrimSpace(ext.Raw)
|
||||
if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
|
||||
continue
|
||||
}
|
||||
if err := ValidateSchema(ext.Raw, v.Schema); err != nil {
|
||||
return fmt.Errorf("error validating %q: %v", v.Source, err)
|
||||
}
|
||||
info, err := v.InfoForData(ext.Raw, v.Source)
|
||||
if err != nil {
|
||||
if fnErr := fn(info, err); fnErr != nil {
|
||||
return fnErr
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := fn(info, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateObjectNamespace(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Object != nil {
|
||||
return info.Mapping.MetadataAccessor.SetNamespace(info.Object, info.Namespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterNamespace omits the namespace if the object is not namespace scoped
|
||||
func FilterNamespace(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Namespaced() {
|
||||
info.Namespace = ""
|
||||
UpdateObjectNamespace(info, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetNamespace ensures that every Info object visited will have a namespace
|
||||
// set. If info.Object is set, it will be mutated as well.
|
||||
func SetNamespace(namespace string) VisitorFunc {
|
||||
return func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Namespaced() {
|
||||
return nil
|
||||
}
|
||||
if len(info.Namespace) == 0 {
|
||||
info.Namespace = namespace
|
||||
UpdateObjectNamespace(info, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RequireNamespace will either set a namespace if none is provided on the
|
||||
// Info object, or if the namespace is set and does not match the provided
|
||||
// value, returns an error. This is intended to guard against administrators
|
||||
// accidentally operating on resources outside their namespace.
|
||||
func RequireNamespace(namespace string) VisitorFunc {
|
||||
return func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Namespaced() {
|
||||
return nil
|
||||
}
|
||||
if len(info.Namespace) == 0 {
|
||||
info.Namespace = namespace
|
||||
UpdateObjectNamespace(info, nil)
|
||||
return nil
|
||||
}
|
||||
if info.Namespace != namespace {
|
||||
return fmt.Errorf("the namespace from the provided object %q does not match the namespace %q. You must pass '--namespace=%s' to perform this operation.", info.Namespace, namespace, info.Namespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RetrieveLatest updates the Object on each Info by invoking a standard client
|
||||
// Get.
|
||||
func RetrieveLatest(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if meta.IsListType(info.Object) {
|
||||
return fmt.Errorf("watch is only supported on individual resources and resource collections, but a list of resources is found")
|
||||
}
|
||||
if len(info.Name) == 0 {
|
||||
return nil
|
||||
}
|
||||
if info.Namespaced() && len(info.Namespace) == 0 {
|
||||
return fmt.Errorf("no namespace set on resource %s %q", info.Mapping.Resource, info.Name)
|
||||
}
|
||||
return info.Get()
|
||||
}
|
||||
|
||||
// RetrieveLazy updates the object if it has not been loaded yet.
|
||||
func RetrieveLazy(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Object == nil {
|
||||
return info.Get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAndRefresh creates an object from input info and refreshes info with that object
|
||||
func CreateAndRefresh(info *Info) error {
|
||||
obj, err := NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.Refresh(obj, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
type FilterFunc func(info *Info, err error) (bool, error)
|
||||
|
||||
type FilteredVisitor struct {
|
||||
visitor Visitor
|
||||
filters []FilterFunc
|
||||
}
|
||||
|
||||
func NewFilteredVisitor(v Visitor, fn ...FilterFunc) Visitor {
|
||||
if len(fn) == 0 {
|
||||
return v
|
||||
}
|
||||
return FilteredVisitor{v, fn}
|
||||
}
|
||||
|
||||
func (v FilteredVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, filter := range v.filters {
|
||||
ok, err := filter(info, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fn(info, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func FilterByLabelSelector(s labels.Selector) FilterFunc {
|
||||
return func(info *Info, err error) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
a, err := meta.Accessor(info.Object)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !s.Matches(labels.Set(a.GetLabels())) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
type InfoListVisitor []*Info
|
||||
|
||||
func (infos InfoListVisitor) Visit(fn VisitorFunc) error {
|
||||
var err error
|
||||
for _, i := range infos {
|
||||
err = fn(i, err)
|
||||
}
|
||||
return err
|
||||
}
|
102
vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor_test.go
generated
vendored
Normal file
102
vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor_test.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright 2016 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 resource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVisitorHttpGet(t *testing.T) {
|
||||
// Test retries on errors
|
||||
i := 0
|
||||
expectedErr := fmt.Errorf("Failed to get http")
|
||||
actualBytes, actualErr := readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
if i > 2 {
|
||||
return 0, "", nil, expectedErr
|
||||
}
|
||||
return 0, "", nil, fmt.Errorf("Unexpected error")
|
||||
}, 0, "hello", 3)
|
||||
assert.Equal(t, expectedErr, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 3, i)
|
||||
|
||||
// Test that 500s are retried.
|
||||
i = 0
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
return 501, "Status", nil, nil
|
||||
}, 0, "hello", 3)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 3, i)
|
||||
|
||||
// Test that 300s are not retried
|
||||
i = 0
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
return 300, "Status", nil, nil
|
||||
}, 0, "hello", 3)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 1, i)
|
||||
|
||||
// Test attempt count is respected
|
||||
i = 0
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
return 501, "Status", nil, nil
|
||||
}, 0, "hello", 1)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 1, i)
|
||||
|
||||
// Test attempts less than 1 results in an error
|
||||
i = 0
|
||||
b := bytes.Buffer{}
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
return 200, "Status", ioutil.NopCloser(&b), nil
|
||||
}, 0, "hello", 0)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 0, i)
|
||||
|
||||
// Test Success
|
||||
i = 0
|
||||
b = bytes.Buffer{}
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
if i > 1 {
|
||||
return 200, "Status", ioutil.NopCloser(&b), nil
|
||||
}
|
||||
return 501, "Status", nil, nil
|
||||
}, 0, "hello", 3)
|
||||
assert.Nil(t, actualErr)
|
||||
assert.NotNil(t, actualBytes)
|
||||
assert.Equal(t, 2, i)
|
||||
}
|
Reference in New Issue
Block a user