Commit b8333bde authored by Clayton Coleman's avatar Clayton Coleman

Merge pull request #3152 from smarterclayton/resource_args_builder

Let Kubectl deal with many objects at the same time
parents 0105a48b 8a4f2259
......@@ -19,11 +19,15 @@ package main
import (
"os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
"github.com/golang/glog"
)
func main() {
clientBuilder := clientcmd.NewInteractiveClientConfig(clientcmd.Config{}, "", &clientcmd.ConfigOverrides{}, os.Stdin)
cmd.NewFactory(clientBuilder).Run(os.Stdout)
cmd := cmd.NewFactory().NewKubectlCommand(os.Stdout)
if err := cmd.Execute(); err != nil {
glog.Errorf("error: %v", err)
os.Exit(1)
}
}
......@@ -100,7 +100,7 @@ func NewTestFactory() (*Factory, *testFactory, runtime.Codec) {
return &Factory{
Mapper: mapper,
Typer: scheme,
Client: func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error) {
RESTClient: func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error) {
return t.Client, t.Err
},
Describer: func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error) {
......
......@@ -20,7 +20,7 @@ import (
"fmt"
"io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/spf13/cobra"
)
......@@ -46,7 +46,7 @@ Examples:
schema, err := f.Validator(cmd)
checkErr(err)
mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema)
client, err := f.Client(cmd, mapping)
client, err := f.RESTClient(cmd, mapping)
checkErr(err)
// use the default namespace if not specified, or check for conflict with the file's namespace
......@@ -57,7 +57,7 @@ Examples:
checkErr(err)
}
err = kubectl.NewRESTHelper(client, mapping).Create(namespace, true, data)
err = resource.NewHelper(client, mapping).Create(namespace, true, data)
checkErr(err)
fmt.Fprintf(out, "%s\n", name)
},
......
......@@ -79,7 +79,7 @@ Examples:
<creates all resources listed in config.json>`,
Run: func(cmd *cobra.Command, args []string) {
clientFunc := func(mapper *meta.RESTMapping) (config.RESTClientPoster, error) {
client, err := f.Client(cmd, mapper)
client, err := f.RESTClient(cmd, mapper)
checkErr(err)
return client, nil
}
......
......@@ -23,7 +23,7 @@ import (
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
)
func (f *Factory) NewCmdDelete(out io.Writer) *cobra.Command {
......@@ -59,9 +59,9 @@ Examples:
checkErr(err)
selector := GetFlagString(cmd, "selector")
found := 0
ResourcesFromArgsOrFile(cmd, args, filename, selector, f.Typer, f.Mapper, f.Client, schema).Visit(func(r *ResourceInfo) error {
ResourcesFromArgsOrFile(cmd, args, filename, selector, f.Typer, f.Mapper, f.RESTClient, schema).Visit(func(r *resource.Info) error {
found++
if err := kubectl.NewRESTHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
return err
}
fmt.Fprintf(out, "%s\n", r.Name)
......
......@@ -21,7 +21,9 @@ import (
"io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/spf13/cobra"
)
......@@ -54,7 +56,7 @@ Examples:
labelSelector, err := labels.ParseSelector(selector)
checkErr(err)
client, err := f.Client(cmd, mapping)
client, err := f.RESTClient(cmd, mapping)
checkErr(err)
outputFormat := GetFlagString(cmd, "output")
......@@ -70,8 +72,13 @@ Examples:
printer, err := kubectl.GetPrinter(outputFormat, templateFile, outputVersion, mapping.ObjectConvertor, defaultPrinter)
checkErr(err)
restHelper := kubectl.NewRESTHelper(client, mapping)
obj, err := restHelper.Get(namespace, name, labelSelector)
restHelper := resource.NewHelper(client, mapping)
var obj runtime.Object
if len(name) == 0 {
obj, err = restHelper.List(namespace, labelSelector)
} else {
obj, err = restHelper.Get(namespace, name)
}
checkErr(err)
isWatch, isWatchOnly := GetFlagBool(cmd, "watch"), GetFlagBool(cmd, "watch-only")
......
......@@ -105,6 +105,7 @@ func FirstNonEmptyString(args ...string) string {
}
// Return a list of file names of a certain type within a given directory.
// TODO: replace with resource.Builder
func GetFilesFromDir(directory string, fileType string) []string {
files := []string{}
......@@ -121,6 +122,7 @@ func GetFilesFromDir(directory string, fileType string) []string {
// ReadConfigData reads the bytes from the specified filesytem or network
// location or from stdin if location == "-".
// TODO: replace with resource.Builder
func ReadConfigData(location string) ([]byte, error) {
if len(location) == 0 {
return nil, fmt.Errorf("location given but empty")
......@@ -144,6 +146,7 @@ func ReadConfigData(location string) ([]byte, error) {
return ReadConfigDataFromLocation(location)
}
// TODO: replace with resource.Builder
func ReadConfigDataFromLocation(location string) ([]byte, error) {
// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
if strings.Index(location, "http://") == 0 || strings.Index(location, "https://") == 0 {
......
......@@ -21,8 +21,6 @@ import (
"strconv"
"github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
)
func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
......@@ -46,9 +44,7 @@ Examples:
}
namespace := GetKubeNamespace(cmd)
config, err := f.ClientConfig.ClientConfig()
checkErr(err)
client, err := client.New(config)
client, err := f.Client(cmd)
checkErr(err)
podID := args[0]
......
......@@ -33,7 +33,7 @@ func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command {
port := GetFlagInt(cmd, "port")
glog.Infof("Starting to serve on localhost:%d", port)
clientConfig, err := f.ClientConfig.ClientConfig()
clientConfig, err := f.ClientConfig(cmd)
checkErr(err)
server, err := kubectl.NewProxyServer(GetFlagString(cmd, "www"), clientConfig, port)
......
......@@ -18,122 +18,19 @@ package cmd
import (
"fmt"
"log"
"strings"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
// ResourceInfo contains temporary info to execute REST call
type ResourceInfo struct {
Client kubectl.RESTClient
Mapping *meta.RESTMapping
Namespace string
Name string
// Optional, this is the most recent value returned by the server if available
runtime.Object
}
// ResourceVisitor lets clients walk the list of resources
type ResourceVisitor interface {
Visit(func(*ResourceInfo) error) error
}
type ResourceVisitorList []ResourceVisitor
// Visit implements ResourceVisitor
func (l ResourceVisitorList) Visit(fn func(r *ResourceInfo) error) error {
for i := range l {
if err := l[i].Visit(fn); err != nil {
return err
}
}
return nil
}
func NewResourceInfo(client kubectl.RESTClient, mapping *meta.RESTMapping, namespace, name string) *ResourceInfo {
return &ResourceInfo{
Client: client,
Mapping: mapping,
Namespace: namespace,
Name: name,
}
}
// Visit implements ResourceVisitor
func (r *ResourceInfo) Visit(fn func(r *ResourceInfo) error) error {
return fn(r)
}
// ResourceSelector is a facade for all the resources fetched via label selector
type ResourceSelector struct {
Client kubectl.RESTClient
Mapping *meta.RESTMapping
Namespace string
Selector labels.Selector
}
// NewResourceSelector creates a resource selector which hides details of getting items by their label selector.
func NewResourceSelector(client kubectl.RESTClient, mapping *meta.RESTMapping, namespace string, selector labels.Selector) *ResourceSelector {
return &ResourceSelector{
Client: client,
Mapping: mapping,
Namespace: namespace,
Selector: selector,
}
}
// Visit implements ResourceVisitor
func (r *ResourceSelector) Visit(fn func(r *ResourceInfo) error) error {
list, err := kubectl.NewRESTHelper(r.Client, r.Mapping).List(r.Namespace, r.Selector)
if err != nil {
if errors.IsBadRequest(err) || errors.IsNotFound(err) {
glog.V(2).Infof("Unable to perform a label selector query on %s with labels %s: %v", r.Mapping.Resource, r.Selector, err)
return nil
}
return err
}
items, err := runtime.ExtractList(list)
if err != nil {
return err
}
accessor := meta.NewAccessor()
for i := range items {
name, err := accessor.Name(items[i])
if err != nil {
// items without names cannot be visited
glog.V(2).Infof("Found %s with labels %s, but can't access the item by name.", r.Mapping.Resource, r.Selector)
continue
}
item := &ResourceInfo{
Client: r.Client,
Mapping: r.Mapping,
Namespace: r.Namespace,
Name: name,
Object: items[i],
}
if err := fn(item); err != nil {
if errors.IsNotFound(err) {
glog.V(2).Infof("Found %s named %q, but can't be accessed now: %v", r.Mapping.Resource, name, err)
return nil
}
log.Printf("got error for resource %s: %v", r.Mapping.Resource, err)
return err
}
}
return nil
}
// ResourcesFromArgsOrFile computes a list of Resources by extracting info from filename or args. It will
// handle label selectors provided.
func ResourcesFromArgsOrFile(
......@@ -144,7 +41,7 @@ func ResourcesFromArgsOrFile(
mapper meta.RESTMapper,
clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error),
schema validation.Schema,
) ResourceVisitor {
) resource.Visitor {
// handling filename & resource id
if len(selector) == 0 {
......@@ -152,34 +49,34 @@ func ResourcesFromArgsOrFile(
client, err := clientBuilder(cmd, mapping)
checkErr(err)
return NewResourceInfo(client, mapping, namespace, name)
return resource.NewInfo(client, mapping, namespace, name)
}
labelSelector, err := labels.ParseSelector(selector)
checkErr(err)
namespace := GetKubeNamespace(cmd)
visitors := ResourceVisitorList{}
visitors := resource.VisitorList{}
if len(args) != 1 {
usageError(cmd, "Must specify the type of resource")
}
types := SplitResourceArgument(args[0])
for _, arg := range types {
resource := kubectl.ExpandResourceShortcut(arg)
if len(resource) == 0 {
usageError(cmd, "Unknown resource %s", resource)
resourceName := arg
if len(resourceName) == 0 {
usageError(cmd, "Unknown resource %s", resourceName)
}
version, kind, err := mapper.VersionAndKindForResource(resource)
version, kind, err := mapper.VersionAndKindForResource(resourceName)
checkErr(err)
mapping, err := mapper.RESTMapping(version, kind)
mapping, err := mapper.RESTMapping(kind, version)
checkErr(err)
client, err := clientBuilder(cmd, mapping)
checkErr(err)
visitors = append(visitors, NewResourceSelector(client, mapping, namespace, labelSelector))
visitors = append(visitors, resource.NewSelector(client, mapping, namespace, labelSelector))
}
return visitors
}
......@@ -194,7 +91,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
}
if len(args) == 2 {
resource := kubectl.ExpandResourceShortcut(args[0])
resource := args[0]
namespace = GetKubeNamespace(cmd)
name = args[1]
if len(name) == 0 || len(resource) == 0 {
......@@ -232,7 +129,7 @@ func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper)
usageError(cmd, "Must provide resource and name command line params")
}
resource := kubectl.ExpandResourceShortcut(args[0])
resource := args[0]
namespace = GetKubeNamespace(cmd)
name = args[1]
if len(name) == 0 || len(resource) == 0 {
......@@ -255,7 +152,7 @@ func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTM
usageError(cmd, "Must provide resource or a resource and name as command line params")
}
resource := kubectl.ExpandResourceShortcut(args[0])
resource := args[0]
if len(resource) == 0 {
usageError(cmd, "Must provide resource or a resource and name as command line params")
}
......
......@@ -21,7 +21,6 @@ import (
"io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/spf13/cobra"
)
......@@ -69,9 +68,7 @@ $ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f -
err = CompareNamespaceFromFile(cmd, namespace)
checkErr(err)
config, err := f.ClientConfig.ClientConfig()
checkErr(err)
client, err := client.New(config)
client, err := f.Client(cmd)
checkErr(err)
obj, err := mapping.Codec.Decode(data)
......
......@@ -20,7 +20,7 @@ import (
"fmt"
"io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/spf13/cobra"
)
......@@ -46,13 +46,13 @@ Examples:
schema, err := f.Validator(cmd)
checkErr(err)
mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema)
client, err := f.Client(cmd, mapping)
client, err := f.RESTClient(cmd, mapping)
checkErr(err)
err = CompareNamespaceFromFile(cmd, namespace)
checkErr(err)
err = kubectl.NewRESTHelper(client, mapping).Update(namespace, name, true, data)
err = resource.NewHelper(client, mapping).Update(namespace, name, true, data)
checkErr(err)
fmt.Fprintf(out, "%s\n", name)
},
......
......@@ -21,7 +21,6 @@ import (
"github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
)
......@@ -32,14 +31,13 @@ func (f *Factory) NewCmdVersion(out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
if GetFlagBool(cmd, "client") {
kubectl.GetClientVersion(out)
} else {
config, err := f.ClientConfig.ClientConfig()
checkErr(err)
client, err := client.New(config)
checkErr(err)
kubectl.GetVersion(out, client)
return
}
client, err := f.Client(cmd)
checkErr(err)
kubectl.GetVersion(out, client)
},
}
cmd.Flags().BoolP("client", "c", false, "Client version only (no server required)")
......
......@@ -26,6 +26,7 @@ import (
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
......@@ -111,12 +112,23 @@ func makeImageList(spec *api.PodSpec) string {
return strings.Join(listOfImages(spec), ",")
}
// ExpandResourceShortcut will return the expanded version of resource
// ShortcutExpander is a RESTMapper that can be used for Kubernetes
// resources.
type ShortcutExpander struct {
meta.RESTMapper
}
// VersionAndKindForResource implements meta.RESTMapper. It expands the resource first, then invokes the wrapped
// mapper.
func (e ShortcutExpander) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
resource = expandResourceShortcut(resource)
return e.RESTMapper.VersionAndKindForResource(resource)
}
// expandResourceShortcut will return the expanded version of resource
// (something that a pkg/api/meta.RESTMapper can understand), if it is
// indeed a shortcut. Otherwise, will return resource unmodified.
// TODO: Combine with RESTMapper stuff to provide a general solution
// to this problem.
func ExpandResourceShortcut(resource string) string {
func expandResourceShortcut(resource string) string {
shortForms := map[string]string{
"po": "pods",
"rc": "replicationcontrollers",
......
/*
Copyright 2014 Google Inc. All rights reserved.
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
......@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
package resource
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
......@@ -23,9 +23,10 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
// RESTHelper provides methods for retrieving or mutating a RESTful
// Helper provides methods for retrieving or mutating a RESTful
// resource.
type RESTHelper struct {
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
......@@ -36,9 +37,9 @@ type RESTHelper struct {
Versioner runtime.ResourceVersioner
}
// NewRESTHelper creates a RESTHelper from a ResourceMapping
func NewRESTHelper(client RESTClient, mapping *meta.RESTMapping) *RESTHelper {
return &RESTHelper{
// NewHelper creates a Helper from a ResourceMapping
func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
return &Helper{
RESTClient: client,
Resource: mapping.Resource,
Codec: mapping.Codec,
......@@ -46,15 +47,25 @@ func NewRESTHelper(client RESTClient, mapping *meta.RESTMapping) *RESTHelper {
}
}
func (m *RESTHelper) Get(namespace, name string, selector labels.Selector) (runtime.Object, error) {
return m.RESTClient.Get().Resource(m.Resource).Namespace(namespace).Name(name).SelectorParam("labels", selector).Do().Get()
func (m *Helper) Get(namespace, name string) (runtime.Object, error) {
return m.RESTClient.Get().
Namespace(namespace).
Resource(m.Resource).
Name(name).
Do().
Get()
}
func (m *RESTHelper) List(namespace string, selector labels.Selector) (runtime.Object, error) {
return m.RESTClient.Get().Resource(m.Resource).Namespace(namespace).SelectorParam("labels", selector).Do().Get()
func (m *Helper) List(namespace string, selector labels.Selector) (runtime.Object, error) {
return m.RESTClient.Get().
Namespace(namespace).
Resource(m.Resource).
SelectorParam("labels", selector).
Do().
Get()
}
func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fieldSelector labels.Selector) (watch.Interface, error) {
func (m *Helper) Watch(namespace, resourceVersion string, labelSelector, fieldSelector labels.Selector) (watch.Interface, error) {
return m.RESTClient.Get().
Prefix("watch").
Namespace(namespace).
......@@ -65,11 +76,26 @@ func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fie
Watch()
}
func (m *RESTHelper) Delete(namespace, name string) error {
return m.RESTClient.Delete().Namespace(namespace).Resource(m.Resource).Name(name).Do().Error()
func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) {
return m.RESTClient.Get().
Prefix("watch").
Namespace(namespace).
Resource(m.Resource).
Name(name).
Param("resourceVersion", resourceVersion).
Watch()
}
func (m *Helper) Delete(namespace, name string) error {
return m.RESTClient.Delete().
Namespace(namespace).
Resource(m.Resource).
Name(name).
Do().
Error()
}
func (m *RESTHelper) Create(namespace string, modify bool, data []byte) error {
func (m *Helper) Create(namespace string, modify bool, data []byte) error {
if modify {
obj, err := m.Codec.Decode(data)
if err != nil {
......@@ -102,7 +128,7 @@ func createResource(c RESTClient, resource, namespace string, data []byte) error
return c.Post().Namespace(namespace).Resource(resource).Body(data).Do().Error()
}
func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte) error {
func (m *Helper) Update(namespace, name string, overwrite bool, data []byte) error {
c := m.RESTClient
obj, err := m.Codec.Decode(data)
......@@ -119,7 +145,7 @@ func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte)
}
if version == "" && overwrite {
// Retrieve the current version of the object to overwrite the server object
serverObj, err := c.Get().Resource(m.Resource).Name(name).Do().Get()
serverObj, err := c.Get().Namespace(namespace).Resource(m.Resource).Name(name).Do().Get()
if err != nil {
// The object does not exist, but we want it to be created
return updateResource(c, m.Resource, namespace, name, data)
......
......@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
package resource
import (
"bytes"
......@@ -46,7 +46,7 @@ func splitPath(path string) []string {
return strings.Split(path, "/")
}
func TestRESTHelperDelete(t *testing.T) {
func TestHelperDelete(t *testing.T) {
tests := []struct {
Err bool
Req func(*http.Request) bool
......@@ -93,7 +93,7 @@ func TestRESTHelperDelete(t *testing.T) {
Resp: test.Resp,
Err: test.HttpErr,
}
modifier := &RESTHelper{
modifier := &Helper{
RESTClient: client,
}
err := modifier.Delete("bar", "foo")
......@@ -109,7 +109,7 @@ func TestRESTHelperDelete(t *testing.T) {
}
}
func TestRESTHelperCreate(t *testing.T) {
func TestHelperCreate(t *testing.T) {
expectPost := func(req *http.Request) bool {
if req.Method != "POST" {
t.Errorf("unexpected method: %#v", req)
......@@ -178,7 +178,7 @@ func TestRESTHelperCreate(t *testing.T) {
if test.RespFunc != nil {
client.Client = test.RespFunc
}
modifier := &RESTHelper{
modifier := &Helper{
RESTClient: client,
Codec: testapi.Codec(),
Versioner: testapi.MetadataAccessor(),
......@@ -213,7 +213,7 @@ func TestRESTHelperCreate(t *testing.T) {
}
}
func TestRESTHelperGet(t *testing.T) {
func TestHelperGet(t *testing.T) {
tests := []struct {
Err bool
Req func(*http.Request) bool
......@@ -260,10 +260,10 @@ func TestRESTHelperGet(t *testing.T) {
Resp: test.Resp,
Err: test.HttpErr,
}
modifier := &RESTHelper{
modifier := &Helper{
RESTClient: client,
}
obj, err := modifier.Get("bar", "foo", labels.Everything())
obj, err := modifier.Get("bar", "foo")
if (err != nil) != test.Err {
t.Errorf("unexpected error: %t %v", test.Err, err)
}
......@@ -279,7 +279,77 @@ func TestRESTHelperGet(t *testing.T) {
}
}
func TestRESTHelperUpdate(t *testing.T) {
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,
Body: objBody(&api.Status{Status: api.StatusFailure}),
},
Err: true,
},
{
Resp: &http.Response{
StatusCode: http.StatusOK,
Body: objBody(&api.PodList{
Items: []api.Pod{{
ObjectMeta: api.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 != "/ns/bar" {
t.Errorf("url doesn't contain name: %#v", req.URL)
return false
}
if req.URL.Query().Get("labels") != 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 := &client.FakeRESTClient{
Codec: testapi.Codec(),
Resp: test.Resp,
Err: test.HttpErr,
}
modifier := &Helper{
RESTClient: client,
}
obj, err := modifier.List("bar", labels.SelectorFromSet(labels.Set{"foo": "baz"}))
if (err != nil) != test.Err {
t.Errorf("unexpected error: %t %v", test.Err, err)
}
if err != nil {
continue
}
if obj.(*api.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 TestHelperUpdate(t *testing.T) {
expectPut := func(req *http.Request) bool {
if req.Method != "PUT" {
t.Errorf("unexpected method: %#v", req)
......@@ -358,7 +428,7 @@ func TestRESTHelperUpdate(t *testing.T) {
if test.RespFunc != nil {
client.Client = test.RespFunc
}
modifier := &RESTHelper{
modifier := &Helper{
RESTClient: client,
Codec: testapi.Codec(),
Versioner: testapi.MetadataAccessor(),
......
/*
Copyright 2014 Google Inc. All rights reserved.
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 (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
)
// RESTClient is a client helper for dealing with RESTful resources
// in a generic way.
type RESTClient interface {
Get() *client.Request
Post() *client.Request
Delete() *client.Request
Put() *client.Request
}
// ClientMapper retrieves a client object for a given mapping
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)
}
/*
Copyright 2014 Google Inc. All rights reserved.
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"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
// Mapper is a convenience struct for holding references to the three interfaces
// needed to create Info for arbitrary objects.
type Mapper struct {
runtime.ObjectTyper
meta.RESTMapper
ClientMapper
}
// 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) {
version, kind, err := m.DataVersionAndKind(data)
if err != nil {
return nil, fmt.Errorf("unable to get type info from %q: %v", source, err)
}
mapping, err := m.RESTMapping(kind, version)
if err != nil {
return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
}
obj, err := mapping.Codec.Decode(data)
if err != nil {
return nil, fmt.Errorf("unable to load %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{
Mapping: mapping,
Client: client,
Namespace: namespace,
Name: name,
Object: obj,
ResourceVersion: resourceVersion,
}, nil
}
// InfoForData 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) (*Info, error) {
version, kind, err := m.ObjectVersionAndKind(obj)
if err != nil {
return nil, fmt.Errorf("unable to get type info from the object %q: %v", reflect.TypeOf(obj), err)
}
mapping, err := m.RESTMapping(kind, version)
if err != nil {
return nil, fmt.Errorf("unable to recognize %q: %v", kind, 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{
Mapping: mapping,
Client: client,
Namespace: namespace,
Name: name,
Object: obj,
ResourceVersion: resourceVersion,
}, nil
}
/*
Copyright 2014 Google Inc. All rights reserved.
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 (
"github.com/golang/glog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
// Selector is a Visitor for resources that match a label selector.
type Selector struct {
Client RESTClient
Mapping *meta.RESTMapping
Namespace string
Selector labels.Selector
}
// NewSelector creates a resource selector which hides details of getting items by their label selector.
func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace string, selector labels.Selector) *Selector {
return &Selector{
Client: client,
Mapping: mapping,
Namespace: namespace,
Selector: selector,
}
}
// Visit implements Visitor
func (r *Selector) Visit(fn VisitorFunc) error {
list, err := NewHelper(r.Client, r.Mapping).List(r.Namespace, r.Selector)
if err != nil {
if errors.IsBadRequest(err) || errors.IsNotFound(err) {
if r.Selector.Empty() {
glog.V(2).Infof("Unable to list %q: %v", r.Mapping.Resource, err)
} else {
glog.V(2).Infof("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, err)
}
return nil
}
return err
}
accessor := r.Mapping.MetadataAccessor
resourceVersion, _ := accessor.ResourceVersion(list)
info := &Info{
Client: r.Client,
Mapping: r.Mapping,
Namespace: r.Namespace,
Object: list,
ResourceVersion: resourceVersion,
}
return fn(info)
}
func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) {
return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, resourceVersion, r.Selector, labels.Everything())
}
// ResourceMapping returns the mapping for this resource and implements ResourceMapping
func (r *Selector) ResourceMapping() *meta.RESTMapping {
return r.Mapping
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment