Move Ingress godeps to vendor/

This commit is contained in:
Manuel de Brito Fontes 2016-05-10 10:30:56 -03:00
parent 0d4f49e50e
commit ca620e4074
2059 changed files with 3706 additions and 213845 deletions

7
vendor/k8s.io/kubernetes/pkg/kubectl/OWNERS generated vendored Normal file
View file

@ -0,0 +1,7 @@
assignees:
- bgrant0607
- brendandburns
- deads2k
- janetkuo
- jlowdermilk
- smarterclayton

197
vendor/k8s.io/kubernetes/pkg/kubectl/apply.go generated vendored Normal file
View file

@ -0,0 +1,197 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"encoding/json"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
)
type debugError interface {
DebugError() (msg string, args []interface{})
}
// LastAppliedConfigAnnotation is the annotation used to store the previous
// configuration of a resource for use in a three way diff by UpdateApplyAnnotation.
const LastAppliedConfigAnnotation = kubectlAnnotationPrefix + "last-applied-configuration"
// GetOriginalConfiguration retrieves the original configuration of the object
// from the annotation, or nil if no annotation was found.
func GetOriginalConfiguration(info *resource.Info) ([]byte, error) {
annotations, err := info.Mapping.MetadataAccessor.Annotations(info.Object)
if err != nil {
return nil, err
}
if annotations == nil {
return nil, nil
}
original, ok := annotations[LastAppliedConfigAnnotation]
if !ok {
return nil, nil
}
return []byte(original), nil
}
// SetOriginalConfiguration sets the original configuration of the object
// as the annotation on the object for later use in computing a three way patch.
func SetOriginalConfiguration(info *resource.Info, original []byte) error {
if len(original) < 1 {
return nil
}
accessor := info.Mapping.MetadataAccessor
annotations, err := accessor.Annotations(info.Object)
if err != nil {
return err
}
if annotations == nil {
annotations = map[string]string{}
}
annotations[LastAppliedConfigAnnotation] = string(original)
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annotations); err != nil {
return err
}
return nil
}
// GetModifiedConfiguration retrieves the modified configuration of the object.
// If annotate is true, it embeds the result as an anotation in the modified
// configuration. If an object was read from the command input, it will use that
// version of the object. Otherwise, it will use the version from the server.
func GetModifiedConfiguration(info *resource.Info, annotate bool, codec runtime.Encoder) ([]byte, error) {
// First serialize the object without the annotation to prevent recursion,
// then add that serialization to it as the annotation and serialize it again.
var modified []byte
if info.VersionedObject != nil {
// If an object was read from input, use that version.
accessor, err := meta.Accessor(info.VersionedObject)
if err != nil {
return nil, err
}
// Get the current annotations from the object.
annotations := accessor.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
original := annotations[LastAppliedConfigAnnotation]
delete(annotations, LastAppliedConfigAnnotation)
accessor.SetAnnotations(annotations)
// TODO: this needs to be abstracted - there should be no assumption that versioned object
// can be marshalled to JSON.
modified, err = json.Marshal(info.VersionedObject)
if err != nil {
return nil, err
}
if annotate {
annotations[LastAppliedConfigAnnotation] = string(modified)
accessor.SetAnnotations(annotations)
// TODO: this needs to be abstracted - there should be no assumption that versioned object
// can be marshalled to JSON.
modified, err = json.Marshal(info.VersionedObject)
if err != nil {
return nil, err
}
}
// Restore the object to its original condition.
annotations[LastAppliedConfigAnnotation] = original
accessor.SetAnnotations(annotations)
} else {
// Otherwise, use the server side version of the object.
accessor := info.Mapping.MetadataAccessor
// Get the current annotations from the object.
annotations, err := accessor.Annotations(info.Object)
if err != nil {
return nil, err
}
if annotations == nil {
annotations = map[string]string{}
}
original := annotations[LastAppliedConfigAnnotation]
delete(annotations, LastAppliedConfigAnnotation)
if err := accessor.SetAnnotations(info.Object, annotations); err != nil {
return nil, err
}
modified, err = runtime.Encode(codec, info.Object)
if err != nil {
return nil, err
}
if annotate {
annotations[LastAppliedConfigAnnotation] = string(modified)
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annotations); err != nil {
return nil, err
}
modified, err = runtime.Encode(codec, info.Object)
if err != nil {
return nil, err
}
}
// Restore the object to its original condition.
annotations[LastAppliedConfigAnnotation] = original
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annotations); err != nil {
return nil, err
}
}
return modified, nil
}
// UpdateApplyAnnotation calls CreateApplyAnnotation if the last applied
// configuration annotation is already present. Otherwise, it does nothing.
func UpdateApplyAnnotation(info *resource.Info, codec runtime.Encoder) error {
if original, err := GetOriginalConfiguration(info); err != nil || len(original) <= 0 {
return err
}
return CreateApplyAnnotation(info, codec)
}
// CreateApplyAnnotation gets the modified configuration of the object,
// without embedding it again, and then sets it on the object as the annotation.
func CreateApplyAnnotation(info *resource.Info, codec runtime.Encoder) error {
modified, err := GetModifiedConfiguration(info, false, codec)
if err != nil {
return err
}
return SetOriginalConfiguration(info, modified)
}
// Create the annotation used by kubectl apply only when createAnnotation is true
// Otherwise, only update the annotation when it already exists
func CreateOrUpdateAnnotation(createAnnotation bool, info *resource.Info, codec runtime.Encoder) error {
if createAnnotation {
return CreateApplyAnnotation(info, codec)
}
return UpdateApplyAnnotation(info, codec)
}

111
vendor/k8s.io/kubernetes/pkg/kubectl/autoscale.go generated vendored Normal file
View file

@ -0,0 +1,111 @@
/*
Copyright 2015 The Kubernetes Authors 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 kubectl
import (
"fmt"
"strconv"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime"
)
const (
scaleSubResource = "scale"
)
type HorizontalPodAutoscalerV1Beta1 struct{}
func (HorizontalPodAutoscalerV1Beta1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"default-name", true},
{"name", false},
{"scaleRef-kind", false},
{"scaleRef-name", false},
{"scaleRef-apiVersion", false},
{"scaleRef-subresource", false},
{"min", false},
{"max", true},
{"cpu-percent", false},
}
}
func (HorizontalPodAutoscalerV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
name, found := params["name"]
if !found || len(name) == 0 {
name, found = params["default-name"]
if !found || len(name) == 0 {
return nil, fmt.Errorf("'name' is a required parameter.")
}
}
minString, found := params["min"]
min := -1
var err error
if found {
if min, err = strconv.Atoi(minString); err != nil {
return nil, err
}
}
maxString, found := params["max"]
if !found {
return nil, fmt.Errorf("'max' is a required parameter.")
}
max, err := strconv.Atoi(maxString)
if err != nil {
return nil, err
}
cpuString, found := params["cpu-percent"]
cpu := -1
if found {
if cpu, err = strconv.Atoi(cpuString); err != nil {
return nil, err
}
}
scaler := extensions.HorizontalPodAutoscaler{
ObjectMeta: api.ObjectMeta{
Name: name,
},
Spec: extensions.HorizontalPodAutoscalerSpec{
ScaleRef: extensions.SubresourceReference{
Kind: params["scaleRef-kind"],
Name: params["scaleRef-name"],
APIVersion: params["scaleRef-apiVersion"],
Subresource: scaleSubResource,
},
MaxReplicas: int32(max),
},
}
if min > 0 {
v := int32(min)
scaler.Spec.MinReplicas = &v
}
if cpu >= 0 {
scaler.Spec.CPUUtilization = &extensions.CPUTargetUtilization{TargetPercentage: int32(cpu)}
}
return &scaler, nil
}

View file

@ -0,0 +1,36 @@
/*
Copyright 2015 The Kubernetes Authors 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.
*/
// A set of common functions needed by cmd/kubectl and pkg/kubectl packages.
package kubectl
import (
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
func AddJsonFilenameFlag(cmd *cobra.Command, value *[]string, usage string) {
cmd.Flags().StringSliceVarP(value, "filename", "f", *value, usage)
annotations := []string{}
for _, ext := range resource.FileExtensions {
annotations = append(annotations, strings.TrimLeft(ext, "."))
}
cmd.Flags().SetAnnotation("filename", cobra.BashCompFilenameExt, annotations)
}

View file

@ -0,0 +1,127 @@
/*
Copyright 2014 The Kubernetes Authors 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 util
import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/restclient"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
)
func NewClientCache(loader clientcmd.ClientConfig) *ClientCache {
return &ClientCache{
clients: make(map[unversioned.GroupVersion]*client.Client),
configs: make(map[unversioned.GroupVersion]*restclient.Config),
loader: loader,
}
}
// ClientCache caches previously loaded clients for reuse, and ensures MatchServerVersion
// is invoked only once
type ClientCache struct {
loader clientcmd.ClientConfig
clients map[unversioned.GroupVersion]*client.Client
configs map[unversioned.GroupVersion]*restclient.Config
defaultConfig *restclient.Config
defaultClient *client.Client
matchVersion bool
}
// ClientConfigForVersion returns the correct config for a server
func (c *ClientCache) ClientConfigForVersion(version *unversioned.GroupVersion) (*restclient.Config, error) {
if c.defaultConfig == nil {
config, err := c.loader.ClientConfig()
if err != nil {
return nil, err
}
c.defaultConfig = config
if c.matchVersion {
if err := client.MatchesServerVersion(c.defaultClient, config); err != nil {
return nil, err
}
}
}
if version != nil {
if config, ok := c.configs[*version]; ok {
return config, nil
}
}
// TODO: have a better config copy method
config := *c.defaultConfig
// TODO these fall out when we finish the refactor
var preferredGV *unversioned.GroupVersion
if version != nil {
versionCopy := *version
preferredGV = &versionCopy
}
negotiatedVersion, err := client.NegotiateVersion(c.defaultClient, &config, preferredGV, registered.EnabledVersions())
if err != nil {
return nil, err
}
config.GroupVersion = negotiatedVersion
client.SetKubernetesDefaults(&config)
if version != nil {
c.configs[*version] = &config
}
// `version` does not necessarily equal `config.Version`. However, we know that we call this method again with
// `config.Version`, we should get the the config we've just built.
configCopy := config
c.configs[*config.GroupVersion] = &configCopy
return &config, nil
}
// ClientForVersion initializes or reuses a client for the specified version, or returns an
// error if that is not possible
func (c *ClientCache) ClientForVersion(version *unversioned.GroupVersion) (*client.Client, error) {
if version != nil {
if client, ok := c.clients[*version]; ok {
return client, nil
}
}
config, err := c.ClientConfigForVersion(version)
if err != nil {
return nil, err
}
kubeclient, err := client.New(config)
if err != nil {
return nil, err
}
c.clients[*config.GroupVersion] = kubeclient
// `version` does not necessarily equal `config.Version`. However, we know that if we call this method again with
// `version`, we should get a client based on the same config we just found. There's no guarantee that a client
// is copiable, so create a new client and save it in the cache.
if version != nil {
configCopy := *config
kubeclient, err := client.New(&configCopy)
if err != nil {
return nil, err
}
c.clients[*version] = kubeclient
}
return kubeclient, nil
}

1039
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory.go generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,530 @@
/*
Copyright 2014 The Kubernetes Authors 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 util
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"strings"
"time"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/strategicpatch"
"github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
const (
ApplyAnnotationsFlag = "save-config"
)
type debugError interface {
DebugError() (msg string, args []interface{})
}
// AddSourceToErr adds handleResourcePrefix and source string to error message.
// verb is the string like "creating", "deleting" etc.
// souce is the filename or URL to the template file(*.json or *.yaml), or stdin to use to handle the resource.
func AddSourceToErr(verb string, source string, err error) error {
if source != "" {
if statusError, ok := err.(errors.APIStatus); ok {
status := statusError.Status()
status.Message = fmt.Sprintf("error when %s %q: %v", verb, source, status.Message)
return &errors.StatusError{ErrStatus: status}
}
return fmt.Errorf("error when %s %q: %v", verb, source, err)
}
return err
}
var fatalErrHandler = fatal
// BehaviorOnFatal allows you to override the default behavior when a fatal
// error occurs, which is call os.Exit(1). You can pass 'panic' as a function
// here if you prefer the panic() over os.Exit(1).
func BehaviorOnFatal(f func(string)) {
fatalErrHandler = f
}
// DefaultBehaviorOnFatal allows you to undo any previous override. Useful in
// tests.
func DefaultBehaviorOnFatal() {
fatalErrHandler = fatal
}
// fatal prints the message and then exits. If V(2) or greater, glog.Fatal
// is invoked for extended information.
func fatal(msg string) {
// add newline if needed
if !strings.HasSuffix(msg, "\n") {
msg += "\n"
}
if glog.V(2) {
glog.FatalDepth(2, msg)
}
fmt.Fprint(os.Stderr, msg)
os.Exit(1)
}
// CheckErr prints a user friendly error to STDERR and exits with a non-zero
// exit code. Unrecognized errors will be printed with an "error: " prefix.
//
// This method is generic to the command in use and may be used by non-Kubectl
// commands.
func CheckErr(err error) {
checkErr(err, fatalErrHandler)
}
func checkErr(err error, handleErr func(string)) {
if err == nil {
return
}
if errors.IsInvalid(err) {
details := err.(*errors.StatusError).Status().Details
prefix := fmt.Sprintf("The %s %q is invalid.\n", details.Kind, details.Name)
errs := statusCausesToAggrError(details.Causes)
handleErr(MultilineError(prefix, errs))
}
if meta.IsNoResourceMatchError(err) {
noMatch := err.(*meta.NoResourceMatchError)
switch {
case len(noMatch.PartialResource.Group) > 0 && len(noMatch.PartialResource.Version) > 0:
handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q and version %q", noMatch.PartialResource.Resource, noMatch.PartialResource.Group, noMatch.PartialResource.Version))
case len(noMatch.PartialResource.Group) > 0:
handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q", noMatch.PartialResource.Resource, noMatch.PartialResource.Group))
case len(noMatch.PartialResource.Version) > 0:
handleErr(fmt.Sprintf("the server doesn't have a resource type %q in version %q", noMatch.PartialResource.Resource, noMatch.PartialResource.Version))
default:
handleErr(fmt.Sprintf("the server doesn't have a resource type %q", noMatch.PartialResource.Resource))
}
return
}
// handle multiline errors
if clientcmd.IsConfigurationInvalid(err) {
handleErr(MultilineError("Error in configuration: ", err))
}
if agg, ok := err.(utilerrors.Aggregate); ok && len(agg.Errors()) > 0 {
handleErr(MultipleErrors("", agg.Errors()))
}
msg, ok := StandardErrorMessage(err)
if !ok {
msg = err.Error()
if !strings.HasPrefix(msg, "error: ") {
msg = fmt.Sprintf("error: %s", msg)
}
}
handleErr(msg)
}
func statusCausesToAggrError(scs []unversioned.StatusCause) utilerrors.Aggregate {
errs := make([]error, len(scs))
for i, sc := range scs {
errs[i] = fmt.Errorf("%s: %s", sc.Field, sc.Message)
}
return utilerrors.NewAggregate(errs)
}
// StandardErrorMessage translates common errors into a human readable message, or returns
// false if the error is not one of the recognized types. It may also log extended
// information to glog.
//
// This method is generic to the command in use and may be used by non-Kubectl
// commands.
func StandardErrorMessage(err error) (string, bool) {
if debugErr, ok := err.(debugError); ok {
glog.V(4).Infof(debugErr.DebugError())
}
status, isStatus := err.(errors.APIStatus)
switch {
case isStatus:
switch s := status.Status(); {
case s.Reason == "Unauthorized":
return fmt.Sprintf("error: You must be logged in to the server (%s)", s.Message), true
default:
return fmt.Sprintf("Error from server: %s", err.Error()), true
}
case errors.IsUnexpectedObjectError(err):
return fmt.Sprintf("Server returned an unexpected response: %s", err.Error()), true
}
switch t := err.(type) {
case *url.Error:
glog.V(4).Infof("Connection error: %s %s: %v", t.Op, t.URL, t.Err)
switch {
case strings.Contains(t.Err.Error(), "connection refused"):
host := t.URL
if server, err := url.Parse(t.URL); err == nil {
host = server.Host
}
return fmt.Sprintf("The connection to the server %s was refused - did you specify the right host or port?", host), true
}
return fmt.Sprintf("Unable to connect to the server: %v", t.Err), true
}
return "", false
}
// MultilineError returns a string representing an error that splits sub errors into their own
// lines. The returned string will end with a newline.
func MultilineError(prefix string, err error) string {
if agg, ok := err.(utilerrors.Aggregate); ok {
errs := utilerrors.Flatten(agg).Errors()
buf := &bytes.Buffer{}
switch len(errs) {
case 0:
return fmt.Sprintf("%s%v\n", prefix, err)
case 1:
return fmt.Sprintf("%s%v\n", prefix, messageForError(errs[0]))
default:
fmt.Fprintln(buf, prefix)
for _, err := range errs {
fmt.Fprintf(buf, "* %v\n", messageForError(err))
}
return buf.String()
}
}
return fmt.Sprintf("%s%s\n", prefix, err)
}
// MultipleErrors returns a newline delimited string containing
// the prefix and referenced errors in standard form.
func MultipleErrors(prefix string, errs []error) string {
buf := &bytes.Buffer{}
for _, err := range errs {
fmt.Fprintf(buf, "%s%v\n", prefix, messageForError(err))
}
return buf.String()
}
// messageForError returns the string representing the error.
func messageForError(err error) string {
msg, ok := StandardErrorMessage(err)
if !ok {
msg = err.Error()
}
return msg
}
func UsageError(cmd *cobra.Command, format string, args ...interface{}) error {
msg := fmt.Sprintf(format, args...)
return fmt.Errorf("%s\nSee '%s -h' for help and examples.", msg, cmd.CommandPath())
}
// Whether this cmd need watching objects.
func isWatch(cmd *cobra.Command) bool {
if w, err := cmd.Flags().GetBool("watch"); w && err == nil {
return true
}
if wo, err := cmd.Flags().GetBool("watch-only"); wo && err == nil {
return true
}
return false
}
func getFlag(cmd *cobra.Command, flag string) *pflag.Flag {
f := cmd.Flags().Lookup(flag)
if f == nil {
glog.Fatalf("flag accessed but not defined for command %s: %s", cmd.Name(), flag)
}
return f
}
func GetFlagString(cmd *cobra.Command, flag string) string {
s, err := cmd.Flags().GetString(flag)
if err != nil {
glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
}
return s
}
// GetFlagStringList can be used to accept multiple argument with flag repetition (e.g. -f arg1 -f arg2 ...)
func GetFlagStringSlice(cmd *cobra.Command, flag string) []string {
s, err := cmd.Flags().GetStringSlice(flag)
if err != nil {
glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
}
return s
}
// GetWideFlag is used to determine if "-o wide" is used
func GetWideFlag(cmd *cobra.Command) bool {
f := cmd.Flags().Lookup("output")
if f.Value.String() == "wide" {
return true
}
return false
}
func GetFlagBool(cmd *cobra.Command, flag string) bool {
b, err := cmd.Flags().GetBool(flag)
if err != nil {
glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
}
return b
}
// Assumes the flag has a default value.
func GetFlagInt(cmd *cobra.Command, flag string) int {
i, err := cmd.Flags().GetInt(flag)
if err != nil {
glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
}
return i
}
// Assumes the flag has a default value.
func GetFlagInt64(cmd *cobra.Command, flag string) int64 {
i, err := cmd.Flags().GetInt64(flag)
if err != nil {
glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
}
return i
}
func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
d, err := cmd.Flags().GetDuration(flag)
if err != nil {
glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
}
return d
}
func AddValidateFlags(cmd *cobra.Command) {
cmd.Flags().Bool("validate", true, "If true, use a schema to validate the input before sending it")
cmd.Flags().String("schema-cache-dir", fmt.Sprintf("~/%s/%s", clientcmd.RecommendedHomeDir, clientcmd.RecommendedSchemaName), fmt.Sprintf("If non-empty, load/store cached API schemas in this directory, default is '$HOME/%s/%s'", clientcmd.RecommendedHomeDir, clientcmd.RecommendedSchemaName))
cmd.MarkFlagFilename("schema-cache-dir")
}
func AddRecursiveFlag(cmd *cobra.Command, value *bool) {
cmd.Flags().BoolVarP(value, "recursive", "R", *value, "If true, process directory recursively.")
}
func AddApplyAnnotationFlags(cmd *cobra.Command) {
cmd.Flags().Bool(ApplyAnnotationsFlag, false, "If true, the configuration of current object will be saved in its annotation. This is useful when you want to perform kubectl apply on this object in the future.")
}
// AddGeneratorFlags adds flags common to resource generation commands
// TODO: need to take a pass at other generator commands to use this set of flags
func AddGeneratorFlags(cmd *cobra.Command, defaultGenerator string) {
cmd.Flags().String("generator", defaultGenerator, "The name of the API generator to use.")
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
}
func ReadConfigDataFromReader(reader io.Reader, source string) ([]byte, error) {
data, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
if len(data) == 0 {
return nil, fmt.Errorf(`Read from %s but no data found`, source)
}
return data, nil
}
// Merge requires JSON serialization
// TODO: merge assumes JSON serialization, and does not properly abstract API retrieval
func Merge(codec runtime.Codec, dst runtime.Object, fragment, kind string) (runtime.Object, error) {
// encode dst into versioned json and apply fragment directly too it
target, err := runtime.Encode(codec, dst)
if err != nil {
return nil, err
}
patched, err := jsonpatch.MergePatch(target, []byte(fragment))
if err != nil {
return nil, err
}
out, err := runtime.Decode(codec, patched)
if err != nil {
return nil, err
}
return out, nil
}
// DumpReaderToFile writes all data from the given io.Reader to the specified file
// (usually for temporary use).
func DumpReaderToFile(reader io.Reader, filename string) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
defer f.Close()
if err != nil {
return err
}
buffer := make([]byte, 1024)
for {
count, err := reader.Read(buffer)
if err == io.EOF {
break
}
if err != nil {
return err
}
_, err = f.Write(buffer[:count])
if err != nil {
return err
}
}
return nil
}
// UpdateObject updates resource object with updateFn
func UpdateObject(info *resource.Info, codec runtime.Codec, updateFn func(runtime.Object) error) (runtime.Object, error) {
helper := resource.NewHelper(info.Client, info.Mapping)
if err := updateFn(info.Object); err != nil {
return nil, err
}
// Update the annotation used by kubectl apply
if err := kubectl.UpdateApplyAnnotation(info, codec); err != nil {
return nil, err
}
if _, err := helper.Replace(info.Namespace, info.Name, true, info.Object); err != nil {
return nil, err
}
return info.Object, nil
}
// AddCmdRecordFlag adds --record flag to command
func AddRecordFlag(cmd *cobra.Command) {
cmd.Flags().Bool("record", false, "Record current kubectl command in the resource annotation.")
}
func GetRecordFlag(cmd *cobra.Command) bool {
return GetFlagBool(cmd, "record")
}
// RecordChangeCause annotate change-cause to input runtime object.
func RecordChangeCause(obj runtime.Object, changeCause string) error {
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
annotations := accessor.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
annotations[kubectl.ChangeCauseAnnotation] = changeCause
accessor.SetAnnotations(annotations)
return nil
}
// ChangeResourcePatch creates a strategic merge patch between the origin input resource info
// and the annotated with change-cause input resource info.
func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error) {
oldData, err := json.Marshal(info.Object)
if err != nil {
return nil, err
}
if err := RecordChangeCause(info.Object, changeCause); err != nil {
return nil, err
}
newData, err := json.Marshal(info.Object)
if err != nil {
return nil, err
}
return strategicpatch.CreateTwoWayMergePatch(oldData, newData, info.Object)
}
// containsChangeCause checks if input resource info contains change-cause annotation.
func ContainsChangeCause(info *resource.Info) bool {
annotations, err := info.Mapping.MetadataAccessor.Annotations(info.Object)
if err != nil {
return false
}
return len(annotations[kubectl.ChangeCauseAnnotation]) > 0
}
// ShouldRecord checks if we should record current change cause
func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool {
return GetRecordFlag(cmd) || ContainsChangeCause(info)
}
func GetThirdPartyGroupVersions(discovery discovery.DiscoveryInterface) ([]unversioned.GroupVersion, []unversioned.GroupVersionKind, error) {
result := []unversioned.GroupVersion{}
gvks := []unversioned.GroupVersionKind{}
groupList, err := discovery.ServerGroups()
if err != nil {
// On forbidden or not found, just return empty lists.
if errors.IsForbidden(err) || errors.IsNotFound(err) {
return result, gvks, nil
}
return nil, nil, err
}
for ix := range groupList.Groups {
group := &groupList.Groups[ix]
for jx := range group.Versions {
gv, err2 := unversioned.ParseGroupVersion(group.Versions[jx].GroupVersion)
if err2 != nil {
return nil, nil, err
}
// Skip GroupVersionKinds that have been statically registered.
if registered.IsRegisteredVersion(gv) {
continue
}
result = append(result, gv)
resourceList, err := discovery.ServerResourcesForGroupVersion(group.Versions[jx].GroupVersion)
if err != nil {
return nil, nil, err
}
for kx := range resourceList.APIResources {
gvks = append(gvks, gv.WithKind(resourceList.APIResources[kx].Kind))
}
}
}
return result, gvks, nil
}
func GetIncludeThirdPartyAPIs(cmd *cobra.Command) bool {
if cmd.Flags().Lookup("include-extended-apis") == nil {
return false
}
return GetFlagBool(cmd, "include-extended-apis")
}
func AddInclude3rdPartyFlags(cmd *cobra.Command) {
cmd.Flags().Bool("include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]")
}

View file

@ -0,0 +1,136 @@
/*
Copyright 2014 The Kubernetes Authors 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 util
import (
"fmt"
"io"
"strings"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
"github.com/spf13/cobra"
)
// AddPrinterFlags adds printing related flags to a command (e.g. output format, no headers, template path)
func AddPrinterFlags(cmd *cobra.Command) {
cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md].")
cmd.Flags().String("output-version", "", "Output the formatted object with the given group version (for ex: 'extensions/v1beta1').")
cmd.Flags().Bool("no-headers", false, "When using the default output, don't print headers.")
cmd.Flags().Bool("show-labels", false, "When printing, show all labels as the last column (default hide labels column)")
cmd.Flags().String("template", "", "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
cmd.MarkFlagFilename("template")
cmd.Flags().String("sort-by", "", "If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.")
cmd.Flags().BoolP("show-all", "a", false, "When printing, show all resources (default hide terminated pods.)")
}
// AddOutputFlagsForMutation adds output related flags to a command. Used by mutations only.
func AddOutputFlagsForMutation(cmd *cobra.Command) {
cmd.Flags().StringP("output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).")
}
// PrintSuccess prints message after finishing mutating operations
func PrintSuccess(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource string, name string, operation string) {
resource, _ = mapper.ResourceSingularizer(resource)
if shortOutput {
// -o name: prints resource/name
if len(resource) > 0 {
fmt.Fprintf(out, "%s/%s\n", resource, name)
} else {
fmt.Fprintf(out, "%s\n", name)
}
} else {
// understandable output by default
if len(resource) > 0 {
fmt.Fprintf(out, "%s \"%s\" %s\n", resource, name, operation)
} else {
fmt.Fprintf(out, "\"%s\" %s\n", name, operation)
}
}
}
// ValidateOutputArgs validates -o flag args for mutations
func ValidateOutputArgs(cmd *cobra.Command) error {
outputMode := GetFlagString(cmd, "output")
if outputMode != "" && outputMode != "name" {
return UsageError(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", outputMode)
}
return nil
}
// OutputVersion returns the preferred output version for generic content (JSON, YAML, or templates)
// defaultVersion is never mutated. Nil simply allows clean passing in common usage from client.Config
func OutputVersion(cmd *cobra.Command, defaultVersion *unversioned.GroupVersion) (unversioned.GroupVersion, error) {
outputVersionString := GetFlagString(cmd, "output-version")
if len(outputVersionString) == 0 {
if defaultVersion == nil {
return unversioned.GroupVersion{}, nil
}
return *defaultVersion, nil
}
return unversioned.ParseGroupVersion(outputVersionString)
}
// PrinterForCommand returns the default printer for this command.
// Requires that printer flags have been added to cmd (see AddPrinterFlags).
func PrinterForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error) {
outputFormat := GetFlagString(cmd, "output")
// templates are logically optional for specifying a format.
// TODO once https://github.com/kubernetes/kubernetes/issues/12668 is fixed, this should fall back to GetFlagString
templateFile, _ := cmd.Flags().GetString("template")
if len(outputFormat) == 0 && len(templateFile) != 0 {
outputFormat = "template"
}
templateFormat := []string{
"go-template=", "go-template-file=", "jsonpath=", "jsonpath-file=", "custom-columns=", "custom-columns-file=",
}
for _, format := range templateFormat {
if strings.HasPrefix(outputFormat, format) {
templateFile = outputFormat[len(format):]
outputFormat = format[:len(format)-1]
}
}
printer, generic, err := kubectl.GetPrinter(outputFormat, templateFile)
if err != nil {
return nil, generic, err
}
return maybeWrapSortingPrinter(cmd, printer), generic, nil
}
func maybeWrapSortingPrinter(cmd *cobra.Command, printer kubectl.ResourcePrinter) kubectl.ResourcePrinter {
sorting, err := cmd.Flags().GetString("sort-by")
if err != nil {
// error can happen on missing flag or bad flag type. In either case, this command didn't intent to sort
return printer
}
if len(sorting) != 0 {
return &kubectl.SortingPrinter{
Delegate: printer,
SortField: fmt.Sprintf("{%s}", sorting),
}
}
return printer
}

212
vendor/k8s.io/kubernetes/pkg/kubectl/configmap.go generated vendored Normal file
View file

@ -0,0 +1,212 @@
/*
Copyright 2016 The Kubernetes Authors 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 kubectl
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/runtime"
)
// ConfigMapGeneratorV1 supports stable generation of a configMap.
type ConfigMapGeneratorV1 struct {
// Name of configMap (required)
Name string
// Type of configMap (optional)
Type string
// FileSources to derive the configMap from (optional)
FileSources []string
// LiteralSources to derive the configMap from (optional)
LiteralSources []string
}
// Ensure it supports the generator pattern that uses parameter injection.
var _ Generator = &ConfigMapGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ StructuredGenerator = &ConfigMapGeneratorV1{}
// Generate returns a configMap using the specified parameters.
func (s ConfigMapGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
delegate := &ConfigMapGeneratorV1{}
fromFileStrings, found := genericParams["from-file"]
if found {
fromFileArray, isArray := fromFileStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
}
delegate.FileSources = fromFileArray
delete(genericParams, "from-file")
}
fromLiteralStrings, found := genericParams["from-literal"]
if found {
fromLiteralArray, isArray := fromLiteralStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
}
delegate.LiteralSources = fromLiteralArray
delete(genericParams, "from-literal")
}
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
delegate.Name = params["name"]
delegate.Type = params["type"]
return delegate.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern.
func (s ConfigMapGeneratorV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"type", false},
{"from-file", false},
{"from-literal", false},
{"force", false},
}
}
// StructuredGenerate outputs a configMap object using the configured fields.
func (s ConfigMapGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
configMap := &api.ConfigMap{}
configMap.Name = s.Name
configMap.Data = map[string]string{}
if len(s.FileSources) > 0 {
if err := handleConfigMapFromFileSources(configMap, s.FileSources); err != nil {
return nil, err
}
}
if len(s.LiteralSources) > 0 {
if err := handleConfigMapFromLiteralSources(configMap, s.LiteralSources); err != nil {
return nil, err
}
}
return configMap, nil
}
// validate validates required fields are set to support structured generation.
func (s ConfigMapGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
return nil
}
// handleConfigMapFromLiteralSources adds the specified literal source
// information into the provided configMap.
func handleConfigMapFromLiteralSources(configMap *api.ConfigMap, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := parseLiteralSource(literalSource)
if err != nil {
return err
}
err = addKeyFromLiteralToConfigMap(configMap, keyName, value)
if err != nil {
return err
}
}
return nil
}
// handleConfigMapFromFileSources adds the specified file source information
// into the provided configMap
func handleConfigMapFromFileSources(configMap *api.ConfigMap, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := parseFileSource(fileSource)
if err != nil {
return err
}
info, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default:
return fmt.Errorf("error reading %s: %v", filePath, err)
}
}
if info.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path.")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
err = addKeyFromFileToConfigMap(configMap, keyName, itemPath)
if err != nil {
return err
}
}
}
} else {
err = addKeyFromFileToConfigMap(configMap, keyName, filePath)
if err != nil {
return err
}
}
}
return nil
}
// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating
// the value with the content of the given file path, or returns an error.
func addKeyFromFileToConfigMap(configMap *api.ConfigMap, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
return addKeyFromLiteralToConfigMap(configMap, keyName, string(data))
}
// addKeyFromLiteralToConfigMap adds the given key and data to the given config map,
// returning an error if the key is not valid or if the key already exists.
func addKeyFromLiteralToConfigMap(configMap *api.ConfigMap, keyName, data string) error {
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys
// to be consistent; validation.IsSecretKey is used here intentionally.
if !validation.IsSecretKey(keyName) {
return fmt.Errorf("%v is not a valid key name for a configMap", keyName)
}
if _, entryExists := configMap.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, configMap.Data)
}
configMap.Data[keyName] = data
return nil
}

View file

@ -0,0 +1,224 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"bufio"
"bytes"
"fmt"
"io"
"reflect"
"regexp"
"strings"
"text/tabwriter"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/jsonpath"
)
const (
columnwidth = 10
tabwidth = 4
padding = 3
padding_character = ' '
flags = 0
)
var jsonRegexp = regexp.MustCompile("^\\{\\.?([^{}]+)\\}$|^\\.?([^{}]+)$")
// MassageJSONPath attempts to be flexible with JSONPath expressions, it accepts:
// * metadata.name (no leading '.' or curly brances '{...}'
// * {metadata.name} (no leading '.')
// * .metadata.name (no curly braces '{...}')
// * {.metadata.name} (complete expression)
// And transforms them all into a valid jsonpat expression:
// {.metadata.name}
func massageJSONPath(pathExpression string) (string, error) {
if len(pathExpression) == 0 {
return pathExpression, nil
}
submatches := jsonRegexp.FindStringSubmatch(pathExpression)
if submatches == nil {
return "", fmt.Errorf("unexpected path string, expected a 'name1.name2' or '.name1.name2' or '{name1.name2}' or '{.name1.name2}'")
}
if len(submatches) != 3 {
return "", fmt.Errorf("unexpected submatch list: %v", submatches)
}
var fieldSpec string
if len(submatches[1]) != 0 {
fieldSpec = submatches[1]
} else {
fieldSpec = submatches[2]
}
return fmt.Sprintf("{.%s}", fieldSpec), nil
}
// NewCustomColumnsPrinterFromSpec creates a custom columns printer from a comma separated list of <header>:<jsonpath-field-spec> pairs.
// e.g. NAME:metadata.name,API_VERSION:apiVersion creates a printer that prints:
//
// NAME API_VERSION
// foo bar
func NewCustomColumnsPrinterFromSpec(spec string, decoder runtime.Decoder) (*CustomColumnsPrinter, error) {
if len(spec) == 0 {
return nil, fmt.Errorf("custom-columns format specified but no custom columns given")
}
parts := strings.Split(spec, ",")
columns := make([]Column, len(parts))
for ix := range parts {
colSpec := strings.Split(parts[ix], ":")
if len(colSpec) != 2 {
return nil, fmt.Errorf("unexpected custom-columns spec: %s, expected <header>:<json-path-expr>", parts[ix])
}
spec, err := massageJSONPath(colSpec[1])
if err != nil {
return nil, err
}
columns[ix] = Column{Header: colSpec[0], FieldSpec: spec}
}
return &CustomColumnsPrinter{Columns: columns, Decoder: decoder}, nil
}
func splitOnWhitespace(line string) []string {
lineScanner := bufio.NewScanner(bytes.NewBufferString(line))
lineScanner.Split(bufio.ScanWords)
result := []string{}
for lineScanner.Scan() {
result = append(result, lineScanner.Text())
}
return result
}
// NewCustomColumnsPrinterFromTemplate creates a custom columns printer from a template stream. The template is expected
// to consist of two lines, whitespace separated. The first line is the header line, the second line is the jsonpath field spec
// For example the template below:
// NAME API_VERSION
// {metadata.name} {apiVersion}
func NewCustomColumnsPrinterFromTemplate(templateReader io.Reader, decoder runtime.Decoder) (*CustomColumnsPrinter, error) {
scanner := bufio.NewScanner(templateReader)
if !scanner.Scan() {
return nil, fmt.Errorf("invalid template, missing header line. Expected format is one line of space separated headers, one line of space separated column specs.")
}
headers := splitOnWhitespace(scanner.Text())
if !scanner.Scan() {
return nil, fmt.Errorf("invalid template, missing spec line. Expected format is one line of space separated headers, one line of space separated column specs.")
}
specs := splitOnWhitespace(scanner.Text())
if len(headers) != len(specs) {
return nil, fmt.Errorf("number of headers (%d) and field specifications (%d) don't match", len(headers), len(specs))
}
columns := make([]Column, len(headers))
for ix := range headers {
spec, err := massageJSONPath(specs[ix])
if err != nil {
return nil, err
}
columns[ix] = Column{
Header: headers[ix],
FieldSpec: spec,
}
}
return &CustomColumnsPrinter{Columns: columns, Decoder: decoder}, nil
}
// Column represents a user specified column
type Column struct {
// The header to print above the column, general style is ALL_CAPS
Header string
// The pointer to the field in the object to print in JSONPath form
// e.g. {.ObjectMeta.Name}, see pkg/util/jsonpath for more details.
FieldSpec string
}
// CustomColumnPrinter is a printer that knows how to print arbitrary columns
// of data from templates specified in the `Columns` array
type CustomColumnsPrinter struct {
Columns []Column
Decoder runtime.Decoder
}
func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
w := tabwriter.NewWriter(out, columnwidth, tabwidth, padding, padding_character, flags)
headers := make([]string, len(s.Columns))
for ix := range s.Columns {
headers[ix] = s.Columns[ix].Header
}
fmt.Fprintln(w, strings.Join(headers, "\t"))
parsers := make([]*jsonpath.JSONPath, len(s.Columns))
for ix := range s.Columns {
parsers[ix] = jsonpath.New(fmt.Sprintf("column%d", ix))
if err := parsers[ix].Parse(s.Columns[ix].FieldSpec); err != nil {
return err
}
}
if meta.IsListType(obj) {
objs, err := meta.ExtractList(obj)
if err != nil {
return err
}
for ix := range objs {
if err := s.printOneObject(objs[ix], parsers, w); err != nil {
return err
}
}
} else {
if err := s.printOneObject(obj, parsers, w); err != nil {
return err
}
}
return w.Flush()
}
func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jsonpath.JSONPath, out io.Writer) error {
columns := make([]string, len(parsers))
switch u := obj.(type) {
case *runtime.Unknown:
if len(u.Raw) > 0 {
var err error
if obj, err = runtime.Decode(s.Decoder, u.Raw); err != nil {
return fmt.Errorf("can't decode object for printing: %v (%s)", err, u.Raw)
}
}
}
for ix := range parsers {
parser := parsers[ix]
values, err := parser.FindResults(reflect.ValueOf(obj).Elem().Interface())
if err != nil {
return err
}
if len(values) == 0 || len(values[0]) == 0 {
fmt.Fprintf(out, "<none>\t")
}
valueStrings := []string{}
for arrIx := range values {
for valIx := range values[arrIx] {
valueStrings = append(valueStrings, fmt.Sprintf("%v", values[arrIx][valIx].Interface()))
}
}
columns[ix] = strings.Join(valueStrings, ",")
}
fmt.Fprintln(out, strings.Join(columns, "\t"))
return nil
}
func (s *CustomColumnsPrinter) HandledResources() []string {
return []string{}
}

2188
vendor/k8s.io/kubernetes/pkg/kubectl/describe.go generated vendored Normal file

File diff suppressed because it is too large Load diff

20
vendor/k8s.io/kubernetes/pkg/kubectl/doc.go generated vendored Normal file
View file

@ -0,0 +1,20 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl is a set of libraries that are used by the kubectl command line tool.
// They are separated out into a library to support unit testing. Most functionality should
// be included in this package, and the main kubectl should really just be an entry point.
package kubectl

251
vendor/k8s.io/kubernetes/pkg/kubectl/explain.go generated vendored Normal file
View file

@ -0,0 +1,251 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"fmt"
"io"
"strings"
"github.com/emicklei/go-restful/swagger"
"k8s.io/kubernetes/pkg/api/meta"
apiutil "k8s.io/kubernetes/pkg/api/util"
)
var allModels = make(map[string]*swagger.NamedModel)
var recursive = false // this is global for convenience, can become int for multiple levels
// SplitAndParseResourceRequest separates the users input into a model and fields
func SplitAndParseResourceRequest(inResource string, mapper meta.RESTMapper) (string, []string, error) {
inResource, fieldsPath := splitDotNotation(inResource)
inResource, _ = mapper.ResourceSingularizer(inResource)
return inResource, fieldsPath, nil
}
// PrintModelDescription prints the description of a specific model or dot path
func PrintModelDescription(inModel string, fieldsPath []string, w io.Writer, swaggerSchema *swagger.ApiDeclaration, r bool) error {
recursive = r // this is global for convenience
apiVer := apiutil.GetVersion(swaggerSchema.ApiVersion) + "."
var pointedModel *swagger.NamedModel
for i := range swaggerSchema.Models.List {
name := swaggerSchema.Models.List[i].Name
allModels[name] = &swaggerSchema.Models.List[i]
if strings.ToLower(name) == strings.ToLower(apiVer+inModel) {
pointedModel = &swaggerSchema.Models.List[i]
}
}
if pointedModel == nil {
return fmt.Errorf("requested resource %q is not defined", inModel)
}
if len(fieldsPath) == 0 {
return printTopLevelResourceInfo(w, pointedModel)
}
var pointedModelAsProp *swagger.NamedModelProperty
for _, field := range fieldsPath {
if prop, nextModel, isModel := getField(pointedModel, field); prop != nil {
if isModel {
pointedModelAsProp = prop
pointedModel = allModels[nextModel]
} else {
return printPrimitive(w, prop)
}
} else {
return fmt.Errorf("field %q does not exist", field)
}
}
return printModelInfo(w, pointedModel, pointedModelAsProp)
}
func splitDotNotation(model string) (string, []string) {
var fieldsPath []string
dotModel := strings.Split(model, ".")
if len(dotModel) >= 1 {
fieldsPath = dotModel[1:]
}
return dotModel[0], fieldsPath
}
func getPointedModel(prop *swagger.ModelProperty) (string, bool) {
if prop.Ref != nil {
return *prop.Ref, true
} else if *prop.Type == "array" && prop.Items.Ref != nil {
return *prop.Items.Ref, true
}
return "", false
}
func getField(model *swagger.NamedModel, sField string) (*swagger.NamedModelProperty, string, bool) {
for _, prop := range model.Model.Properties.List {
if prop.Name == sField {
pointedModel, isModel := getPointedModel(&prop.Property)
return &prop, pointedModel, isModel
}
}
return nil, "", false
}
func printModelInfo(w io.Writer, model *swagger.NamedModel, modelProp *swagger.NamedModelProperty) error {
t, _ := getFieldType(&modelProp.Property)
fmt.Fprintf(w, "RESOURCE: %s <%s>\n\n", modelProp.Name, t)
fieldDesc, _ := wrapAndIndentText(modelProp.Property.Description, " ", 80)
fmt.Fprintf(w, "DESCRIPTION:\n%s\n\n%s\n", fieldDesc, indentText(model.Model.Description, " "))
return printFields(w, model)
}
func printPrimitive(w io.Writer, field *swagger.NamedModelProperty) error {
t, _ := getFieldType(&field.Property)
fmt.Fprintf(w, "FIELD: %s <%s>\n\n", field.Name, t)
d, _ := wrapAndIndentText(field.Property.Description, " ", 80)
fmt.Fprintf(w, "DESCRIPTION:\n%s\n", d)
return nil
}
func printTopLevelResourceInfo(w io.Writer, model *swagger.NamedModel) error {
fmt.Fprintf(w, "DESCRIPTION:\n%s\n", model.Model.Description)
return printFields(w, model)
}
func printFields(w io.Writer, model *swagger.NamedModel) error {
fmt.Fprint(w, "\nFIELDS:\n")
for _, field := range model.Model.Properties.List {
fieldType, err := getFieldType(&field.Property)
if err != nil {
return err
}
if arrayContains(model.Model.Required, field.Name) {
fmt.Fprintf(w, " %s\t<%s> -required-\n", field.Name, fieldType)
} else {
fmt.Fprintf(w, " %s\t<%s>\n", field.Name, fieldType)
}
if recursive {
pointedModel, isModel := getPointedModel(&field.Property)
if isModel {
for _, nestedField := range allModels[pointedModel].Model.Properties.List {
t, _ := getFieldType(&nestedField.Property)
fmt.Fprintf(w, " %s\t<%s>\n", nestedField.Name, t)
}
}
} else {
fieldDesc, _ := wrapAndIndentText(field.Property.Description, " ", 80)
fmt.Fprintf(w, "%s\n\n", fieldDesc)
}
}
fmt.Fprint(w, "\n")
return nil
}
func getFieldType(prop *swagger.ModelProperty) (string, error) {
if prop.Type == nil {
return "Object", nil
} else if *prop.Type == "any" {
// Swagger Spec doesn't return information for maps.
return "map[string]string", nil
} else if *prop.Type == "array" {
if prop.Items == nil {
return "", fmt.Errorf("error in swagger spec. Property: %v contains an array without type", prop)
}
if prop.Items.Ref != nil {
fieldType := "[]Object"
return fieldType, nil
}
fieldType := "[]" + *prop.Items.Type
return fieldType, nil
}
return *prop.Type, nil
}
func wrapAndIndentText(desc, indent string, lim int) (string, error) {
words := strings.Split(strings.Replace(strings.TrimSpace(desc), "\n", " ", -1), " ")
n := len(words)
for i := 0; i < n; i++ {
if len(words[i]) > lim {
if strings.Contains(words[i], "/") {
s := breakURL(words[i])
words = append(words[:i], append(s, words[i+1:]...)...)
i = i + len(s) - 1
} else {
fmt.Println(len(words[i]))
return "", fmt.Errorf("there are words longer that the break limit is")
}
}
}
var lines []string
line := []string{indent}
lineL := len(indent)
for i := 0; i < len(words); i++ {
w := words[i]
if strings.HasSuffix(w, "/") && lineL+len(w)-1 < lim {
prev := line[len(line)-1]
if strings.HasSuffix(prev, "/") {
if i+1 < len(words)-1 && !strings.HasSuffix(words[i+1], "/") {
w = strings.TrimSuffix(w, "/")
}
line[len(line)-1] = prev + w
lineL += len(w)
} else {
line = append(line, w)
lineL += len(w) + 1
}
} else if lineL+len(w) < lim {
line = append(line, w)
lineL += len(w) + 1
} else {
lines = append(lines, strings.Join(line, " "))
line = []string{indent, w}
lineL = len(indent) + len(w)
}
}
lines = append(lines, strings.Join(line, " "))
return strings.Join(lines, "\n"), nil
}
func breakURL(url string) []string {
var buf []string
for _, part := range strings.Split(url, "/") {
buf = append(buf, part+"/")
}
return buf
}
func indentText(text, indent string) string {
lines := strings.Split(text, "\n")
for i := range lines {
lines[i] = indent + lines[i]
}
return strings.Join(lines, "\n")
}
func arrayContains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

172
vendor/k8s.io/kubernetes/pkg/kubectl/generate.go generated vendored Normal file
View file

@ -0,0 +1,172 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
)
// GeneratorParam is a parameter for a generator
// TODO: facilitate structured json generator input schemes
type GeneratorParam struct {
Name string
Required bool
}
// Generator is an interface for things that can generate API objects from input parameters.
type Generator interface {
// Generate creates an API object given a set of parameters
Generate(params map[string]interface{}) (runtime.Object, error)
// ParamNames returns the list of parameters that this generator uses
ParamNames() []GeneratorParam
}
// StructuredGenerator is an interface for things that can generate API objects not using parameter injection
type StructuredGenerator interface {
// StructuredGenerator creates an API object using pre-configured parameters
StructuredGenerate() (runtime.Object, error)
}
func IsZero(i interface{}) bool {
if i == nil {
return true
}
return reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface())
}
// ValidateParams ensures that all required params are present in the params map
func ValidateParams(paramSpec []GeneratorParam, params map[string]interface{}) error {
allErrs := []error{}
for ix := range paramSpec {
if paramSpec[ix].Required {
value, found := params[paramSpec[ix].Name]
if !found || IsZero(value) {
allErrs = append(allErrs, fmt.Errorf("Parameter: %s is required", paramSpec[ix].Name))
}
}
}
return utilerrors.NewAggregate(allErrs)
}
// AnnotateFlags annotates all flags that are used by generators.
func AnnotateFlags(cmd *cobra.Command, generators map[string]Generator) {
// Iterate over all generators and mark any flags used by them.
for name, generator := range generators {
generatorParams := map[string]struct{}{}
for _, param := range generator.ParamNames() {
generatorParams[param.Name] = struct{}{}
}
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if _, found := generatorParams[flag.Name]; !found {
// This flag is not used by the current generator
// so skip it.
return
}
if flag.Annotations == nil {
flag.Annotations = map[string][]string{}
}
if annotations := flag.Annotations["generator"]; annotations == nil {
flag.Annotations["generator"] = []string{}
}
flag.Annotations["generator"] = append(flag.Annotations["generator"], name)
})
}
}
// EnsureFlagsValid ensures that no invalid flags are being used against a generator.
func EnsureFlagsValid(cmd *cobra.Command, generators map[string]Generator, generatorInUse string) error {
AnnotateFlags(cmd, generators)
allErrs := []error{}
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
// If the flag hasn't changed, don't validate it.
if !flag.Changed {
return
}
// Look into the flag annotations for the generators that can use it.
if annotations := flag.Annotations["generator"]; len(annotations) > 0 {
annotationMap := map[string]struct{}{}
for _, ann := range annotations {
annotationMap[ann] = struct{}{}
}
// If the current generator is not annotated, then this flag shouldn't
// be used with it.
if _, found := annotationMap[generatorInUse]; !found {
allErrs = append(allErrs, fmt.Errorf("cannot use --%s with --generator=%s", flag.Name, generatorInUse))
}
}
})
return utilerrors.NewAggregate(allErrs)
}
// MakeParams is a utility that creates generator parameters from a command line
func MakeParams(cmd *cobra.Command, params []GeneratorParam) map[string]interface{} {
result := map[string]interface{}{}
for ix := range params {
f := cmd.Flags().Lookup(params[ix].Name)
if f != nil {
result[params[ix].Name] = f.Value.String()
}
}
return result
}
func MakeLabels(labels map[string]string) string {
out := []string{}
for key, value := range labels {
out = append(out, fmt.Sprintf("%s=%s", key, value))
}
return strings.Join(out, ",")
}
// ParseLabels turns a string representation of a label set into a map[string]string
func ParseLabels(labelSpec interface{}) (map[string]string, error) {
labelString, isString := labelSpec.(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", labelSpec)
}
if len(labelString) == 0 {
return nil, fmt.Errorf("no label spec passed")
}
labels := map[string]string{}
labelSpecs := strings.Split(labelString, ",")
for ix := range labelSpecs {
labelSpec := strings.Split(labelSpecs[ix], "=")
if len(labelSpec) != 2 {
return nil, fmt.Errorf("unexpected label spec: %s", labelSpecs[ix])
}
labels[labelSpec[0]] = labelSpec[1]
}
return labels, nil
}
func GetBool(params map[string]string, key string, defValue bool) (bool, error) {
if val, found := params[key]; !found {
return defValue, nil
} else {
return strconv.ParseBool(val)
}
}

137
vendor/k8s.io/kubernetes/pkg/kubectl/history.go generated vendored Normal file
View file

@ -0,0 +1,137 @@
/*
Copyright 2016 The Kubernetes Authors 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 kubectl
import (
"fmt"
"io"
"sort"
"strconv"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/runtime"
deploymentutil "k8s.io/kubernetes/pkg/util/deployment"
"k8s.io/kubernetes/pkg/util/errors"
)
const (
ChangeCauseAnnotation = "kubernetes.io/change-cause"
)
// HistoryViewer provides an interface for resources that can be rolled back.
type HistoryViewer interface {
History(namespace, name string) (HistoryInfo, error)
}
func HistoryViewerFor(kind unversioned.GroupKind, c clientset.Interface) (HistoryViewer, error) {
switch kind {
case extensions.Kind("Deployment"):
return &DeploymentHistoryViewer{c}, nil
}
return nil, fmt.Errorf("no history viewer has been implemented for %q", kind)
}
// HistoryInfo stores the mapping from revision to podTemplate;
// note that change-cause annotation should be copied to podTemplate
type HistoryInfo struct {
RevisionToTemplate map[int64]*api.PodTemplateSpec
}
type DeploymentHistoryViewer struct {
c clientset.Interface
}
// History returns a revision-to-replicaset map as the revision history of a deployment
func (h *DeploymentHistoryViewer) History(namespace, name string) (HistoryInfo, error) {
historyInfo := HistoryInfo{
RevisionToTemplate: make(map[int64]*api.PodTemplateSpec),
}
deployment, err := h.c.Extensions().Deployments(namespace).Get(name)
if err != nil {
return historyInfo, fmt.Errorf("failed to retrieve deployment %s: %v", name, err)
}
_, allOldRSs, err := deploymentutil.GetOldReplicaSets(deployment, h.c)
if err != nil {
return historyInfo, fmt.Errorf("failed to retrieve old replica sets from deployment %s: %v", name, err)
}
newRS, err := deploymentutil.GetNewReplicaSet(deployment, h.c)
if err != nil {
return historyInfo, fmt.Errorf("failed to retrieve new replica set from deployment %s: %v", name, err)
}
allRSs := append(allOldRSs, newRS)
for _, rs := range allRSs {
v, err := deploymentutil.Revision(rs)
if err != nil {
continue
}
historyInfo.RevisionToTemplate[v] = &rs.Spec.Template
changeCause := getChangeCause(rs)
if historyInfo.RevisionToTemplate[v].Annotations == nil {
historyInfo.RevisionToTemplate[v].Annotations = make(map[string]string)
}
if len(changeCause) > 0 {
historyInfo.RevisionToTemplate[v].Annotations[ChangeCauseAnnotation] = changeCause
}
}
return historyInfo, nil
}
// PrintRolloutHistory prints a formatted table of the input revision history of the deployment
func PrintRolloutHistory(historyInfo HistoryInfo, resource, name string) (string, error) {
if len(historyInfo.RevisionToTemplate) == 0 {
return fmt.Sprintf("No rollout history found in %s %q", resource, name), nil
}
// Sort the revisionToChangeCause map by revision
var revisions []string
for k := range historyInfo.RevisionToTemplate {
revisions = append(revisions, strconv.FormatInt(k, 10))
}
sort.Strings(revisions)
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "%s %q:\n", resource, name)
fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n")
errs := []error{}
for _, r := range revisions {
// Find the change-cause of revision r
r64, err := strconv.ParseInt(r, 10, 64)
if err != nil {
errs = append(errs, err)
continue
}
changeCause := historyInfo.RevisionToTemplate[r64].Annotations[ChangeCauseAnnotation]
if len(changeCause) == 0 {
changeCause = "<none>"
}
fmt.Fprintf(out, "%s\t%s\n", r, changeCause)
}
return errors.NewAggregate(errs)
})
}
// getChangeCause returns the change-cause annotation of the input object
func getChangeCause(obj runtime.Object) string {
accessor, err := meta.Accessor(obj)
if err != nil {
return ""
}
return accessor.GetAnnotations()[ChangeCauseAnnotation]
}

32
vendor/k8s.io/kubernetes/pkg/kubectl/interfaces.go generated vendored Normal file
View file

@ -0,0 +1,32 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"k8s.io/kubernetes/pkg/api"
client "k8s.io/kubernetes/pkg/client/restclient"
)
// RESTClient is a client helper for dealing with RESTful resources
// in a generic way.
type RESTClient interface {
Get() *client.Request
Post() *client.Request
Patch(api.PatchType) *client.Request
Delete() *client.Request
Put() *client.Request
}

243
vendor/k8s.io/kubernetes/pkg/kubectl/kubectl.go generated vendored Normal file
View file

@ -0,0 +1,243 @@
/*
Copyright 2014 The Kubernetes Authors 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.
*/
// A set of common functions needed by cmd/kubectl and pkg/kubectl packages.
package kubectl
import (
"errors"
"fmt"
"path"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
)
const (
kubectlAnnotationPrefix = "kubectl.kubernetes.io/"
// TODO: auto-generate this
PossibleResourceTypes = `Possible resource types include (case insensitive): pods (po), services (svc), deployments,
replicasets (rs), replicationcontrollers (rc), nodes (no), events (ev), limitranges (limits),
persistentvolumes (pv), persistentvolumeclaims (pvc), resourcequotas (quota), namespaces (ns),
serviceaccounts (sa), ingresses (ing), horizontalpodautoscalers (hpa), daemonsets (ds), configmaps,
componentstatuses (cs), endpoints (ep), and secrets.`
)
type NamespaceInfo struct {
Namespace string
}
func listOfImages(spec *api.PodSpec) []string {
var images []string
for _, container := range spec.Containers {
images = append(images, container.Image)
}
return images
}
func makeImageList(spec *api.PodSpec) string {
return strings.Join(listOfImages(spec), ",")
}
func NewThirdPartyResourceMapper(gvs []unversioned.GroupVersion, gvks []unversioned.GroupVersionKind) (meta.RESTMapper, error) {
mapper := meta.NewDefaultRESTMapper(gvs, func(gv unversioned.GroupVersion) (*meta.VersionInterfaces, error) {
for ix := range gvs {
if gvs[ix].Group == gv.Group && gvs[ix].Version == gv.Version {
return &meta.VersionInterfaces{
ObjectConvertor: api.Scheme,
MetadataAccessor: meta.NewAccessor(),
}, nil
}
}
groupVersions := []string{}
for ix := range gvs {
groupVersions = append(groupVersions, gvs[ix].String())
}
return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", gv.String(), strings.Join(groupVersions, ", "))
})
for ix := range gvks {
mapper.Add(gvks[ix], meta.RESTScopeNamespace)
}
return mapper, nil
}
// OutputVersionMapper is a RESTMapper that will prefer mappings that
// correspond to a preferred output version (if feasible)
type OutputVersionMapper struct {
meta.RESTMapper
// output versions takes a list of preferred GroupVersions. Only the first
// hit for a given group will have effect. This allows different output versions
// depending upon the group of the kind being requested
OutputVersions []unversioned.GroupVersion
}
// RESTMapping implements meta.RESTMapper by prepending the output version to the preferred version list.
func (m OutputVersionMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (*meta.RESTMapping, error) {
for _, preferredVersion := range m.OutputVersions {
if gk.Group == preferredVersion.Group {
mapping, err := m.RESTMapper.RESTMapping(gk, preferredVersion.Version)
if err == nil {
return mapping, nil
}
break
}
}
return m.RESTMapper.RESTMapping(gk, versions...)
}
// ShortcutExpander is a RESTMapper that can be used for Kubernetes
// resources. It expands the resource first, then invokes the wrapped RESTMapper
type ShortcutExpander struct {
RESTMapper meta.RESTMapper
}
var _ meta.RESTMapper = &ShortcutExpander{}
func (e ShortcutExpander) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) {
return e.RESTMapper.KindFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) KindsFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) {
return e.RESTMapper.KindsFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) {
return e.RESTMapper.ResourcesFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) {
return e.RESTMapper.ResourceFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) ResourceSingularizer(resource string) (string, error) {
return e.RESTMapper.ResourceSingularizer(expandResourceShortcut(unversioned.GroupVersionResource{Resource: resource}).Resource)
}
func (e ShortcutExpander) RESTMapping(gk unversioned.GroupKind, versions ...string) (*meta.RESTMapping, error) {
return e.RESTMapper.RESTMapping(gk, versions...)
}
func (e ShortcutExpander) AliasesForResource(resource string) ([]string, bool) {
return e.RESTMapper.AliasesForResource(expandResourceShortcut(unversioned.GroupVersionResource{Resource: resource}).Resource)
}
// shortForms is the list of short names to their expanded names
var shortForms = map[string]string{
// Please keep this alphabetized
// If you add an entry here, please also take a look at pkg/kubectl/cmd/cmd.go
// and add an entry to valid_resources when appropriate.
"cs": "componentstatuses",
"ds": "daemonsets",
"ep": "endpoints",
"ev": "events",
"hpa": "horizontalpodautoscalers",
"ing": "ingresses",
"limits": "limitranges",
"no": "nodes",
"ns": "namespaces",
"po": "pods",
"psp": "podSecurityPolicies",
"pvc": "persistentvolumeclaims",
"pv": "persistentvolumes",
"quota": "resourcequotas",
"rc": "replicationcontrollers",
"rs": "replicasets",
"sa": "serviceaccounts",
"svc": "services",
}
// 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.
func expandResourceShortcut(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource {
if expanded, ok := shortForms[resource.Resource]; ok {
// don't change the group or version that's already been specified
resource.Resource = expanded
}
return resource
}
// ResourceAliases returns the resource shortcuts and plural forms for the given resources.
func ResourceAliases(rs []string) []string {
as := make([]string, 0, len(rs))
plurals := make(map[string]struct{}, len(rs))
for _, r := range rs {
var plural string
switch {
case r == "endpoints":
plural = r // exception. "endpoint" does not exist. Why?
case strings.HasSuffix(r, "y"):
plural = r[0:len(r)-1] + "ies"
case strings.HasSuffix(r, "s"):
plural = r + "es"
default:
plural = r + "s"
}
as = append(as, plural)
plurals[plural] = struct{}{}
}
for sf, r := range shortForms {
if _, found := plurals[r]; found {
as = append(as, sf)
}
}
return as
}
// parseFileSource parses the source given. Acceptable formats include:
//
// 1. source-path: the basename will become the key name
// 2. source-name=source-path: the source-name will become the key name and source-path is the path to the key file
//
// Key names cannot include '='.
func parseFileSource(source string) (keyName, filePath string, err error) {
numSeparators := strings.Count(source, "=")
switch {
case numSeparators == 0:
return path.Base(source), source, nil
case numSeparators == 1 && strings.HasPrefix(source, "="):
return "", "", fmt.Errorf("key name for file path %v missing.", strings.TrimPrefix(source, "="))
case numSeparators == 1 && strings.HasSuffix(source, "="):
return "", "", fmt.Errorf("file path for key name %v missing.", strings.TrimSuffix(source, "="))
case numSeparators > 1:
return "", "", errors.New("Key names or file paths cannot contain '='.")
default:
components := strings.Split(source, "=")
return components[0], components[1], nil
}
}
// parseLiteralSource parses the source key=val pair
func parseLiteralSource(source string) (keyName, value string, err error) {
// leading equal is invalid
if strings.Index(source, "=") == 0 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
// split after the first equal (so values can have the = character)
items := strings.SplitN(source, "=", 2)
if len(items) != 2 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
return items[0], items[1], nil
}

79
vendor/k8s.io/kubernetes/pkg/kubectl/namespace.go generated vendored Normal file
View file

@ -0,0 +1,79 @@
/*
Copyright 2015 The Kubernetes Authors 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 kubectl
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/runtime"
)
// NamespaceGeneratorV1 supports stable generation of a namespace
type NamespaceGeneratorV1 struct {
// Name of namespace
Name string
}
// Ensure it supports the generator pattern that uses parameter injection
var _ Generator = &NamespaceGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ StructuredGenerator = &NamespaceGeneratorV1{}
// Generate returns a namespace using the specified parameters
func (g NamespaceGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(g.ParamNames(), genericParams)
if err != nil {
return nil, err
}
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
delegate := &NamespaceGeneratorV1{Name: params["name"]}
return delegate.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern
func (g NamespaceGeneratorV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
}
}
// StructuredGenerate outputs a namespace object using the configured fields
func (g *NamespaceGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := g.validate(); err != nil {
return nil, err
}
namespace := &api.Namespace{}
namespace.Name = g.Name
return namespace, nil
}
// validate validates required fields are set to support structured generation
func (g *NamespaceGeneratorV1) validate() error {
if len(g.Name) == 0 {
return fmt.Errorf("name must be specified")
}
return nil
}

248
vendor/k8s.io/kubernetes/pkg/kubectl/proxy_server.go generated vendored Normal file
View file

@ -0,0 +1,248 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"regexp"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/util"
)
const (
DefaultHostAcceptRE = "^localhost$,^127\\.0\\.0\\.1$,^\\[::1\\]$"
DefaultPathAcceptRE = "^/.*"
DefaultPathRejectRE = "^/api/.*/exec,^/api/.*/run,^/api/.*/attach"
DefaultMethodRejectRE = "POST,PUT,PATCH"
)
var (
// The reverse proxy will periodically flush the io writer at this frequency.
// Only matters for long poll connections like the one used to watch. With an
// interval of 0 the reverse proxy will buffer content sent on any connection
// with transfer-encoding=chunked.
// TODO: Flush after each chunk so the client doesn't suffer a 100ms latency per
// watch event.
ReverseProxyFlushInterval = 100 * time.Millisecond
)
// FilterServer rejects requests which don't match one of the specified regular expressions
type FilterServer struct {
// Only paths that match this regexp will be accepted
AcceptPaths []*regexp.Regexp
// Paths that match this regexp will be rejected, even if they match the above
RejectPaths []*regexp.Regexp
// Hosts are required to match this list of regexp
AcceptHosts []*regexp.Regexp
// Methods that match this regexp are rejected
RejectMethods []*regexp.Regexp
// The delegate to call to handle accepted requests.
delegate http.Handler
}
// Splits a comma separated list of regexps into a array of Regexp objects.
func MakeRegexpArray(str string) ([]*regexp.Regexp, error) {
parts := strings.Split(str, ",")
result := make([]*regexp.Regexp, len(parts))
for ix := range parts {
re, err := regexp.Compile(parts[ix])
if err != nil {
return nil, err
}
result[ix] = re
}
return result, nil
}
func MakeRegexpArrayOrDie(str string) []*regexp.Regexp {
result, err := MakeRegexpArray(str)
if err != nil {
glog.Fatalf("Error compiling re: %v", err)
}
return result
}
func matchesRegexp(str string, regexps []*regexp.Regexp) bool {
for _, re := range regexps {
if re.MatchString(str) {
glog.V(6).Infof("%v matched %s", str, re)
return true
}
}
return false
}
func (f *FilterServer) accept(method, path, host string) bool {
if matchesRegexp(path, f.RejectPaths) {
glog.V(3).Infof("Filter rejecting %v %v %v", method, path, host)
return false
}
if matchesRegexp(method, f.RejectMethods) {
glog.V(3).Infof("Filter rejecting %v %v %v", method, path, host)
return false
}
if matchesRegexp(path, f.AcceptPaths) && matchesRegexp(host, f.AcceptHosts) {
glog.V(3).Infof("Filter accepting %v %v %v", method, path, host)
return true
}
glog.V(3).Infof("Filter rejecting %v %v %v", method, path, host)
return false
}
// Make a copy of f which passes requests along to the new delegate.
func (f *FilterServer) HandlerFor(delegate http.Handler) *FilterServer {
f2 := *f
f2.delegate = delegate
return &f2
}
// Get host from a host header value like "localhost" or "localhost:8080"
func extractHost(header string) (host string) {
host, _, err := net.SplitHostPort(header)
if err != nil {
host = header
}
return host
}
func (f *FilterServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
host := extractHost(req.Host)
if f.accept(req.Method, req.URL.Path, host) {
f.delegate.ServeHTTP(rw, req)
return
}
rw.WriteHeader(http.StatusForbidden)
rw.Write([]byte("<h3>Unauthorized</h3>"))
}
// ProxyServer is a http.Handler which proxies Kubernetes APIs to remote API server.
type ProxyServer struct {
handler http.Handler
}
// NewProxyServer creates and installs a new ProxyServer.
// It automatically registers the created ProxyServer to http.DefaultServeMux.
// 'filter', if non-nil, protects requests to the api only.
func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *restclient.Config) (*ProxyServer, error) {
host := cfg.Host
if !strings.HasSuffix(host, "/") {
host = host + "/"
}
target, err := url.Parse(host)
if err != nil {
return nil, err
}
proxy := newProxy(target)
if proxy.Transport, err = restclient.TransportFor(cfg); err != nil {
return nil, err
}
proxyServer := http.Handler(proxy)
if filter != nil {
proxyServer = filter.HandlerFor(proxyServer)
}
if !strings.HasPrefix(apiProxyPrefix, "/api") {
proxyServer = stripLeaveSlash(apiProxyPrefix, proxyServer)
}
mux := http.NewServeMux()
mux.Handle(apiProxyPrefix, proxyServer)
if filebase != "" {
// Require user to explicitly request this behavior rather than
// serving their working directory by default.
mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase))
}
return &ProxyServer{handler: mux}, nil
}
// Listen is a simple wrapper around net.Listen.
func (s *ProxyServer) Listen(address string, port int) (net.Listener, error) {
return net.Listen("tcp", fmt.Sprintf("%s:%d", address, port))
}
// ListenUnix does net.Listen for a unix socket
func (s *ProxyServer) ListenUnix(path string) (net.Listener, error) {
// Remove any socket, stale or not, but fall through for other files
fi, err := os.Stat(path)
if err == nil && (fi.Mode()&os.ModeSocket) != 0 {
os.Remove(path)
}
// Default to only user accessible socket, caller can open up later if desired
oldmask, _ := util.Umask(0077)
l, err := net.Listen("unix", path)
util.Umask(oldmask)
return l, err
}
// Serve starts the server using given listener, loops forever.
func (s *ProxyServer) ServeOnListener(l net.Listener) error {
server := http.Server{
Handler: s.handler,
}
return server.Serve(l)
}
func newProxy(target *url.URL) *httputil.ReverseProxy {
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
}
return &httputil.ReverseProxy{Director: director, FlushInterval: ReverseProxyFlushInterval}
}
func newFileHandler(prefix, base string) http.Handler {
return http.StripPrefix(prefix, http.FileServer(http.Dir(base)))
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
// like http.StripPrefix, but always leaves an initial slash. (so that our
// regexps will work.)
func stripLeaveSlash(prefix string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
p := strings.TrimPrefix(req.URL.Path, prefix)
if len(p) >= len(req.URL.Path) {
http.NotFound(w, req)
return
}
if len(p) > 0 && p[:1] != "/" {
p = "/" + p
}
req.URL.Path = p
h.ServeHTTP(w, req)
})
}

View file

@ -0,0 +1,734 @@
/*
Copyright 2014 The Kubernetes Authors 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"
"io"
"net/url"
"os"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/sets"
)
var FileExtensions = []string{".json", ".yaml", ".yml"}
var InputExtensions = append(FileExtensions, "stdin")
// Builder provides convenience functions for taking arguments and parameters
// from the command line and converting them to a list of resources to iterate
// over using the Visitor interface.
type Builder struct {
mapper *Mapper
errs []error
paths []Visitor
stream bool
dir bool
selector labels.Selector
selectAll bool
resources []string
namespace string
names []string
resourceTuples []resourceTuple
defaultNamespace bool
requireNamespace bool
flatten bool
latest bool
requireObject bool
singleResourceType bool
continueOnError bool
export bool
schema validation.Schema
}
type resourceTuple struct {
Resource string
Name string
}
// NewBuilder creates a builder that operates on generic objects.
func NewBuilder(mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper, decoder runtime.Decoder) *Builder {
return &Builder{
mapper: &Mapper{typer, mapper, clientMapper, decoder},
requireObject: true,
}
}
func (b *Builder) Schema(schema validation.Schema) *Builder {
b.schema = schema
return b
}
// FilenameParam groups input in two categories: URLs and files (files, directories, STDIN)
// If enforceNamespace is false, namespaces in the specs will be allowed to
// override the default namespace. If it is true, namespaces that don't match
// will cause an error.
// If ContinueOnError() is set prior to this method, objects on the path that are not
// recognized will be ignored (but logged at V(2)).
func (b *Builder) FilenameParam(enforceNamespace, recursive bool, paths ...string) *Builder {
for _, s := range paths {
switch {
case s == "-":
b.Stdin()
case strings.Index(s, "http://") == 0 || strings.Index(s, "https://") == 0:
url, err := url.Parse(s)
if err != nil {
b.errs = append(b.errs, fmt.Errorf("the URL passed to filename %q is not valid: %v", s, err))
continue
}
b.URL(url)
default:
b.Path(recursive, s)
}
}
if enforceNamespace {
b.RequireNamespace()
}
return b
}
// URL accepts a number of URLs directly.
func (b *Builder) URL(urls ...*url.URL) *Builder {
for _, u := range urls {
b.paths = append(b.paths, &URLVisitor{
URL: u,
StreamVisitor: NewStreamVisitor(nil, b.mapper, u.String(), b.schema),
})
}
return b
}
// Stdin will read objects from the standard input. If ContinueOnError() is set
// prior to this method being called, objects in the stream that are unrecognized
// will be ignored (but logged at V(2)).
func (b *Builder) Stdin() *Builder {
b.stream = true
b.paths = append(b.paths, FileVisitorForSTDIN(b.mapper, b.schema))
return b
}
// Stream will read objects from the provided reader, and if an error occurs will
// include the name string in the error message. If ContinueOnError() is set
// prior to this method being called, objects in the stream that are unrecognized
// will be ignored (but logged at V(2)).
func (b *Builder) Stream(r io.Reader, name string) *Builder {
b.stream = true
b.paths = append(b.paths, NewStreamVisitor(r, b.mapper, name, b.schema))
return b
}
// Path accepts a set of paths that may be files, directories (all can containing
// one or more resources). Creates a FileVisitor for each file and then each
// FileVisitor is streaming the content to a StreamVisitor. If ContinueOnError() is set
// prior to this method being called, objects on the path that are unrecognized will be
// ignored (but logged at V(2)).
func (b *Builder) Path(recursive bool, paths ...string) *Builder {
for _, p := range paths {
_, err := os.Stat(p)
if os.IsNotExist(err) {
b.errs = append(b.errs, fmt.Errorf("the path %q does not exist", p))
continue
}
if err != nil {
b.errs = append(b.errs, fmt.Errorf("the path %q cannot be accessed: %v", p, err))
continue
}
visitors, err := ExpandPathsToFileVisitors(b.mapper, p, recursive, FileExtensions, b.schema)
if err != nil {
b.errs = append(b.errs, fmt.Errorf("error reading %q: %v", p, err))
}
if len(visitors) > 1 {
b.dir = true
}
b.paths = append(b.paths, visitors...)
}
return b
}
// ResourceTypes is a list of types of resources to operate on, when listing objects on
// the server or retrieving objects that match a selector.
func (b *Builder) ResourceTypes(types ...string) *Builder {
b.resources = append(b.resources, types...)
return b
}
// ResourceNames accepts a default type and one or more names, and creates tuples of
// resources
func (b *Builder) ResourceNames(resource string, names ...string) *Builder {
for _, name := range names {
// See if this input string is of type/name format
tuple, ok, err := splitResourceTypeName(name)
if err != nil {
b.errs = append(b.errs, err)
return b
}
if ok {
b.resourceTuples = append(b.resourceTuples, tuple)
continue
}
// Use the given default type to create a resource tuple
b.resourceTuples = append(b.resourceTuples, resourceTuple{Resource: resource, Name: name})
}
return b
}
// SelectorParam defines a selector that should be applied to the object types to load.
// This will not affect files loaded from disk or URL. If the parameter is empty it is
// a no-op - to select all resources invoke `b.Selector(labels.Everything)`.
func (b *Builder) SelectorParam(s string) *Builder {
selector, err := labels.Parse(s)
if err != nil {
b.errs = append(b.errs, fmt.Errorf("the provided selector %q is not valid: %v", s, err))
return b
}
if selector.Empty() {
return b
}
if b.selectAll {
b.errs = append(b.errs, fmt.Errorf("found non empty selector %q with previously set 'all' parameter. ", s))
return b
}
return b.Selector(selector)
}
// Selector accepts a selector directly, and if non nil will trigger a list action.
func (b *Builder) Selector(selector labels.Selector) *Builder {
b.selector = selector
return b
}
// ExportParam accepts the export boolean for these resources
func (b *Builder) ExportParam(export bool) *Builder {
b.export = export
return b
}
// NamespaceParam accepts the namespace that these resources should be
// considered under from - used by DefaultNamespace() and RequireNamespace()
func (b *Builder) NamespaceParam(namespace string) *Builder {
b.namespace = namespace
return b
}
// DefaultNamespace instructs the builder to set the namespace value for any object found
// to NamespaceParam() if empty.
func (b *Builder) DefaultNamespace() *Builder {
b.defaultNamespace = true
return b
}
// AllNamespaces instructs the builder to use NamespaceAll as a namespace to request resources
// acroll all namespace. This overrides the namespace set by NamespaceParam().
func (b *Builder) AllNamespaces(allNamespace bool) *Builder {
if allNamespace {
b.namespace = api.NamespaceAll
}
return b
}
// RequireNamespace instructs the builder to set the namespace value for any object found
// to NamespaceParam() if empty, and if the value on the resource does not match
// NamespaceParam() an error will be returned.
func (b *Builder) RequireNamespace() *Builder {
b.requireNamespace = true
return b
}
// SelectEverythingParam
func (b *Builder) SelectAllParam(selectAll bool) *Builder {
if selectAll && b.selector != nil {
b.errs = append(b.errs, fmt.Errorf("setting 'all' parameter but found a non empty selector. "))
return b
}
b.selectAll = selectAll
return b
}
// ResourceTypeOrNameArgs indicates that the builder should accept arguments
// of the form `(<type1>[,<type2>,...]|<type> <name1>[,<name2>,...])`. When one argument is
// received, the types provided will be retrieved from the server (and be comma delimited).
// When two or more arguments are received, they must be a single type and resource name(s).
// The allowEmptySelector permits to select all the resources (via Everything func).
func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string) *Builder {
args = normalizeMultipleResourcesArgs(args)
if ok, err := hasCombinedTypeArgs(args); ok {
if err != nil {
b.errs = append(b.errs, err)
return b
}
for _, s := range args {
tuple, ok, err := splitResourceTypeName(s)
if err != nil {
b.errs = append(b.errs, err)
return b
}
if ok {
b.resourceTuples = append(b.resourceTuples, tuple)
}
}
return b
}
if len(args) > 0 {
// Try replacing aliases only in types
args[0] = b.replaceAliases(args[0])
}
switch {
case len(args) > 2:
b.names = append(b.names, args[1:]...)
b.ResourceTypes(SplitResourceArgument(args[0])...)
case len(args) == 2:
b.names = append(b.names, args[1])
b.ResourceTypes(SplitResourceArgument(args[0])...)
case len(args) == 1:
b.ResourceTypes(SplitResourceArgument(args[0])...)
if b.selector == nil && allowEmptySelector {
b.selector = labels.Everything()
}
case len(args) == 0:
default:
b.errs = append(b.errs, fmt.Errorf("when passing arguments, must be resource or resource and name"))
}
return b
}
// replaceAliases accepts an argument and tries to expand any existing
// aliases found in it
func (b *Builder) replaceAliases(input string) string {
replaced := []string{}
for _, arg := range strings.Split(input, ",") {
if aliases, ok := b.mapper.AliasesForResource(arg); ok {
arg = strings.Join(aliases, ",")
}
replaced = append(replaced, arg)
}
return strings.Join(replaced, ",")
}
func hasCombinedTypeArgs(args []string) (bool, error) {
hasSlash := 0
for _, s := range args {
if strings.Contains(s, "/") {
hasSlash++
}
}
switch {
case hasSlash > 0 && hasSlash == len(args):
return true, nil
case hasSlash > 0 && hasSlash != len(args):
return true, fmt.Errorf("when passing arguments in resource/name form, all arguments must include the resource")
default:
return false, nil
}
}
// Normalize args convert multiple resources to resource tuples, a,b,c d
// as a transform to a/d b/d c/d
func normalizeMultipleResourcesArgs(args []string) []string {
if len(args) >= 2 {
resources := []string{}
resources = append(resources, SplitResourceArgument(args[0])...)
if len(resources) > 1 {
names := []string{}
names = append(names, args[1:]...)
newArgs := []string{}
for _, resource := range resources {
for _, name := range names {
newArgs = append(newArgs, strings.Join([]string{resource, name}, "/"))
}
}
return newArgs
}
}
return args
}
// splitResourceTypeName handles type/name resource formats and returns a resource tuple
// (empty or not), whether it successfully found one, and an error
func splitResourceTypeName(s string) (resourceTuple, bool, error) {
if !strings.Contains(s, "/") {
return resourceTuple{}, false, nil
}
seg := strings.Split(s, "/")
if len(seg) != 2 {
return resourceTuple{}, false, fmt.Errorf("arguments in resource/name form may not have more than one slash")
}
resource, name := seg[0], seg[1]
if len(resource) == 0 || len(name) == 0 || len(SplitResourceArgument(resource)) != 1 {
return resourceTuple{}, false, fmt.Errorf("arguments in resource/name form must have a single resource and name")
}
return resourceTuple{Resource: resource, Name: name}, true, nil
}
// Flatten will convert any objects with a field named "Items" that is an array of runtime.Object
// compatible types into individual entries and give them their own items. The original object
// is not passed to any visitors.
func (b *Builder) Flatten() *Builder {
b.flatten = true
return b
}
// Latest will fetch the latest copy of any objects loaded from URLs or files from the server.
func (b *Builder) Latest() *Builder {
b.latest = true
return b
}
// RequireObject ensures that resulting infos have an object set. If false, resulting info may not have an object set.
func (b *Builder) RequireObject(require bool) *Builder {
b.requireObject = require
return b
}
// ContinueOnError will attempt to load and visit as many objects as possible, even if some visits
// return errors or some objects cannot be loaded. The default behavior is to terminate after
// the first error is returned from a VisitorFunc.
func (b *Builder) ContinueOnError() *Builder {
b.continueOnError = true
return b
}
// SingleResourceType will cause the builder to error if the user specifies more than a single type
// of resource.
func (b *Builder) SingleResourceType() *Builder {
b.singleResourceType = true
return b
}
// mappingFor returns the RESTMapping for the Kind referenced by the resource.
// prefers a fully specified GroupVersionResource match. If we don't have one match on GroupResource
func (b *Builder) mappingFor(resourceArg string) (*meta.RESTMapping, error) {
fullySpecifiedGVR, groupResource := unversioned.ParseResourceArg(resourceArg)
gvk := unversioned.GroupVersionKind{}
if fullySpecifiedGVR != nil {
gvk, _ = b.mapper.KindFor(*fullySpecifiedGVR)
}
if gvk.IsEmpty() {
var err error
gvk, err = b.mapper.KindFor(groupResource.WithVersion(""))
if err != nil {
return nil, err
}
}
return b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
}
func (b *Builder) resourceMappings() ([]*meta.RESTMapping, error) {
if len(b.resources) > 1 && b.singleResourceType {
return nil, fmt.Errorf("you may only specify a single resource type")
}
mappings := []*meta.RESTMapping{}
for _, r := range b.resources {
mapping, err := b.mappingFor(r)
if err != nil {
return nil, err
}
mappings = append(mappings, mapping)
}
return mappings, nil
}
func (b *Builder) resourceTupleMappings() (map[string]*meta.RESTMapping, error) {
mappings := make(map[string]*meta.RESTMapping)
canonical := make(map[string]struct{})
for _, r := range b.resourceTuples {
if _, ok := mappings[r.Resource]; ok {
continue
}
mapping, err := b.mappingFor(r.Resource)
if err != nil {
return nil, err
}
mappings[mapping.Resource] = mapping
mappings[r.Resource] = mapping
canonical[mapping.Resource] = struct{}{}
}
if len(canonical) > 1 && b.singleResourceType {
return nil, fmt.Errorf("you may only specify a single resource type")
}
return mappings, nil
}
func (b *Builder) visitorResult() *Result {
if len(b.errs) > 0 {
return &Result{err: utilerrors.NewAggregate(b.errs)}
}
if b.selectAll {
b.selector = labels.Everything()
}
// visit selectors
if b.selector != nil {
if len(b.names) != 0 {
return &Result{err: fmt.Errorf("name cannot be provided when a selector is specified")}
}
if len(b.resourceTuples) != 0 {
return &Result{err: fmt.Errorf("selectors and the all flag cannot be used when passing resource/name arguments")}
}
if len(b.resources) == 0 {
return &Result{err: fmt.Errorf("at least one resource must be specified to use a selector")}
}
// empty selector has different error message for paths being provided
if len(b.paths) != 0 {
if b.selector.Empty() {
return &Result{err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
} else {
return &Result{err: fmt.Errorf("a selector may not be specified when path, URL, or stdin is provided as input")}
}
}
mappings, err := b.resourceMappings()
if err != nil {
return &Result{err: err}
}
visitors := []Visitor{}
for _, mapping := range mappings {
client, err := b.mapper.ClientForMapping(mapping)
if err != nil {
return &Result{err: err}
}
selectorNamespace := b.namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
selectorNamespace = ""
}
visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, b.selector, b.export))
}
if b.continueOnError {
return &Result{visitor: EagerVisitorList(visitors), sources: visitors}
}
return &Result{visitor: VisitorList(visitors), sources: visitors}
}
// visit items specified by resource and name
if len(b.resourceTuples) != 0 {
isSingular := len(b.resourceTuples) == 1
if len(b.paths) != 0 {
return &Result{singular: isSingular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
}
if len(b.resources) != 0 {
return &Result{singular: isSingular, err: fmt.Errorf("you may not specify individual resources and bulk resources in the same call")}
}
// retrieve one client for each resource
mappings, err := b.resourceTupleMappings()
if err != nil {
return &Result{singular: isSingular, err: err}
}
clients := make(map[string]RESTClient)
for _, mapping := range mappings {
s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource)
if _, ok := clients[s]; ok {
continue
}
client, err := b.mapper.ClientForMapping(mapping)
if err != nil {
return &Result{err: err}
}
clients[s] = client
}
items := []Visitor{}
for _, tuple := range b.resourceTuples {
mapping, ok := mappings[tuple.Resource]
if !ok {
return &Result{singular: isSingular, err: fmt.Errorf("resource %q is not recognized: %v", tuple.Resource, mappings)}
}
s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource)
client, ok := clients[s]
if !ok {
return &Result{singular: isSingular, err: fmt.Errorf("could not find a client for resource %q", tuple.Resource)}
}
selectorNamespace := b.namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
selectorNamespace = ""
} else {
if len(b.namespace) == 0 {
return &Result{singular: isSingular, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")}
}
}
info := NewInfo(client, mapping, selectorNamespace, tuple.Name, b.export)
items = append(items, info)
}
var visitors Visitor
if b.continueOnError {
visitors = EagerVisitorList(items)
} else {
visitors = VisitorList(items)
}
return &Result{singular: isSingular, visitor: visitors, sources: items}
}
// visit items specified by name
if len(b.names) != 0 {
isSingular := len(b.names) == 1
if len(b.paths) != 0 {
return &Result{singular: isSingular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
}
if len(b.resources) == 0 {
return &Result{singular: isSingular, err: fmt.Errorf("you must provide a resource and a resource name together")}
}
if len(b.resources) > 1 {
return &Result{singular: isSingular, err: fmt.Errorf("you must specify only one resource")}
}
mappings, err := b.resourceMappings()
if err != nil {
return &Result{singular: isSingular, err: err}
}
mapping := mappings[0]
client, err := b.mapper.ClientForMapping(mapping)
if err != nil {
return &Result{err: err}
}
selectorNamespace := b.namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
selectorNamespace = ""
} else {
if len(b.namespace) == 0 {
return &Result{singular: isSingular, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")}
}
}
visitors := []Visitor{}
for _, name := range b.names {
info := NewInfo(client, mapping, selectorNamespace, name, b.export)
visitors = append(visitors, info)
}
return &Result{singular: isSingular, visitor: VisitorList(visitors), sources: visitors}
}
// visit items specified by paths
if len(b.paths) != 0 {
singular := !b.dir && !b.stream && len(b.paths) == 1
if len(b.resources) != 0 {
return &Result{singular: singular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well")}
}
var visitors Visitor
if b.continueOnError {
visitors = EagerVisitorList(b.paths)
} else {
visitors = VisitorList(b.paths)
}
// only items from disk can be refetched
if b.latest {
// must flatten lists prior to fetching
if b.flatten {
visitors = NewFlattenListVisitor(visitors, b.mapper)
}
// must set namespace prior to fetching
if b.defaultNamespace {
visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace))
}
visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
}
return &Result{singular: singular, visitor: visitors, sources: b.paths}
}
return &Result{err: fmt.Errorf("you must provide one or more resources by argument or filename (%s)", strings.Join(InputExtensions, "|"))}
}
// Do returns a Result object with a Visitor for the resources identified by the Builder.
// The visitor will respect the error behavior specified by ContinueOnError. Note that stream
// inputs are consumed by the first execution - use Infos() or Object() on the Result to capture a list
// for further iteration.
func (b *Builder) Do() *Result {
r := b.visitorResult()
if r.err != nil {
return r
}
if b.flatten {
r.visitor = NewFlattenListVisitor(r.visitor, b.mapper)
}
helpers := []VisitorFunc{}
if b.defaultNamespace {
helpers = append(helpers, SetNamespace(b.namespace))
}
if b.requireNamespace {
helpers = append(helpers, RequireNamespace(b.namespace))
}
helpers = append(helpers, FilterNamespace)
if b.requireObject {
helpers = append(helpers, RetrieveLazy)
}
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
if b.continueOnError {
r.visitor = ContinueOnErrorVisitor{r.visitor}
}
return r
}
// SplitResourceArgument splits the argument with commas and returns unique
// strings in the original order.
func SplitResourceArgument(arg string) []string {
out := []string{}
set := sets.NewString()
for _, s := range strings.Split(arg, ",") {
if set.Has(s) {
continue
}
set.Insert(s)
out = append(out, s)
}
return out
}
// HasNames returns true if the provided args contain resource names
func HasNames(args []string) (bool, error) {
args = normalizeMultipleResourcesArgs(args)
hasCombinedTypes, err := hasCombinedTypeArgs(args)
if err != nil {
return false, err
}
return hasCombinedTypes || len(args) > 1, nil
}

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

166
vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper.go generated vendored Normal file
View file

@ -0,0 +1,166 @@
/*
Copyright 2014 The Kubernetes Authors 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 (
"strconv"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/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 {
req.Param("export", strconv.FormatBool(export))
}
return req.Do().Get()
}
// TODO: add field selector
func (m *Helper) List(namespace, apiVersion string, selector labels.Selector, export bool) (runtime.Object, error) {
req := m.RESTClient.Get().
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
LabelsSelectorParam(selector)
if export {
req.Param("export", strconv.FormatBool(export))
}
return req.Do().Get()
}
func (m *Helper) Watch(namespace, resourceVersion, apiVersion string, labelSelector labels.Selector) (watch.Interface, error) {
return m.RESTClient.Get().
Prefix("watch").
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Param("resourceVersion", resourceVersion).
LabelsSelectorParam(labelSelector).
Watch()
}
func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) {
return m.RESTClient.Get().
Prefix("watch").
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name).
Param("resourceVersion", resourceVersion).
Watch()
}
func (m *Helper) Delete(namespace, name string) error {
return m.RESTClient.Delete().
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name).
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 api.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,46 @@
/*
Copyright 2014 The Kubernetes Authors 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 (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
client "k8s.io/kubernetes/pkg/client/restclient"
)
// RESTClient is a client helper for dealing with RESTful resources
// in a generic way.
type RESTClient interface {
Get() *client.Request
Post() *client.Request
Patch(api.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)
}

165
vendor/k8s.io/kubernetes/pkg/kubectl/resource/mapper.go generated vendored Normal file
View file

@ -0,0 +1,165 @@
/*
Copyright 2014 The Kubernetes Authors 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"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
"k8s.io/kubernetes/pkg/runtime"
)
// 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
}
// 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
runtime.Decoder
}
// 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) {
versions := &runtime.VersionedObjects{}
_, gvk, err := m.Decode(data, nil, versions)
if err != nil {
return nil, fmt.Errorf("unable to decode %q: %v", source, err)
}
var obj runtime.Object
var versioned runtime.Object
if registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
obj, err = runtime.Decode(thirdpartyresourcedata.NewDecoder(nil, gvk.Kind), data)
versioned = obj
} else {
obj, versioned = versions.Last(), versions.First()
}
if err != nil {
return nil, fmt.Errorf("unable to decode %q: %v [%v]", source, err, gvk)
}
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{
Mapping: mapping,
Client: client,
Namespace: namespace,
Name: name,
Source: source,
VersionedObject: versioned,
Object: obj,
ResourceVersion: resourceVersion,
}, 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 []unversioned.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{
Mapping: mapping,
Client: client,
Namespace: namespace,
Name: name,
Object: obj,
ResourceVersion: resourceVersion,
}, 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 []unversioned.GroupVersionKind, preferences []unversioned.GroupVersionKind) unversioned.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]
}

291
vendor/k8s.io/kubernetes/pkg/kubectl/resource/result.go generated vendored Normal file
View file

@ -0,0 +1,291 @@
/*
Copyright 2014 The Kubernetes Authors 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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/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
singular bool
ignoreErrors []utilerrors.Matcher
// populated by a call to Infos
info []*Info
}
// 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
}
// 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...)
}
// IntoSingular sets the provided boolean pointer to true if the Builder input
// reflected a single item, or multiple.
func (r *Result) IntoSingular(b *bool) *Result {
*b = r.singular
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
// api.List. The ResourceVersion of the api.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.singular {
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 &api.List{
ListMeta: unversioned.ListMeta{
ResourceVersion: version,
},
Items: objects,
}, err
}
// 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)
}
// AsVersionedObject converts a list of infos into a single object - either a List containing
// the objects as children, or if only a single Object is present, as that object. The provided
// version will be preferred as the conversion target, but the Object's mapping version will be
// used if that version is not present.
func AsVersionedObject(infos []*Info, forceList bool, version string, encoder runtime.Encoder) (runtime.Object, error) {
objects, err := AsVersionedObjects(infos, version, encoder)
if err != nil {
return nil, err
}
var object runtime.Object
if len(objects) == 1 && !forceList {
object = objects[0]
} else {
object = &api.List{Items: objects}
converted, err := tryConvert(api.Scheme, object, version, registered.GroupOrDie(api.GroupName).GroupVersion.Version)
if err != nil {
return nil, err
}
object = converted
}
return object, nil
}
// AsVersionedObjects converts a list of infos into versioned objects. The provided
// version will be preferred as the conversion target, but the Object's mapping version will be
// used if that version is not present.
func AsVersionedObjects(infos []*Info, version string, encoder runtime.Encoder) ([]runtime.Object, error) {
objects := []runtime.Object{}
for _, info := range infos {
if info.Object == nil {
continue
}
// TODO: use info.VersionedObject as the value?
switch obj := info.Object.(type) {
case *extensions.ThirdPartyResourceData:
objects = append(objects, &runtime.Unknown{Raw: obj.Data})
continue
}
// objects that are not part of api.Scheme must be converted to JSON
// TODO: convert to map[string]interface{}, attach to runtime.Unknown?
if len(version) > 0 {
if _, err := api.Scheme.ObjectKind(info.Object); runtime.IsNotRegisteredError(err) {
// TODO: ideally this would encode to version, but we don't expose multiple codecs here.
data, err := runtime.Encode(encoder, info.Object)
if err != nil {
return nil, err
}
// TODO: Set ContentEncoding and ContentType.
objects = append(objects, &runtime.Unknown{Raw: data})
continue
}
}
converted, err := tryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.GroupVersionKind.GroupVersion().String())
if err != nil {
return nil, err
}
objects = append(objects, converted)
}
return objects, nil
}
// tryConvert attempts to convert the given object to the provided versions in order. This function assumes
// the object is in internal version.
func tryConvert(convertor runtime.ObjectConvertor, object runtime.Object, versions ...string) (runtime.Object, error) {
var last error
for _, version := range versions {
if len(version) == 0 {
return object, nil
}
obj, err := convertor.ConvertToVersion(object, version)
if err != nil {
last = err
continue
}
return obj, nil
}
return nil, last
}

View file

@ -0,0 +1,81 @@
/*
Copyright 2014 The Kubernetes Authors 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"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/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
Export bool
}
// 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, export bool) *Selector {
return &Selector{
Client: client,
Mapping: mapping,
Namespace: namespace,
Selector: selector,
Export: export,
}
}
// Visit implements Visitor
func (r *Selector) Visit(fn VisitorFunc) error {
list, err := NewHelper(r.Client, r.Mapping).List(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector, r.Export)
if err != nil {
if errors.IsBadRequest(err) || errors.IsNotFound(err) {
if r.Selector.Empty() {
return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err)
} else {
return fmt.Errorf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, err)
}
}
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, nil)
}
func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) {
return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, resourceVersion, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector)
}
// ResourceMapping returns the mapping for this resource and implements ResourceMapping
func (r *Selector) ResourceMapping() *meta.RESTMapping {
return r.Mapping
}

View file

@ -0,0 +1,595 @@
/*
Copyright 2014 The Kubernetes Authors 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 (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/yaml"
"k8s.io/kubernetes/pkg/watch"
)
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 *meta.RESTMapping
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 provided object in a versioned type before defaulting
// and conversions into its corresponding internal type. This is useful for
// reflecting on user intent which may be lost after defaulting and conversions.
VersionedObject interface{}
// Optional, this is the most recent value returned by the server if available
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
}
// NewInfo returns a new info object
func NewInfo(client RESTClient, mapping *meta.RESTMapping, namespace, name string, export bool) *Info {
return &Info{
Client: client,
Mapping: mapping,
Namespace: namespace,
Name: name,
Export: export,
}
}
// 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 {
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
}
// 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
}
data, err := yaml.ToJSON(data)
if err != nil {
return fmt.Errorf("error converting to YAML: %v", err)
}
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
}
func (v *URLVisitor) Visit(fn VisitorFunc) error {
res, err := http.Get(v.URL.String())
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("unable to read URL %q, server reported %d %s", v.URL, res.StatusCode, res.Status)
}
v.StreamVisitor.Reader = res.Body
return v.StreamVisitor.Visit(fn)
}
// 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, struct {
runtime.ObjectTyper
runtime.Decoder
}{v.Mapper, 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 []unversioned.GroupVersionKind
if info.Mapping != nil && !info.Mapping.GroupVersionKind.IsEmpty() {
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 a 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
if f, err = os.Open(v.Path); err != nil {
return err
}
}
defer f.Close()
v.StreamVisitor.Reader = f
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)
}
obj, err := NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name, info.Export)
if err != nil {
return err
}
info.Object = obj
info.ResourceVersion, _ = info.Mapping.MetadataAccessor.ResourceVersion(obj)
return nil
}
// 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
}

2042
vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer.go generated vendored Normal file

File diff suppressed because it is too large Load diff

122
vendor/k8s.io/kubernetes/pkg/kubectl/rollback.go generated vendored Normal file
View file

@ -0,0 +1,122 @@
/*
Copyright 2016 The Kubernetes Authors 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 kubectl
import (
"fmt"
"os"
"os/signal"
"syscall"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/runtime"
deploymentutil "k8s.io/kubernetes/pkg/util/deployment"
"k8s.io/kubernetes/pkg/watch"
)
// Rollbacker provides an interface for resources that can be rolled back.
type Rollbacker interface {
Rollback(namespace, name string, updatedAnnotations map[string]string, toRevision int64, obj runtime.Object) (string, error)
}
func RollbackerFor(kind unversioned.GroupKind, c client.Interface) (Rollbacker, error) {
switch kind {
case extensions.Kind("Deployment"):
return &DeploymentRollbacker{c}, nil
}
return nil, fmt.Errorf("no rollbacker has been implemented for %q", kind)
}
type DeploymentRollbacker struct {
c client.Interface
}
func (r *DeploymentRollbacker) Rollback(namespace, name string, updatedAnnotations map[string]string, toRevision int64, obj runtime.Object) (string, error) {
d := obj.(*extensions.Deployment)
if d.Spec.Paused {
return "", fmt.Errorf("you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume' and try again")
}
deploymentRollback := &extensions.DeploymentRollback{
Name: name,
UpdatedAnnotations: updatedAnnotations,
RollbackTo: extensions.RollbackConfig{
Revision: toRevision,
},
}
result := ""
// Get current events
events, err := r.c.Events(namespace).List(api.ListOptions{})
if err != nil {
return result, err
}
// Do the rollback
if err := r.c.Extensions().Deployments(namespace).Rollback(deploymentRollback); err != nil {
return result, err
}
// Watch for the changes of events
watch, err := r.c.Events(namespace).Watch(api.ListOptions{Watch: true, ResourceVersion: events.ResourceVersion})
if err != nil {
return result, err
}
result = watchRollbackEvent(watch)
return result, err
}
// watchRollbackEvent watches for rollback events and returns rollback result
func watchRollbackEvent(w watch.Interface) string {
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGTERM)
for {
select {
case event, ok := <-w.ResultChan():
if !ok {
return ""
}
obj, ok := event.Object.(*api.Event)
if !ok {
w.Stop()
return ""
}
isRollback, result := isRollbackEvent(obj)
if isRollback {
w.Stop()
return result
}
case <-signals:
w.Stop()
}
}
}
// isRollbackEvent checks if the input event is about rollback, and returns true and
// related result string back if it is.
func isRollbackEvent(e *api.Event) (bool, string) {
rollbackEventReasons := []string{deploymentutil.RollbackRevisionNotFound, deploymentutil.RollbackTemplateUnchanged, deploymentutil.RollbackDone}
for _, reason := range rollbackEventReasons {
if e.Reason == reason {
if reason == deploymentutil.RollbackDone {
return true, "rolled back"
}
return true, fmt.Sprintf("skipped rollback (%s: %s)", e.Reason, e.Message)
}
}
return false, ""
}

739
vendor/k8s.io/kubernetes/pkg/kubectl/rolling_updater.go generated vendored Normal file
View file

@ -0,0 +1,739 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
goerrors "errors"
"fmt"
"io"
"strconv"
"strings"
"time"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/deployment"
"k8s.io/kubernetes/pkg/util/integer"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/wait"
)
const (
sourceIdAnnotation = kubectlAnnotationPrefix + "update-source-id"
desiredReplicasAnnotation = kubectlAnnotationPrefix + "desired-replicas"
originalReplicasAnnotation = kubectlAnnotationPrefix + "original-replicas"
nextControllerAnnotation = kubectlAnnotationPrefix + "next-controller-id"
)
// RollingUpdaterConfig is the configuration for a rolling deployment process.
type RollingUpdaterConfig struct {
// Out is a writer for progress output.
Out io.Writer
// OldRC is an existing controller to be replaced.
OldRc *api.ReplicationController
// NewRc is a controller that will take ownership of updated pods (will be
// created if needed).
NewRc *api.ReplicationController
// UpdatePeriod is the time to wait between individual pod updates.
UpdatePeriod time.Duration
// Interval is the time to wait between polling controller status after
// update.
Interval time.Duration
// Timeout is the time to wait for controller updates before giving up.
Timeout time.Duration
// CleanupPolicy defines the cleanup action to take after the deployment is
// complete.
CleanupPolicy RollingUpdaterCleanupPolicy
// MaxUnavailable is the maximum number of pods that can be unavailable during the update.
// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
// Absolute number is calculated from percentage by rounding up.
// This can not be 0 if MaxSurge is 0.
// By default, a fixed value of 1 is used.
// Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods
// immediately when the rolling update starts. Once new pods are ready, old RC
// can be scaled down further, followed by scaling up the new RC, ensuring
// that the total number of pods available at all times during the update is at
// least 70% of desired pods.
MaxUnavailable intstr.IntOrString
// MaxSurge is the maximum number of pods that can be scheduled above the desired number of pods.
// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
// This can not be 0 if MaxUnavailable is 0.
// Absolute number is calculated from percentage by rounding up.
// By default, a value of 1 is used.
// Example: when this is set to 30%, the new RC can be scaled up immediately
// when the rolling update starts, such that the total number of old and new pods do not exceed
// 130% of desired pods. Once old pods have been killed, new RC can be scaled up
// further, ensuring that total number of pods running at any time during
// the update is atmost 130% of desired pods.
MaxSurge intstr.IntOrString
}
// RollingUpdaterCleanupPolicy is a cleanup action to take after the
// deployment is complete.
type RollingUpdaterCleanupPolicy string
const (
// DeleteRollingUpdateCleanupPolicy means delete the old controller.
DeleteRollingUpdateCleanupPolicy RollingUpdaterCleanupPolicy = "Delete"
// PreserveRollingUpdateCleanupPolicy means keep the old controller.
PreserveRollingUpdateCleanupPolicy RollingUpdaterCleanupPolicy = "Preserve"
// RenameRollingUpdateCleanupPolicy means delete the old controller, and rename
// the new controller to the name of the old controller.
RenameRollingUpdateCleanupPolicy RollingUpdaterCleanupPolicy = "Rename"
)
// RollingUpdater provides methods for updating replicated pods in a predictable,
// fault-tolerant way.
type RollingUpdater struct {
// Client interface for creating and updating controllers
c client.Interface
// Namespace for resources
ns string
// scaleAndWait scales a controller and returns its updated state.
scaleAndWait func(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error)
//getOrCreateTargetController gets and validates an existing controller or
//makes a new one.
getOrCreateTargetController func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error)
// cleanup performs post deployment cleanup tasks for newRc and oldRc.
cleanup func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error
// getReadyPods returns the amount of old and new ready pods.
getReadyPods func(oldRc, newRc *api.ReplicationController) (int32, int32, error)
}
// NewRollingUpdater creates a RollingUpdater from a client.
func NewRollingUpdater(namespace string, client client.Interface) *RollingUpdater {
updater := &RollingUpdater{
c: client,
ns: namespace,
}
// Inject real implementations.
updater.scaleAndWait = updater.scaleAndWaitWithScaler
updater.getOrCreateTargetController = updater.getOrCreateTargetControllerWithClient
updater.getReadyPods = updater.readyPods
updater.cleanup = updater.cleanupWithClients
return updater
}
// Update all pods for a ReplicationController (oldRc) by creating a new
// controller (newRc) with 0 replicas, and synchronously scaling oldRc and
// newRc until oldRc has 0 replicas and newRc has the original # of desired
// replicas. Cleanup occurs based on a RollingUpdaterCleanupPolicy.
//
// Each interval, the updater will attempt to make progress however it can
// without violating any availability constraints defined by the config. This
// means the amount scaled up or down each interval will vary based on the
// timeliness of readiness and the updater will always try to make progress,
// even slowly.
//
// If an update from newRc to oldRc is already in progress, we attempt to
// drive it to completion. If an error occurs at any step of the update, the
// error will be returned.
//
// A scaling event (either up or down) is considered progress; if no progress
// is made within the config.Timeout, an error is returned.
//
// TODO: make this handle performing a rollback of a partially completed
// rollout.
func (r *RollingUpdater) Update(config *RollingUpdaterConfig) error {
out := config.Out
oldRc := config.OldRc
scaleRetryParams := NewRetryParams(config.Interval, config.Timeout)
// Find an existing controller (for continuing an interrupted update) or
// create a new one if necessary.
sourceId := fmt.Sprintf("%s:%s", oldRc.Name, oldRc.UID)
newRc, existed, err := r.getOrCreateTargetController(config.NewRc, sourceId)
if err != nil {
return err
}
if existed {
fmt.Fprintf(out, "Continuing update with existing controller %s.\n", newRc.Name)
} else {
fmt.Fprintf(out, "Created %s\n", newRc.Name)
}
// Extract the desired replica count from the controller.
desiredAnnotation, err := strconv.Atoi(newRc.Annotations[desiredReplicasAnnotation])
if err != nil {
return fmt.Errorf("Unable to parse annotation for %s: %s=%s",
newRc.Name, desiredReplicasAnnotation, newRc.Annotations[desiredReplicasAnnotation])
}
desired := int32(desiredAnnotation)
// Extract the original replica count from the old controller, adding the
// annotation if it doesn't yet exist.
_, hasOriginalAnnotation := oldRc.Annotations[originalReplicasAnnotation]
if !hasOriginalAnnotation {
existing, err := r.c.ReplicationControllers(oldRc.Namespace).Get(oldRc.Name)
if err != nil {
return err
}
if existing.Annotations == nil {
existing.Annotations = map[string]string{}
}
existing.Annotations[originalReplicasAnnotation] = strconv.Itoa(int(existing.Spec.Replicas))
updated, err := r.c.ReplicationControllers(existing.Namespace).Update(existing)
if err != nil {
return err
}
oldRc = updated
}
// maxSurge is the maximum scaling increment and maxUnavailable are the maximum pods
// that can be unavailable during a rollout.
maxSurge, maxUnavailable, err := deployment.ResolveFenceposts(&config.MaxSurge, &config.MaxUnavailable, desired)
if err != nil {
return err
}
// Validate maximums.
if desired > 0 && maxUnavailable == 0 && maxSurge == 0 {
return fmt.Errorf("one of maxSurge or maxUnavailable must be specified")
}
// The minumum pods which must remain available througout the update
// calculated for internal convenience.
minAvailable := int32(integer.IntMax(0, int(desired-maxUnavailable)))
// If the desired new scale is 0, then the max unavailable is necessarily
// the effective scale of the old RC regardless of the configuration
// (equivalent to 100% maxUnavailable).
if desired == 0 {
maxUnavailable = oldRc.Spec.Replicas
minAvailable = 0
}
fmt.Fprintf(out, "Scaling up %s from %d to %d, scaling down %s from %d to 0 (keep %d pods available, don't exceed %d pods)\n",
newRc.Name, newRc.Spec.Replicas, desired, oldRc.Name, oldRc.Spec.Replicas, minAvailable, desired+maxSurge)
// Scale newRc and oldRc until newRc has the desired number of replicas and
// oldRc has 0 replicas.
progressDeadline := time.Now().UnixNano() + config.Timeout.Nanoseconds()
for newRc.Spec.Replicas != desired || oldRc.Spec.Replicas != 0 {
// Store the existing replica counts for progress timeout tracking.
newReplicas := newRc.Spec.Replicas
oldReplicas := oldRc.Spec.Replicas
// Scale up as much as possible.
scaledRc, err := r.scaleUp(newRc, oldRc, desired, maxSurge, maxUnavailable, scaleRetryParams, config)
if err != nil {
return err
}
newRc = scaledRc
// Wait between scaling operations for things to settle.
time.Sleep(config.UpdatePeriod)
// Scale down as much as possible.
scaledRc, err = r.scaleDown(newRc, oldRc, desired, minAvailable, maxUnavailable, maxSurge, config)
if err != nil {
return err
}
oldRc = scaledRc
// If we are making progress, continue to advance the progress deadline.
// Otherwise, time out with an error.
progressMade := (newRc.Spec.Replicas != newReplicas) || (oldRc.Spec.Replicas != oldReplicas)
if progressMade {
progressDeadline = time.Now().UnixNano() + config.Timeout.Nanoseconds()
} else if time.Now().UnixNano() > progressDeadline {
return fmt.Errorf("timed out waiting for any update progress to be made")
}
}
// Housekeeping and cleanup policy execution.
return r.cleanup(oldRc, newRc, config)
}
// scaleUp scales up newRc to desired by whatever increment is possible given
// the configured surge threshold. scaleUp will safely no-op as necessary when
// it detects redundancy or other relevant conditions.
func (r *RollingUpdater) scaleUp(newRc, oldRc *api.ReplicationController, desired, maxSurge, maxUnavailable int32, scaleRetryParams *RetryParams, config *RollingUpdaterConfig) (*api.ReplicationController, error) {
// If we're already at the desired, do nothing.
if newRc.Spec.Replicas == desired {
return newRc, nil
}
// Scale up as far as we can based on the surge limit.
increment := (desired + maxSurge) - (oldRc.Spec.Replicas + newRc.Spec.Replicas)
// If the old is already scaled down, go ahead and scale all the way up.
if oldRc.Spec.Replicas == 0 {
increment = desired - newRc.Spec.Replicas
}
// We can't scale up without violating the surge limit, so do nothing.
if increment <= 0 {
return newRc, nil
}
// Increase the replica count, and deal with fenceposts.
newRc.Spec.Replicas += increment
if newRc.Spec.Replicas > desired {
newRc.Spec.Replicas = desired
}
// Perform the scale-up.
fmt.Fprintf(config.Out, "Scaling %s up to %d\n", newRc.Name, newRc.Spec.Replicas)
scaledRc, err := r.scaleAndWait(newRc, scaleRetryParams, scaleRetryParams)
if err != nil {
return nil, err
}
return scaledRc, nil
}
// scaleDown scales down oldRc to 0 at whatever decrement possible given the
// thresholds defined on the config. scaleDown will safely no-op as necessary
// when it detects redundancy or other relevant conditions.
func (r *RollingUpdater) scaleDown(newRc, oldRc *api.ReplicationController, desired, minAvailable, maxUnavailable, maxSurge int32, config *RollingUpdaterConfig) (*api.ReplicationController, error) {
// Already scaled down; do nothing.
if oldRc.Spec.Replicas == 0 {
return oldRc, nil
}
// Get ready pods. We shouldn't block, otherwise in case both old and new
// pods are unavailable then the rolling update process blocks.
// Timeout-wise we are already covered by the progress check.
_, newAvailable, err := r.getReadyPods(oldRc, newRc)
if err != nil {
return nil, err
}
// The old controller is considered as part of the total because we want to
// maintain minimum availability even with a volatile old controller.
// Scale down as much as possible while maintaining minimum availability
allPods := oldRc.Spec.Replicas + newRc.Spec.Replicas
newUnavailable := newRc.Spec.Replicas - newAvailable
decrement := allPods - minAvailable - newUnavailable
// The decrement normally shouldn't drop below 0 because the available count
// always starts below the old replica count, but the old replica count can
// decrement due to externalities like pods death in the replica set. This
// will be considered a transient condition; do nothing and try again later
// with new readiness values.
//
// If the most we can scale is 0, it means we can't scale down without
// violating the minimum. Do nothing and try again later when conditions may
// have changed.
if decrement <= 0 {
return oldRc, nil
}
// Reduce the replica count, and deal with fenceposts.
oldRc.Spec.Replicas -= decrement
if oldRc.Spec.Replicas < 0 {
oldRc.Spec.Replicas = 0
}
// If the new is already fully scaled and available up to the desired size, go
// ahead and scale old all the way down.
if newRc.Spec.Replicas == desired && newAvailable == desired {
oldRc.Spec.Replicas = 0
}
// Perform the scale-down.
fmt.Fprintf(config.Out, "Scaling %s down to %d\n", oldRc.Name, oldRc.Spec.Replicas)
retryWait := &RetryParams{config.Interval, config.Timeout}
scaledRc, err := r.scaleAndWait(oldRc, retryWait, retryWait)
if err != nil {
return nil, err
}
return scaledRc, nil
}
// scalerScaleAndWait scales a controller using a Scaler and a real client.
func (r *RollingUpdater) scaleAndWaitWithScaler(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error) {
scaler, err := ScalerFor(api.Kind("ReplicationController"), r.c)
if err != nil {
return nil, fmt.Errorf("Couldn't make scaler: %s", err)
}
if err := scaler.Scale(rc.Namespace, rc.Name, uint(rc.Spec.Replicas), &ScalePrecondition{-1, ""}, retry, wait); err != nil {
return nil, err
}
return r.c.ReplicationControllers(rc.Namespace).Get(rc.Name)
}
// readyPods returns the old and new ready counts for their pods.
// If a pod is observed as being ready, it's considered ready even
// if it later becomes notReady.
func (r *RollingUpdater) readyPods(oldRc, newRc *api.ReplicationController) (int32, int32, error) {
controllers := []*api.ReplicationController{oldRc, newRc}
oldReady := int32(0)
newReady := int32(0)
for i := range controllers {
controller := controllers[i]
selector := labels.Set(controller.Spec.Selector).AsSelector()
options := api.ListOptions{LabelSelector: selector}
pods, err := r.c.Pods(controller.Namespace).List(options)
if err != nil {
return 0, 0, err
}
for _, pod := range pods.Items {
if api.IsPodReady(&pod) {
switch controller.Name {
case oldRc.Name:
oldReady++
case newRc.Name:
newReady++
}
}
}
}
return oldReady, newReady, nil
}
// getOrCreateTargetControllerWithClient looks for an existing controller with
// sourceId. If found, the existing controller is returned with true
// indicating that the controller already exists. If the controller isn't
// found, a new one is created and returned along with false indicating the
// controller was created.
//
// Existing controllers are validated to ensure their sourceIdAnnotation
// matches sourceId; if there's a mismatch, an error is returned.
func (r *RollingUpdater) getOrCreateTargetControllerWithClient(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) {
existingRc, err := r.existingController(controller)
if err != nil {
if !errors.IsNotFound(err) {
// There was an error trying to find the controller; don't assume we
// should create it.
return nil, false, err
}
if controller.Spec.Replicas <= 0 {
return nil, false, fmt.Errorf("Invalid controller spec for %s; required: > 0 replicas, actual: %d\n", controller.Name, controller.Spec.Replicas)
}
// The controller wasn't found, so create it.
if controller.Annotations == nil {
controller.Annotations = map[string]string{}
}
controller.Annotations[desiredReplicasAnnotation] = fmt.Sprintf("%d", controller.Spec.Replicas)
controller.Annotations[sourceIdAnnotation] = sourceId
controller.Spec.Replicas = 0
newRc, err := r.c.ReplicationControllers(r.ns).Create(controller)
return newRc, false, err
}
// Validate and use the existing controller.
annotations := existingRc.Annotations
source := annotations[sourceIdAnnotation]
_, ok := annotations[desiredReplicasAnnotation]
if source != sourceId || !ok {
return nil, false, fmt.Errorf("Missing/unexpected annotations for controller %s, expected %s : %s", controller.Name, sourceId, annotations)
}
return existingRc, true, nil
}
// existingController verifies if the controller already exists
func (r *RollingUpdater) existingController(controller *api.ReplicationController) (*api.ReplicationController, error) {
// without rc name but generate name, there's no existing rc
if len(controller.Name) == 0 && len(controller.GenerateName) > 0 {
return nil, errors.NewNotFound(api.Resource("replicationcontrollers"), controller.Name)
}
// controller name is required to get rc back
return r.c.ReplicationControllers(controller.Namespace).Get(controller.Name)
}
// cleanupWithClients performs cleanup tasks after the rolling update. Update
// process related annotations are removed from oldRc and newRc. The
// CleanupPolicy on config is executed.
func (r *RollingUpdater) cleanupWithClients(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error {
// Clean up annotations
var err error
newRc, err = r.c.ReplicationControllers(r.ns).Get(newRc.Name)
if err != nil {
return err
}
delete(newRc.Annotations, sourceIdAnnotation)
delete(newRc.Annotations, desiredReplicasAnnotation)
newRc, err = r.c.ReplicationControllers(r.ns).Update(newRc)
if err != nil {
return err
}
if err = wait.Poll(config.Interval, config.Timeout, client.ControllerHasDesiredReplicas(r.c, newRc)); err != nil {
return err
}
newRc, err = r.c.ReplicationControllers(r.ns).Get(newRc.Name)
if err != nil {
return err
}
switch config.CleanupPolicy {
case DeleteRollingUpdateCleanupPolicy:
// delete old rc
fmt.Fprintf(config.Out, "Update succeeded. Deleting %s\n", oldRc.Name)
return r.c.ReplicationControllers(r.ns).Delete(oldRc.Name)
case RenameRollingUpdateCleanupPolicy:
// delete old rc
fmt.Fprintf(config.Out, "Update succeeded. Deleting old controller: %s\n", oldRc.Name)
if err := r.c.ReplicationControllers(r.ns).Delete(oldRc.Name); err != nil {
return err
}
fmt.Fprintf(config.Out, "Renaming %s to %s\n", newRc.Name, oldRc.Name)
return Rename(r.c, newRc, oldRc.Name)
case PreserveRollingUpdateCleanupPolicy:
return nil
default:
return nil
}
}
func Rename(c client.ReplicationControllersNamespacer, rc *api.ReplicationController, newName string) error {
oldName := rc.Name
rc.Name = newName
rc.ResourceVersion = ""
_, err := c.ReplicationControllers(rc.Namespace).Create(rc)
if err != nil {
return err
}
err = c.ReplicationControllers(rc.Namespace).Delete(oldName)
if err != nil && !errors.IsNotFound(err) {
return err
}
return nil
}
func LoadExistingNextReplicationController(c client.ReplicationControllersNamespacer, namespace, newName string) (*api.ReplicationController, error) {
if len(newName) == 0 {
return nil, nil
}
newRc, err := c.ReplicationControllers(namespace).Get(newName)
if err != nil && errors.IsNotFound(err) {
return nil, nil
}
return newRc, err
}
type NewControllerConfig struct {
Namespace string
OldName, NewName string
Image string
Container string
DeploymentKey string
PullPolicy api.PullPolicy
}
func CreateNewControllerFromCurrentController(c client.Interface, codec runtime.Codec, cfg *NewControllerConfig) (*api.ReplicationController, error) {
containerIndex := 0
// load the old RC into the "new" RC
newRc, err := c.ReplicationControllers(cfg.Namespace).Get(cfg.OldName)
if err != nil {
return nil, err
}
if len(cfg.Container) != 0 {
containerFound := false
for i, c := range newRc.Spec.Template.Spec.Containers {
if c.Name == cfg.Container {
containerIndex = i
containerFound = true
break
}
}
if !containerFound {
return nil, fmt.Errorf("container %s not found in pod", cfg.Container)
}
}
if len(newRc.Spec.Template.Spec.Containers) > 1 && len(cfg.Container) == 0 {
return nil, goerrors.New("Must specify container to update when updating a multi-container pod")
}
if len(newRc.Spec.Template.Spec.Containers) == 0 {
return nil, goerrors.New(fmt.Sprintf("Pod has no containers! (%v)", newRc))
}
newRc.Spec.Template.Spec.Containers[containerIndex].Image = cfg.Image
if len(cfg.PullPolicy) != 0 {
newRc.Spec.Template.Spec.Containers[containerIndex].ImagePullPolicy = cfg.PullPolicy
}
newHash, err := api.HashObject(newRc, codec)
if err != nil {
return nil, err
}
if len(cfg.NewName) == 0 {
cfg.NewName = fmt.Sprintf("%s-%s", newRc.Name, newHash)
}
newRc.Name = cfg.NewName
newRc.Spec.Selector[cfg.DeploymentKey] = newHash
newRc.Spec.Template.Labels[cfg.DeploymentKey] = newHash
// Clear resource version after hashing so that identical updates get different hashes.
newRc.ResourceVersion = ""
return newRc, nil
}
func AbortRollingUpdate(c *RollingUpdaterConfig) error {
// Swap the controllers
tmp := c.OldRc
c.OldRc = c.NewRc
c.NewRc = tmp
if c.NewRc.Annotations == nil {
c.NewRc.Annotations = map[string]string{}
}
c.NewRc.Annotations[sourceIdAnnotation] = fmt.Sprintf("%s:%s", c.OldRc.Name, c.OldRc.UID)
// Use the original value since the replica count change from old to new
// could be asymmetric. If we don't know the original count, we can't safely
// roll back to a known good size.
originalSize, foundOriginal := tmp.Annotations[originalReplicasAnnotation]
if !foundOriginal {
return fmt.Errorf("couldn't find original replica count of %q", tmp.Name)
}
fmt.Fprintf(c.Out, "Setting %q replicas to %s\n", c.NewRc.Name, originalSize)
c.NewRc.Annotations[desiredReplicasAnnotation] = originalSize
c.CleanupPolicy = DeleteRollingUpdateCleanupPolicy
return nil
}
func GetNextControllerAnnotation(rc *api.ReplicationController) (string, bool) {
res, found := rc.Annotations[nextControllerAnnotation]
return res, found
}
func SetNextControllerAnnotation(rc *api.ReplicationController, name string) {
if rc.Annotations == nil {
rc.Annotations = map[string]string{}
}
rc.Annotations[nextControllerAnnotation] = name
}
func UpdateExistingReplicationController(c client.Interface, oldRc *api.ReplicationController, namespace, newName, deploymentKey, deploymentValue string, out io.Writer) (*api.ReplicationController, error) {
SetNextControllerAnnotation(oldRc, newName)
if _, found := oldRc.Spec.Selector[deploymentKey]; !found {
return AddDeploymentKeyToReplicationController(oldRc, c, deploymentKey, deploymentValue, namespace, out)
} else {
// If we didn't need to update the controller for the deployment key, we still need to write
// the "next" controller.
return c.ReplicationControllers(namespace).Update(oldRc)
}
}
const MaxRetries = 3
func AddDeploymentKeyToReplicationController(oldRc *api.ReplicationController, client client.Interface, deploymentKey, deploymentValue, namespace string, out io.Writer) (*api.ReplicationController, error) {
var err error
// First, update the template label. This ensures that any newly created pods will have the new label
if oldRc, err = updateWithRetries(client.ReplicationControllers(namespace), oldRc, func(rc *api.ReplicationController) {
if rc.Spec.Template.Labels == nil {
rc.Spec.Template.Labels = map[string]string{}
}
rc.Spec.Template.Labels[deploymentKey] = deploymentValue
}); err != nil {
return nil, err
}
// Update all pods managed by the rc to have the new hash label, so they are correctly adopted
// TODO: extract the code from the label command and re-use it here.
selector := labels.SelectorFromSet(oldRc.Spec.Selector)
options := api.ListOptions{LabelSelector: selector}
podList, err := client.Pods(namespace).List(options)
if err != nil {
return nil, err
}
for ix := range podList.Items {
pod := &podList.Items[ix]
if pod.Labels == nil {
pod.Labels = map[string]string{
deploymentKey: deploymentValue,
}
} else {
pod.Labels[deploymentKey] = deploymentValue
}
err = nil
delay := 3
for i := 0; i < MaxRetries; i++ {
_, err = client.Pods(namespace).Update(pod)
if err != nil {
fmt.Fprintf(out, "Error updating pod (%v), retrying after %d seconds", err, delay)
time.Sleep(time.Second * time.Duration(delay))
delay *= delay
} else {
break
}
}
if err != nil {
return nil, err
}
}
if oldRc.Spec.Selector == nil {
oldRc.Spec.Selector = map[string]string{}
}
// Copy the old selector, so that we can scrub out any orphaned pods
selectorCopy := map[string]string{}
for k, v := range oldRc.Spec.Selector {
selectorCopy[k] = v
}
oldRc.Spec.Selector[deploymentKey] = deploymentValue
// Update the selector of the rc so it manages all the pods we updated above
if oldRc, err = updateWithRetries(client.ReplicationControllers(namespace), oldRc, func(rc *api.ReplicationController) {
rc.Spec.Selector[deploymentKey] = deploymentValue
}); err != nil {
return nil, err
}
// Clean up any orphaned pods that don't have the new label, this can happen if the rc manager
// doesn't see the update to its pod template and creates a new pod with the old labels after
// we've finished re-adopting existing pods to the rc.
selector = labels.SelectorFromSet(selectorCopy)
options = api.ListOptions{LabelSelector: selector}
podList, err = client.Pods(namespace).List(options)
for ix := range podList.Items {
pod := &podList.Items[ix]
if value, found := pod.Labels[deploymentKey]; !found || value != deploymentValue {
if err := client.Pods(namespace).Delete(pod.Name, nil); err != nil {
return nil, err
}
}
}
return oldRc, nil
}
type updateFunc func(controller *api.ReplicationController)
// updateWithRetries updates applies the given rc as an update.
func updateWithRetries(rcClient client.ReplicationControllerInterface, rc *api.ReplicationController, applyUpdate updateFunc) (*api.ReplicationController, error) {
var err error
oldRc := rc
err = wait.Poll(10*time.Millisecond, 1*time.Minute, func() (bool, error) {
// Apply the update, then attempt to push it to the apiserver.
applyUpdate(rc)
if rc, err = rcClient.Update(rc); err == nil {
// rc contains the latest controller post update
return true, nil
}
// Update the controller with the latest resource version, if the update failed we
// can't trust rc so use oldRc.Name.
if rc, err = rcClient.Get(oldRc.Name); err != nil {
// The Get failed: Value in rc cannot be trusted.
rc = oldRc
}
// The Get passed: rc contains the latest controller, expect a poll for the update.
return false, nil
})
// If the error is non-nil the returned controller cannot be trusted, if it is nil, the returned
// controller contains the applied update.
return rc, err
}
func FindSourceController(r client.ReplicationControllersNamespacer, namespace, name string) (*api.ReplicationController, error) {
list, err := r.ReplicationControllers(namespace).List(api.ListOptions{})
if err != nil {
return nil, err
}
for ix := range list.Items {
rc := &list.Items[ix]
if rc.Annotations != nil && strings.HasPrefix(rc.Annotations[sourceIdAnnotation], name) {
return rc, nil
}
}
return nil, fmt.Errorf("couldn't find a replication controller with source id == %s/%s", namespace, name)
}

869
vendor/k8s.io/kubernetes/pkg/kubectl/run.go generated vendored Normal file
View file

@ -0,0 +1,869 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"fmt"
"strconv"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/batch"
batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/validation"
)
type DeploymentV1Beta1 struct{}
func (DeploymentV1Beta1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"labels", false},
{"default-name", false},
{"name", true},
{"replicas", true},
{"image", true},
{"port", false},
{"hostport", false},
{"stdin", false},
{"tty", false},
{"command", false},
{"args", false},
{"env", false},
{"requests", false},
{"limits", false},
}
}
func (DeploymentV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
envs, err := getEnvs(genericParams)
if err != nil {
return nil, err
}
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
name, err := getName(params)
if err != nil {
return nil, err
}
labels, err := getLabels(params, true, name)
if err != nil {
return nil, err
}
count, err := strconv.Atoi(params["replicas"])
if err != nil {
return nil, err
}
podSpec, err := makePodSpec(params, name)
if err != nil {
return nil, err
}
if err = updatePodContainers(params, args, envs, podSpec); err != nil {
return nil, err
}
if err := updatePodPorts(params, podSpec); err != nil {
return nil, err
}
// TODO: use versioned types for generators so that we don't need to
// set default values manually (see issue #17384)
deployment := extensions.Deployment{
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: extensions.DeploymentSpec{
Replicas: int32(count),
Selector: &unversioned.LabelSelector{MatchLabels: labels},
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: labels,
},
Spec: *podSpec,
},
},
}
return &deployment, nil
}
func getLabels(params map[string]string, defaultRunLabel bool, name string) (map[string]string, error) {
labelString, found := params["labels"]
var labels map[string]string
var err error
if found && len(labelString) > 0 {
labels, err = ParseLabels(labelString)
if err != nil {
return nil, err
}
} else if defaultRunLabel {
labels = map[string]string{
"run": name,
}
}
return labels, nil
}
func getName(params map[string]string) (string, error) {
name, found := params["name"]
if !found || len(name) == 0 {
name, found = params["default-name"]
if !found || len(name) == 0 {
return "", fmt.Errorf("'name' is a required parameter.")
}
}
return name, nil
}
func getParams(genericParams map[string]interface{}) (map[string]string, error) {
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
return params, nil
}
func getArgs(genericParams map[string]interface{}) ([]string, error) {
args := []string{}
val, found := genericParams["args"]
if found {
var isArray bool
args, isArray = val.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found: %v", val)
}
delete(genericParams, "args")
}
return args, nil
}
func getEnvs(genericParams map[string]interface{}) ([]api.EnvVar, error) {
var envs []api.EnvVar
envStrings, found := genericParams["env"]
if found {
if envStringArray, isArray := envStrings.([]string); isArray {
var err error
envs, err = parseEnvs(envStringArray)
if err != nil {
return nil, err
}
delete(genericParams, "env")
} else {
return nil, fmt.Errorf("expected []string, found: %v", envStrings)
}
}
return envs, nil
}
func getV1Envs(genericParams map[string]interface{}) ([]v1.EnvVar, error) {
var envs []v1.EnvVar
envStrings, found := genericParams["env"]
if found {
if envStringArray, isArray := envStrings.([]string); isArray {
var err error
envs, err = parseV1Envs(envStringArray)
if err != nil {
return nil, err
}
delete(genericParams, "env")
} else {
return nil, fmt.Errorf("expected []string, found: %v", envStrings)
}
}
return envs, nil
}
type JobV1Beta1 struct{}
func (JobV1Beta1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"labels", false},
{"default-name", false},
{"name", true},
{"image", true},
{"port", false},
{"hostport", false},
{"stdin", false},
{"leave-stdin-open", false},
{"tty", false},
{"command", false},
{"args", false},
{"env", false},
{"requests", false},
{"limits", false},
{"restart", false},
}
}
func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
envs, err := getEnvs(genericParams)
if err != nil {
return nil, err
}
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
name, err := getName(params)
if err != nil {
return nil, err
}
labels, err := getLabels(params, true, name)
if err != nil {
return nil, err
}
podSpec, err := makePodSpec(params, name)
if err != nil {
return nil, err
}
if err = updatePodContainers(params, args, envs, podSpec); err != nil {
return nil, err
}
leaveStdinOpen, err := GetBool(params, "leave-stdin-open", false)
if err != nil {
return nil, err
}
podSpec.Containers[0].StdinOnce = !leaveStdinOpen && podSpec.Containers[0].Stdin
if err := updatePodPorts(params, podSpec); err != nil {
return nil, err
}
restartPolicy := api.RestartPolicy(params["restart"])
if len(restartPolicy) == 0 {
restartPolicy = api.RestartPolicyNever
}
podSpec.RestartPolicy = restartPolicy
job := batch.Job{
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: batch.JobSpec{
Selector: &unversioned.LabelSelector{
MatchLabels: labels,
},
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: labels,
},
Spec: *podSpec,
},
},
}
return &job, nil
}
type JobV1 struct{}
func (JobV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"labels", false},
{"default-name", false},
{"name", true},
{"image", true},
{"port", false},
{"hostport", false},
{"stdin", false},
{"leave-stdin-open", false},
{"tty", false},
{"command", false},
{"args", false},
{"env", false},
{"requests", false},
{"limits", false},
{"restart", false},
}
}
func (JobV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
envs, err := getV1Envs(genericParams)
if err != nil {
return nil, err
}
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
name, err := getName(params)
if err != nil {
return nil, err
}
labels, err := getLabels(params, true, name)
if err != nil {
return nil, err
}
podSpec, err := makeV1PodSpec(params, name)
if err != nil {
return nil, err
}
if err = updateV1PodContainers(params, args, envs, podSpec); err != nil {
return nil, err
}
leaveStdinOpen, err := GetBool(params, "leave-stdin-open", false)
if err != nil {
return nil, err
}
podSpec.Containers[0].StdinOnce = !leaveStdinOpen && podSpec.Containers[0].Stdin
if err := updateV1PodPorts(params, podSpec); err != nil {
return nil, err
}
restartPolicy := v1.RestartPolicy(params["restart"])
if len(restartPolicy) == 0 {
restartPolicy = v1.RestartPolicyNever
}
podSpec.RestartPolicy = restartPolicy
job := batchv1.Job{
ObjectMeta: v1.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Labels: labels,
},
Spec: *podSpec,
},
},
}
return &job, nil
}
type BasicReplicationController struct{}
func (BasicReplicationController) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"labels", false},
{"default-name", false},
{"name", true},
{"replicas", true},
{"image", true},
{"port", false},
{"hostport", false},
{"stdin", false},
{"tty", false},
{"command", false},
{"args", false},
{"env", false},
{"requests", false},
{"limits", false},
}
}
// populateResourceList takes strings of form <resourceName1>=<value1>,<resourceName1>=<value2>
func populateResourceList(spec string) (api.ResourceList, error) {
// empty input gets a nil response to preserve generator test expected behaviors
if spec == "" {
return nil, nil
}
result := api.ResourceList{}
resourceStatements := strings.Split(spec, ",")
for _, resourceStatement := range resourceStatements {
parts := strings.Split(resourceStatement, "=")
if len(parts) != 2 {
return nil, fmt.Errorf("Invalid argument syntax %v, expected <resource>=<value>", resourceStatement)
}
resourceName := api.ResourceName(parts[0])
resourceQuantity, err := resource.ParseQuantity(parts[1])
if err != nil {
return nil, err
}
result[resourceName] = *resourceQuantity
}
return result, nil
}
// populateResourceList takes strings of form <resourceName1>=<value1>,<resourceName1>=<value2>
func populateV1ResourceList(spec string) (v1.ResourceList, error) {
// empty input gets a nil response to preserve generator test expected behaviors
if spec == "" {
return nil, nil
}
result := v1.ResourceList{}
resourceStatements := strings.Split(spec, ",")
for _, resourceStatement := range resourceStatements {
parts := strings.Split(resourceStatement, "=")
if len(parts) != 2 {
return nil, fmt.Errorf("Invalid argument syntax %v, expected <resource>=<value>", resourceStatement)
}
resourceName := v1.ResourceName(parts[0])
resourceQuantity, err := resource.ParseQuantity(parts[1])
if err != nil {
return nil, err
}
result[resourceName] = *resourceQuantity
}
return result, nil
}
// HandleResourceRequirements parses the limits and requests parameters if specified
func HandleResourceRequirements(params map[string]string) (api.ResourceRequirements, error) {
result := api.ResourceRequirements{}
limits, err := populateResourceList(params["limits"])
if err != nil {
return result, err
}
result.Limits = limits
requests, err := populateResourceList(params["requests"])
if err != nil {
return result, err
}
result.Requests = requests
return result, nil
}
// HandleResourceRequirements parses the limits and requests parameters if specified
func handleV1ResourceRequirements(params map[string]string) (v1.ResourceRequirements, error) {
result := v1.ResourceRequirements{}
limits, err := populateV1ResourceList(params["limits"])
if err != nil {
return result, err
}
result.Limits = limits
requests, err := populateV1ResourceList(params["requests"])
if err != nil {
return result, err
}
result.Requests = requests
return result, nil
}
func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) {
stdin, err := GetBool(params, "stdin", false)
if err != nil {
return nil, err
}
tty, err := GetBool(params, "tty", false)
if err != nil {
return nil, err
}
resourceRequirements, err := HandleResourceRequirements(params)
if err != nil {
return nil, err
}
spec := api.PodSpec{
Containers: []api.Container{
{
Name: name,
Image: params["image"],
Stdin: stdin,
TTY: tty,
Resources: resourceRequirements,
},
},
}
return &spec, nil
}
func makeV1PodSpec(params map[string]string, name string) (*v1.PodSpec, error) {
stdin, err := GetBool(params, "stdin", false)
if err != nil {
return nil, err
}
tty, err := GetBool(params, "tty", false)
if err != nil {
return nil, err
}
resourceRequirements, err := handleV1ResourceRequirements(params)
if err != nil {
return nil, err
}
spec := v1.PodSpec{
Containers: []v1.Container{
{
Name: name,
Image: params["image"],
Stdin: stdin,
TTY: tty,
Resources: resourceRequirements,
},
},
}
return &spec, nil
}
func (BasicReplicationController) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
envs, err := getEnvs(genericParams)
if err != nil {
return nil, err
}
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
name, err := getName(params)
if err != nil {
return nil, err
}
labels, err := getLabels(params, true, name)
if err != nil {
return nil, err
}
count, err := strconv.Atoi(params["replicas"])
if err != nil {
return nil, err
}
podSpec, err := makePodSpec(params, name)
if err != nil {
return nil, err
}
if err = updatePodContainers(params, args, envs, podSpec); err != nil {
return nil, err
}
if err := updatePodPorts(params, podSpec); err != nil {
return nil, err
}
controller := api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: api.ReplicationControllerSpec{
Replicas: int32(count),
Selector: labels,
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: labels,
},
Spec: *podSpec,
},
},
}
return &controller, nil
}
func updatePodContainers(params map[string]string, args []string, envs []api.EnvVar, podSpec *api.PodSpec) error {
if len(args) > 0 {
command, err := GetBool(params, "command", false)
if err != nil {
return err
}
if command {
podSpec.Containers[0].Command = args
} else {
podSpec.Containers[0].Args = args
}
}
if len(envs) > 0 {
podSpec.Containers[0].Env = envs
}
return nil
}
func updateV1PodContainers(params map[string]string, args []string, envs []v1.EnvVar, podSpec *v1.PodSpec) error {
if len(args) > 0 {
command, err := GetBool(params, "command", false)
if err != nil {
return err
}
if command {
podSpec.Containers[0].Command = args
} else {
podSpec.Containers[0].Args = args
}
}
if len(envs) > 0 {
podSpec.Containers[0].Env = envs
}
return nil
}
func updatePodPorts(params map[string]string, podSpec *api.PodSpec) (err error) {
port := -1
hostPort := -1
if len(params["port"]) > 0 {
port, err = strconv.Atoi(params["port"])
if err != nil {
return err
}
}
if len(params["hostport"]) > 0 {
hostPort, err = strconv.Atoi(params["hostport"])
if err != nil {
return err
}
if hostPort > 0 && port < 0 {
return fmt.Errorf("--hostport requires --port to be specified")
}
}
// Don't include the port if it was not specified.
if port > 0 {
podSpec.Containers[0].Ports = []api.ContainerPort{
{
ContainerPort: int32(port),
},
}
if hostPort > 0 {
podSpec.Containers[0].Ports[0].HostPort = int32(hostPort)
}
}
return nil
}
func updateV1PodPorts(params map[string]string, podSpec *v1.PodSpec) (err error) {
port := -1
hostPort := -1
if len(params["port"]) > 0 {
port, err = strconv.Atoi(params["port"])
if err != nil {
return err
}
}
if len(params["hostport"]) > 0 {
hostPort, err = strconv.Atoi(params["hostport"])
if err != nil {
return err
}
if hostPort > 0 && port < 0 {
return fmt.Errorf("--hostport requires --port to be specified")
}
}
// Don't include the port if it was not specified.
if port > 0 {
podSpec.Containers[0].Ports = []v1.ContainerPort{
{
ContainerPort: int32(port),
},
}
if hostPort > 0 {
podSpec.Containers[0].Ports[0].HostPort = int32(hostPort)
}
}
return nil
}
type BasicPod struct{}
func (BasicPod) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"labels", false},
{"default-name", false},
{"name", true},
{"image", true},
{"port", false},
{"hostport", false},
{"stdin", false},
{"leave-stdin-open", false},
{"tty", false},
{"restart", false},
{"command", false},
{"args", false},
{"env", false},
{"requests", false},
{"limits", false},
}
}
func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
envs, err := getEnvs(genericParams)
if err != nil {
return nil, err
}
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
name, err := getName(params)
if err != nil {
return nil, err
}
labels, err := getLabels(params, false, name)
if err != nil {
return nil, err
}
stdin, err := GetBool(params, "stdin", false)
if err != nil {
return nil, err
}
leaveStdinOpen, err := GetBool(params, "leave-stdin-open", false)
if err != nil {
return nil, err
}
tty, err := GetBool(params, "tty", false)
if err != nil {
return nil, err
}
resourceRequirements, err := HandleResourceRequirements(params)
if err != nil {
return nil, err
}
restartPolicy := api.RestartPolicy(params["restart"])
if len(restartPolicy) == 0 {
restartPolicy = api.RestartPolicyAlways
}
pod := api.Pod{
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: name,
Image: params["image"],
ImagePullPolicy: api.PullIfNotPresent,
Stdin: stdin,
StdinOnce: !leaveStdinOpen && stdin,
TTY: tty,
Resources: resourceRequirements,
},
},
DNSPolicy: api.DNSClusterFirst,
RestartPolicy: restartPolicy,
},
}
if err = updatePodContainers(params, args, envs, &pod.Spec); err != nil {
return nil, err
}
if err := updatePodPorts(params, &pod.Spec); err != nil {
return nil, err
}
return &pod, nil
}
func parseEnvs(envArray []string) ([]api.EnvVar, error) {
envs := []api.EnvVar{}
for _, env := range envArray {
pos := strings.Index(env, "=")
if pos == -1 {
return nil, fmt.Errorf("invalid env: %v", env)
}
name := env[:pos]
value := env[pos+1:]
if len(name) == 0 || !validation.IsCIdentifier(name) || len(value) == 0 {
return nil, fmt.Errorf("invalid env: %v", env)
}
envVar := api.EnvVar{Name: name, Value: value}
envs = append(envs, envVar)
}
return envs, nil
}
func parseV1Envs(envArray []string) ([]v1.EnvVar, error) {
envs := []v1.EnvVar{}
for _, env := range envArray {
pos := strings.Index(env, "=")
if pos == -1 {
return nil, fmt.Errorf("invalid env: %v", env)
}
name := env[:pos]
value := env[pos+1:]
if len(name) == 0 || !validation.IsCIdentifier(name) || len(value) == 0 {
return nil, fmt.Errorf("invalid env: %v", env)
}
envVar := v1.EnvVar{Name: name, Value: value}
envs = append(envs, envVar)
}
return envs, nil
}
func newBool(val bool) *bool {
p := new(bool)
*p = val
return p
}

382
vendor/k8s.io/kubernetes/pkg/kubectl/scale.go generated vendored Normal file
View file

@ -0,0 +1,382 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"fmt"
"strconv"
"time"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/util/wait"
)
// Scaler provides an interface for resources that can be scaled.
type Scaler interface {
// Scale scales the named resource after checking preconditions. It optionally
// retries in the event of resource version mismatch (if retry is not nil),
// and optionally waits until the status of the resource matches newSize (if wait is not nil)
Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, wait *RetryParams) error
// ScaleSimple does a simple one-shot attempt at scaling - not useful on it's own, but
// a necessary building block for Scale
ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) error
}
func ScalerFor(kind unversioned.GroupKind, c client.Interface) (Scaler, error) {
switch kind {
case api.Kind("ReplicationController"):
return &ReplicationControllerScaler{c}, nil
case extensions.Kind("ReplicaSet"):
return &ReplicaSetScaler{c.Extensions()}, nil
case extensions.Kind("Job"), batch.Kind("Job"):
return &JobScaler{c.Batch()}, nil // Either kind of job can be scaled with Batch interface.
case extensions.Kind("Deployment"):
return &DeploymentScaler{c.Extensions()}, nil
}
return nil, fmt.Errorf("no scaler has been implemented for %q", kind)
}
// ScalePrecondition describes a condition that must be true for the scale to take place
// If CurrentSize == -1, it is ignored.
// If CurrentResourceVersion is the empty string, it is ignored.
// Otherwise they must equal the values in the resource for it to be valid.
type ScalePrecondition struct {
Size int
ResourceVersion string
}
// A PreconditionError is returned when a resource fails to match
// the scale preconditions passed to kubectl.
type PreconditionError struct {
Precondition string
ExpectedValue string
ActualValue string
}
func (pe PreconditionError) Error() string {
return fmt.Sprintf("Expected %s to be %s, was %s", pe.Precondition, pe.ExpectedValue, pe.ActualValue)
}
type ScaleErrorType int
const (
ScaleGetFailure ScaleErrorType = iota
ScaleUpdateFailure
ScaleUpdateInvalidFailure
)
// A ScaleError is returned when a scale request passes
// preconditions but fails to actually scale the controller.
type ScaleError struct {
FailureType ScaleErrorType
ResourceVersion string
ActualError error
}
func (c ScaleError) Error() string {
return fmt.Sprintf(
"Scaling the resource failed with: %v; Current resource version %s",
c.ActualError, c.ResourceVersion)
}
// RetryParams encapsulates the retry parameters used by kubectl's scaler.
type RetryParams struct {
Interval, Timeout time.Duration
}
func NewRetryParams(interval, timeout time.Duration) *RetryParams {
return &RetryParams{interval, timeout}
}
// ScaleCondition is a closure around Scale that facilitates retries via util.wait
func ScaleCondition(r Scaler, precondition *ScalePrecondition, namespace, name string, count uint) wait.ConditionFunc {
return func() (bool, error) {
err := r.ScaleSimple(namespace, name, precondition, count)
switch e, _ := err.(ScaleError); err.(type) {
case nil:
return true, nil
case ScaleError:
// if it's invalid we shouldn't keep waiting
if e.FailureType == ScaleUpdateInvalidFailure {
return false, err
}
if e.FailureType == ScaleUpdateFailure {
return false, nil
}
}
return false, err
}
}
// ValidateReplicationController ensures that the preconditions match. Returns nil if they are valid, an error otherwise
func (precondition *ScalePrecondition) ValidateReplicationController(controller *api.ReplicationController) error {
if precondition.Size != -1 && int(controller.Spec.Replicas) != precondition.Size {
return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(int(controller.Spec.Replicas))}
}
if len(precondition.ResourceVersion) != 0 && controller.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, controller.ResourceVersion}
}
return nil
}
type ReplicationControllerScaler struct {
c client.Interface
}
func (scaler *ReplicationControllerScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) error {
controller, err := scaler.c.ReplicationControllers(namespace).Get(name)
if err != nil {
return ScaleError{ScaleGetFailure, "Unknown", err}
}
if preconditions != nil {
if err := preconditions.ValidateReplicationController(controller); err != nil {
return err
}
}
controller.Spec.Replicas = int32(newSize)
// TODO: do retry on 409 errors here?
if _, err := scaler.c.ReplicationControllers(namespace).Update(controller); err != nil {
if errors.IsInvalid(err) {
return ScaleError{ScaleUpdateInvalidFailure, controller.ResourceVersion, err}
}
return ScaleError{ScaleUpdateFailure, controller.ResourceVersion, err}
}
return nil
}
// Scale updates a ReplicationController to a new size, with optional precondition check (if preconditions is not nil),
// optional retries (if retry is not nil), and then optionally waits for it's replica count to reach the new value
// (if wait is not nil).
func (scaler *ReplicationControllerScaler) Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams) error {
if preconditions == nil {
preconditions = &ScalePrecondition{-1, ""}
}
if retry == nil {
// Make it try only once, immediately
retry = &RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond}
}
cond := ScaleCondition(scaler, preconditions, namespace, name, newSize)
if err := wait.Poll(retry.Interval, retry.Timeout, cond); err != nil {
return err
}
if waitForReplicas != nil {
rc, err := scaler.c.ReplicationControllers(namespace).Get(name)
if err != nil {
return err
}
return wait.Poll(waitForReplicas.Interval, waitForReplicas.Timeout,
client.ControllerHasDesiredReplicas(scaler.c, rc))
}
return nil
}
// ValidateReplicaSet ensures that the preconditions match. Returns nil if they are valid, an error otherwise
func (precondition *ScalePrecondition) ValidateReplicaSet(replicaSet *extensions.ReplicaSet) error {
if precondition.Size != -1 && int(replicaSet.Spec.Replicas) != precondition.Size {
return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(int(replicaSet.Spec.Replicas))}
}
if len(precondition.ResourceVersion) != 0 && replicaSet.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, replicaSet.ResourceVersion}
}
return nil
}
type ReplicaSetScaler struct {
c client.ExtensionsInterface
}
func (scaler *ReplicaSetScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) error {
rs, err := scaler.c.ReplicaSets(namespace).Get(name)
if err != nil {
return ScaleError{ScaleGetFailure, "Unknown", err}
}
if preconditions != nil {
if err := preconditions.ValidateReplicaSet(rs); err != nil {
return err
}
}
rs.Spec.Replicas = int32(newSize)
// TODO: do retry on 409 errors here?
if _, err := scaler.c.ReplicaSets(namespace).Update(rs); err != nil {
if errors.IsInvalid(err) {
return ScaleError{ScaleUpdateInvalidFailure, rs.ResourceVersion, err}
}
return ScaleError{ScaleUpdateFailure, rs.ResourceVersion, err}
}
return nil
}
// Scale updates a ReplicaSet to a new size, with optional precondition check (if preconditions is
// not nil), optional retries (if retry is not nil), and then optionally waits for it's replica
// count to reach the new value (if wait is not nil).
func (scaler *ReplicaSetScaler) Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams) error {
if preconditions == nil {
preconditions = &ScalePrecondition{-1, ""}
}
if retry == nil {
// Make it try only once, immediately
retry = &RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond}
}
cond := ScaleCondition(scaler, preconditions, namespace, name, newSize)
if err := wait.Poll(retry.Interval, retry.Timeout, cond); err != nil {
return err
}
if waitForReplicas != nil {
rs, err := scaler.c.ReplicaSets(namespace).Get(name)
if err != nil {
return err
}
return wait.Poll(waitForReplicas.Interval, waitForReplicas.Timeout,
client.ReplicaSetHasDesiredReplicas(scaler.c, rs))
}
return nil
}
// ValidateJob ensures that the preconditions match. Returns nil if they are valid, an error otherwise.
func (precondition *ScalePrecondition) ValidateJob(job *batch.Job) error {
if precondition.Size != -1 && job.Spec.Parallelism == nil {
return PreconditionError{"parallelism", strconv.Itoa(precondition.Size), "nil"}
}
if precondition.Size != -1 && int(*job.Spec.Parallelism) != precondition.Size {
return PreconditionError{"parallelism", strconv.Itoa(precondition.Size), strconv.Itoa(int(*job.Spec.Parallelism))}
}
if len(precondition.ResourceVersion) != 0 && job.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, job.ResourceVersion}
}
return nil
}
type JobScaler struct {
c client.BatchInterface
}
// ScaleSimple is responsible for updating job's parallelism.
func (scaler *JobScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) error {
job, err := scaler.c.Jobs(namespace).Get(name)
if err != nil {
return ScaleError{ScaleGetFailure, "Unknown", err}
}
if preconditions != nil {
if err := preconditions.ValidateJob(job); err != nil {
return err
}
}
parallelism := int32(newSize)
job.Spec.Parallelism = &parallelism
if _, err := scaler.c.Jobs(namespace).Update(job); err != nil {
if errors.IsInvalid(err) {
return ScaleError{ScaleUpdateInvalidFailure, job.ResourceVersion, err}
}
return ScaleError{ScaleUpdateFailure, job.ResourceVersion, err}
}
return nil
}
// Scale updates a Job to a new size, with optional precondition check (if preconditions is not nil),
// optional retries (if retry is not nil), and then optionally waits for parallelism to reach desired
// number, which can be less than requested based on job's current progress.
func (scaler *JobScaler) Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams) error {
if preconditions == nil {
preconditions = &ScalePrecondition{-1, ""}
}
if retry == nil {
// Make it try only once, immediately
retry = &RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond}
}
cond := ScaleCondition(scaler, preconditions, namespace, name, newSize)
if err := wait.Poll(retry.Interval, retry.Timeout, cond); err != nil {
return err
}
if waitForReplicas != nil {
job, err := scaler.c.Jobs(namespace).Get(name)
if err != nil {
return err
}
return wait.Poll(waitForReplicas.Interval, waitForReplicas.Timeout,
client.JobHasDesiredParallelism(scaler.c, job))
}
return nil
}
// ValidateDeployment ensures that the preconditions match. Returns nil if they are valid, an error otherwise.
func (precondition *ScalePrecondition) ValidateDeployment(deployment *extensions.Deployment) error {
if precondition.Size != -1 && int(deployment.Spec.Replicas) != precondition.Size {
return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(int(deployment.Spec.Replicas))}
}
if len(precondition.ResourceVersion) != 0 && deployment.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, deployment.ResourceVersion}
}
return nil
}
type DeploymentScaler struct {
c client.ExtensionsInterface
}
// ScaleSimple is responsible for updating a deployment's desired replicas count.
func (scaler *DeploymentScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) error {
deployment, err := scaler.c.Deployments(namespace).Get(name)
if err != nil {
return ScaleError{ScaleGetFailure, "Unknown", err}
}
if preconditions != nil {
if err := preconditions.ValidateDeployment(deployment); err != nil {
return err
}
}
// TODO(madhusudancs): Fix this when Scale group issues are resolved (see issue #18528).
// For now I'm falling back to regular Deployment update operation.
deployment.Spec.Replicas = int32(newSize)
if _, err := scaler.c.Deployments(namespace).Update(deployment); err != nil {
if errors.IsInvalid(err) {
return ScaleError{ScaleUpdateInvalidFailure, deployment.ResourceVersion, err}
}
return ScaleError{ScaleUpdateFailure, deployment.ResourceVersion, err}
}
return nil
}
// Scale updates a deployment to a new size, with optional precondition check (if preconditions is not nil),
// optional retries (if retry is not nil), and then optionally waits for the status to reach desired count.
func (scaler *DeploymentScaler) Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams) error {
if preconditions == nil {
preconditions = &ScalePrecondition{-1, ""}
}
if retry == nil {
// Make it try only once, immediately
retry = &RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond}
}
cond := ScaleCondition(scaler, preconditions, namespace, name, newSize)
if err := wait.Poll(retry.Interval, retry.Timeout, cond); err != nil {
return err
}
if waitForReplicas != nil {
deployment, err := scaler.c.Deployments(namespace).Get(name)
if err != nil {
return err
}
return wait.Poll(waitForReplicas.Interval, waitForReplicas.Timeout,
client.DeploymentHasDesiredReplicas(scaler.c, deployment))
}
return nil
}

207
vendor/k8s.io/kubernetes/pkg/kubectl/secret.go generated vendored Normal file
View file

@ -0,0 +1,207 @@
/*
Copyright 2015 The Kubernetes Authors 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 kubectl
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/runtime"
)
// SecretGeneratorV1 supports stable generation of an opaque secret
type SecretGeneratorV1 struct {
// Name of secret (required)
Name string
// Type of secret (optional)
Type string
// FileSources to derive the secret from (optional)
FileSources []string
// LiteralSources to derive the secret from (optional)
LiteralSources []string
}
// Ensure it supports the generator pattern that uses parameter injection
var _ Generator = &SecretGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ StructuredGenerator = &SecretGeneratorV1{}
// Generate returns a secret using the specified parameters
func (s SecretGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
delegate := &SecretGeneratorV1{}
fromFileStrings, found := genericParams["from-file"]
if found {
fromFileArray, isArray := fromFileStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
}
delegate.FileSources = fromFileArray
delete(genericParams, "from-file")
}
fromLiteralStrings, found := genericParams["from-literal"]
if found {
fromLiteralArray, isArray := fromLiteralStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
}
delegate.LiteralSources = fromLiteralArray
delete(genericParams, "from-literal")
}
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
delegate.Name = params["name"]
delegate.Type = params["type"]
return delegate.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern
func (s SecretGeneratorV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"type", false},
{"from-file", false},
{"from-literal", false},
{"force", false},
}
}
// StructuredGenerate outputs a secret object using the configured fields
func (s SecretGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
secret := &api.Secret{}
secret.Name = s.Name
secret.Data = map[string][]byte{}
if len(s.Type) > 0 {
secret.Type = api.SecretType(s.Type)
}
if len(s.FileSources) > 0 {
if err := handleFromFileSources(secret, s.FileSources); err != nil {
return nil, err
}
}
if len(s.LiteralSources) > 0 {
if err := handleFromLiteralSources(secret, s.LiteralSources); err != nil {
return nil, err
}
}
return secret, nil
}
// validate validates required fields are set to support structured generation
func (s SecretGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
return nil
}
// handleFromLiteralSources adds the specified literal source information into the provided secret
func handleFromLiteralSources(secret *api.Secret, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := parseLiteralSource(literalSource)
if err != nil {
return err
}
err = addKeyFromLiteralToSecret(secret, keyName, []byte(value))
if err != nil {
return err
}
}
return nil
}
// handleFromFileSources adds the specified file source information into the provided secret
func handleFromFileSources(secret *api.Secret, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := parseFileSource(fileSource)
if err != nil {
return err
}
info, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default:
return fmt.Errorf("error reading %s: %v", filePath, err)
}
}
if info.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path.")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
err = addKeyFromFileToSecret(secret, keyName, itemPath)
if err != nil {
return err
}
}
}
} else {
err = addKeyFromFileToSecret(secret, keyName, filePath)
if err != nil {
return err
}
}
}
return nil
}
func addKeyFromFileToSecret(secret *api.Secret, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
return addKeyFromLiteralToSecret(secret, keyName, data)
}
func addKeyFromLiteralToSecret(secret *api.Secret, keyName string, data []byte) error {
if !validation.IsSecretKey(keyName) {
return fmt.Errorf("%v is not a valid key name for a secret", keyName)
}
if _, entryExists := secret.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, secret.Data)
}
secret.Data[keyName] = data
return nil
}

View file

@ -0,0 +1,131 @@
/*
Copyright 2015 The Kubernetes Authors 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 kubectl
import (
"encoding/json"
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/credentialprovider"
"k8s.io/kubernetes/pkg/runtime"
)
// SecretForDockerRegistryGeneratorV1 supports stable generation of a docker registry secret
type SecretForDockerRegistryGeneratorV1 struct {
// Name of secret (required)
Name string
// Username for registry (required)
Username string
// Email for registry (required)
Email string
// Password for registry (required)
Password string
// Server for registry (required)
Server string
}
// Ensure it supports the generator pattern that uses parameter injection
var _ Generator = &SecretForDockerRegistryGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ StructuredGenerator = &SecretForDockerRegistryGeneratorV1{}
// Generate returns a secret using the specified parameters
func (s SecretForDockerRegistryGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
delegate := &SecretForDockerRegistryGeneratorV1{
Name: params["name"],
Username: params["docker-username"],
Email: params["docker-email"],
Password: params["docker-password"],
Server: params["docker-server"],
}
return delegate.StructuredGenerate()
}
// StructuredGenerate outputs a secret object using the configured fields
func (s SecretForDockerRegistryGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
dockercfgContent, err := handleDockercfgContent(s.Username, s.Password, s.Email, s.Server)
if err != nil {
return nil, err
}
secret := &api.Secret{}
secret.Name = s.Name
secret.Type = api.SecretTypeDockercfg
secret.Data = map[string][]byte{}
secret.Data[api.DockerConfigKey] = dockercfgContent
return secret, nil
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern
func (s SecretForDockerRegistryGeneratorV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"docker-username", true},
{"docker-email", true},
{"docker-password", true},
{"docker-server", true},
}
}
// validate validates required fields are set to support structured generation
func (s SecretForDockerRegistryGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.Username) == 0 {
return fmt.Errorf("username must be specified")
}
if len(s.Email) == 0 {
return fmt.Errorf("email must be specified")
}
if len(s.Password) == 0 {
return fmt.Errorf("password must be specified")
}
if len(s.Server) == 0 {
return fmt.Errorf("server must be specified")
}
return nil
}
// handleDockercfgContent serializes a dockercfg json file
func handleDockercfgContent(username, password, email, server string) ([]byte, error) {
dockercfgAuth := credentialprovider.DockerConfigEntry{
Username: username,
Password: password,
Email: email,
}
dockerCfg := map[string]credentialprovider.DockerConfigEntry{server: dockercfgAuth}
return json.Marshal(dockerCfg)
}

200
vendor/k8s.io/kubernetes/pkg/kubectl/service.go generated vendored Normal file
View file

@ -0,0 +1,200 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"fmt"
"strconv"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/intstr"
)
// The only difference between ServiceGeneratorV1 and V2 is that the service port is named "default" in V1, while it is left unnamed in V2.
type ServiceGeneratorV1 struct{}
func (ServiceGeneratorV1) ParamNames() []GeneratorParam {
return paramNames()
}
func (ServiceGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
params["port-name"] = "default"
return generate(params)
}
type ServiceGeneratorV2 struct{}
func (ServiceGeneratorV2) ParamNames() []GeneratorParam {
return paramNames()
}
func (ServiceGeneratorV2) Generate(params map[string]interface{}) (runtime.Object, error) {
return generate(params)
}
func paramNames() []GeneratorParam {
return []GeneratorParam{
{"default-name", true},
{"name", false},
{"selector", true},
// port will be used if a user specifies --port OR the exposed object
// has one port
{"port", false},
// ports will be used iff a user doesn't specify --port AND the
// exposed object has multiple ports
{"ports", false},
{"labels", false},
{"external-ip", false},
{"create-external-load-balancer", false},
{"load-balancer-ip", false},
{"type", false},
{"protocol", false},
{"container-port", false}, // alias of target-port
{"target-port", false},
{"port-name", false},
{"session-affinity", false},
}
}
func generate(genericParams map[string]interface{}) (runtime.Object, error) {
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
selectorString, found := params["selector"]
if !found || len(selectorString) == 0 {
return nil, fmt.Errorf("'selector' is a required parameter.")
}
selector, err := ParseLabels(selectorString)
if err != nil {
return nil, err
}
labelsString, found := params["labels"]
var labels map[string]string
if found && len(labelsString) > 0 {
labels, err = ParseLabels(labelsString)
if err != nil {
return nil, err
}
}
name, found := params["name"]
if !found || len(name) == 0 {
name, found = params["default-name"]
if !found || len(name) == 0 {
return nil, fmt.Errorf("'name' is a required parameter.")
}
}
ports := []api.ServicePort{}
servicePortName, found := params["port-name"]
if !found {
// Leave the port unnamed.
servicePortName = ""
}
// ports takes precedence over port since it will be
// specified only when the user hasn't specified a port
// via --port and the exposed object has multiple ports.
var portString string
if portString, found = params["ports"]; !found {
portString, found = params["port"]
if !found {
return nil, fmt.Errorf("'port' is a required parameter.")
}
}
portStringSlice := strings.Split(portString, ",")
for i, stillPortString := range portStringSlice {
port, err := strconv.Atoi(stillPortString)
if err != nil {
return nil, err
}
name := servicePortName
// If we are going to assign multiple ports to a service, we need to
// generate a different name for each one.
if len(portStringSlice) > 1 {
name = fmt.Sprintf("port-%d", i+1)
}
ports = append(ports, api.ServicePort{
Name: name,
Port: int32(port),
Protocol: api.Protocol(params["protocol"]),
})
}
service := api.Service{
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: api.ServiceSpec{
Selector: selector,
Ports: ports,
},
}
targetPortString, found := params["target-port"]
if !found {
targetPortString, found = params["container-port"]
}
if found && len(targetPortString) > 0 {
var targetPort intstr.IntOrString
if portNum, err := strconv.Atoi(targetPortString); err != nil {
targetPort = intstr.FromString(targetPortString)
} else {
targetPort = intstr.FromInt(portNum)
}
// Use the same target-port for every port
for i := range service.Spec.Ports {
service.Spec.Ports[i].TargetPort = targetPort
}
} else {
// If --target-port or --container-port haven't been specified, this
// should be the same as Port
for i := range service.Spec.Ports {
port := service.Spec.Ports[i].Port
service.Spec.Ports[i].TargetPort = intstr.FromInt(int(port))
}
}
if params["create-external-load-balancer"] == "true" {
service.Spec.Type = api.ServiceTypeLoadBalancer
}
if len(params["external-ip"]) > 0 {
service.Spec.ExternalIPs = []string{params["external-ip"]}
}
if len(params["type"]) != 0 {
service.Spec.Type = api.ServiceType(params["type"])
}
if service.Spec.Type == api.ServiceTypeLoadBalancer {
service.Spec.LoadBalancerIP = params["load-balancer-ip"]
}
if len(params["session-affinity"]) != 0 {
switch api.ServiceAffinity(params["session-affinity"]) {
case api.ServiceAffinityNone:
service.Spec.SessionAffinity = api.ServiceAffinityNone
case api.ServiceAffinityClientIP:
service.Spec.SessionAffinity = api.ServiceAffinityClientIP
default:
return nil, fmt.Errorf("unknown session affinity: %s", params["session-affinity"])
}
}
return &service, nil
}

51
vendor/k8s.io/kubernetes/pkg/kubectl/serviceaccount.go generated vendored Normal file
View file

@ -0,0 +1,51 @@
/*
Copyright 2016 The Kubernetes Authors 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 kubectl
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/runtime"
)
// ServiceAccountGeneratorV1 supports stable generation of a service account
type ServiceAccountGeneratorV1 struct {
// Name of service account
Name string
}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ StructuredGenerator = &ServiceAccountGeneratorV1{}
// StructuredGenerate outputs a service account object using the configured fields
func (g *ServiceAccountGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := g.validate(); err != nil {
return nil, err
}
serviceAccount := &api.ServiceAccount{}
serviceAccount.Name = g.Name
return serviceAccount, nil
}
// validate validates required fields are set to support structured generation
func (g *ServiceAccountGeneratorV1) validate() error {
if len(g.Name) == 0 {
return fmt.Errorf("name must be specified")
}
return nil
}

View file

@ -0,0 +1,36 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"k8s.io/kubernetes/pkg/api"
)
// SortableEvents implements sort.Interface for []api.Event based on the Timestamp field
type SortableEvents []api.Event
func (list SortableEvents) Len() int {
return len(list)
}
func (list SortableEvents) Swap(i, j int) {
list[i], list[j] = list[j], list[i]
}
func (list SortableEvents) Less(i, j int) bool {
return list[i].LastTimestamp.Time.Before(list[j].LastTimestamp.Time)
}

View file

@ -0,0 +1,49 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"k8s.io/kubernetes/pkg/api"
)
type SortableResourceNames []api.ResourceName
func (list SortableResourceNames) Len() int {
return len(list)
}
func (list SortableResourceNames) Swap(i, j int) {
list[i], list[j] = list[j], list[i]
}
func (list SortableResourceNames) Less(i, j int) bool {
return list[i] < list[j]
}
type SortableResourceQuotas []api.ResourceQuota
func (list SortableResourceQuotas) Len() int {
return len(list)
}
func (list SortableResourceQuotas) Swap(i, j int) {
list[i], list[j] = list[j], list[i]
}
func (list SortableResourceQuotas) Less(i, j int) bool {
return list[i].Name < list[j].Name
}

194
vendor/k8s.io/kubernetes/pkg/kubectl/sorting_printer.go generated vendored Normal file
View file

@ -0,0 +1,194 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"fmt"
"io"
"reflect"
"sort"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/jsonpath"
"github.com/golang/glog"
)
// Sorting printer sorts list types before delegating to another printer.
// Non-list types are simply passed through
type SortingPrinter struct {
SortField string
Delegate ResourcePrinter
Decoder runtime.Decoder
}
func (s *SortingPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
if !meta.IsListType(obj) {
return s.Delegate.PrintObj(obj, out)
}
if err := s.sortObj(obj); err != nil {
return err
}
return s.Delegate.PrintObj(obj, out)
}
// TODO: implement HandledResources()
func (p *SortingPrinter) HandledResources() []string {
return []string{}
}
func (s *SortingPrinter) sortObj(obj runtime.Object) error {
objs, err := meta.ExtractList(obj)
if err != nil {
return err
}
if len(objs) == 0 {
return nil
}
sorter, err := SortObjects(s.Decoder, objs, s.SortField)
if err != nil {
return err
}
switch list := obj.(type) {
case *v1.List:
outputList := make([]runtime.RawExtension, len(objs))
for ix := range objs {
outputList[ix] = list.Items[sorter.OriginalPosition(ix)]
}
list.Items = outputList
return nil
}
return meta.SetList(obj, objs)
}
func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput string) (*RuntimeSort, error) {
parser := jsonpath.New("sorting")
field, err := massageJSONPath(fieldInput)
if err != nil {
return nil, err
}
if err := parser.Parse(field); err != nil {
return nil, err
}
for ix := range objs {
item := objs[ix]
switch u := item.(type) {
case *runtime.Unknown:
var err error
if objs[ix], _, err = decoder.Decode(u.Raw, nil, nil); err != nil {
return nil, err
}
}
}
values, err := parser.FindResults(reflect.ValueOf(objs[0]).Elem().Interface())
if err != nil {
return nil, err
}
if len(values) == 0 {
return nil, fmt.Errorf("couldn't find any field with path: %s", field)
}
sorter := NewRuntimeSort(field, objs)
sort.Sort(sorter)
return sorter, nil
}
// RuntimeSort is an implementation of the golang sort interface that knows how to sort
// lists of runtime.Object
type RuntimeSort struct {
field string
objs []runtime.Object
origPosition []int
}
func NewRuntimeSort(field string, objs []runtime.Object) *RuntimeSort {
sorter := &RuntimeSort{field: field, objs: objs, origPosition: make([]int, len(objs))}
for ix := range objs {
sorter.origPosition[ix] = ix
}
return sorter
}
func (r *RuntimeSort) Len() int {
return len(r.objs)
}
func (r *RuntimeSort) Swap(i, j int) {
r.objs[i], r.objs[j] = r.objs[j], r.objs[i]
r.origPosition[i], r.origPosition[j] = r.origPosition[j], r.origPosition[i]
}
func isLess(i, j reflect.Value) (bool, error) {
switch i.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return i.Int() < j.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return i.Uint() < j.Uint(), nil
case reflect.Float32, reflect.Float64:
return i.Float() < j.Float(), nil
case reflect.String:
return i.String() < j.String(), nil
case reflect.Ptr:
return isLess(i.Elem(), j.Elem())
default:
return false, fmt.Errorf("unsortable type: %v", i.Kind())
}
}
func (r *RuntimeSort) Less(i, j int) bool {
iObj := r.objs[i]
jObj := r.objs[j]
parser := jsonpath.New("sorting")
parser.Parse(r.field)
iValues, err := parser.FindResults(reflect.ValueOf(iObj).Elem().Interface())
if err != nil {
glog.Fatalf("Failed to get i values for %#v using %s (%#v)", iObj, r.field, err)
}
jValues, err := parser.FindResults(reflect.ValueOf(jObj).Elem().Interface())
if err != nil {
glog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err)
}
iField := iValues[0][0]
jField := jValues[0][0]
less, err := isLess(iField, jField)
if err != nil {
glog.Fatalf("Field %s in %v is an unsortable type: %s, err: %v", r.field, iObj, iField.Kind().String(), err)
}
return less
}
// Returns the starting (original) position of a particular index. e.g. If OriginalPosition(0) returns 5 than the
// the item currently at position 0 was at position 5 in the original unsorted array.
func (r *RuntimeSort) OriginalPosition(ix int) int {
if ix < 0 || ix > len(r.origPosition) {
return -1
}
return r.origPosition[ix]
}

447
vendor/k8s.io/kubernetes/pkg/kubectl/stop.go generated vendored Normal file
View file

@ -0,0 +1,447 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"fmt"
"strings"
"time"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util"
deploymentutil "k8s.io/kubernetes/pkg/util/deployment"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/wait"
)
const (
Interval = time.Second * 1
Timeout = time.Minute * 5
)
// A Reaper handles terminating an object as gracefully as possible.
// timeout is how long we'll wait for the termination to be successful
// gracePeriod is time given to an API object for it to delete itself cleanly,
// e.g., pod shutdown. It may or may not be supported by the API object.
type Reaper interface {
Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error
}
type NoSuchReaperError struct {
kind unversioned.GroupKind
}
func (n *NoSuchReaperError) Error() string {
return fmt.Sprintf("no reaper has been implemented for %v", n.kind)
}
func IsNoSuchReaperError(err error) bool {
_, ok := err.(*NoSuchReaperError)
return ok
}
func ReaperFor(kind unversioned.GroupKind, c client.Interface) (Reaper, error) {
switch kind {
case api.Kind("ReplicationController"):
return &ReplicationControllerReaper{c, Interval, Timeout}, nil
case extensions.Kind("ReplicaSet"):
return &ReplicaSetReaper{c, Interval, Timeout}, nil
case extensions.Kind("DaemonSet"):
return &DaemonSetReaper{c, Interval, Timeout}, nil
case api.Kind("Pod"):
return &PodReaper{c}, nil
case api.Kind("Service"):
return &ServiceReaper{c}, nil
case extensions.Kind("Job"), batch.Kind("Job"):
return &JobReaper{c, Interval, Timeout}, nil
case extensions.Kind("Deployment"):
return &DeploymentReaper{c, Interval, Timeout}, nil
}
return nil, &NoSuchReaperError{kind}
}
func ReaperForReplicationController(c client.Interface, timeout time.Duration) (Reaper, error) {
return &ReplicationControllerReaper{c, Interval, timeout}, nil
}
type ReplicationControllerReaper struct {
client.Interface
pollInterval, timeout time.Duration
}
type ReplicaSetReaper struct {
client.Interface
pollInterval, timeout time.Duration
}
type DaemonSetReaper struct {
client.Interface
pollInterval, timeout time.Duration
}
type JobReaper struct {
client.Interface
pollInterval, timeout time.Duration
}
type DeploymentReaper struct {
client.Interface
pollInterval, timeout time.Duration
}
type PodReaper struct {
client.Interface
}
type ServiceReaper struct {
client.Interface
}
type objInterface interface {
Delete(name string) error
Get(name string) (meta.Object, error)
}
// getOverlappingControllers finds rcs that this controller overlaps, as well as rcs overlapping this controller.
func getOverlappingControllers(c client.ReplicationControllerInterface, rc *api.ReplicationController) ([]api.ReplicationController, error) {
rcs, err := c.List(api.ListOptions{})
if err != nil {
return nil, fmt.Errorf("error getting replication controllers: %v", err)
}
var matchingRCs []api.ReplicationController
rcLabels := labels.Set(rc.Spec.Selector)
for _, controller := range rcs.Items {
newRCLabels := labels.Set(controller.Spec.Selector)
if labels.SelectorFromSet(newRCLabels).Matches(rcLabels) || labels.SelectorFromSet(rcLabels).Matches(newRCLabels) {
matchingRCs = append(matchingRCs, controller)
}
}
return matchingRCs, nil
}
func (reaper *ReplicationControllerReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
rc := reaper.ReplicationControllers(namespace)
scaler, err := ScalerFor(api.Kind("ReplicationController"), *reaper)
if err != nil {
return err
}
ctrl, err := rc.Get(name)
if err != nil {
return err
}
if timeout == 0 {
timeout = Timeout + time.Duration(10*ctrl.Spec.Replicas)*time.Second
}
// The rc manager will try and detect all matching rcs for a pod's labels,
// and only sync the oldest one. This means if we have a pod with labels
// [(k1: v1), (k2: v2)] and two rcs: rc1 with selector [(k1=v1)], and rc2 with selector [(k1=v1),(k2=v2)],
// the rc manager will sync the older of the two rcs.
//
// If there are rcs with a superset of labels, eg:
// deleting: (k1=v1), superset: (k2=v2, k1=v1)
// - It isn't safe to delete the rc because there could be a pod with labels
// (k1=v1) that isn't managed by the superset rc. We can't scale it down
// either, because there could be a pod (k2=v2, k1=v1) that it deletes
// causing a fight with the superset rc.
// If there are rcs with a subset of labels, eg:
// deleting: (k2=v2, k1=v1), subset: (k1=v1), superset: (k2=v2, k1=v1, k3=v3)
// - Even if it's safe to delete this rc without a scale down because all it's pods
// are being controlled by the subset rc the code returns an error.
// In theory, creating overlapping controllers is user error, so the loop below
// tries to account for this logic only in the common case, where we end up
// with multiple rcs that have an exact match on selectors.
overlappingCtrls, err := getOverlappingControllers(rc, ctrl)
if err != nil {
return fmt.Errorf("error getting replication controllers: %v", err)
}
exactMatchRCs := []api.ReplicationController{}
overlapRCs := []string{}
for _, overlappingRC := range overlappingCtrls {
if len(overlappingRC.Spec.Selector) == len(ctrl.Spec.Selector) {
exactMatchRCs = append(exactMatchRCs, overlappingRC)
} else {
overlapRCs = append(overlapRCs, overlappingRC.Name)
}
}
if len(overlapRCs) > 0 {
return fmt.Errorf(
"Detected overlapping controllers for rc %v: %v, please manage deletion individually with --cascade=false.",
ctrl.Name, strings.Join(overlapRCs, ","))
}
if len(exactMatchRCs) == 1 {
// No overlapping controllers.
retry := NewRetryParams(reaper.pollInterval, reaper.timeout)
waitForReplicas := NewRetryParams(reaper.pollInterval, timeout)
if err = scaler.Scale(namespace, name, 0, nil, retry, waitForReplicas); err != nil {
return err
}
}
return rc.Delete(name)
}
// TODO(madhusudancs): Implement it when controllerRef is implemented - https://github.com/kubernetes/kubernetes/issues/2210
// getOverlappingReplicaSets finds ReplicaSets that this ReplicaSet overlaps, as well as ReplicaSets overlapping this ReplicaSet.
func getOverlappingReplicaSets(c client.ReplicaSetInterface, rs *extensions.ReplicaSet) ([]extensions.ReplicaSet, []extensions.ReplicaSet, error) {
var overlappingRSs, exactMatchRSs []extensions.ReplicaSet
return overlappingRSs, exactMatchRSs, nil
}
func (reaper *ReplicaSetReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
rsc := reaper.Extensions().ReplicaSets(namespace)
scaler, err := ScalerFor(extensions.Kind("ReplicaSet"), *reaper)
if err != nil {
return err
}
rs, err := rsc.Get(name)
if err != nil {
return err
}
if timeout == 0 {
timeout = Timeout + time.Duration(10*rs.Spec.Replicas)*time.Second
}
// The ReplicaSet controller will try and detect all matching ReplicaSets
// for a pod's labels, and only sync the oldest one. This means if we have
// a pod with labels [(k1: v1), (k2: v2)] and two ReplicaSets: rs1 with
// selector [(k1=v1)], and rs2 with selector [(k1=v1),(k2=v2)], the
// ReplicaSet controller will sync the older of the two ReplicaSets.
//
// If there are ReplicaSets with a superset of labels, eg:
// deleting: (k1=v1), superset: (k2=v2, k1=v1)
// - It isn't safe to delete the ReplicaSet because there could be a pod
// with labels (k1=v1) that isn't managed by the superset ReplicaSet.
// We can't scale it down either, because there could be a pod
// (k2=v2, k1=v1) that it deletes causing a fight with the superset
// ReplicaSet.
// If there are ReplicaSets with a subset of labels, eg:
// deleting: (k2=v2, k1=v1), subset: (k1=v1), superset: (k2=v2, k1=v1, k3=v3)
// - Even if it's safe to delete this ReplicaSet without a scale down because
// all it's pods are being controlled by the subset ReplicaSet the code
// returns an error.
// In theory, creating overlapping ReplicaSets is user error, so the loop below
// tries to account for this logic only in the common case, where we end up
// with multiple ReplicaSets that have an exact match on selectors.
// TODO(madhusudancs): Re-evaluate again when controllerRef is implemented -
// https://github.com/kubernetes/kubernetes/issues/2210
overlappingRSs, exactMatchRSs, err := getOverlappingReplicaSets(rsc, rs)
if err != nil {
return fmt.Errorf("error getting ReplicaSets: %v", err)
}
if len(overlappingRSs) > 0 {
var names []string
for _, overlappingRS := range overlappingRSs {
names = append(names, overlappingRS.Name)
}
return fmt.Errorf(
"Detected overlapping ReplicaSets for ReplicaSet %v: %v, please manage deletion individually with --cascade=false.",
rs.Name, strings.Join(names, ","))
}
if len(exactMatchRSs) == 0 {
// No overlapping ReplicaSets.
retry := NewRetryParams(reaper.pollInterval, reaper.timeout)
waitForReplicas := NewRetryParams(reaper.pollInterval, timeout)
if err = scaler.Scale(namespace, name, 0, nil, retry, waitForReplicas); err != nil {
return err
}
}
if err := rsc.Delete(name, nil); err != nil {
return err
}
return nil
}
func (reaper *DaemonSetReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
ds, err := reaper.Extensions().DaemonSets(namespace).Get(name)
if err != nil {
return err
}
// We set the nodeSelector to a random label. This label is nearly guaranteed
// to not be set on any node so the DameonSetController will start deleting
// daemon pods. Once it's done deleting the daemon pods, it's safe to delete
// the DaemonSet.
ds.Spec.Template.Spec.NodeSelector = map[string]string{
string(util.NewUUID()): string(util.NewUUID()),
}
// force update to avoid version conflict
ds.ResourceVersion = ""
if ds, err = reaper.Extensions().DaemonSets(namespace).Update(ds); err != nil {
return err
}
// Wait for the daemon set controller to kill all the daemon pods.
if err := wait.Poll(reaper.pollInterval, reaper.timeout, func() (bool, error) {
updatedDS, err := reaper.Extensions().DaemonSets(namespace).Get(name)
if err != nil {
return false, nil
}
return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
}); err != nil {
return err
}
return reaper.Extensions().DaemonSets(namespace).Delete(name)
}
func (reaper *JobReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
jobs := reaper.Batch().Jobs(namespace)
pods := reaper.Pods(namespace)
scaler, err := ScalerFor(batch.Kind("Job"), *reaper)
if err != nil {
return err
}
job, err := jobs.Get(name)
if err != nil {
return err
}
if timeout == 0 {
// we will never have more active pods than job.Spec.Parallelism
parallelism := *job.Spec.Parallelism
timeout = Timeout + time.Duration(10*parallelism)*time.Second
}
// TODO: handle overlapping jobs
retry := NewRetryParams(reaper.pollInterval, reaper.timeout)
waitForJobs := NewRetryParams(reaper.pollInterval, timeout)
if err = scaler.Scale(namespace, name, 0, nil, retry, waitForJobs); err != nil {
return err
}
// at this point only dead pods are left, that should be removed
selector, _ := unversioned.LabelSelectorAsSelector(job.Spec.Selector)
options := api.ListOptions{LabelSelector: selector}
podList, err := pods.List(options)
if err != nil {
return err
}
errList := []error{}
for _, pod := range podList.Items {
if err := pods.Delete(pod.Name, gracePeriod); err != nil {
// ignores the error when the pod isn't found
if !errors.IsNotFound(err) {
errList = append(errList, err)
}
}
}
if len(errList) > 0 {
return utilerrors.NewAggregate(errList)
}
// once we have all the pods removed we can safely remove the job itself
return jobs.Delete(name, nil)
}
func (reaper *DeploymentReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
deployments := reaper.Extensions().Deployments(namespace)
replicaSets := reaper.Extensions().ReplicaSets(namespace)
rsReaper, _ := ReaperFor(extensions.Kind("ReplicaSet"), reaper)
deployment, err := reaper.updateDeploymentWithRetries(namespace, name, func(d *extensions.Deployment) {
// set deployment's history and scale to 0
// TODO replace with patch when available: https://github.com/kubernetes/kubernetes/issues/20527
d.Spec.RevisionHistoryLimit = util.Int32Ptr(0)
d.Spec.Replicas = 0
d.Spec.Paused = true
})
if err != nil {
return err
}
// Use observedGeneration to determine if the deployment controller noticed the pause.
if err := deploymentutil.WaitForObservedDeployment(func() (*extensions.Deployment, error) {
return deployments.Get(name)
}, deployment.Generation, 10*time.Millisecond, 1*time.Minute); err != nil {
return err
}
// Stop all replica sets.
selector, err := unversioned.LabelSelectorAsSelector(deployment.Spec.Selector)
if err != nil {
return err
}
options := api.ListOptions{LabelSelector: selector}
rsList, err := replicaSets.List(options)
if err != nil {
return err
}
errList := []error{}
for _, rc := range rsList.Items {
if err := rsReaper.Stop(rc.Namespace, rc.Name, timeout, gracePeriod); err != nil {
if !errors.IsNotFound(err) {
errList = append(errList, err)
}
}
}
if len(errList) > 0 {
return utilerrors.NewAggregate(errList)
}
// Delete deployment at the end.
// Note: We delete deployment at the end so that if removing RSs fails, we atleast have the deployment to retry.
return deployments.Delete(name, nil)
}
type updateDeploymentFunc func(d *extensions.Deployment)
func (reaper *DeploymentReaper) updateDeploymentWithRetries(namespace, name string, applyUpdate updateDeploymentFunc) (deployment *extensions.Deployment, err error) {
deployments := reaper.Extensions().Deployments(namespace)
err = wait.Poll(10*time.Millisecond, 1*time.Minute, func() (bool, error) {
if deployment, err = deployments.Get(name); err != nil {
return false, err
}
// Apply the update, then attempt to push it to the apiserver.
applyUpdate(deployment)
if deployment, err = deployments.Update(deployment); err == nil {
return true, nil
}
return false, nil
})
return deployment, err
}
func (reaper *PodReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
pods := reaper.Pods(namespace)
_, err := pods.Get(name)
if err != nil {
return err
}
return pods.Delete(name, gracePeriod)
}
func (reaper *ServiceReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
services := reaper.Services(namespace)
_, err := services.Get(name)
if err != nil {
return err
}
return services.Delete(name)
}

40
vendor/k8s.io/kubernetes/pkg/kubectl/version.go generated vendored Normal file
View file

@ -0,0 +1,40 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"fmt"
"io"
"os"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/version"
)
func GetServerVersion(w io.Writer, kubeClient client.Interface) {
serverVersion, err := kubeClient.Discovery().ServerVersion()
if err != nil {
fmt.Printf("Couldn't read server version from server: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(w, "Server Version: %#v\n", *serverVersion)
}
func GetClientVersion(w io.Writer) {
fmt.Fprintf(w, "Client Version: %#v\n", version.Get())
}

45
vendor/k8s.io/kubernetes/pkg/kubectl/watchloop.go generated vendored Normal file
View file

@ -0,0 +1,45 @@
/*
Copyright 2014 The Kubernetes Authors 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 kubectl
import (
"os"
"os/signal"
"k8s.io/kubernetes/pkg/watch"
)
// WatchLoop loops, passing events in w to fn.
// If user sends interrupt signal, shut down cleanly. Otherwise, never return.
func WatchLoop(w watch.Interface, fn func(watch.Event) error) {
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
defer signal.Stop(signals)
for {
select {
case event, ok := <-w.ResultChan():
if !ok {
return
}
if err := fn(event); err != nil {
w.Stop()
}
case <-signals:
w.Stop()
}
}
}