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 ...@@ -19,11 +19,15 @@ package main
import ( import (
"os" "os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
"github.com/golang/glog"
) )
func main() { func main() {
clientBuilder := clientcmd.NewInteractiveClientConfig(clientcmd.Config{}, "", &clientcmd.ConfigOverrides{}, os.Stdin) cmd := cmd.NewFactory().NewKubectlCommand(os.Stdout)
cmd.NewFactory(clientBuilder).Run(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) { ...@@ -100,7 +100,7 @@ func NewTestFactory() (*Factory, *testFactory, runtime.Codec) {
return &Factory{ return &Factory{
Mapper: mapper, Mapper: mapper,
Typer: scheme, 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 return t.Client, t.Err
}, },
Describer: func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error) { Describer: func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error) {
......
...@@ -20,7 +20,7 @@ import ( ...@@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
...@@ -46,7 +46,7 @@ Examples: ...@@ -46,7 +46,7 @@ Examples:
schema, err := f.Validator(cmd) schema, err := f.Validator(cmd)
checkErr(err) checkErr(err)
mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema) 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) checkErr(err)
// use the default namespace if not specified, or check for conflict with the file's namespace // use the default namespace if not specified, or check for conflict with the file's namespace
...@@ -57,7 +57,7 @@ Examples: ...@@ -57,7 +57,7 @@ Examples:
checkErr(err) checkErr(err)
} }
err = kubectl.NewRESTHelper(client, mapping).Create(namespace, true, data) err = resource.NewHelper(client, mapping).Create(namespace, true, data)
checkErr(err) checkErr(err)
fmt.Fprintf(out, "%s\n", name) fmt.Fprintf(out, "%s\n", name)
}, },
......
...@@ -79,7 +79,7 @@ Examples: ...@@ -79,7 +79,7 @@ Examples:
<creates all resources listed in config.json>`, <creates all resources listed in config.json>`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
clientFunc := func(mapper *meta.RESTMapping) (config.RESTClientPoster, error) { clientFunc := func(mapper *meta.RESTMapping) (config.RESTClientPoster, error) {
client, err := f.Client(cmd, mapper) client, err := f.RESTClient(cmd, mapper)
checkErr(err) checkErr(err)
return client, nil return client, nil
} }
......
...@@ -23,7 +23,7 @@ import ( ...@@ -23,7 +23,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "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 { func (f *Factory) NewCmdDelete(out io.Writer) *cobra.Command {
...@@ -59,9 +59,9 @@ Examples: ...@@ -59,9 +59,9 @@ Examples:
checkErr(err) checkErr(err)
selector := GetFlagString(cmd, "selector") selector := GetFlagString(cmd, "selector")
found := 0 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++ 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 return err
} }
fmt.Fprintf(out, "%s\n", r.Name) fmt.Fprintf(out, "%s\n", r.Name)
......
...@@ -21,7 +21,9 @@ import ( ...@@ -21,7 +21,9 @@ import (
"io" "io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
...@@ -54,7 +56,7 @@ Examples: ...@@ -54,7 +56,7 @@ Examples:
labelSelector, err := labels.ParseSelector(selector) labelSelector, err := labels.ParseSelector(selector)
checkErr(err) checkErr(err)
client, err := f.Client(cmd, mapping) client, err := f.RESTClient(cmd, mapping)
checkErr(err) checkErr(err)
outputFormat := GetFlagString(cmd, "output") outputFormat := GetFlagString(cmd, "output")
...@@ -70,8 +72,13 @@ Examples: ...@@ -70,8 +72,13 @@ Examples:
printer, err := kubectl.GetPrinter(outputFormat, templateFile, outputVersion, mapping.ObjectConvertor, defaultPrinter) printer, err := kubectl.GetPrinter(outputFormat, templateFile, outputVersion, mapping.ObjectConvertor, defaultPrinter)
checkErr(err) checkErr(err)
restHelper := kubectl.NewRESTHelper(client, mapping) restHelper := resource.NewHelper(client, mapping)
obj, err := restHelper.Get(namespace, name, labelSelector) var obj runtime.Object
if len(name) == 0 {
obj, err = restHelper.List(namespace, labelSelector)
} else {
obj, err = restHelper.Get(namespace, name)
}
checkErr(err) checkErr(err)
isWatch, isWatchOnly := GetFlagBool(cmd, "watch"), GetFlagBool(cmd, "watch-only") isWatch, isWatchOnly := GetFlagBool(cmd, "watch"), GetFlagBool(cmd, "watch-only")
......
...@@ -105,6 +105,7 @@ func FirstNonEmptyString(args ...string) string { ...@@ -105,6 +105,7 @@ func FirstNonEmptyString(args ...string) string {
} }
// Return a list of file names of a certain type within a given directory. // 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 { func GetFilesFromDir(directory string, fileType string) []string {
files := []string{} files := []string{}
...@@ -121,6 +122,7 @@ func GetFilesFromDir(directory string, fileType string) []string { ...@@ -121,6 +122,7 @@ func GetFilesFromDir(directory string, fileType string) []string {
// ReadConfigData reads the bytes from the specified filesytem or network // ReadConfigData reads the bytes from the specified filesytem or network
// location or from stdin if location == "-". // location or from stdin if location == "-".
// TODO: replace with resource.Builder
func ReadConfigData(location string) ([]byte, error) { func ReadConfigData(location string) ([]byte, error) {
if len(location) == 0 { if len(location) == 0 {
return nil, fmt.Errorf("location given but empty") return nil, fmt.Errorf("location given but empty")
...@@ -144,6 +146,7 @@ func ReadConfigData(location string) ([]byte, error) { ...@@ -144,6 +146,7 @@ func ReadConfigData(location string) ([]byte, error) {
return ReadConfigDataFromLocation(location) return ReadConfigDataFromLocation(location)
} }
// TODO: replace with resource.Builder
func ReadConfigDataFromLocation(location string) ([]byte, error) { func ReadConfigDataFromLocation(location string) ([]byte, error) {
// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO // 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 { if strings.Index(location, "http://") == 0 || strings.Index(location, "https://") == 0 {
......
...@@ -21,8 +21,6 @@ import ( ...@@ -21,8 +21,6 @@ import (
"strconv" "strconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
) )
func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command { func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
...@@ -46,9 +44,7 @@ Examples: ...@@ -46,9 +44,7 @@ Examples:
} }
namespace := GetKubeNamespace(cmd) namespace := GetKubeNamespace(cmd)
config, err := f.ClientConfig.ClientConfig() client, err := f.Client(cmd)
checkErr(err)
client, err := client.New(config)
checkErr(err) checkErr(err)
podID := args[0] podID := args[0]
......
...@@ -33,7 +33,7 @@ func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command { ...@@ -33,7 +33,7 @@ func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command {
port := GetFlagInt(cmd, "port") port := GetFlagInt(cmd, "port")
glog.Infof("Starting to serve on localhost:%d", port) glog.Infof("Starting to serve on localhost:%d", port)
clientConfig, err := f.ClientConfig.ClientConfig() clientConfig, err := f.ClientConfig(cmd)
checkErr(err) checkErr(err)
server, err := kubectl.NewProxyServer(GetFlagString(cmd, "www"), clientConfig, port) server, err := kubectl.NewProxyServer(GetFlagString(cmd, "www"), clientConfig, port)
......
...@@ -18,122 +18,19 @@ package cmd ...@@ -18,122 +18,19 @@ package cmd
import ( import (
"fmt" "fmt"
"log"
"strings" "strings"
"github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "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 // ResourcesFromArgsOrFile computes a list of Resources by extracting info from filename or args. It will
// handle label selectors provided. // handle label selectors provided.
func ResourcesFromArgsOrFile( func ResourcesFromArgsOrFile(
...@@ -144,7 +41,7 @@ func ResourcesFromArgsOrFile( ...@@ -144,7 +41,7 @@ func ResourcesFromArgsOrFile(
mapper meta.RESTMapper, mapper meta.RESTMapper,
clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error), clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error),
schema validation.Schema, schema validation.Schema,
) ResourceVisitor { ) resource.Visitor {
// handling filename & resource id // handling filename & resource id
if len(selector) == 0 { if len(selector) == 0 {
...@@ -152,34 +49,34 @@ func ResourcesFromArgsOrFile( ...@@ -152,34 +49,34 @@ func ResourcesFromArgsOrFile(
client, err := clientBuilder(cmd, mapping) client, err := clientBuilder(cmd, mapping)
checkErr(err) checkErr(err)
return NewResourceInfo(client, mapping, namespace, name) return resource.NewInfo(client, mapping, namespace, name)
} }
labelSelector, err := labels.ParseSelector(selector) labelSelector, err := labels.ParseSelector(selector)
checkErr(err) checkErr(err)
namespace := GetKubeNamespace(cmd) namespace := GetKubeNamespace(cmd)
visitors := ResourceVisitorList{} visitors := resource.VisitorList{}
if len(args) != 1 { if len(args) != 1 {
usageError(cmd, "Must specify the type of resource") usageError(cmd, "Must specify the type of resource")
} }
types := SplitResourceArgument(args[0]) types := SplitResourceArgument(args[0])
for _, arg := range types { for _, arg := range types {
resource := kubectl.ExpandResourceShortcut(arg) resourceName := arg
if len(resource) == 0 { if len(resourceName) == 0 {
usageError(cmd, "Unknown resource %s", resource) usageError(cmd, "Unknown resource %s", resourceName)
} }
version, kind, err := mapper.VersionAndKindForResource(resource) version, kind, err := mapper.VersionAndKindForResource(resourceName)
checkErr(err) checkErr(err)
mapping, err := mapper.RESTMapping(version, kind) mapping, err := mapper.RESTMapping(kind, version)
checkErr(err) checkErr(err)
client, err := clientBuilder(cmd, mapping) client, err := clientBuilder(cmd, mapping)
checkErr(err) checkErr(err)
visitors = append(visitors, NewResourceSelector(client, mapping, namespace, labelSelector)) visitors = append(visitors, resource.NewSelector(client, mapping, namespace, labelSelector))
} }
return visitors return visitors
} }
...@@ -194,7 +91,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string, ...@@ -194,7 +91,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
} }
if len(args) == 2 { if len(args) == 2 {
resource := kubectl.ExpandResourceShortcut(args[0]) resource := args[0]
namespace = GetKubeNamespace(cmd) namespace = GetKubeNamespace(cmd)
name = args[1] name = args[1]
if len(name) == 0 || len(resource) == 0 { if len(name) == 0 || len(resource) == 0 {
...@@ -232,7 +129,7 @@ func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) ...@@ -232,7 +129,7 @@ func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper)
usageError(cmd, "Must provide resource and name command line params") usageError(cmd, "Must provide resource and name command line params")
} }
resource := kubectl.ExpandResourceShortcut(args[0]) resource := args[0]
namespace = GetKubeNamespace(cmd) namespace = GetKubeNamespace(cmd)
name = args[1] name = args[1]
if len(name) == 0 || len(resource) == 0 { if len(name) == 0 || len(resource) == 0 {
...@@ -255,7 +152,7 @@ func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTM ...@@ -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") 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 { if len(resource) == 0 {
usageError(cmd, "Must provide resource or a resource and name as command line params") usageError(cmd, "Must provide resource or a resource and name as command line params")
} }
......
...@@ -21,7 +21,6 @@ import ( ...@@ -21,7 +21,6 @@ import (
"io" "io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
...@@ -69,9 +68,7 @@ $ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f - ...@@ -69,9 +68,7 @@ $ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f -
err = CompareNamespaceFromFile(cmd, namespace) err = CompareNamespaceFromFile(cmd, namespace)
checkErr(err) checkErr(err)
config, err := f.ClientConfig.ClientConfig() client, err := f.Client(cmd)
checkErr(err)
client, err := client.New(config)
checkErr(err) checkErr(err)
obj, err := mapping.Codec.Decode(data) obj, err := mapping.Codec.Decode(data)
......
...@@ -20,7 +20,7 @@ import ( ...@@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
...@@ -46,13 +46,13 @@ Examples: ...@@ -46,13 +46,13 @@ Examples:
schema, err := f.Validator(cmd) schema, err := f.Validator(cmd)
checkErr(err) checkErr(err)
mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema) 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) checkErr(err)
err = CompareNamespaceFromFile(cmd, namespace) err = CompareNamespaceFromFile(cmd, namespace)
checkErr(err) checkErr(err)
err = kubectl.NewRESTHelper(client, mapping).Update(namespace, name, true, data) err = resource.NewHelper(client, mapping).Update(namespace, name, true, data)
checkErr(err) checkErr(err)
fmt.Fprintf(out, "%s\n", name) fmt.Fprintf(out, "%s\n", name)
}, },
......
...@@ -21,7 +21,6 @@ import ( ...@@ -21,7 +21,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
) )
...@@ -32,14 +31,13 @@ func (f *Factory) NewCmdVersion(out io.Writer) *cobra.Command { ...@@ -32,14 +31,13 @@ func (f *Factory) NewCmdVersion(out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if GetFlagBool(cmd, "client") { if GetFlagBool(cmd, "client") {
kubectl.GetClientVersion(out) kubectl.GetClientVersion(out)
} else { return
config, err := f.ClientConfig.ClientConfig()
checkErr(err)
client, err := client.New(config)
checkErr(err)
kubectl.GetVersion(out, client)
} }
client, err := f.Client(cmd)
checkErr(err)
kubectl.GetVersion(out, client)
}, },
} }
cmd.Flags().BoolP("client", "c", false, "Client version only (no server required)") cmd.Flags().BoolP("client", "c", false, "Client version only (no server required)")
......
...@@ -26,6 +26,7 @@ import ( ...@@ -26,6 +26,7 @@ import (
"strings" "strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
...@@ -111,12 +112,23 @@ func makeImageList(spec *api.PodSpec) string { ...@@ -111,12 +112,23 @@ func makeImageList(spec *api.PodSpec) string {
return strings.Join(listOfImages(spec), ",") 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 // (something that a pkg/api/meta.RESTMapper can understand), if it is
// indeed a shortcut. Otherwise, will return resource unmodified. // indeed a shortcut. Otherwise, will return resource unmodified.
// TODO: Combine with RESTMapper stuff to provide a general solution func expandResourceShortcut(resource string) string {
// to this problem.
func ExpandResourceShortcut(resource string) string {
shortForms := map[string]string{ shortForms := map[string]string{
"po": "pods", "po": "pods",
"rc": "replicationcontrollers", "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 ...@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package kubectl package resource
import ( import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
...@@ -23,9 +23,10 @@ import ( ...@@ -23,9 +23,10 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "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. // resource.
type RESTHelper struct { type Helper struct {
// The name of this resource as the server would recognize it
Resource string Resource string
// A RESTClient capable of mutating this resource // A RESTClient capable of mutating this resource
RESTClient RESTClient RESTClient RESTClient
...@@ -36,9 +37,9 @@ type RESTHelper struct { ...@@ -36,9 +37,9 @@ type RESTHelper struct {
Versioner runtime.ResourceVersioner Versioner runtime.ResourceVersioner
} }
// NewRESTHelper creates a RESTHelper from a ResourceMapping // NewHelper creates a Helper from a ResourceMapping
func NewRESTHelper(client RESTClient, mapping *meta.RESTMapping) *RESTHelper { func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
return &RESTHelper{ return &Helper{
RESTClient: client, RESTClient: client,
Resource: mapping.Resource, Resource: mapping.Resource,
Codec: mapping.Codec, Codec: mapping.Codec,
...@@ -46,15 +47,25 @@ func NewRESTHelper(client RESTClient, mapping *meta.RESTMapping) *RESTHelper { ...@@ -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) { func (m *Helper) Get(namespace, name string) (runtime.Object, error) {
return m.RESTClient.Get().Resource(m.Resource).Namespace(namespace).Name(name).SelectorParam("labels", selector).Do().Get() 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) { func (m *Helper) List(namespace string, selector labels.Selector) (runtime.Object, error) {
return m.RESTClient.Get().Resource(m.Resource).Namespace(namespace).SelectorParam("labels", selector).Do().Get() 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(). return m.RESTClient.Get().
Prefix("watch"). Prefix("watch").
Namespace(namespace). Namespace(namespace).
...@@ -65,11 +76,26 @@ func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fie ...@@ -65,11 +76,26 @@ func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fie
Watch() Watch()
} }
func (m *RESTHelper) Delete(namespace, name string) error { func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) {
return m.RESTClient.Delete().Namespace(namespace).Resource(m.Resource).Name(name).Do().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 { if modify {
obj, err := m.Codec.Decode(data) obj, err := m.Codec.Decode(data)
if err != nil { if err != nil {
...@@ -102,7 +128,7 @@ func createResource(c RESTClient, resource, namespace string, data []byte) error ...@@ -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() 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 c := m.RESTClient
obj, err := m.Codec.Decode(data) obj, err := m.Codec.Decode(data)
...@@ -119,7 +145,7 @@ func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte) ...@@ -119,7 +145,7 @@ func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte)
} }
if version == "" && overwrite { if version == "" && overwrite {
// Retrieve the current version of the object to overwrite the server object // 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 { if err != nil {
// The object does not exist, but we want it to be created // The object does not exist, but we want it to be created
return updateResource(c, m.Resource, namespace, name, data) return updateResource(c, m.Resource, namespace, name, data)
......
...@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and ...@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package kubectl package resource
import ( import (
"bytes" "bytes"
...@@ -46,7 +46,7 @@ func splitPath(path string) []string { ...@@ -46,7 +46,7 @@ func splitPath(path string) []string {
return strings.Split(path, "/") return strings.Split(path, "/")
} }
func TestRESTHelperDelete(t *testing.T) { func TestHelperDelete(t *testing.T) {
tests := []struct { tests := []struct {
Err bool Err bool
Req func(*http.Request) bool Req func(*http.Request) bool
...@@ -93,7 +93,7 @@ func TestRESTHelperDelete(t *testing.T) { ...@@ -93,7 +93,7 @@ func TestRESTHelperDelete(t *testing.T) {
Resp: test.Resp, Resp: test.Resp,
Err: test.HttpErr, Err: test.HttpErr,
} }
modifier := &RESTHelper{ modifier := &Helper{
RESTClient: client, RESTClient: client,
} }
err := modifier.Delete("bar", "foo") err := modifier.Delete("bar", "foo")
...@@ -109,7 +109,7 @@ func TestRESTHelperDelete(t *testing.T) { ...@@ -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 { expectPost := func(req *http.Request) bool {
if req.Method != "POST" { if req.Method != "POST" {
t.Errorf("unexpected method: %#v", req) t.Errorf("unexpected method: %#v", req)
...@@ -178,7 +178,7 @@ func TestRESTHelperCreate(t *testing.T) { ...@@ -178,7 +178,7 @@ func TestRESTHelperCreate(t *testing.T) {
if test.RespFunc != nil { if test.RespFunc != nil {
client.Client = test.RespFunc client.Client = test.RespFunc
} }
modifier := &RESTHelper{ modifier := &Helper{
RESTClient: client, RESTClient: client,
Codec: testapi.Codec(), Codec: testapi.Codec(),
Versioner: testapi.MetadataAccessor(), Versioner: testapi.MetadataAccessor(),
...@@ -213,7 +213,7 @@ func TestRESTHelperCreate(t *testing.T) { ...@@ -213,7 +213,7 @@ func TestRESTHelperCreate(t *testing.T) {
} }
} }
func TestRESTHelperGet(t *testing.T) { func TestHelperGet(t *testing.T) {
tests := []struct { tests := []struct {
Err bool Err bool
Req func(*http.Request) bool Req func(*http.Request) bool
...@@ -260,10 +260,10 @@ func TestRESTHelperGet(t *testing.T) { ...@@ -260,10 +260,10 @@ func TestRESTHelperGet(t *testing.T) {
Resp: test.Resp, Resp: test.Resp,
Err: test.HttpErr, Err: test.HttpErr,
} }
modifier := &RESTHelper{ modifier := &Helper{
RESTClient: client, RESTClient: client,
} }
obj, err := modifier.Get("bar", "foo", labels.Everything()) obj, err := modifier.Get("bar", "foo")
if (err != nil) != test.Err { if (err != nil) != test.Err {
t.Errorf("unexpected error: %t %v", test.Err, err) t.Errorf("unexpected error: %t %v", test.Err, err)
} }
...@@ -279,7 +279,77 @@ func TestRESTHelperGet(t *testing.T) { ...@@ -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 { expectPut := func(req *http.Request) bool {
if req.Method != "PUT" { if req.Method != "PUT" {
t.Errorf("unexpected method: %#v", req) t.Errorf("unexpected method: %#v", req)
...@@ -358,7 +428,7 @@ func TestRESTHelperUpdate(t *testing.T) { ...@@ -358,7 +428,7 @@ func TestRESTHelperUpdate(t *testing.T) {
if test.RespFunc != nil { if test.RespFunc != nil {
client.Client = test.RespFunc client.Client = test.RespFunc
} }
modifier := &RESTHelper{ modifier := &Helper{
RESTClient: client, RESTClient: client,
Codec: testapi.Codec(), Codec: testapi.Codec(),
Versioner: testapi.MetadataAccessor(), 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