Commit 357db0c3 authored by zhengjiajin's avatar zhengjiajin

Add kubectl set env command

parent 16fee229
......@@ -285,7 +285,8 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
NewCmdCreate(f, out, err),
NewCmdExposeService(f, out),
NewCmdRun(f, in, out, err),
set.NewCmdSet(f, out, err),
set.NewCmdSet(f, in, out, err),
deprecatedAlias("run-container", NewCmdRun(f, in, out, err)),
},
},
{
......
......@@ -9,6 +9,7 @@ go_library(
srcs = [
"helper.go",
"set.go",
"set_env.go",
"set_image.go",
"set_resources.go",
"set_selector.go",
......@@ -22,6 +23,7 @@ go_library(
"//pkg/kubectl: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/util/i18n:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
......@@ -40,6 +42,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"set_env_test.go",
"set_image_test.go",
"set_resources_test.go",
"set_selector_test.go",
......
......@@ -23,6 +23,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/kubernetes/pkg/api"
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
......@@ -158,3 +159,37 @@ func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn
}
return patches
}
func findEnv(env []api.EnvVar, name string) (api.EnvVar, bool) {
for _, e := range env {
if e.Name == name {
return e, true
}
}
return api.EnvVar{}, false
}
func updateEnv(existing []api.EnvVar, env []api.EnvVar, remove []string) []api.EnvVar {
out := []api.EnvVar{}
covered := sets.NewString(remove...)
for _, e := range existing {
if covered.Has(e.Name) {
continue
}
newer, ok := findEnv(env, e.Name)
if ok {
covered.Insert(e.Name)
out = append(out, newer)
continue
}
out = append(out, e)
}
for _, e := range env {
if covered.Has(e.Name) {
continue
}
covered.Insert(e.Name)
out = append(out, e)
}
return out
}
......@@ -32,7 +32,7 @@ var (
These commands help you make changes to existing application resources.`)
)
func NewCmdSet(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
func NewCmdSet(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "set SUBCOMMAND",
Short: i18n.T("Set specific features on objects"),
......@@ -46,5 +46,7 @@ func NewCmdSet(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdSelector(f, out))
cmd.AddCommand(NewCmdSubject(f, out, err))
cmd.AddCommand(NewCmdServiceAccount(f, out, err))
cmd.AddCommand(NewCmdEnv(f, in, out, err))
return cmd
}
/*
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 set
import (
"bytes"
"net/http"
"strings"
"testing"
"k8s.io/apimachinery/pkg/runtime"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers"
"os"
)
func TestSetEnvLocal(t *testing.T) {
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
APIRegistry: api.Registry,
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
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdEnv(f, os.Stdin, buf, buf)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("local", "true")
mapper, typer := f.Object()
tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper}
opts := EnvOptions{FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../../examples/storage/cassandra/cassandra-controller.yaml"}},
Out: buf,
Local: true}
err := opts.Complete(f, cmd, []string{"env=prod"})
if err == nil {
err = opts.RunEnv(f)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(buf.String(), "replicationcontrollers/cassandra") {
t.Errorf("did not set env: %s", buf.String())
}
}
......@@ -23,13 +23,14 @@ import (
"github.com/spf13/cobra"
clientcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"os"
)
func TestLocalAndDryRunFlags(t *testing.T) {
out := &bytes.Buffer{}
errout := &bytes.Buffer{}
f := clientcmdutil.NewFactory(nil)
setCmd := NewCmdSet(f, out, errout)
setCmd := NewCmdSet(f, os.Stdin, out, errout)
ensureLocalAndDryRunFlagsOnChildren(t, setCmd, "")
}
......
......@@ -142,6 +142,7 @@ filegroup(
srcs = [
":package-srcs",
"//pkg/kubectl/cmd/util/editor:all-srcs",
"//pkg/kubectl/cmd/util/env:all-srcs",
"//pkg/kubectl/cmd/util/jsonmerge:all-srcs",
"//pkg/kubectl/cmd/util/openapi:all-srcs",
"//pkg/kubectl/cmd/util/sanity:all-srcs",
......
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"env_parse.go",
"env_resolve.go",
],
visibility = ["//visibility:public"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/fieldpath: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",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
/*
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"
"os"
"regexp"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api"
)
// Env returns an environment variable or a default value if not specified.
func Env(key string, defaultValue string) string {
val := os.Getenv(key)
if len(val) == 0 {
return defaultValue
}
return val
}
// GetEnv returns an environment value if specified
func GetEnv(key string) (string, bool) {
val := os.Getenv(key)
if len(val) == 0 {
return "", false
}
return val, true
}
var argumentEnvironment = regexp.MustCompile("(?ms)^(.+)\\=(.*)$")
var validArgumentEnvironment = regexp.MustCompile("(?ms)^(\\w+)\\=(.*)$")
// IsEnvironmentArgument check str is env args
func IsEnvironmentArgument(s string) bool {
return argumentEnvironment.MatchString(s)
}
// IsValidEnvironmentArgument check str is valid env
func IsValidEnvironmentArgument(s string) bool {
return validArgumentEnvironment.MatchString(s)
}
// SplitEnvironmentFromResources returns resources and envargs
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) ([]api.EnvVar, []string, error) {
env := []api.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, api.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 parse env from reader
func ParseEnv(spec []string, defaultReader io.Reader) ([]api.EnvVar, []string, error) {
return parseIntoEnvVar(spec, defaultReader, "environment variable")
}
func readEnv(r io.Reader, envVarType string) ([]api.EnvVar, error) {
env := []api.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, api.EnvVar{
Name: parts[0],
Value: parts[1],
})
}
}
if err := scanner.Err(); err != nil && err != io.EOF {
return nil, err
}
return env, nil
}
/*
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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/fieldpath"
)
// ResourceStore defines a new resource store data structure
type ResourceStore struct {
SecretStore map[string]*api.Secret
ConfigMapStore map[string]*api.ConfigMap
}
// NewResourceStore returns a pointer to a new resource store data structure
func NewResourceStore() *ResourceStore {
return &ResourceStore{
SecretStore: make(map[string]*api.Secret),
ConfigMapStore: make(map[string]*api.ConfigMap),
}
}
// getSecretRefValue returns the value of a secret in the supplied namespace
func getSecretRefValue(client clientset.Interface, namespace string, store *ResourceStore, secretSelector *api.SecretKeySelector) (string, error) {
secret, ok := store.SecretStore[secretSelector.Name]
if !ok {
var err error
secret, err = client.Core().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 clientset.Interface, namespace string, store *ResourceStore, configMapSelector *api.ConfigMapKeySelector) (string, error) {
configMap, ok := store.ConfigMapStore[configMapSelector.Name]
if !ok {
var err error
configMap, err = client.Core().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 *api.EnvVarSource) (string, error) {
return fieldpath.ExtractFieldPathAsString(obj, from.FieldRef.FieldPath)
}
// getResourceFieldRef returns the value of a resource in the given container
func getResourceFieldRef(from *api.EnvVarSource, c *api.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 clientset.Interface, ns string, store *ResourceStore, from *api.EnvVarSource, obj runtime.Object, c *api.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 the supplied envvarsource
func GetEnvVarRefString(from *api.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"
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment