vendor update for CSI 0.3.0

This commit is contained in:
gman
2018-07-18 16:47:22 +02:00
parent 6f484f92fc
commit 8ea659f0d5
6810 changed files with 438061 additions and 193861 deletions

View File

@ -21,13 +21,19 @@ go_library(
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/set/env:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/env:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
@ -53,18 +59,17 @@ go_test(
"set_test.go",
],
data = [
"//examples:config",
"//test/e2e/testing-manifests:all-srcs",
"//test/fixtures",
],
embed = [":go_default_library"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
@ -74,9 +79,11 @@ go_test(
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
],
@ -90,7 +97,10 @@ filegroup(
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
srcs = [
":package-srcs",
"//pkg/kubectl/cmd/set/env:all-srcs",
],
tags = ["automanaged"],
visibility = [
"//build/visible_to:pkg_kubectl_cmd_set_CONSUMERS",

41
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/set/env/BUILD generated vendored Normal file
View File

@ -0,0 +1,41 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"env_parse.go",
"env_resolve.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/set/env",
visibility = ["//visibility:public"],
deps = [
"//pkg/api/v1/resource:go_default_library",
"//pkg/fieldpath:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["env_parse_test.go"],
embed = [":go_default_library"],
)

View File

@ -0,0 +1,18 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package env provides functions to incorporate environment variables into set env.
package env

View File

@ -0,0 +1,137 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package env
import (
"bufio"
"fmt"
"io"
"regexp"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
)
var argumentEnvironment = regexp.MustCompile("(?ms)^(.+)\\=(.*)$")
var validArgumentEnvironment = regexp.MustCompile("(?ms)^(\\w+)\\=(.*)$")
// IsEnvironmentArgument checks whether a string is an environment argument, that is, whether it matches the "anycharacters=anycharacters" pattern.
func IsEnvironmentArgument(s string) bool {
return argumentEnvironment.MatchString(s)
}
// IsValidEnvironmentArgument checks whether a string is a valid environment argument, that is, whether it matches the "wordcharacters=anycharacters" pattern. Word characters can be letters, numbers, and underscores.
func IsValidEnvironmentArgument(s string) bool {
return validArgumentEnvironment.MatchString(s)
}
// SplitEnvironmentFromResources separates resources from environment arguments.
// Resources must come first. Arguments may have the "DASH-" syntax.
func SplitEnvironmentFromResources(args []string) (resources, envArgs []string, ok bool) {
first := true
for _, s := range args {
// this method also has to understand env removal syntax, i.e. KEY-
isEnv := IsEnvironmentArgument(s) || strings.HasSuffix(s, "-")
switch {
case first && isEnv:
first = false
fallthrough
case !first && isEnv:
envArgs = append(envArgs, s)
case first && !isEnv:
resources = append(resources, s)
case !first && !isEnv:
return nil, nil, false
}
}
return resources, envArgs, true
}
// parseIntoEnvVar parses the list of key-value pairs into kubernetes EnvVar.
// envVarType is for making errors more specific to user intentions.
func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) ([]v1.EnvVar, []string, error) {
env := []v1.EnvVar{}
exists := sets.NewString()
var remove []string
for _, envSpec := range spec {
switch {
case !IsValidEnvironmentArgument(envSpec) && !strings.HasSuffix(envSpec, "-"):
return nil, nil, fmt.Errorf("%ss must be of the form key=value and can only contain letters, numbers, and underscores", envVarType)
case envSpec == "-":
if defaultReader == nil {
return nil, nil, fmt.Errorf("when '-' is used, STDIN must be open")
}
fileEnv, err := readEnv(defaultReader, envVarType)
if err != nil {
return nil, nil, err
}
env = append(env, fileEnv...)
case strings.Index(envSpec, "=") != -1:
parts := strings.SplitN(envSpec, "=", 2)
if len(parts) != 2 {
return nil, nil, fmt.Errorf("invalid %s: %v", envVarType, envSpec)
}
exists.Insert(parts[0])
env = append(env, v1.EnvVar{
Name: parts[0],
Value: parts[1],
})
case strings.HasSuffix(envSpec, "-"):
remove = append(remove, envSpec[:len(envSpec)-1])
default:
return nil, nil, fmt.Errorf("unknown %s: %v", envVarType, envSpec)
}
}
for _, removeLabel := range remove {
if _, found := exists[removeLabel]; found {
return nil, nil, fmt.Errorf("can not both modify and remove the same %s in the same command", envVarType)
}
}
return env, remove, nil
}
// ParseEnv parses the elements of the first argument looking for environment variables in key=value form and, if one of those values is "-", it also scans the reader.
// The same environment variable cannot be both modified and removed in the same command.
func ParseEnv(spec []string, defaultReader io.Reader) ([]v1.EnvVar, []string, error) {
return parseIntoEnvVar(spec, defaultReader, "environment variable")
}
func readEnv(r io.Reader, envVarType string) ([]v1.EnvVar, error) {
env := []v1.EnvVar{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
envSpec := scanner.Text()
if pos := strings.Index(envSpec, "#"); pos != -1 {
envSpec = envSpec[:pos]
}
if strings.Index(envSpec, "=") != -1 {
parts := strings.SplitN(envSpec, "=", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid %s: %v", envVarType, envSpec)
}
env = append(env, v1.EnvVar{
Name: parts[0],
Value: parts[1],
})
}
}
if err := scanner.Err(); err != nil && err != io.EOF {
return nil, err
}
return env, nil
}

View File

@ -0,0 +1,69 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package env
import (
"fmt"
"io"
"strings"
)
func ExampleIsEnvironmentArgument_true() {
test := "returns=true"
fmt.Println(IsEnvironmentArgument(test))
// Output: true
}
func ExampleIsEnvironmentArgument_false() {
test := "returnsfalse"
fmt.Println(IsEnvironmentArgument(test))
// Output: false
}
func ExampleIsValidEnvironmentArgument_true() {
test := "wordcharacters=true"
fmt.Println(IsValidEnvironmentArgument(test))
// Output: true
}
func ExampleIsValidEnvironmentArgument_false() {
test := "not$word^characters=test"
fmt.Println(IsValidEnvironmentArgument(test))
// Output: false
}
func ExampleSplitEnvironmentFromResources() {
args := []string{`resource`, "ENV\\=ARG", `ONE\=MORE`, `DASH-`}
fmt.Println(SplitEnvironmentFromResources(args))
// Output: [resource] [ENV\=ARG ONE\=MORE DASH-] true
}
func ExampleParseEnv_good() {
r := strings.NewReader("FROM=READER")
ss := []string{"ENV=VARIABLE", "AND=ANOTHER", "REMOVE-", "-"}
fmt.Println(ParseEnv(ss, r))
// Output:
// [{ENV VARIABLE nil} {AND ANOTHER nil} {FROM READER nil}] [REMOVE] <nil>
}
func ExampleParseEnv_bad() {
var r io.Reader
bad := []string{"This not in the key=value format."}
fmt.Println(ParseEnv(bad, r))
// Output:
// [] [] environment variables must be of the form key=value and can only contain letters, numbers, and underscores
}

View File

@ -0,0 +1,133 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package env
import (
"fmt"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/api/v1/resource"
"k8s.io/kubernetes/pkg/fieldpath"
)
// ResourceStore defines a new resource store data structure.
type ResourceStore struct {
SecretStore map[string]*v1.Secret
ConfigMapStore map[string]*v1.ConfigMap
}
// NewResourceStore returns a pointer to a new resource store data structure.
func NewResourceStore() *ResourceStore {
return &ResourceStore{
SecretStore: make(map[string]*v1.Secret),
ConfigMapStore: make(map[string]*v1.ConfigMap),
}
}
// getSecretRefValue returns the value of a secret in the supplied namespace
func getSecretRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, secretSelector *v1.SecretKeySelector) (string, error) {
secret, ok := store.SecretStore[secretSelector.Name]
if !ok {
var err error
secret, err = client.CoreV1().Secrets(namespace).Get(secretSelector.Name, metav1.GetOptions{})
if err != nil {
return "", err
}
store.SecretStore[secretSelector.Name] = secret
}
if data, ok := secret.Data[secretSelector.Key]; ok {
return string(data), nil
}
return "", fmt.Errorf("key %s not found in secret %s", secretSelector.Key, secretSelector.Name)
}
// getConfigMapRefValue returns the value of a configmap in the supplied namespace
func getConfigMapRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, configMapSelector *v1.ConfigMapKeySelector) (string, error) {
configMap, ok := store.ConfigMapStore[configMapSelector.Name]
if !ok {
var err error
configMap, err = client.CoreV1().ConfigMaps(namespace).Get(configMapSelector.Name, metav1.GetOptions{})
if err != nil {
return "", err
}
store.ConfigMapStore[configMapSelector.Name] = configMap
}
if data, ok := configMap.Data[configMapSelector.Key]; ok {
return string(data), nil
}
return "", fmt.Errorf("key %s not found in config map %s", configMapSelector.Key, configMapSelector.Name)
}
// getFieldRef returns the value of the supplied path in the given object
func getFieldRef(obj runtime.Object, from *v1.EnvVarSource) (string, error) {
return fieldpath.ExtractFieldPathAsString(obj, from.FieldRef.FieldPath)
}
// getResourceFieldRef returns the value of a resource in the given container
func getResourceFieldRef(from *v1.EnvVarSource, c *v1.Container) (string, error) {
return resource.ExtractContainerResourceValue(from.ResourceFieldRef, c)
}
// GetEnvVarRefValue returns the value referenced by the supplied EnvVarSource given the other supplied information.
func GetEnvVarRefValue(kc kubernetes.Interface, ns string, store *ResourceStore, from *v1.EnvVarSource, obj runtime.Object, c *v1.Container) (string, error) {
if from.SecretKeyRef != nil {
return getSecretRefValue(kc, ns, store, from.SecretKeyRef)
}
if from.ConfigMapKeyRef != nil {
return getConfigMapRefValue(kc, ns, store, from.ConfigMapKeyRef)
}
if from.FieldRef != nil {
return getFieldRef(obj, from)
}
if from.ResourceFieldRef != nil {
return getResourceFieldRef(from, c)
}
return "", fmt.Errorf("invalid valueFrom")
}
// GetEnvVarRefString returns a text description of whichever field is set within the supplied EnvVarSource argument.
func GetEnvVarRefString(from *v1.EnvVarSource) string {
if from.ConfigMapKeyRef != nil {
return fmt.Sprintf("configmap %s, key %s", from.ConfigMapKeyRef.Name, from.ConfigMapKeyRef.Key)
}
if from.SecretKeyRef != nil {
return fmt.Sprintf("secret %s, key %s", from.SecretKeyRef.Name, from.SecretKeyRef.Key)
}
if from.FieldRef != nil {
return fmt.Sprintf("field path %s", from.FieldRef.FieldPath)
}
if from.ResourceFieldRef != nil {
containerPrefix := ""
if from.ResourceFieldRef.ContainerName != "" {
containerPrefix = fmt.Sprintf("%s/", from.ResourceFieldRef.ContainerName)
}
return fmt.Sprintf("resource field %s%s", containerPrefix, from.ResourceFieldRef.Resource)
}
return "invalid valueFrom"
}

View File

@ -27,7 +27,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
)
// selectContainers allows one or more containers to be matched against a string or wildcard
@ -118,16 +118,16 @@ type Patch struct {
Patch []byte
}
// patchFn is a function type that accepts an info object and returns a byte slice.
// Implementations of patchFn should update the object and return it encoded.
type patchFn func(*resource.Info) ([]byte, error)
// PatchFn is a function type that accepts an info object and returns a byte slice.
// Implementations of PatchFn should update the object and return it encoded.
type PatchFn func(runtime.Object) ([]byte, error)
// CalculatePatch calls the mutation function on the provided info object, and generates a strategic merge patch for
// the changes in the object. Encoder must be able to encode the info into the appropriate destination type.
// This function returns whether the mutation function made any change in the original object.
func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn patchFn) bool {
func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn PatchFn) bool {
patch.Before, patch.Err = runtime.Encode(encoder, patch.Info.Object)
patch.After, patch.Err = mutateFn(patch.Info)
patch.After, patch.Err = mutateFn(patch.Info.Object)
if patch.Err != nil {
return true
}
@ -141,7 +141,7 @@ func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn patchFn) boo
// CalculatePatches calculates patches on each provided info object. If the provided mutateFn
// makes no change in an object, the object is not included in the final list of patches.
func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn patchFn) []*Patch {
func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn PatchFn) []*Patch {
var patches []*Patch
for _, info := range infos {
patch := &Patch{Info: info}

View File

@ -17,11 +17,10 @@ limitations under the License.
package set
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -32,22 +31,22 @@ var (
These commands help you make changes to existing application resources.`)
)
func NewCmdSet(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command {
func NewCmdSet(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "set SUBCOMMAND",
DisableFlagsInUseLine: true,
Short: i18n.T("Set specific features on objects"),
Long: set_long,
Run: cmdutil.DefaultSubCommandRun(err),
Run: cmdutil.DefaultSubCommandRun(streams.ErrOut),
}
// add subcommands
cmd.AddCommand(NewCmdImage(f, out, err))
cmd.AddCommand(NewCmdResources(f, out, err))
cmd.AddCommand(NewCmdSelector(f, out))
cmd.AddCommand(NewCmdSubject(f, out, err))
cmd.AddCommand(NewCmdServiceAccount(f, out, err))
cmd.AddCommand(NewCmdEnv(f, in, out, err))
cmd.AddCommand(NewCmdImage(f, streams))
cmd.AddCommand(NewCmdResources(f, streams))
cmd.AddCommand(NewCmdSelector(f, streams))
cmd.AddCommand(NewCmdSubject(f, streams))
cmd.AddCommand(NewCmdServiceAccount(f, streams))
cmd.AddCommand(NewCmdEnv(f, streams))
return cmd
}

View File

@ -19,22 +19,26 @@ package set
import (
"errors"
"fmt"
"io"
"regexp"
"sort"
"strings"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/kubernetes"
envutil "k8s.io/kubernetes/pkg/kubectl/cmd/set/env"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
envutil "k8s.io/kubernetes/pkg/kubectl/cmd/util/env"
"k8s.io/kubernetes/pkg/kubectl/resource"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
var (
@ -58,7 +62,7 @@ var (
` + envResources)
envExample = templates.Examples(`
# Update deployment 'registry' with a new environment variable
# Update deployment 'registry' with a new environment variable
kubectl set env deployment/registry STORAGE_DIR=/local
# List the environment variables defined on a deployments 'sample-build'
@ -79,6 +83,9 @@ var (
# Import environment from a config map with a prefix
kubectl set env --from=configmap/myconfigmap --prefix=MYSQL_ deployment/myapp
# Import specific keys from a config map
kubectl set env --keys=my-example-key --from=configmap/myconfigmap deployment/myapp
# Remove the environment variable ENV from container 'c1' in all deployment configs
kubectl set env deployments --all --containers="c1" ENV-
@ -91,45 +98,52 @@ var (
)
type EnvOptions struct {
Out io.Writer
Err io.Writer
In io.Reader
PrintFlags *genericclioptions.PrintFlags
resource.FilenameOptions
EnvParams []string
EnvArgs []string
Resources []string
All bool
Resolve bool
List bool
ShortOutput bool
Local bool
Overwrite bool
DryRun bool
ResourceVersion string
EnvParams []string
All bool
Resolve bool
List bool
Local bool
Overwrite bool
ContainerSelector string
Selector string
Output string
From string
Prefix string
Keys []string
Builder *resource.Builder
Infos []*resource.Info
PrintObj printers.ResourcePrinterFunc
Cmd *cobra.Command
envArgs []string
resources []string
output string
dryRun bool
builder func() *resource.Builder
updatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
namespace string
enforceNamespace bool
clientset *kubernetes.Clientset
UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error)
genericclioptions.IOStreams
}
// NewEnvOptions returns an EnvOptions indicating all containers in the selected
// pod templates are selected by default and allowing environment to be overwritten
func NewEnvOptions(streams genericclioptions.IOStreams) *EnvOptions {
return &EnvOptions{
PrintFlags: genericclioptions.NewPrintFlags("env updated").WithTypeSetter(scheme.Scheme),
ContainerSelector: "*",
Overwrite: true,
IOStreams: streams,
}
}
// NewCmdEnv implements the OpenShift cli env command
func NewCmdEnv(f cmdutil.Factory, in io.Reader, out, errout io.Writer) *cobra.Command {
options := &EnvOptions{
Out: out,
Err: errout,
In: in,
}
func NewCmdEnv(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewEnvOptions(streams)
cmd := &cobra.Command{
Use: "env RESOURCE/NAME KEY_1=VAL_1 ... KEY_N=VAL_N",
DisableFlagsInUseLine: true,
@ -137,26 +151,28 @@ func NewCmdEnv(f cmdutil.Factory, in io.Reader, out, errout io.Writer) *cobra.Co
Long: envLong,
Example: fmt.Sprintf(envExample),
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.RunEnv(f))
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunEnv())
},
}
usage := "the resource to update the env"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.Flags().StringVarP(&options.ContainerSelector, "containers", "c", "*", "The names of containers in the selected pod templates to change - may use wildcards")
cmd.Flags().StringP("from", "", "", "The name of a resource from which to inject environment variables")
cmd.Flags().StringP("prefix", "", "", "Prefix to append to variable names")
cmd.Flags().StringArrayVarP(&options.EnvParams, "env", "e", options.EnvParams, "Specify a key-value pair for an environment variable to set into each container.")
cmd.Flags().BoolVar(&options.List, "list", options.List, "If true, display the environment and any changes in the standard format. this flag will removed when we have kubectl view env.")
cmd.Flags().BoolVar(&options.Resolve, "resolve", options.Resolve, "If true, show secret or configmap references when listing variables")
cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on")
cmd.Flags().BoolVar(&options.Local, "local", options.Local, "If true, set env will NOT contact api-server but run locally.")
cmd.Flags().BoolVar(&options.All, "all", options.All, "If true, select all resources in the namespace of the specified resource types")
cmd.Flags().BoolVar(&options.Overwrite, "overwrite", true, "If true, allow environment to be overwritten, otherwise reject updates that overwrite existing environment.")
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmd.Flags().StringVarP(&o.ContainerSelector, "containers", "c", o.ContainerSelector, "The names of containers in the selected pod templates to change - may use wildcards")
cmd.Flags().StringVarP(&o.From, "from", "", "", "The name of a resource from which to inject environment variables")
cmd.Flags().StringVarP(&o.Prefix, "prefix", "", "", "Prefix to append to variable names")
cmd.Flags().StringArrayVarP(&o.EnvParams, "env", "e", o.EnvParams, "Specify a key-value pair for an environment variable to set into each container.")
cmd.Flags().StringSliceVarP(&o.Keys, "keys", "", o.Keys, "Comma-separated list of keys to import from specified resource")
cmd.Flags().BoolVar(&o.List, "list", o.List, "If true, display the environment and any changes in the standard format. this flag will removed when we have kubectl view env.")
cmd.Flags().BoolVar(&o.Resolve, "resolve", o.Resolve, "If true, show secret or configmap references when listing variables")
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on")
cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, set env will NOT contact api-server but run locally.")
cmd.Flags().BoolVar(&o.All, "all", o.All, "If true, select all resources in the namespace of the specified resource types")
cmd.Flags().BoolVar(&o.Overwrite, "overwrite", o.Overwrite, "If true, allow environment to be overwritten, otherwise reject updates that overwrite existing environment.")
o.PrintFlags.AddFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddPrinterFlags(cmd)
return cmd
}
@ -173,71 +189,86 @@ func keyToEnvName(key string) string {
return strings.ToUpper(validEnvNameRegexp.ReplaceAllString(key, "_"))
}
func contains(key string, keyList []string) bool {
if len(keyList) == 0 {
return true
}
for _, k := range keyList {
if k == key {
return true
}
}
return false
}
func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
if o.All && len(o.Selector) > 0 {
return fmt.Errorf("cannot set --all and --selector at the same time")
}
resources, envArgs, ok := envutil.SplitEnvironmentFromResources(args)
ok := false
o.resources, o.envArgs, ok = envutil.SplitEnvironmentFromResources(args)
if !ok {
return cmdutil.UsageErrorf(o.Cmd, "all resources must be specified before environment changes: %s", strings.Join(args, " "))
}
if len(o.Filenames) == 0 && len(resources) < 1 {
return cmdutil.UsageErrorf(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>")
return fmt.Errorf("all resources must be specified before environment changes: %s", strings.Join(args, " "))
}
o.UpdatePodSpecForObject = f.UpdatePodSpecForObject
o.ContainerSelector = cmdutil.GetFlagString(cmd, "containers")
o.List = cmdutil.GetFlagBool(cmd, "list")
o.Resolve = cmdutil.GetFlagBool(cmd, "resolve")
o.Selector = cmdutil.GetFlagString(cmd, "selector")
o.All = cmdutil.GetFlagBool(cmd, "all")
o.Overwrite = cmdutil.GetFlagBool(cmd, "overwrite")
o.Output = cmdutil.GetFlagString(cmd, "output")
o.From = cmdutil.GetFlagString(cmd, "from")
o.Prefix = cmdutil.GetFlagString(cmd, "prefix")
o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.updatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
o.output = cmdutil.GetFlagString(cmd, "output")
o.dryRun = cmdutil.GetDryRunFlag(cmd)
o.EnvArgs = envArgs
o.Resources = resources
o.Cmd = cmd
o.ShortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
if o.List && len(o.Output) > 0 {
return cmdutil.UsageErrorf(o.Cmd, "--list and --output may not be specified together")
if o.dryRun {
// TODO(juanvallejo): This can be cleaned up even further by creating
// a PrintFlags struct that binds the --dry-run flag, and whose
// ToPrinter method returns a printer that understands how to print
// this success message.
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = printer.PrintObj
o.clientset, err = f.KubernetesClientSet()
if err != nil {
return err
}
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
o.builder = f.NewBuilder
return nil
}
func (o *EnvOptions) Validate() error {
if len(o.Filenames) == 0 && len(o.resources) < 1 {
return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
}
if o.List && len(o.output) > 0 {
return fmt.Errorf("--list and --output may not be specified together")
}
if len(o.Keys) > 0 && len(o.From) == 0 {
return fmt.Errorf("when specifying --keys, a configmap or secret must be provided with --from")
}
return nil
}
// RunEnv contains all the necessary functionality for the OpenShift cli env command
func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
var kubeClient *kubernetes.Clientset
if o.List {
client, err := f.KubernetesClientSet()
if err != nil {
return err
}
kubeClient = client
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
env, remove, err := envutil.ParseEnv(append(o.EnvParams, o.EnvArgs...), o.In)
func (o *EnvOptions) RunEnv() error {
env, remove, err := envutil.ParseEnv(append(o.EnvParams, o.envArgs...), o.In)
if err != nil {
return err
}
if len(o.From) != 0 {
b := f.NewBuilder().
Internal().
b := o.builder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(o.enforceNamespace, &o.FilenameOptions).
Flatten()
if !o.Local {
@ -253,40 +284,40 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
}
for _, info := range infos {
versionedObject, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion())
if err != nil {
return err
}
switch from := versionedObject.(type) {
switch from := info.Object.(type) {
case *v1.Secret:
for key := range from.Data {
envVar := v1.EnvVar{
Name: keyToEnvName(key),
ValueFrom: &v1.EnvVarSource{
SecretKeyRef: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: from.Name,
if contains(key, o.Keys) {
envVar := v1.EnvVar{
Name: keyToEnvName(key),
ValueFrom: &v1.EnvVarSource{
SecretKeyRef: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: from.Name,
},
Key: key,
},
Key: key,
},
},
}
env = append(env, envVar)
}
env = append(env, envVar)
}
case *v1.ConfigMap:
for key := range from.Data {
envVar := v1.EnvVar{
Name: keyToEnvName(key),
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: from.Name,
if contains(key, o.Keys) {
envVar := v1.EnvVar{
Name: keyToEnvName(key),
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: from.Name,
},
Key: key,
},
Key: key,
},
},
}
env = append(env, envVar)
}
env = append(env, envVar)
}
default:
return fmt.Errorf("unsupported resource specified in --from")
@ -300,31 +331,71 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
}
}
b := f.NewBuilder().
Internal().
b := o.builder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(o.enforceNamespace, &o.FilenameOptions).
Flatten()
if !o.Local {
b.LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.Resources...).
ResourceTypeOrNameArgs(o.All, o.resources...).
Latest()
}
o.Infos, err = b.Do().Infos()
infos, err := b.Do().Infos()
if err != nil {
return err
}
patches := CalculatePatches(o.Infos, cmdutil.InternalVersionJSONEncoder(), func(info *resource.Info) ([]byte, error) {
info.Object = info.AsVersioned()
_, err := o.UpdatePodSpecForObject(info.Object, func(spec *v1.PodSpec) error {
patches := CalculatePatches(infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
_, err := o.updatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
resolutionErrorsEncountered := false
containers, _ := selectContainers(spec.Containers, o.ContainerSelector)
objName, err := meta.NewAccessor().Name(obj)
if err != nil {
return err
}
gvks, _, err := scheme.Scheme.ObjectKinds(obj)
if err != nil {
return err
}
objKind := obj.GetObjectKind().GroupVersionKind().Kind
if len(objKind) == 0 {
for _, gvk := range gvks {
if len(gvk.Kind) == 0 {
continue
}
if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
continue
}
objKind = gvk.Kind
break
}
}
if len(containers) == 0 {
fmt.Fprintf(o.Err, "warning: %s/%s does not have any containers matching %q\n", info.Mapping.Resource, info.Name, o.ContainerSelector)
if gvks, _, err := scheme.Scheme.ObjectKinds(obj); err == nil {
objKind := obj.GetObjectKind().GroupVersionKind().Kind
if len(objKind) == 0 {
for _, gvk := range gvks {
if len(gvk.Kind) == 0 {
continue
}
if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
continue
}
objKind = gvk.Kind
break
}
}
fmt.Fprintf(o.ErrOut, "warning: %s/%s does not have any containers matching %q\n", objKind, objName, o.ContainerSelector)
}
return nil
}
for _, c := range containers {
@ -339,7 +410,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
resolveErrors := map[string][]string{}
store := envutil.NewResourceStore()
fmt.Fprintf(o.Out, "# %s %s, container %s\n", info.Mapping.Resource, info.Name, c.Name)
fmt.Fprintf(o.Out, "# %s %s, container %s\n", objKind, objName, c.Name)
for _, env := range c.Env {
// Print the simple value
if env.ValueFrom == nil {
@ -353,7 +424,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
continue
}
value, err := envutil.GetEnvVarRefValue(kubeClient, cmdNamespace, store, env.ValueFrom, info.Object, c)
value, err := envutil.GetEnvVarRefValue(o.clientset, o.namespace, store, env.ValueFrom, obj, c)
// Print the resolved value
if err == nil {
fmt.Fprintf(o.Out, "%s=%s\n", env.Name, value)
@ -375,7 +446,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
}
sort.Strings(errs)
for _, err := range errs {
fmt.Fprintln(o.Err, err)
fmt.Fprintln(o.ErrOut, err)
}
}
}
@ -386,7 +457,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
})
if err == nil {
return runtime.Encode(cmdutil.InternalVersionJSONEncoder(), info.Object)
return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
}
return nil, err
})
@ -409,34 +480,28 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
continue
}
if o.Local || o.DryRun {
if err := cmdutil.PrintObject(o.Cmd, patch.Info.AsVersioned(), o.Out); err != nil {
return err
if o.Local || o.dryRun {
if err := o.PrintObj(info.Object, o.Out); err != nil {
allErrs = append(allErrs, err)
}
continue
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch env update to pod template: %v\n", err))
continue
}
info.Refresh(obj, true)
// make sure arguments to set or replace environment variables are set
// before returning a successful message
if len(env) == 0 && len(o.EnvArgs) == 0 {
if len(env) == 0 && len(o.envArgs) == 0 {
return fmt.Errorf("at least one environment variable must be provided")
}
if len(o.Output) > 0 {
if err := cmdutil.PrintObject(o.Cmd, info.AsVersioned(), o.Out); err != nil {
return err
}
continue
if err := o.PrintObj(actual, o.Out); err != nil {
allErrs = append(allErrs, err)
}
cmdutil.PrintSuccess(o.ShortOutput, o.Out, info.Object, false, "env updated")
}
return utilerrors.NewAggregate(allErrs)
}

View File

@ -17,11 +17,9 @@ limitations under the License.
package set
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"strings"
"testing"
@ -36,45 +34,47 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestSetEnvLocal(t *testing.T) {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: ""},
NegotiatedSerializer: ns,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
outputFormat := "name"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdEnv(tf, os.Stdin, buf, buf)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("local", "true")
opts := EnvOptions{FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}},
Out: buf,
Local: true}
err := opts.Complete(tf, cmd, []string{"env=prod"})
if err == nil {
err = opts.RunEnv(tf)
streams, _, buf, bufErr := genericclioptions.NewTestIOStreams()
opts := NewEnvOptions(streams)
opts.PrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme)
opts.FilenameOptions = resource.FilenameOptions{
Filenames: []string{"../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"},
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
opts.Local = true
err := opts.Complete(tf, NewCmdEnv(tf, streams), []string{"env=prod"})
assert.NoError(t, err)
err = opts.Validate()
assert.NoError(t, err)
err = opts.RunEnv()
assert.NoError(t, err)
if bufErr.Len() > 0 {
t.Errorf("unexpected error: %s", string(bufErr.String()))
}
if !strings.Contains(buf.String(), "replicationcontroller/cassandra") {
t.Errorf("did not set env: %s", buf.String())
@ -82,38 +82,37 @@ func TestSetEnvLocal(t *testing.T) {
}
func TestSetMultiResourcesEnvLocal(t *testing.T) {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: ""},
NegotiatedSerializer: ns,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdEnv(tf, os.Stdin, buf, buf)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("local", "true")
opts := EnvOptions{FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}},
Out: buf,
Local: true}
err := opts.Complete(tf, cmd, []string{"env=prod"})
if err == nil {
err = opts.RunEnv(tf)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
outputFormat := "name"
streams, _, buf, bufErr := genericclioptions.NewTestIOStreams()
opts := NewEnvOptions(streams)
opts.PrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme)
opts.FilenameOptions = resource.FilenameOptions{
Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"},
}
opts.Local = true
err := opts.Complete(tf, NewCmdEnv(tf, streams), []string{"env=prod"})
assert.NoError(t, err)
err = opts.Validate()
assert.NoError(t, err)
err = opts.RunEnv()
assert.NoError(t, err)
if bufErr.Len() > 0 {
t.Errorf("unexpected error: %s", string(bufErr.String()))
}
expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n"
if buf.String() != expectedOut {
t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String())
@ -122,12 +121,14 @@ func TestSetMultiResourcesEnvLocal(t *testing.T) {
func TestSetEnvRemote(t *testing.T) {
inputs := []struct {
name string
object runtime.Object
apiPrefix, apiGroup, apiVersion string
testAPIGroup string
args []string
}{
{
name: "test extensions.v1beta1 replicaset",
object: &extensionsv1beta1.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: extensionsv1beta1.ReplicaSetSpec{
@ -148,6 +149,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"replicaset", "nginx", "env=prod"},
},
{
name: "test apps.v1beta2 replicaset",
object: &appsv1beta2.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta2.ReplicaSetSpec{
@ -168,6 +170,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"replicaset", "nginx", "env=prod"},
},
{
name: "test appsv1 replicaset",
object: &appsv1.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1.ReplicaSetSpec{
@ -188,6 +191,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"replicaset", "nginx", "env=prod"},
},
{
name: "test extensions.v1beta1 daemonset",
object: &extensionsv1beta1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: extensionsv1beta1.DaemonSetSpec{
@ -208,6 +212,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"daemonset", "nginx", "env=prod"},
},
{
name: "test appsv1beta2 daemonset",
object: &appsv1beta2.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta2.DaemonSetSpec{
@ -228,6 +233,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"daemonset", "nginx", "env=prod"},
},
{
name: "test appsv1 daemonset",
object: &appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1.DaemonSetSpec{
@ -248,6 +254,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"daemonset", "nginx", "env=prod"},
},
{
name: "test extensions.v1beta1 deployment",
object: &extensionsv1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: extensionsv1beta1.DeploymentSpec{
@ -268,6 +275,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"deployment", "nginx", "env=prod"},
},
{
name: "test appsv1bta1 deployment",
object: &appsv1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta1.DeploymentSpec{
@ -288,6 +296,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"deployment", "nginx", "env=prod"},
},
{
name: "test appsv1beta2n deployment",
object: &appsv1beta2.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta2.DeploymentSpec{
@ -308,6 +317,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"deployment", "nginx", "env=prod"},
},
{
name: "test appsv1 deployment",
object: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1.DeploymentSpec{
@ -328,6 +338,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"deployment", "nginx", "env=prod"},
},
{
name: "test appsv1beta1 statefulset",
object: &appsv1beta1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta1.StatefulSetSpec{
@ -348,6 +359,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"statefulset", "nginx", "env=prod"},
},
{
name: "test appsv1beta2 statefulset",
object: &appsv1beta2.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta2.StatefulSetSpec{
@ -368,6 +380,7 @@ func TestSetEnvRemote(t *testing.T) {
args: []string{"statefulset", "nginx", "env=prod"},
},
{
name: "test appsv1 statefulset",
object: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1.StatefulSetSpec{
@ -429,48 +442,196 @@ func TestSetEnvRemote(t *testing.T) {
},
}
for _, input := range inputs {
groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion}
testapi.Default = testapi.Groups[input.testAPIGroup]
tf := cmdtesting.NewTestFactory()
codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion)
ns := legacyscheme.Codecs
tf.Namespace = "test"
tf.Client = &fake.RESTClient{
GroupVersion: groupVersion,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1])
switch p, m := req.URL.Path, req.Method; {
case p == resourcePath && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil
case p == resourcePath && m == http.MethodPatch:
stream, err := req.GetBody()
if err != nil {
return nil, err
t.Run(input.name, func(t *testing.T) {
groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion}
testapi.Default = testapi.Groups[input.testAPIGroup]
tf := cmdtesting.NewTestFactory().WithNamespace("test")
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: groupVersion,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", "test", input.args[1])
switch p, m := req.URL.Path, req.Method; {
case p == resourcePath && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(input.object)}, nil
case p == resourcePath && m == http.MethodPatch:
stream, err := req.GetBody()
if err != nil {
return nil, err
}
bytes, err := ioutil.ReadAll(stream)
if err != nil {
return nil, err
}
assert.Contains(t, string(bytes), `"value":`+`"`+"prod"+`"`, fmt.Sprintf("env not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(input.object)}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
bytes, err := ioutil.ReadAll(stream)
if err != nil {
return nil, err
}
assert.Contains(t, string(bytes), `"value":`+`"`+"prod"+`"`, fmt.Sprintf("env not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()),
}
out := new(bytes.Buffer)
cmd := NewCmdEnv(tf, out, out, out)
cmd.SetOutput(out)
cmd.Flags().Set("output", "yaml")
opts := EnvOptions{
Out: out,
Local: false}
err := opts.Complete(tf, cmd, input.args)
assert.NoError(t, err)
err = opts.RunEnv(tf)
assert.NoError(t, err)
}),
VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()),
}
outputFormat := "yaml"
streams := genericclioptions.NewTestIOStreamsDiscard()
opts := NewEnvOptions(streams)
opts.PrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme)
opts.Local = false
opts.IOStreams = streams
err := opts.Complete(tf, NewCmdEnv(tf, streams), input.args)
assert.NoError(t, err)
err = opts.RunEnv()
assert.NoError(t, err)
})
}
}
func TestSetEnvFromResource(t *testing.T) {
mockConfigMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "testconfigmap"},
Data: map[string]string{
"env": "prod",
"test-key": "testValue",
"test-key-two": "testValueTwo",
},
}
mockSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "testsecret"},
Data: map[string][]byte{
"env": []byte("prod"),
"test-key": []byte("testValue"),
"test-key-two": []byte("testValueTwo"),
},
}
inputs := []struct {
name string
args []string
from string
keys []string
assertIncludes []string
assertExcludes []string
}{
{
name: "test from configmap",
args: []string{"deployment", "nginx"},
from: "configmap/testconfigmap",
keys: []string{},
assertIncludes: []string{
`{"name":"ENV","valueFrom":{"configMapKeyRef":{"key":"env","name":"testconfigmap"}}}`,
`{"name":"TEST_KEY","valueFrom":{"configMapKeyRef":{"key":"test-key","name":"testconfigmap"}}}`,
`{"name":"TEST_KEY_TWO","valueFrom":{"configMapKeyRef":{"key":"test-key-two","name":"testconfigmap"}}}`,
},
assertExcludes: []string{},
},
{
name: "test from secret",
args: []string{"deployment", "nginx"},
from: "secret/testsecret",
keys: []string{},
assertIncludes: []string{
`{"name":"ENV","valueFrom":{"secretKeyRef":{"key":"env","name":"testsecret"}}}`,
`{"name":"TEST_KEY","valueFrom":{"secretKeyRef":{"key":"test-key","name":"testsecret"}}}`,
`{"name":"TEST_KEY_TWO","valueFrom":{"secretKeyRef":{"key":"test-key-two","name":"testsecret"}}}`,
},
assertExcludes: []string{},
},
{
name: "test from configmap with keys",
args: []string{"deployment", "nginx"},
from: "configmap/testconfigmap",
keys: []string{"env", "test-key-two"},
assertIncludes: []string{
`{"name":"ENV","valueFrom":{"configMapKeyRef":{"key":"env","name":"testconfigmap"}}}`,
`{"name":"TEST_KEY_TWO","valueFrom":{"configMapKeyRef":{"key":"test-key-two","name":"testconfigmap"}}}`,
},
assertExcludes: []string{`{"name":"TEST_KEY","valueFrom":{"configMapKeyRef":{"key":"test-key","name":"testconfigmap"}}}`},
},
{
name: "test from secret with keys",
args: []string{"deployment", "nginx"},
from: "secret/testsecret",
keys: []string{"env", "test-key-two"},
assertIncludes: []string{
`{"name":"ENV","valueFrom":{"secretKeyRef":{"key":"env","name":"testsecret"}}}`,
`{"name":"TEST_KEY_TWO","valueFrom":{"secretKeyRef":{"key":"test-key-two","name":"testsecret"}}}`,
},
assertExcludes: []string{`{"name":"TEST_KEY","valueFrom":{"secretKeyRef":{"key":"test-key","name":"testsecret"}}}`},
},
}
for _, input := range inputs {
mockDeployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1.DeploymentSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "nginx",
Image: "nginx",
},
},
},
},
},
}
t.Run(input.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/configmaps/testconfigmap" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(mockConfigMap)}, nil
case p == "/namespaces/test/secrets/testsecret" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(mockSecret)}, nil
case p == "/namespaces/test/deployments/nginx" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(mockDeployment)}, nil
case p == "/namespaces/test/deployments/nginx" && m == http.MethodPatch:
stream, err := req.GetBody()
if err != nil {
return nil, err
}
bytes, err := ioutil.ReadAll(stream)
if err != nil {
return nil, err
}
for _, include := range input.assertIncludes {
assert.Contains(t, string(bytes), include)
}
for _, exclude := range input.assertExcludes {
assert.NotContains(t, string(bytes), exclude)
}
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(mockDeployment)}, nil
default:
t.Errorf("%s: unexpected request: %#v\n%#v", input.name, req.URL, req)
return nil, nil
}
}),
}
outputFormat := "yaml"
streams := genericclioptions.NewTestIOStreamsDiscard()
opts := NewEnvOptions(streams)
opts.From = input.from
opts.Keys = input.keys
opts.PrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme)
opts.Local = false
opts.IOStreams = streams
err := opts.Complete(tf, NewCmdEnv(tf, streams), input.args)
assert.NoError(t, err)
err = opts.RunEnv()
assert.NoError(t, err)
})
}
}

View File

@ -18,41 +18,48 @@ package set
import (
"fmt"
"io"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
// ImageOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags()
type ImageOptions struct {
type SetImageOptions struct {
resource.FilenameOptions
PrintFlags *genericclioptions.PrintFlags
RecordFlags *genericclioptions.RecordFlags
Infos []*resource.Info
Selector string
Out io.Writer
Err io.Writer
DryRun bool
ShortOutput bool
All bool
Record bool
Output string
ChangeCause string
Local bool
Cmd *cobra.Command
ResolveImage func(in string) (string, error)
ResolveImage ImageResolver
UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error)
PrintObj printers.ResourcePrinterFunc
Recorder genericclioptions.Recorder
UpdatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
Resources []string
ContainerImages map[string]string
genericclioptions.IOStreams
}
var (
@ -79,11 +86,19 @@ var (
kubectl set image -f path/to/file.yaml nginx=nginx:1.9.1 --local -o yaml`)
)
func NewCmdImage(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
options := &ImageOptions{
Out: out,
Err: err,
func NewImageOptions(streams genericclioptions.IOStreams) *SetImageOptions {
return &SetImageOptions{
PrintFlags: genericclioptions.NewPrintFlags("image updated").WithTypeSetter(scheme.Scheme),
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
IOStreams: streams,
}
}
func NewCmdImage(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewImageOptions(streams)
cmd := &cobra.Command{
Use: "image (-f FILENAME | TYPE NAME) CONTAINER_NAME_1=CONTAINER_IMAGE_1 ... CONTAINER_NAME_N=CONTAINER_IMAGE_N",
@ -92,35 +107,50 @@ func NewCmdImage(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
Long: image_long,
Example: image_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.Run())
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
cmdutil.AddPrinterFlags(cmd)
o.PrintFlags.AddFlags(cmd)
o.RecordFlags.AddFlags(cmd)
usage := "identifying the resource to get from a server."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.Flags().BoolVar(&options.All, "all", options.All, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&options.Local, "local", options.Local, "If true, set image will NOT contact api-server but run locally.")
cmdutil.AddRecordFlag(cmd)
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, set image will NOT contact api-server but run locally.")
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd
}
func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.UpdatePodSpecForObject = f.UpdatePodSpecForObject
o.ShortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
o.Record = cmdutil.GetRecordFlag(cmd)
o.ChangeCause = f.Command(cmd, false)
func (o *SetImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.RecordFlags.Complete(cmd)
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
o.UpdatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.Output = cmdutil.GetFlagString(cmd, "output")
o.ResolveImage = f.ResolveImage
o.Cmd = cmd
o.ResolveImage = resolveImageFunc
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = printer.PrintObj
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
@ -132,7 +162,7 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder().
Internal().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
@ -161,7 +191,7 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
return nil
}
func (o *ImageOptions) Validate() error {
func (o *SetImageOptions) Validate() error {
errors := []error{}
if o.All && len(o.Selector) > 0 {
errors = append(errors, fmt.Errorf("cannot set --all and --selector at the same time"))
@ -177,13 +207,12 @@ func (o *ImageOptions) Validate() error {
return utilerrors.NewAggregate(errors)
}
func (o *ImageOptions) Run() error {
func (o *SetImageOptions) Run() error {
allErrs := []error{}
patches := CalculatePatches(o.Infos, cmdutil.InternalVersionJSONEncoder(), func(info *resource.Info) ([]byte, error) {
patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
transformed := false
info.Object = info.AsVersioned()
_, err := o.UpdatePodSpecForObject(info.Object, func(spec *v1.PodSpec) error {
_, err := o.UpdatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
for name, image := range o.ContainerImages {
var (
containerFound bool
@ -219,10 +248,18 @@ func (o *ImageOptions) Run() error {
}
return nil
})
if transformed && err == nil {
return runtime.Encode(cmdutil.InternalVersionJSONEncoder(), info.Object)
if err != nil {
return nil, err
}
return nil, err
if !transformed {
return nil, nil
}
// record this change (for rollout history)
if err := o.Recorder.Record(obj); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
})
for _, patch := range patches {
@ -238,38 +275,22 @@ func (o *ImageOptions) Run() error {
}
if o.Local || o.DryRun {
if err := cmdutil.PrintObject(o.Cmd, patch.Info.AsVersioned(), o.Out); err != nil {
return err
if err := o.PrintObj(info.Object, o.Out); err != nil {
allErrs = append(allErrs, err)
}
continue
}
// patch the change
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch image update to pod template: %v\n", err))
continue
}
info.Refresh(obj, true)
// record this change (for rollout history)
if o.Record || cmdutil.ContainsChangeCause(info) {
if patch, patchType, err := cmdutil.ChangeResourcePatch(info, o.ChangeCause); err == nil {
if obj, err = resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, patchType, patch); err != nil {
fmt.Fprintf(o.Err, "WARNING: changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err)
}
}
if err := o.PrintObj(actual, o.Out); err != nil {
allErrs = append(allErrs, err)
}
info.Refresh(obj, true)
if len(o.Output) > 0 {
if err := cmdutil.PrintObject(o.Cmd, info.AsVersioned(), o.Out); err != nil {
return err
}
continue
}
cmdutil.PrintSuccess(o.ShortOutput, o.Out, info.Object, o.DryRun, "image updated")
}
return utilerrors.NewAggregate(allErrs)
}
@ -289,3 +310,13 @@ func hasWildcardKey(containerImages map[string]string) bool {
_, ok := containerImages["*"]
return ok
}
// ImageResolver is a func that receives an image name, and
// resolves it to an appropriate / compatible image name.
// Adds flexibility for future image resolving methods.
type ImageResolver func(in string) (string, error)
// implements ImageResolver
func resolveImageFunc(in string) (string, error) {
return in, nil
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package set
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
@ -26,6 +25,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
@ -35,40 +35,45 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestImageLocal(t *testing.T) {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: ""},
NegotiatedSerializer: ns,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdImage(tf, buf, buf)
outputFormat := "name"
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdImage(tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("output", outputFormat)
cmd.Flags().Set("local", "true")
opts := ImageOptions{FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}},
Out: buf,
Local: true}
opts := SetImageOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}},
Local: true,
IOStreams: streams,
}
err := opts.Complete(tf, cmd, []string{"cassandra=thingy"})
if err == nil {
err = opts.Validate()
@ -85,20 +90,23 @@ func TestImageLocal(t *testing.T) {
}
func TestSetImageValidation(t *testing.T) {
printFlags := genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme)
testCases := []struct {
name string
imageOptions *ImageOptions
imageOptions *SetImageOptions
expectErr string
}{
{
name: "test resource < 1 and filenames empty",
imageOptions: &ImageOptions{},
imageOptions: &SetImageOptions{PrintFlags: printFlags},
expectErr: "[one or more resources must be specified as <resource> <name> or <resource>/<name>, at least one image update is required]",
},
{
name: "test containerImages < 1",
imageOptions: &ImageOptions{
Resources: []string{"a", "b", "c"},
imageOptions: &SetImageOptions{
PrintFlags: printFlags,
Resources: []string{"a", "b", "c"},
FilenameOptions: resource.FilenameOptions{
Filenames: []string{"testFile"},
@ -108,8 +116,9 @@ func TestSetImageValidation(t *testing.T) {
},
{
name: "test containerImages > 1 and all containers are already specified by *",
imageOptions: &ImageOptions{
Resources: []string{"a", "b", "c"},
imageOptions: &SetImageOptions{
PrintFlags: printFlags,
Resources: []string{"a", "b", "c"},
FilenameOptions: resource.FilenameOptions{
Filenames: []string{"testFile"},
},
@ -122,8 +131,9 @@ func TestSetImageValidation(t *testing.T) {
},
{
name: "success case",
imageOptions: &ImageOptions{
Resources: []string{"a", "b", "c"},
imageOptions: &SetImageOptions{
PrintFlags: printFlags,
Resources: []string{"a", "b", "c"},
FilenameOptions: resource.FilenameOptions{
Filenames: []string{"testFile"},
},
@ -148,30 +158,34 @@ func TestSetImageValidation(t *testing.T) {
}
func TestSetMultiResourcesImageLocal(t *testing.T) {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: ""},
NegotiatedSerializer: ns,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdImage(tf, buf, buf)
outputFormat := "name"
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdImage(tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("output", outputFormat)
cmd.Flags().Set("local", "true")
opts := ImageOptions{FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}},
Out: buf,
Local: true}
opts := SetImageOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}},
Local: true,
IOStreams: streams,
}
err := opts.Complete(tf, cmd, []string{"*=thingy"})
if err == nil {
err = opts.Validate()
@ -190,12 +204,14 @@ func TestSetMultiResourcesImageLocal(t *testing.T) {
func TestSetImageRemote(t *testing.T) {
inputs := []struct {
name string
object runtime.Object
apiPrefix, apiGroup, apiVersion string
testAPIGroup string
args []string
}{
{
name: "set image extensionsv1beta1 ReplicaSet",
object: &extensionsv1beta1.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: extensionsv1beta1.ReplicaSetSpec{
@ -216,6 +232,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"replicaset", "nginx", "*=thingy"},
},
{
name: "set image appsv1beta2 ReplicaSet",
object: &appsv1beta2.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta2.ReplicaSetSpec{
@ -236,6 +253,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"replicaset", "nginx", "*=thingy"},
},
{
name: "set image appsv1 ReplicaSet",
object: &appsv1.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1.ReplicaSetSpec{
@ -256,6 +274,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"replicaset", "nginx", "*=thingy"},
},
{
name: "set image extensionsv1beta1 DaemonSet",
object: &extensionsv1beta1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: extensionsv1beta1.DaemonSetSpec{
@ -276,6 +295,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"daemonset", "nginx", "*=thingy"},
},
{
name: "set image appsv1beta2 DaemonSet",
object: &appsv1beta2.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta2.DaemonSetSpec{
@ -296,6 +316,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"daemonset", "nginx", "*=thingy"},
},
{
name: "set image appsv1 DaemonSet",
object: &appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1.DaemonSetSpec{
@ -316,6 +337,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"daemonset", "nginx", "*=thingy"},
},
{
name: "set image extensionsv1beta1 Deployment",
object: &extensionsv1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: extensionsv1beta1.DeploymentSpec{
@ -336,6 +358,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"deployment", "nginx", "*=thingy"},
},
{
name: "set image appsv1beta1 Deployment",
object: &appsv1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta1.DeploymentSpec{
@ -356,6 +379,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"deployment", "nginx", "*=thingy"},
},
{
name: "set image appsv1beta2 Deployment",
object: &appsv1beta2.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta2.DeploymentSpec{
@ -376,6 +400,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"deployment", "nginx", "*=thingy"},
},
{
name: "set image appsv1 Deployment",
object: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1.DeploymentSpec{
@ -396,6 +421,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"deployment", "nginx", "*=thingy"},
},
{
name: "set image appsv1beta1 StatefulSet",
object: &appsv1beta1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta1.StatefulSetSpec{
@ -416,6 +442,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"statefulset", "nginx", "*=thingy"},
},
{
name: "set image appsv1beta2 StatefulSet",
object: &appsv1beta2.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1beta2.StatefulSetSpec{
@ -436,6 +463,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"statefulset", "nginx", "*=thingy"},
},
{
name: "set image appsv1 StatefulSet",
object: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: appsv1.StatefulSetSpec{
@ -456,6 +484,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"statefulset", "nginx", "*=thingy"},
},
{
name: "set image batchv1 Job",
object: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: batchv1.JobSpec{
@ -476,6 +505,7 @@ func TestSetImageRemote(t *testing.T) {
args: []string{"job", "nginx", "*=thingy"},
},
{
name: "set image v1.ReplicationController",
object: &v1.ReplicationController{
ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
Spec: v1.ReplicationControllerSpec{
@ -497,48 +527,54 @@ func TestSetImageRemote(t *testing.T) {
},
}
for _, input := range inputs {
groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion}
testapi.Default = testapi.Groups[input.testAPIGroup]
tf := cmdtesting.NewTestFactory()
codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion)
ns := legacyscheme.Codecs
tf.Namespace = "test"
tf.Client = &fake.RESTClient{
GroupVersion: groupVersion,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1])
switch p, m := req.URL.Path, req.Method; {
case p == resourcePath && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil
case p == resourcePath && m == http.MethodPatch:
stream, err := req.GetBody()
if err != nil {
return nil, err
t.Run(input.name, func(t *testing.T) {
groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion}
testapi.Default = testapi.Groups[input.testAPIGroup]
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: groupVersion,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", "test", input.args[1])
switch p, m := req.URL.Path, req.Method; {
case p == resourcePath && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(input.object)}, nil
case p == resourcePath && m == http.MethodPatch:
stream, err := req.GetBody()
if err != nil {
return nil, err
}
bytes, err := ioutil.ReadAll(stream)
if err != nil {
return nil, err
}
assert.Contains(t, string(bytes), `"image":`+`"`+"thingy"+`"`, fmt.Sprintf("image not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(input.object)}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
bytes, err := ioutil.ReadAll(stream)
if err != nil {
return nil, err
}
assert.Contains(t, string(bytes), `"image":`+`"`+"thingy"+`"`, fmt.Sprintf("image not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()),
}
out := new(bytes.Buffer)
cmd := NewCmdImage(tf, out, out)
cmd.SetOutput(out)
cmd.Flags().Set("output", "yaml")
opts := ImageOptions{
Out: out,
Local: false}
err := opts.Complete(tf, cmd, input.args)
assert.NoError(t, err)
err = opts.Run()
assert.NoError(t, err)
}),
VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()),
}
outputFormat := "yaml"
streams := genericclioptions.NewTestIOStreamsDiscard()
cmd := NewCmdImage(tf, streams)
cmd.Flags().Set("output", outputFormat)
opts := SetImageOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
Local: false,
IOStreams: streams,
}
err := opts.Complete(tf, cmd, input.args)
assert.NoError(t, err)
err = opts.Run()
assert.NoError(t, err)
})
}
}

View File

@ -18,19 +18,22 @@ package set
import (
"fmt"
"io"
"strings"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -58,85 +61,113 @@ var (
// ResourcesOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags
type ResourcesOptions struct {
type SetResourcesOptions struct {
resource.FilenameOptions
PrintFlags *genericclioptions.PrintFlags
RecordFlags *genericclioptions.RecordFlags
Infos []*resource.Info
Out io.Writer
Err io.Writer
Selector string
ContainerSelector string
Output string
All bool
Record bool
ChangeCause string
Local bool
Cmd *cobra.Command
DryRun bool
PrintObj printers.ResourcePrinterFunc
Recorder genericclioptions.Recorder
Limits string
Requests string
ResourceRequirements v1.ResourceRequirements
UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error)
UpdatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
Resources []string
genericclioptions.IOStreams
}
func NewCmdResources(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command {
options := &ResourcesOptions{
Out: out,
Err: errOut,
}
// NewResourcesOptions returns a ResourcesOptions indicating all containers in the selected
// pod templates are selected by default.
func NewResourcesOptions(streams genericclioptions.IOStreams) *SetResourcesOptions {
return &SetResourcesOptions{
PrintFlags: genericclioptions.NewPrintFlags("resource requirements updated").WithTypeSetter(scheme.Scheme),
RecordFlags: genericclioptions.NewRecordFlags(),
resourceTypesWithPodTemplate := []string{}
for _, resource := range f.SuggestedPodTemplateResources() {
resourceTypesWithPodTemplate = append(resourceTypesWithPodTemplate, resource.Resource)
Recorder: genericclioptions.NoopRecorder{},
ContainerSelector: "*",
IOStreams: streams,
}
}
func NewCmdResources(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewResourcesOptions(streams)
cmd := &cobra.Command{
Use: "resources (-f FILENAME | TYPE NAME) ([--limits=LIMITS & --requests=REQUESTS]",
DisableFlagsInUseLine: true,
Short: i18n.T("Update resource requests/limits on objects with pod templates"),
Long: fmt.Sprintf(resources_long, strings.Join(resourceTypesWithPodTemplate, ", ")),
Long: fmt.Sprintf(resources_long, cmdutil.SuggestApiResources("kubectl")),
Example: resources_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.Run())
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
cmdutil.AddPrinterFlags(cmd)
o.PrintFlags.AddFlags(cmd)
o.RecordFlags.AddFlags(cmd)
//usage := "Filename, directory, or URL to a file identifying the resource to get from the server"
//kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
usage := "identifying the resource to get from a server."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.Flags().BoolVar(&options.All, "all", options.All, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, not including uninitialized ones,supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().StringVarP(&options.ContainerSelector, "containers", "c", "*", "The names of containers in the selected pod templates to change, all containers are selected by default - may use wildcards")
cmd.Flags().BoolVar(&options.Local, "local", options.Local, "If true, set resources will NOT contact api-server but run locally.")
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, not including uninitialized ones,supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().StringVarP(&o.ContainerSelector, "containers", "c", o.ContainerSelector, "The names of containers in the selected pod templates to change, all containers are selected by default - may use wildcards")
cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, set resources will NOT contact api-server but run locally.")
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
cmd.Flags().StringVar(&options.Limits, "limits", options.Limits, "The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'. Note that server side components may assign requests depending on the server configuration, such as limit ranges.")
cmd.Flags().StringVar(&options.Requests, "requests", options.Requests, "The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'. Note that server side components may assign requests depending on the server configuration, such as limit ranges.")
cmd.Flags().StringVar(&o.Limits, "limits", o.Limits, "The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'. Note that server side components may assign requests depending on the server configuration, such as limit ranges.")
cmd.Flags().StringVar(&o.Requests, "requests", o.Requests, "The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'. Note that server side components may assign requests depending on the server configuration, such as limit ranges.")
return cmd
}
func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.UpdatePodSpecForObject = f.UpdatePodSpecForObject
o.Output = cmdutil.GetFlagString(cmd, "output")
o.Record = cmdutil.GetRecordFlag(cmd)
o.ChangeCause = f.Command(cmd, false)
o.Cmd = cmd
func (o *SetResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
o.RecordFlags.Complete(cmd)
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
o.UpdatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
o.Output = cmdutil.GetFlagString(cmd, "output")
o.DryRun = cmdutil.GetDryRunFlag(cmd)
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = printer.PrintObj
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder().
Internal().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
@ -166,7 +197,7 @@ func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
return nil
}
func (o *ResourcesOptions) Validate() error {
func (o *SetResourcesOptions) Validate() error {
var err error
if o.All && len(o.Selector) > 0 {
return fmt.Errorf("cannot set --all and --selector at the same time")
@ -183,12 +214,11 @@ func (o *ResourcesOptions) Validate() error {
return nil
}
func (o *ResourcesOptions) Run() error {
func (o *SetResourcesOptions) Run() error {
allErrs := []error{}
patches := CalculatePatches(o.Infos, cmdutil.InternalVersionJSONEncoder(), func(info *resource.Info) ([]byte, error) {
patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
transformed := false
info.Object = info.AsVersioned()
_, err := o.UpdatePodSpecForObject(info.Object, func(spec *v1.PodSpec) error {
_, err := o.UpdatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
containers, _ := selectContainers(spec.Containers, o.ContainerSelector)
if len(containers) != 0 {
for i := range containers {
@ -212,10 +242,18 @@ func (o *ResourcesOptions) Run() error {
}
return nil
})
if transformed && err == nil {
return runtime.Encode(cmdutil.InternalVersionJSONEncoder(), info.Object)
if err != nil {
return nil, err
}
return nil, err
if !transformed {
return nil, nil
}
// record this change (for rollout history)
if err := o.Recorder.Record(obj); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
})
for _, patch := range patches {
@ -231,38 +269,22 @@ func (o *ResourcesOptions) Run() error {
continue
}
if o.Local || cmdutil.GetDryRunFlag(o.Cmd) {
if err := cmdutil.PrintObject(o.Cmd, patch.Info.AsVersioned(), o.Out); err != nil {
return err
if o.Local || o.DryRun {
if err := o.PrintObj(info.Object, o.Out); err != nil {
allErrs = append(allErrs, err)
}
continue
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch limit update to pod template %v\n", err))
continue
}
info.Refresh(obj, true)
//record this change (for rollout history)
if o.Record || cmdutil.ContainsChangeCause(info) {
if err := cmdutil.RecordChangeCause(obj, o.ChangeCause); err == nil {
if obj, err = resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, false, obj); err != nil {
allErrs = append(allErrs, fmt.Errorf("changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err))
}
}
if err := o.PrintObj(actual, o.Out); err != nil {
allErrs = append(allErrs, err)
}
info.Refresh(obj, true)
shortOutput := o.Output == "name"
if len(o.Output) > 0 && !shortOutput {
if err := cmdutil.PrintObject(o.Cmd, info.AsVersioned(), o.Out); err != nil {
return err
}
continue
}
cmdutil.PrintSuccess(shortOutput, o.Out, info.Object, false, "resource requirements updated")
}
return utilerrors.NewAggregate(allErrs)
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package set
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
@ -35,43 +34,48 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestResourcesLocal(t *testing.T) {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: ""},
NegotiatedSerializer: ns,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdResources(tf, buf, buf)
outputFormat := "name"
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdResources(tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("output", outputFormat)
cmd.Flags().Set("local", "true")
opts := ResourcesOptions{FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}},
Out: buf,
opts := SetResourcesOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}},
Local: true,
Limits: "cpu=200m,memory=512Mi",
Requests: "cpu=200m,memory=512Mi",
ContainerSelector: "*"}
ContainerSelector: "*",
IOStreams: streams,
}
err := opts.Complete(tf, cmd, []string{})
if err == nil {
@ -89,33 +93,37 @@ func TestResourcesLocal(t *testing.T) {
}
func TestSetMultiResourcesLimitsLocal(t *testing.T) {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: ""},
NegotiatedSerializer: ns,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdResources(tf, buf, buf)
outputFormat := "name"
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdResources(tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("output", outputFormat)
cmd.Flags().Set("local", "true")
opts := ResourcesOptions{FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}},
Out: buf,
opts := SetResourcesOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}},
Local: true,
Limits: "cpu=200m,memory=512Mi",
Requests: "cpu=200m,memory=512Mi",
ContainerSelector: "*"}
ContainerSelector: "*",
IOStreams: streams,
}
err := opts.Complete(tf, cmd, []string{})
if err == nil {
@ -445,18 +453,17 @@ func TestSetResourcesRemote(t *testing.T) {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion}
testapi.Default = testapi.Groups[input.testAPIGroup]
tf := cmdtesting.NewTestFactory()
codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion)
ns := legacyscheme.Codecs
tf.Namespace = "test"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: groupVersion,
NegotiatedSerializer: ns,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1])
resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", "test", input.args[1])
switch p, m := req.URL.Path, req.Method; {
case p == resourcePath && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(input.object)}, nil
case p == resourcePath && m == http.MethodPatch:
stream, err := req.GetBody()
if err != nil {
@ -467,7 +474,7 @@ func TestSetResourcesRemote(t *testing.T) {
return nil, err
}
assert.Contains(t, string(bytes), "200m", fmt.Sprintf("resources not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(input.object)}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "resources", req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
@ -475,14 +482,19 @@ func TestSetResourcesRemote(t *testing.T) {
}),
VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()),
}
buf := new(bytes.Buffer)
cmd := NewCmdResources(tf, buf, buf)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "yaml")
opts := ResourcesOptions{
Out: buf,
outputFormat := "yaml"
streams := genericclioptions.NewTestIOStreamsDiscard()
cmd := NewCmdResources(tf, streams)
cmd.Flags().Set("output", outputFormat)
opts := SetResourcesOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
Limits: "cpu=200m,memory=512Mi",
ContainerSelector: "*"}
ContainerSelector: "*",
IOStreams: streams,
}
err := opts.Complete(tf, cmd, input.args)
if err == nil {
err = opts.Validate()

View File

@ -18,42 +18,45 @@ package set
import (
"fmt"
"io"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
// SelectorOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags()
type SelectorOptions struct {
fileOptions resource.FilenameOptions
local bool
dryrun bool
all bool
record bool
changeCause string
output string
type SetSelectorOptions struct {
// Bound
ResourceBuilderFlags *genericclioptions.ResourceBuilderFlags
PrintFlags *genericclioptions.PrintFlags
RecordFlags *genericclioptions.RecordFlags
dryrun bool
// set by args
resources []string
selector *metav1.LabelSelector
out io.Writer
PrintObject func(obj runtime.Object) error
ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
// computed
WriteToServer bool
PrintObj printers.ResourcePrinterFunc
Recorder genericclioptions.Recorder
ResourceFinder genericclioptions.ResourceFinder
builder *resource.Builder
mapper meta.RESTMapper
// set at initialization
genericclioptions.IOStreams
}
var (
@ -70,11 +73,26 @@ var (
kubectl create deployment my-dep -o yaml --dry-run | kubectl label --local -f - environment=qa -o yaml | kubectl create -f -`)
)
// NewCmdSelector is the "set selector" command.
func NewCmdSelector(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &SelectorOptions{
out: out,
func NewSelectorOptions(streams genericclioptions.IOStreams) *SetSelectorOptions {
return &SetSelectorOptions{
ResourceBuilderFlags: genericclioptions.NewResourceBuilderFlags().
WithScheme(scheme.Scheme).
WithAll(false).
WithLocal(false).
WithUninitialized(false).
WithLatest(),
PrintFlags: genericclioptions.NewPrintFlags("selector updated").WithTypeSetter(scheme.Scheme),
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
IOStreams: streams,
}
}
// NewCmdSelector is the "set selector" command.
func NewCmdSelector(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewSelectorOptions(streams)
cmd := &cobra.Command{
Use: "selector (-f FILENAME | TYPE NAME) EXPRESSIONS [--resource-version=version]",
@ -83,83 +101,56 @@ func NewCmdSelector(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: fmt.Sprintf(selectorLong, validation.LabelValueMaxLength),
Example: selectorExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.RunSelector())
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunSelector())
},
}
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().Bool("all", false, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().Bool("local", false, "If true, set selector will NOT contact api-server but run locally.")
o.ResourceBuilderFlags.AddFlags(cmd.Flags())
o.PrintFlags.AddFlags(cmd)
o.RecordFlags.AddFlags(cmd)
cmd.Flags().String("resource-version", "", "If non-empty, the selectors update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.")
usage := "the resource to update the selectors"
cmdutil.AddFilenameOptionFlags(cmd, &options.fileOptions, usage)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd
}
// Complete assigns the SelectorOptions from args.
func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.local = cmdutil.GetFlagBool(cmd, "local")
o.all = cmdutil.GetFlagBool(cmd, "all")
o.record = cmdutil.GetRecordFlag(cmd)
o.dryrun = cmdutil.GetDryRunFlag(cmd)
o.output = cmdutil.GetFlagString(cmd, "output")
func (o *SetSelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
o.RecordFlags.Complete(cmd)
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
o.changeCause = f.Command(cmd, false)
mapper, _ := f.Object()
o.mapper = mapper
o.dryrun = cmdutil.GetDryRunFlag(cmd)
o.resources, o.selector, err = getResourcesAndSelector(args)
if err != nil {
return err
}
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
o.builder = f.NewBuilder().
Internal().
LocalParam(o.local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.fileOptions).
IncludeUninitialized(includeUninitialized).
Flatten()
o.ResourceFinder = o.ResourceBuilderFlags.ToBuilder(f, o.resources)
o.WriteToServer = !(*o.ResourceBuilderFlags.Local || o.dryrun)
if !o.local {
o.builder.
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
} else {
// if a --local flag was provided, and a resource was specified in the form
// <resource>/<name>, fail immediately as --local cannot query the api server
// for the specified resource.
if len(o.resources) > 0 {
return resource.LocalResourceError
}
if o.dryrun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = printer.PrintObj
o.PrintObject = func(obj runtime.Object) error {
return cmdutil.PrintObject(cmd, obj, o.out)
}
o.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
return f.ClientForMapping(mapping)
}
return err
}
// Validate basic inputs
func (o *SelectorOptions) Validate() error {
if len(o.resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.fileOptions.Filenames) {
return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
}
func (o *SetSelectorOptions) Validate() error {
if o.selector == nil {
return fmt.Errorf("one selector is required")
}
@ -167,54 +158,38 @@ func (o *SelectorOptions) Validate() error {
}
// RunSelector executes the command.
func (o *SelectorOptions) RunSelector() error {
r := o.builder.Do()
err := r.Err()
if err != nil {
return err
}
func (o *SetSelectorOptions) RunSelector() error {
r := o.ResourceFinder.Do()
return r.Visit(func(info *resource.Info, err error) error {
patch := &Patch{Info: info}
CalculatePatch(patch, cmdutil.InternalVersionJSONEncoder(), func(info *resource.Info) ([]byte, error) {
versioned := info.AsVersioned()
patch.Info.Object = versioned
CalculatePatch(patch, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
selectErr := updateSelectorForObject(info.Object, *o.selector)
if selectErr == nil {
return runtime.Encode(cmdutil.InternalVersionJSONEncoder(), info.Object)
if selectErr != nil {
return nil, selectErr
}
return nil, selectErr
// record this change (for rollout history)
if err := o.Recorder.Record(patch.Info.Object); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
return runtime.Encode(scheme.DefaultJSONEncoder(), info.Object)
})
if patch.Err != nil {
return patch.Err
}
if o.local || o.dryrun {
return o.PrintObject(info.Object)
if !o.WriteToServer {
return o.PrintObj(info.Object, o.Out)
}
patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
if err != nil {
return err
}
if o.record || cmdutil.ContainsChangeCause(info) {
if err := cmdutil.RecordChangeCause(patched, o.changeCause); err == nil {
if patched, err = resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, false, patched); err != nil {
return fmt.Errorf("changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err)
}
}
}
info.Refresh(patched, true)
shortOutput := o.output == "name"
if len(o.output) > 0 && !shortOutput {
return o.PrintObject(patched)
}
cmdutil.PrintSuccess(shortOutput, o.out, info.Object, o.dryrun, "selector updated")
return nil
return o.PrintObj(actual, o.Out)
})
}

View File

@ -17,8 +17,6 @@ limitations under the License.
package set
import (
"bytes"
"net/http"
"reflect"
"strings"
"testing"
@ -29,11 +27,9 @@ import (
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
)
func TestUpdateSelectorForObjectTypes(t *testing.T) {
@ -316,28 +312,30 @@ func TestGetResourcesAndSelector(t *testing.T) {
}
func TestSelectorTest(t *testing.T) {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: ""},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
info := &resource.Info{
Object: &v1.Service{
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Service"},
ObjectMeta: metav1.ObjectMeta{Namespace: "some-ns", Name: "cassandra"},
},
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdSelector(tf, buf)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("local", "true")
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/statefulset/cassandra/service.yaml")
labelToSet, err := metav1.ParseToLabelSelector("environment=qa")
if err != nil {
t.Fatal(err)
}
cmd.Run(cmd, []string{"environment=qa"})
iostreams, _, buf, _ := genericclioptions.NewTestIOStreams()
o := &SetSelectorOptions{
selector: labelToSet,
ResourceFinder: genericclioptions.NewSimpleFakeResourceFinder(info),
Recorder: genericclioptions.NoopRecorder{},
PrintObj: (&printers.NamePrinter{}).PrintObj,
IOStreams: iostreams,
}
if err := o.RunSelector(); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "service/cassandra") {
t.Errorf("did not set selector: %s", buf.String())
}

View File

@ -19,8 +19,8 @@ package set
import (
"errors"
"fmt"
"io"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
@ -29,7 +29,11 @@ import (
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -53,29 +57,40 @@ var (
)
// serviceAccountConfig encapsulates the data required to perform the operation.
type serviceAccountConfig struct {
type SetServiceAccountOptions struct {
PrintFlags *genericclioptions.PrintFlags
RecordFlags *genericclioptions.RecordFlags
fileNameOptions resource.FilenameOptions
out io.Writer
err io.Writer
dryRun bool
cmd *cobra.Command
shortOutput bool
all bool
record bool
output string
changeCause string
local bool
updatePodSpecForObject func(runtime.Object, func(*v1.PodSpec) error) (bool, error)
updatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
infos []*resource.Info
serviceAccountName string
PrintObj printers.ResourcePrinterFunc
Recorder genericclioptions.Recorder
genericclioptions.IOStreams
}
func NewSetServiceAccountOptions(streams genericclioptions.IOStreams) *SetServiceAccountOptions {
return &SetServiceAccountOptions{
PrintFlags: genericclioptions.NewPrintFlags("serviceaccount updated").WithTypeSetter(scheme.Scheme),
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
IOStreams: streams,
}
}
// NewCmdServiceAccount returns the "set serviceaccount" command.
func NewCmdServiceAccount(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
saConfig := &serviceAccountConfig{
out: out,
err: err,
}
func NewCmdServiceAccount(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewSetServiceAccountOptions(streams)
cmd := &cobra.Command{
Use: "serviceaccount (-f FILENAME | TYPE NAME) SERVICE_ACCOUNT",
@ -85,55 +100,70 @@ func NewCmdServiceAccount(f cmdutil.Factory, out, err io.Writer) *cobra.Command
Long: serviceaccountLong,
Example: serviceaccountExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(saConfig.Complete(f, cmd, args))
cmdutil.CheckErr(saConfig.Run())
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Run())
},
}
cmdutil.AddPrinterFlags(cmd)
o.PrintFlags.AddFlags(cmd)
o.RecordFlags.AddFlags(cmd)
usage := "identifying the resource to get from a server."
cmdutil.AddFilenameOptionFlags(cmd, &saConfig.fileNameOptions, usage)
cmd.Flags().BoolVar(&saConfig.all, "all", false, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().BoolVar(&saConfig.local, "local", false, "If true, set serviceaccount will NOT contact api-server but run locally.")
cmdutil.AddRecordFlag(cmd)
cmdutil.AddFilenameOptionFlags(cmd, &o.fileNameOptions, usage)
cmd.Flags().BoolVar(&o.all, "all", o.all, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, set serviceaccount will NOT contact api-server but run locally.")
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd
}
// Complete configures serviceAccountConfig from command line args.
func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
saConfig.shortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
saConfig.record = cmdutil.GetRecordFlag(cmd)
saConfig.changeCause = f.Command(cmd, false)
saConfig.dryRun = cmdutil.GetDryRunFlag(cmd)
saConfig.output = cmdutil.GetFlagString(cmd, "output")
saConfig.updatePodSpecForObject = f.UpdatePodSpecForObject
saConfig.cmd = cmd
func (o *SetServiceAccountOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
o.RecordFlags.Complete(cmd)
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
o.shortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
o.dryRun = cmdutil.GetDryRunFlag(cmd)
o.output = cmdutil.GetFlagString(cmd, "output")
o.updatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
if o.dryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = printer.PrintObj
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
if len(args) == 0 {
return errors.New("serviceaccount is required")
}
saConfig.serviceAccountName = args[len(args)-1]
o.serviceAccountName = args[len(args)-1]
resources := args[:len(args)-1]
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder().
Internal().
LocalParam(saConfig.local).
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &saConfig.fileNameOptions).
FilenameParam(enforceNamespace, &o.fileNameOptions).
IncludeUninitialized(includeUninitialized).
Flatten()
if !saConfig.local {
builder.ResourceTypeOrNameArgs(saConfig.all, resources...).
if !o.local {
builder.ResourceTypeOrNameArgs(o.all, resources...).
Latest()
}
saConfig.infos, err = builder.Do().Infos()
o.infos, err = builder.Do().Infos()
if err != nil {
return err
}
@ -141,49 +171,46 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com
}
// Run creates and applies the patch either locally or calling apiserver.
func (saConfig *serviceAccountConfig) Run() error {
func (o *SetServiceAccountOptions) Run() error {
patchErrs := []error{}
patchFn := func(info *resource.Info) ([]byte, error) {
info.Object = info.AsVersioned()
saConfig.updatePodSpecForObject(info.Object, func(podSpec *v1.PodSpec) error {
podSpec.ServiceAccountName = saConfig.serviceAccountName
patchFn := func(obj runtime.Object) ([]byte, error) {
_, err := o.updatePodSpecForObject(obj, func(podSpec *v1.PodSpec) error {
podSpec.ServiceAccountName = o.serviceAccountName
return nil
})
return runtime.Encode(cmdutil.InternalVersionJSONEncoder(), info.Object)
if err != nil {
return nil, err
}
// record this change (for rollout history)
if err := o.Recorder.Record(obj); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
}
patches := CalculatePatches(saConfig.infos, cmdutil.InternalVersionJSONEncoder(), patchFn)
patches := CalculatePatches(o.infos, scheme.DefaultJSONEncoder(), patchFn)
for _, patch := range patches {
info := patch.Info
if patch.Err != nil {
patchErrs = append(patchErrs, fmt.Errorf("error: %s/%s %v\n", info.Mapping.Resource, info.Name, patch.Err))
continue
}
if saConfig.local || saConfig.dryRun {
if err := cmdutil.PrintObject(saConfig.cmd, patch.Info.AsVersioned(), saConfig.out); err != nil {
return err
if o.local || o.dryRun {
if err := o.PrintObj(info.Object, o.Out); err != nil {
patchErrs = append(patchErrs, err)
}
continue
}
patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
if err != nil {
patchErrs = append(patchErrs, fmt.Errorf("failed to patch ServiceAccountName %v", err))
continue
}
info.Refresh(patched, true)
if saConfig.record || cmdutil.ContainsChangeCause(info) {
if patch, patchType, err := cmdutil.ChangeResourcePatch(info, saConfig.changeCause); err == nil {
if patched, err = resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, patchType, patch); err != nil {
fmt.Fprintf(saConfig.err, "WARNING: changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err)
}
}
if err := o.PrintObj(actual, o.Out); err != nil {
patchErrs = append(patchErrs, err)
}
if len(saConfig.output) > 0 {
if err := cmdutil.PrintObject(saConfig.cmd, info.AsVersioned(), saConfig.out); err != nil {
return err
}
continue
}
cmdutil.PrintSuccess(saConfig.shortOutput, saConfig.out, info.Object, saConfig.dryRun, "serviceaccount updated")
}
return utilerrors.NewAggregate(patchErrs)
}

View File

@ -35,12 +35,13 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@ -67,7 +68,9 @@ func TestSetServiceAccountLocal(t *testing.T) {
for i, input := range inputs {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
tf := cmdtesting.NewTestFactory()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -75,50 +78,58 @@ func TestSetServiceAccountLocal(t *testing.T) {
return nil, nil
}),
}
tf.Namespace = "test"
out := new(bytes.Buffer)
cmd := NewCmdServiceAccount(tf, out, out)
cmd.SetOutput(out)
cmd.Flags().Set("output", "yaml")
outputFormat := "yaml"
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdServiceAccount(tf, streams)
cmd.Flags().Set("output", outputFormat)
cmd.Flags().Set("local", "true")
testapi.Default = testapi.Groups[input.apiGroup]
saConfig := serviceAccountConfig{fileNameOptions: resource.FilenameOptions{
Filenames: []string{input.yaml}},
out: out,
local: true}
saConfig := SetServiceAccountOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
fileNameOptions: resource.FilenameOptions{
Filenames: []string{input.yaml}},
local: true,
IOStreams: streams,
}
err := saConfig.Complete(tf, cmd, []string{serviceAccount})
assert.NoError(t, err)
err = saConfig.Run()
assert.NoError(t, err)
assert.Contains(t, out.String(), "serviceAccountName: "+serviceAccount, fmt.Sprintf("serviceaccount not updated for %s", input.yaml))
assert.Contains(t, buf.String(), "serviceAccountName: "+serviceAccount, fmt.Sprintf("serviceaccount not updated for %s", input.yaml))
})
}
}
func TestSetServiceAccountMultiLocal(t *testing.T) {
testapi.Default = testapi.Groups[""]
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: ""},
NegotiatedSerializer: ns,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdServiceAccount(tf, buf, buf)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "name")
outputFormat := "name"
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdServiceAccount(tf, streams)
cmd.Flags().Set("output", outputFormat)
cmd.Flags().Set("local", "true")
opts := serviceAccountConfig{fileNameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}},
out: buf,
local: true}
opts := SetServiceAccountOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
fileNameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}},
local: true,
IOStreams: streams,
}
err := opts.Complete(tf, cmd, []string{serviceAccount})
if err == nil {
@ -310,82 +321,97 @@ func TestSetServiceAccountRemote(t *testing.T) {
},
}
for _, input := range inputs {
groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion}
testapi.Default = testapi.Groups[input.testAPIGroup]
tf := cmdtesting.NewTestFactory()
codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion)
ns := legacyscheme.Codecs
tf.Namespace = "test"
tf.Client = &fake.RESTClient{
GroupVersion: groupVersion,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1])
switch p, m := req.URL.Path, req.Method; {
case p == resourcePath && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil
case p == resourcePath && m == http.MethodPatch:
stream, err := req.GetBody()
if err != nil {
return nil, err
t.Run(input.apiPrefix, func(t *testing.T) {
groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion}
testapi.Default = testapi.Groups[input.testAPIGroup]
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: groupVersion,
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", "test", input.args[1])
switch p, m := req.URL.Path, req.Method; {
case p == resourcePath && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(input.object)}, nil
case p == resourcePath && m == http.MethodPatch:
stream, err := req.GetBody()
if err != nil {
return nil, err
}
bytes, err := ioutil.ReadAll(stream)
if err != nil {
return nil, err
}
assert.Contains(t, string(bytes), `"serviceAccountName":`+`"`+serviceAccount+`"`, fmt.Sprintf("serviceaccount not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(input.object)}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "serviceaccount", req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
bytes, err := ioutil.ReadAll(stream)
if err != nil {
return nil, err
}
assert.Contains(t, string(bytes), `"serviceAccountName":`+`"`+serviceAccount+`"`, fmt.Sprintf("serviceaccount not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "serviceaccount", req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()),
}
out := new(bytes.Buffer)
cmd := NewCmdServiceAccount(tf, out, out)
cmd.SetOutput(out)
cmd.Flags().Set("output", "yaml")
saConfig := serviceAccountConfig{
out: out,
local: false}
err := saConfig.Complete(tf, cmd, input.args)
assert.NoError(t, err)
err = saConfig.Run()
assert.NoError(t, err)
}),
VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()),
}
outputFormat := "yaml"
streams := genericclioptions.NewTestIOStreamsDiscard()
cmd := NewCmdServiceAccount(tf, streams)
cmd.Flags().Set("output", outputFormat)
saConfig := SetServiceAccountOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
local: false,
IOStreams: streams,
}
err := saConfig.Complete(tf, cmd, input.args)
assert.NoError(t, err)
err = saConfig.Run()
assert.NoError(t, err)
})
}
}
func TestServiceAccountValidation(t *testing.T) {
inputs := []struct {
name string
args []string
errorString string
}{
{args: []string{}, errorString: serviceAccountMissingErrString},
{args: []string{serviceAccount}, errorString: resourceMissingErrString},
{name: "test service account missing", args: []string{}, errorString: serviceAccountMissingErrString},
{name: "test service account resource missing", args: []string{serviceAccount}, errorString: resourceMissingErrString},
}
for _, input := range inputs {
tf := cmdtesting.NewTestFactory()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
out := bytes.NewBuffer([]byte{})
cmd := NewCmdServiceAccount(tf, out, out)
cmd.SetOutput(out)
t.Run(input.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
saConfig := &serviceAccountConfig{}
err := saConfig.Complete(tf, cmd, input.args)
assert.EqualError(t, err, input.errorString)
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
outputFormat := ""
streams := genericclioptions.NewTestIOStreamsDiscard()
cmd := NewCmdServiceAccount(tf, streams)
saConfig := &SetServiceAccountOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
IOStreams: streams,
}
err := saConfig.Complete(tf, cmd, input.args)
assert.EqualError(t, err, input.errorString)
})
}
}
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
return bytesBody([]byte(runtime.EncodeOrDie(codec, obj)))
func objBody(obj runtime.Object) io.ReadCloser {
return bytesBody([]byte(runtime.EncodeOrDie(scheme.DefaultJSONEncoder(), obj)))
}
func defaultHeader() http.Header {

View File

@ -18,11 +18,11 @@ package set
import (
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
@ -30,7 +30,10 @@ import (
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -49,16 +52,16 @@ var (
kubectl create rolebinding admin --role=admin --user=admin -o yaml --dry-run | kubectl set subject --local -f - --user=foo -o yaml`)
)
type updateSubjects func(existings []rbac.Subject, targets []rbac.Subject) (bool, []rbac.Subject)
type updateSubjects func(existings []rbacv1.Subject, targets []rbacv1.Subject) (bool, []rbacv1.Subject)
// SubjectOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags
type SubjectOptions struct {
PrintFlags *genericclioptions.PrintFlags
resource.FilenameOptions
Infos []*resource.Info
Out io.Writer
Err io.Writer
Selector string
ContainerSelector string
Output string
@ -70,15 +73,23 @@ type SubjectOptions struct {
Groups []string
ServiceAccounts []string
PrintObject func(obj runtime.Object, out io.Writer) error
namespace string
PrintObj printers.ResourcePrinterFunc
genericclioptions.IOStreams
}
func NewCmdSubject(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command {
options := &SubjectOptions{
Out: out,
Err: errOut,
}
func NewSubjectOptions(streams genericclioptions.IOStreams) *SubjectOptions {
return &SubjectOptions{
PrintFlags: genericclioptions.NewPrintFlags("subjects updated").WithTypeSetter(scheme.Scheme),
IOStreams: streams,
}
}
func NewCmdSubject(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewSubjectOptions(streams)
cmd := &cobra.Command{
Use: "subject (-f FILENAME | TYPE NAME) [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run]",
DisableFlagsInUseLine: true,
@ -86,22 +97,22 @@ func NewCmdSubject(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Co
Long: subject_long,
Example: subject_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.Run(f, addSubjects))
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run(addSubjects))
},
}
cmdutil.AddPrinterFlags(cmd)
usage := "the resource to update the subjects"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.Flags().BoolVar(&options.All, "all", options.All, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&options.Local, "local", options.Local, "If true, set subject will NOT contact api-server but run locally.")
o.PrintFlags.AddFlags(cmd)
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "the resource to update the subjects")
cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, set subject will NOT contact api-server but run locally.")
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringArrayVar(&options.Users, "user", []string{}, "Usernames to bind to the role")
cmd.Flags().StringArrayVar(&options.Groups, "group", []string{}, "Groups to bind to the role")
cmd.Flags().StringArrayVar(&options.ServiceAccounts, "serviceaccount", []string{}, "Service accounts to bind to the role")
cmd.Flags().StringArrayVar(&o.Users, "user", o.Users, "Usernames to bind to the role")
cmd.Flags().StringArrayVar(&o.Groups, "group", o.Groups, "Groups to bind to the role")
cmd.Flags().StringArrayVar(&o.ServiceAccounts, "serviceaccount", o.ServiceAccounts, "Service accounts to bind to the role")
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd
}
@ -109,21 +120,28 @@ func NewCmdSubject(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Co
func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.Output = cmdutil.GetFlagString(cmd, "output")
o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.PrintObject = func(obj runtime.Object, out io.Writer) error {
return cmdutil.PrintObject(cmd, obj, out)
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = printer.PrintObj
var enforceNamespace bool
o.namespace, enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder().
Internal().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten()
@ -175,12 +193,11 @@ func (o *SubjectOptions) Validate() error {
return nil
}
func (o *SubjectOptions) Run(f cmdutil.Factory, fn updateSubjects) error {
var err error
patches := CalculatePatches(o.Infos, cmdutil.InternalVersionJSONEncoder(), func(info *resource.Info) ([]byte, error) {
subjects := []rbac.Subject{}
func (o *SubjectOptions) Run(fn updateSubjects) error {
patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
subjects := []rbacv1.Subject{}
for _, user := range sets.NewString(o.Users...).List() {
subject := rbac.Subject{
subject := rbacv1.Subject{
Kind: rbac.UserKind,
APIGroup: rbac.GroupName,
Name: user,
@ -188,7 +205,7 @@ func (o *SubjectOptions) Run(f cmdutil.Factory, fn updateSubjects) error {
subjects = append(subjects, subject)
}
for _, group := range sets.NewString(o.Groups...).List() {
subject := rbac.Subject{
subject := rbacv1.Subject{
Kind: rbac.GroupKind,
APIGroup: rbac.GroupName,
Name: group,
@ -200,12 +217,9 @@ func (o *SubjectOptions) Run(f cmdutil.Factory, fn updateSubjects) error {
namespace := tokens[0]
name := tokens[1]
if len(namespace) == 0 {
namespace, _, err = f.DefaultNamespace()
if err != nil {
return nil, err
}
namespace = o.namespace
}
subject := rbac.Subject{
subject := rbacv1.Subject{
Kind: rbac.ServiceAccountKind,
Namespace: namespace,
Name: name,
@ -213,10 +227,10 @@ func (o *SubjectOptions) Run(f cmdutil.Factory, fn updateSubjects) error {
subjects = append(subjects, subject)
}
transformed, err := updateSubjectForObject(info.Object, subjects, fn)
transformed, err := updateSubjectForObject(obj, subjects, fn)
if transformed && err == nil {
// TODO: switch UpdatePodSpecForObject to work on v1.PodSpec
return runtime.Encode(cmdutil.InternalVersionJSONEncoder(), info.AsVersioned())
return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
}
return nil, err
})
@ -236,36 +250,33 @@ func (o *SubjectOptions) Run(f cmdutil.Factory, fn updateSubjects) error {
}
if o.Local || o.DryRun {
if err := o.PrintObject(info.Object, o.Out); err != nil {
return err
if err := o.PrintObj(info.Object, o.Out); err != nil {
allErrs = append(allErrs, err)
}
continue
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch subjects to rolebinding: %v\n", err))
continue
}
info.Refresh(obj, true)
shortOutput := o.Output == "name"
if len(o.Output) > 0 && !shortOutput {
return o.PrintObject(info.AsVersioned(), o.Out)
if err := o.PrintObj(actual, o.Out); err != nil {
allErrs = append(allErrs, err)
}
cmdutil.PrintSuccess(shortOutput, o.Out, info.Object, false, "subjects updated")
}
return utilerrors.NewAggregate(allErrs)
}
//Note: the obj mutates in the function
func updateSubjectForObject(obj runtime.Object, subjects []rbac.Subject, fn updateSubjects) (bool, error) {
func updateSubjectForObject(obj runtime.Object, subjects []rbacv1.Subject, fn updateSubjects) (bool, error) {
switch t := obj.(type) {
case *rbac.RoleBinding:
case *rbacv1.RoleBinding:
transformed, result := fn(t.Subjects, subjects)
t.Subjects = result
return transformed, nil
case *rbac.ClusterRoleBinding:
case *rbacv1.ClusterRoleBinding:
transformed, result := fn(t.Subjects, subjects)
t.Subjects = result
return transformed, nil
@ -274,7 +285,7 @@ func updateSubjectForObject(obj runtime.Object, subjects []rbac.Subject, fn upda
}
}
func addSubjects(existings []rbac.Subject, targets []rbac.Subject) (bool, []rbac.Subject) {
func addSubjects(existings []rbacv1.Subject, targets []rbacv1.Subject) (bool, []rbacv1.Subject) {
transformed := false
updated := existings
for _, item := range targets {
@ -286,7 +297,7 @@ func addSubjects(existings []rbac.Subject, targets []rbac.Subject) (bool, []rbac
return transformed, updated
}
func contain(slice []rbac.Subject, item rbac.Subject) bool {
func contain(slice []rbacv1.Subject, item rbacv1.Subject) bool {
for _, v := range slice {
if v == item {
return true

View File

@ -20,16 +20,16 @@ import (
"reflect"
"testing"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/apis/rbac"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
)
func TestValidate(t *testing.T) {
tf := cmdtesting.NewTestFactory()
tf.Namespace = "test"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tests := map[string]struct {
options *SubjectOptions
@ -63,11 +63,11 @@ func TestValidate(t *testing.T) {
options: &SubjectOptions{
Infos: []*resource.Info{
{
Object: &rbac.ClusterRoleBinding{
Object: &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "clusterrolebinding",
},
RoleRef: rbac.RoleRef{
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "role",
@ -85,12 +85,12 @@ func TestValidate(t *testing.T) {
options: &SubjectOptions{
Infos: []*resource.Info{
{
Object: &rbac.RoleBinding{
Object: &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "rolebinding",
Namespace: "one",
},
RoleRef: rbac.RoleRef{
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "role",
@ -121,13 +121,13 @@ func TestUpdateSubjectForObject(t *testing.T) {
tests := []struct {
Name string
obj runtime.Object
subjects []rbac.Subject
expected []rbac.Subject
subjects []rbacv1.Subject
expected []rbacv1.Subject
wantErr bool
}{
{
Name: "invalid object type",
obj: &rbac.Role{
obj: &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: "role",
Namespace: "one",
@ -137,12 +137,12 @@ func TestUpdateSubjectForObject(t *testing.T) {
},
{
Name: "add resource with users in rolebinding",
obj: &rbac.RoleBinding{
obj: &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "rolebinding",
Namespace: "one",
},
Subjects: []rbac.Subject{
Subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
@ -150,7 +150,7 @@ func TestUpdateSubjectForObject(t *testing.T) {
},
},
},
subjects: []rbac.Subject{
subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
@ -162,7 +162,7 @@ func TestUpdateSubjectForObject(t *testing.T) {
Name: "b",
},
},
expected: []rbac.Subject{
expected: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
@ -178,12 +178,12 @@ func TestUpdateSubjectForObject(t *testing.T) {
},
{
Name: "add resource with groups in rolebinding",
obj: &rbac.RoleBinding{
obj: &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "rolebinding",
Namespace: "one",
},
Subjects: []rbac.Subject{
Subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Group",
@ -191,7 +191,7 @@ func TestUpdateSubjectForObject(t *testing.T) {
},
},
},
subjects: []rbac.Subject{
subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Group",
@ -203,7 +203,7 @@ func TestUpdateSubjectForObject(t *testing.T) {
Name: "b",
},
},
expected: []rbac.Subject{
expected: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Group",
@ -219,12 +219,12 @@ func TestUpdateSubjectForObject(t *testing.T) {
},
{
Name: "add resource with serviceaccounts in rolebinding",
obj: &rbac.RoleBinding{
obj: &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "rolebinding",
Namespace: "one",
},
Subjects: []rbac.Subject{
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Namespace: "one",
@ -232,7 +232,7 @@ func TestUpdateSubjectForObject(t *testing.T) {
},
},
},
subjects: []rbac.Subject{
subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Namespace: "one",
@ -244,7 +244,7 @@ func TestUpdateSubjectForObject(t *testing.T) {
Name: "b",
},
},
expected: []rbac.Subject{
expected: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Namespace: "one",
@ -260,11 +260,11 @@ func TestUpdateSubjectForObject(t *testing.T) {
},
{
Name: "add resource with serviceaccounts in clusterrolebinding",
obj: &rbac.ClusterRoleBinding{
obj: &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "clusterrolebinding",
},
Subjects: []rbac.Subject{
Subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
@ -277,14 +277,14 @@ func TestUpdateSubjectForObject(t *testing.T) {
},
},
},
subjects: []rbac.Subject{
subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Namespace: "one",
Name: "a",
},
},
expected: []rbac.Subject{
expected: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
@ -310,11 +310,11 @@ func TestUpdateSubjectForObject(t *testing.T) {
}
want := tt.expected
var got []rbac.Subject
var got []rbacv1.Subject
switch t := tt.obj.(type) {
case *rbac.RoleBinding:
case *rbacv1.RoleBinding:
got = t.Subjects
case *rbac.ClusterRoleBinding:
case *rbacv1.ClusterRoleBinding:
got = t.Subjects
}
if !reflect.DeepEqual(got, want) {
@ -328,14 +328,14 @@ func TestUpdateSubjectForObject(t *testing.T) {
func TestAddSubject(t *testing.T) {
tests := []struct {
Name string
existing []rbac.Subject
subjects []rbac.Subject
expected []rbac.Subject
existing []rbacv1.Subject
subjects []rbacv1.Subject
expected []rbacv1.Subject
wantChange bool
}{
{
Name: "add resource with users",
existing: []rbac.Subject{
existing: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
@ -347,14 +347,14 @@ func TestAddSubject(t *testing.T) {
Name: "b",
},
},
subjects: []rbac.Subject{
subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
Name: "a",
},
},
expected: []rbac.Subject{
expected: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
@ -370,7 +370,7 @@ func TestAddSubject(t *testing.T) {
},
{
Name: "add resource with serviceaccounts",
existing: []rbac.Subject{
existing: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Namespace: "one",
@ -382,14 +382,14 @@ func TestAddSubject(t *testing.T) {
Name: "b",
},
},
subjects: []rbac.Subject{
subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Namespace: "two",
Name: "a",
},
},
expected: []rbac.Subject{
expected: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Namespace: "one",
@ -411,7 +411,7 @@ func TestAddSubject(t *testing.T) {
}
for _, tt := range tests {
changed := false
got := []rbac.Subject{}
got := []rbacv1.Subject{}
if changed, got = addSubjects(tt.existing, tt.subjects); (changed != false) != tt.wantChange {
t.Errorf("%q. addSubjects() changed = %v, wantChange = %v", tt.Name, changed, tt.wantChange)
}

View File

@ -17,20 +17,17 @@ limitations under the License.
package set
import (
"bytes"
"testing"
"github.com/spf13/cobra"
clientcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"os"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestLocalAndDryRunFlags(t *testing.T) {
out := &bytes.Buffer{}
errout := &bytes.Buffer{}
f := clientcmdutil.NewFactory(nil)
setCmd := NewCmdSet(f, os.Stdin, out, errout)
f := clientcmdutil.NewFactory(genericclioptions.NewTestConfigFlags())
setCmd := NewCmdSet(f, genericclioptions.NewTestIOStreamsDiscard())
ensureLocalAndDryRunFlagsOnChildren(t, setCmd, "")
}