vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

97
vendor/k8s.io/kubernetes/pkg/kubectl/resource/BUILD generated vendored Normal file
View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

24
vendor/k8s.io/kubernetes/pkg/kubectl/resource/doc.go generated vendored Normal file
View 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
View 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()
}

View 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))
}
}
}

View 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
View 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
View 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)
}

View 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
}

View 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
}

View 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)
}