mirror of https://github.com/k3s-io/k3s
Add kubectl set env command
parent
16fee22953
commit
357db0c39c
|
@ -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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,430 @@
|
|||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
envResources = `
|
||||
pod (po), replicationcontroller (rc), deployment (deploy), daemonset (ds), job, replicaset (rs)`
|
||||
|
||||
envLong = templates.LongDesc(`
|
||||
Update environment variables on a pod template.
|
||||
|
||||
List environment variable definitions in one or more pods, pod templates.
|
||||
Add, update, or remove container environment variable definitions in one or
|
||||
more pod templates (within replication controllers or deployment configurations).
|
||||
View or modify the environment variable definitions on all containers in the
|
||||
specified pods or pod templates, or just those that match a wildcard.
|
||||
|
||||
If "--env -" is passed, environment variables can be read from STDIN using the standard env
|
||||
syntax.
|
||||
|
||||
Possible resources include (case insensitive):
|
||||
` + envResources)
|
||||
|
||||
envExample = templates.Examples(`
|
||||
# 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'
|
||||
kubectl set env deployment/sample-build --list
|
||||
|
||||
# List the environment variables defined on all pods
|
||||
kubectl set env pods --all --list
|
||||
|
||||
# Output modified deployment in YAML, and does not alter the object on the server
|
||||
kubectl set env deployment/sample-build STORAGE_DIR=/data -o yaml
|
||||
|
||||
# Update all containers in all replication controllers in the project to have ENV=prod
|
||||
kubectl set env rc --all ENV=prod
|
||||
|
||||
# Import environment from a secret
|
||||
kubectl set env --from=secret/mysecret dc/myapp
|
||||
|
||||
# Import environment from a config map with a prefix
|
||||
kubectl set env --from=configmap/myconfigmap --prefix=MYSQL_ dc/myapp
|
||||
|
||||
# Remove the environment variable ENV from container 'c1' in all deployment configs
|
||||
kubectl set env deployments --all --containers="c1" ENV-
|
||||
|
||||
# Remove the environment variable ENV from a deployment definition on disk and
|
||||
# update the deployment config on the server
|
||||
kubectl set env -f deploy.json ENV-
|
||||
|
||||
# Set some of the local shell environment into a deployment config on the server
|
||||
env | grep RAILS_ | kubectl set env -e - dc/registry`)
|
||||
)
|
||||
|
||||
type EnvOptions struct {
|
||||
Out io.Writer
|
||||
Err io.Writer
|
||||
In io.Reader
|
||||
|
||||
resource.FilenameOptions
|
||||
EnvParams []string
|
||||
EnvArgs []string
|
||||
Resources []string
|
||||
|
||||
All bool
|
||||
Resolve bool
|
||||
List bool
|
||||
ShortOutput bool
|
||||
Local bool
|
||||
Overwrite bool
|
||||
DryRun bool
|
||||
|
||||
ResourceVersion string
|
||||
ContainerSelector string
|
||||
Selector string
|
||||
Output string
|
||||
From string
|
||||
Prefix string
|
||||
|
||||
Mapper meta.RESTMapper
|
||||
Typer runtime.ObjectTyper
|
||||
Builder *resource.Builder
|
||||
Infos []*resource.Info
|
||||
Encoder runtime.Encoder
|
||||
|
||||
Cmd *cobra.Command
|
||||
|
||||
UpdatePodSpecForObject func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error)
|
||||
PrintObject func(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "env RESOURCE/NAME KEY_1=VAL_1 ... KEY_N=VAL_N",
|
||||
Short: "Update environment variables on a pod template",
|
||||
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))
|
||||
},
|
||||
}
|
||||
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")
|
||||
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", false, "If true, set image 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.AddDryRunFlag(cmd)
|
||||
cmdutil.AddPrinterFlags(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func validateNoOverwrites(existing []api.EnvVar, env []api.EnvVar) error {
|
||||
for _, e := range env {
|
||||
if current, exists := findEnv(existing, e.Name); exists && current.Value != e.Value {
|
||||
return fmt.Errorf("'%s' already has a value (%s), and --overwrite is false", current.Name, current.Value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyToEnvName(key string) string {
|
||||
validEnvNameRegexp := regexp.MustCompile("[^a-zA-Z0-9_]")
|
||||
return strings.ToUpper(validEnvNameRegexp.ReplaceAllString(key, "_"))
|
||||
}
|
||||
|
||||
func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
resources, 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>")
|
||||
}
|
||||
|
||||
o.Mapper, o.Typer = f.Object()
|
||||
o.UpdatePodSpecForObject = f.UpdatePodSpecForObject
|
||||
o.Encoder = f.JSONEncoder()
|
||||
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.PrintObject = f.PrintObject
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunEnv contains all the necessary functionality for the OpenShift cli env command
|
||||
func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
|
||||
kubeClient, err := f.ClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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(!o.Local).
|
||||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, &o.FilenameOptions).
|
||||
Flatten()
|
||||
|
||||
if !o.Local {
|
||||
b = b.
|
||||
SelectorParam(o.Selector).
|
||||
ResourceTypeOrNameArgs(o.All, o.From).
|
||||
Latest()
|
||||
}
|
||||
|
||||
infos, err := b.Do().Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, info := range infos {
|
||||
switch from := info.Object.(type) {
|
||||
case *api.Secret:
|
||||
for key := range from.Data {
|
||||
envVar := api.EnvVar{
|
||||
Name: keyToEnvName(key),
|
||||
ValueFrom: &api.EnvVarSource{
|
||||
SecretKeyRef: &api.SecretKeySelector{
|
||||
LocalObjectReference: api.LocalObjectReference{
|
||||
Name: from.Name,
|
||||
},
|
||||
Key: key,
|
||||
},
|
||||
},
|
||||
}
|
||||
env = append(env, envVar)
|
||||
}
|
||||
case *api.ConfigMap:
|
||||
for key := range from.Data {
|
||||
envVar := api.EnvVar{
|
||||
Name: keyToEnvName(key),
|
||||
ValueFrom: &api.EnvVarSource{
|
||||
ConfigMapKeyRef: &api.ConfigMapKeySelector{
|
||||
LocalObjectReference: api.LocalObjectReference{
|
||||
Name: from.Name,
|
||||
},
|
||||
Key: key,
|
||||
},
|
||||
},
|
||||
}
|
||||
env = append(env, envVar)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported resource specified in --from")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(o.Prefix) != 0 {
|
||||
for i := range env {
|
||||
env[i].Name = fmt.Sprintf("%s%s", o.Prefix, env[i].Name)
|
||||
}
|
||||
}
|
||||
|
||||
b := f.NewBuilder(!o.Local).
|
||||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, &o.FilenameOptions).
|
||||
Flatten()
|
||||
|
||||
if !o.Local {
|
||||
b = b.
|
||||
SelectorParam(o.Selector).
|
||||
ResourceTypeOrNameArgs(o.All, o.Resources...).
|
||||
Latest()
|
||||
}
|
||||
|
||||
o.Infos, err = b.Do().Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) ([]byte, error) {
|
||||
_, err := f.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error {
|
||||
resolutionErrorsEncountered := false
|
||||
containers, _ := selectContainers(spec.Containers, o.ContainerSelector)
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
for _, c := range containers {
|
||||
if !o.Overwrite {
|
||||
if err := validateNoOverwrites(c.Env, env); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.Env = updateEnv(c.Env, env, remove)
|
||||
if o.List {
|
||||
resolveErrors := map[string][]string{}
|
||||
store := envutil.NewResourceStore()
|
||||
|
||||
fmt.Fprintf(o.Out, "# %s %s, container %s\n", info.Mapping.Resource, info.Name, c.Name)
|
||||
for _, env := range c.Env {
|
||||
// Print the simple value
|
||||
if env.ValueFrom == nil {
|
||||
fmt.Fprintf(o.Out, "%s=%s\n", env.Name, env.Value)
|
||||
continue
|
||||
}
|
||||
|
||||
// Print the reference version
|
||||
if !o.Resolve {
|
||||
fmt.Fprintf(o.Out, "# %s from %s\n", env.Name, envutil.GetEnvVarRefString(env.ValueFrom))
|
||||
continue
|
||||
}
|
||||
|
||||
value, err := envutil.GetEnvVarRefValue(kubeClient, cmdNamespace, store, env.ValueFrom, info.Object, c)
|
||||
// Print the resolved value
|
||||
if err == nil {
|
||||
fmt.Fprintf(o.Out, "%s=%s\n", env.Name, value)
|
||||
continue
|
||||
}
|
||||
|
||||
// Print the reference version and save the resolve error
|
||||
fmt.Fprintf(o.Out, "# %s from %s\n", env.Name, envutil.GetEnvVarRefString(env.ValueFrom))
|
||||
errString := err.Error()
|
||||
resolveErrors[errString] = append(resolveErrors[errString], env.Name)
|
||||
resolutionErrorsEncountered = true
|
||||
}
|
||||
|
||||
// Print any resolution errors
|
||||
errs := []string{}
|
||||
for err, vars := range resolveErrors {
|
||||
sort.Strings(vars)
|
||||
errs = append(errs, fmt.Sprintf("error retrieving reference for %s: %v", strings.Join(vars, ", "), err))
|
||||
}
|
||||
sort.Strings(errs)
|
||||
for _, err := range errs {
|
||||
fmt.Fprintln(o.Err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if resolutionErrorsEncountered {
|
||||
return errors.New("failed to retrieve valueFrom references")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
return runtime.Encode(o.Encoder, info.Object)
|
||||
}
|
||||
return nil, err
|
||||
})
|
||||
|
||||
if o.List {
|
||||
return nil
|
||||
}
|
||||
|
||||
allErrs := []error{}
|
||||
|
||||
for _, patch := range patches {
|
||||
info := patch.Info
|
||||
if patch.Err != nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("error: %s/%s %v\n", info.Mapping.Resource, info.Name, patch.Err))
|
||||
continue
|
||||
}
|
||||
|
||||
// no changes
|
||||
if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if o.PrintObject != nil && (o.Local || o.DryRun) {
|
||||
if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, info.Object, o.Out); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
obj, 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 {
|
||||
return fmt.Errorf("at least one environment variable must be provided")
|
||||
}
|
||||
|
||||
if len(o.Output) > 0 {
|
||||
return o.PrintObject(o.Cmd, o.Local, o.Mapper, obj, o.Out)
|
||||
}
|
||||
|
||||
cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, false, "env updated")
|
||||
}
|
||||
return utilerrors.NewAggregate(allErrs)
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
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",
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
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"],
|
||||
)
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
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
|
||||
}
|
|
@ -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"
|
||||
|
||||
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"
|
||||
}
|
Loading…
Reference in New Issue