From afea127a3e7a32099f676399efc13dbb653bdf28 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Wed, 16 Sep 2015 16:31:45 -0700 Subject: [PATCH] Move the default schema cache to the home directory --- docs/man/man1/kubectl-create.1 | 4 +- docs/man/man1/kubectl-replace.1 | 4 +- docs/man/man1/kubectl-rolling-update.1 | 4 +- docs/user-guide/kubectl/kubectl_create.md | 4 +- docs/user-guide/kubectl/kubectl_replace.md | 4 +- .../kubectl/kubectl_rolling-update.md | 4 +- pkg/client/unversioned/clientcmd/loader.go | 7 +- pkg/kubectl/cmd/config/config.go | 2 +- pkg/kubectl/cmd/util/factory.go | 69 +++++++++++++++---- pkg/kubectl/cmd/util/factory_test.go | 30 ++++++++ pkg/kubectl/cmd/util/helpers.go | 2 +- 11 files changed, 106 insertions(+), 28 deletions(-) diff --git a/docs/man/man1/kubectl-create.1 b/docs/man/man1/kubectl-create.1 index 2c28bd1fabd..653554aaa4a 100644 --- a/docs/man/man1/kubectl-create.1 +++ b/docs/man/man1/kubectl-create.1 @@ -29,8 +29,8 @@ JSON and YAML formats are accepted. Output mode. Use "\-o name" for shorter output (resource/name). .PP -\fB\-\-schema\-cache\-dir\fP="/tmp/kubectl.schema" - If non\-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema' +\fB\-\-schema\-cache\-dir\fP="\~/.kube/schema" + If non\-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' .PP \fB\-\-validate\fP=true diff --git a/docs/man/man1/kubectl-replace.1 b/docs/man/man1/kubectl-replace.1 index c3124c575ee..7ab5b244344 100644 --- a/docs/man/man1/kubectl-replace.1 +++ b/docs/man/man1/kubectl-replace.1 @@ -47,8 +47,8 @@ Please refer to the models in Output mode. Use "\-o name" for shorter output (resource/name). .PP -\fB\-\-schema\-cache\-dir\fP="/tmp/kubectl.schema" - If non\-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema' +\fB\-\-schema\-cache\-dir\fP="\~/.kube/schema" + If non\-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' .PP \fB\-\-timeout\fP=0 diff --git a/docs/man/man1/kubectl-rolling-update.1 b/docs/man/man1/kubectl-rolling-update.1 index f0da1054ab2..de5b275f807 100644 --- a/docs/man/man1/kubectl-rolling-update.1 +++ b/docs/man/man1/kubectl-rolling-update.1 @@ -61,8 +61,8 @@ existing replication controller and overwrite at least one (common) label in its If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout .PP -\fB\-\-schema\-cache\-dir\fP="/tmp/kubectl.schema" - If non\-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema' +\fB\-\-schema\-cache\-dir\fP="\~/.kube/schema" + If non\-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' .PP \fB\-a\fP, \fB\-\-show\-all\fP=false diff --git a/docs/user-guide/kubectl/kubectl_create.md b/docs/user-guide/kubectl/kubectl_create.md index a5d3d9621db..fc197dcdcd8 100644 --- a/docs/user-guide/kubectl/kubectl_create.md +++ b/docs/user-guide/kubectl/kubectl_create.md @@ -61,7 +61,7 @@ $ cat pod.json | kubectl create -f - ``` -f, --filename=[]: Filename, directory, or URL to file to use to create the resource -o, --output="": Output mode. Use "-o name" for shorter output (resource/name). - --schema-cache-dir="/tmp/kubectl.schema": If non-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema' + --schema-cache-dir="~/.kube/schema": If non-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' --validate[=true]: If true, use a schema to validate the input before sending it ``` @@ -97,7 +97,7 @@ $ cat pod.json | kubectl create -f - * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-09-11 20:48:33.289761103 +0000 UTC +###### Auto generated by spf13/cobra at 2015-09-17 21:39:48.399116592 +0000 UTC [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create.md?pixel)]() diff --git a/docs/user-guide/kubectl/kubectl_replace.md b/docs/user-guide/kubectl/kubectl_replace.md index 60485a4c0aa..d4b28afc034 100644 --- a/docs/user-guide/kubectl/kubectl_replace.md +++ b/docs/user-guide/kubectl/kubectl_replace.md @@ -74,7 +74,7 @@ kubectl replace --force -f ./pod.json --force[=false]: Delete and re-create the specified resource --grace-period=-1: Only relevant during a force replace. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative. -o, --output="": Output mode. Use "-o name" for shorter output (resource/name). - --schema-cache-dir="/tmp/kubectl.schema": If non-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema' + --schema-cache-dir="~/.kube/schema": If non-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' --timeout=0: Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object --validate[=true]: If true, use a schema to validate the input before sending it ``` @@ -111,7 +111,7 @@ kubectl replace --force -f ./pod.json * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-09-11 20:48:33.290279625 +0000 UTC +###### Auto generated by spf13/cobra at 2015-09-17 21:39:48.399461456 +0000 UTC [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_replace.md?pixel)]() diff --git a/docs/user-guide/kubectl/kubectl_rolling-update.md b/docs/user-guide/kubectl/kubectl_rolling-update.md index 07cbaebc177..bcccd141ed6 100644 --- a/docs/user-guide/kubectl/kubectl_rolling-update.md +++ b/docs/user-guide/kubectl/kubectl_rolling-update.md @@ -78,7 +78,7 @@ $ kubectl rolling-update frontend --image=image:v2 --output-version="": Output the formatted object with the given version (default api-version). --poll-interval=3s: Time delay between polling for replication controller status after the update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". --rollback[=false]: If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout - --schema-cache-dir="/tmp/kubectl.schema": If non-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema' + --schema-cache-dir="~/.kube/schema": If non-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' -a, --show-all[=false]: When printing, show all resources (default hide terminated pods.) --sort-by="": If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. 'ObjectMeta.Name'). The field in the API resource specified by this JSONPath expression must be an integer or a string. --template="": Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. @@ -119,7 +119,7 @@ $ kubectl rolling-update frontend --image=image:v2 * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-09-11 20:48:33.293748592 +0000 UTC +###### Auto generated by spf13/cobra at 2015-09-17 21:39:48.40113721 +0000 UTC [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_rolling-update.md?pixel)]() diff --git a/pkg/client/unversioned/clientcmd/loader.go b/pkg/client/unversioned/clientcmd/loader.go index 09c9ae7a8e6..2d8a99a6009 100644 --- a/pkg/client/unversioned/clientcmd/loader.go +++ b/pkg/client/unversioned/clientcmd/loader.go @@ -36,11 +36,14 @@ import ( const ( RecommendedConfigPathFlag = "kubeconfig" RecommendedConfigPathEnvVar = "KUBECONFIG" - RecommendedHomeFileName = "/.kube/config" + RecommendedHomeDir = ".kube" + RecommendedFileName = "config" + RecommendedSchemaName = "schema" ) var OldRecommendedHomeFile = path.Join(os.Getenv("HOME"), "/.kube/.kubeconfig") -var RecommendedHomeFile = path.Join(os.Getenv("HOME"), RecommendedHomeFileName) +var RecommendedHomeFile = path.Join(os.Getenv("HOME"), RecommendedHomeDir, RecommendedFileName) +var RecommendedSchemaFile = path.Join(os.Getenv("HOME"), RecommendedHomeDir, RecommendedSchemaName) // ClientConfigLoadingRules is an ExplicitPath and string slice of specific locations that are used for merging together a Config // Callers can put the chain together however they want, but we'd recommend: diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index d45b1dac772..d73ea26804e 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -100,7 +100,7 @@ func NewDefaultPathOptions() *PathOptions { EnvVar: clientcmd.RecommendedConfigPathEnvVar, ExplicitFileFlag: clientcmd.RecommendedConfigPathFlag, - GlobalFileSubpath: clientcmd.RecommendedHomeFileName, + GlobalFileSubpath: path.Join(clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName), LoadingRules: clientcmd.NewDefaultClientConfigLoadingRules(), } diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 45fbe8771b9..4981e225dd9 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -24,6 +24,7 @@ import ( "io" "io/ioutil" "os" + "os/user" "path" "strconv" @@ -308,9 +309,63 @@ type schemaClient interface { Get() *client.Request } +func recursiveSplit(dir string) []string { + parent, file := path.Split(dir) + if len(parent) == 0 { + return []string{file} + } + return append(recursiveSplit(parent[:len(parent)-1]), file) +} + +func substituteUserHome(dir string) (string, error) { + if len(dir) == 0 || dir[0] != '~' { + return dir, nil + } + parts := recursiveSplit(dir) + if len(parts[0]) == 1 { + parts[0] = os.Getenv("HOME") + } else { + usr, err := user.Lookup(parts[0][1:]) + if err != nil { + return "", err + } + parts[0] = usr.HomeDir + } + return path.Join(parts...), nil +} + +func writeSchemaFile(schemaData []byte, cacheDir, cacheFile, prefix, groupVersion string) error { + if err := os.MkdirAll(path.Join(cacheDir, prefix, groupVersion), 0755); err != nil { + return err + } + tmpFile, err := ioutil.TempFile(cacheDir, "schema") + if err != nil { + // If we can't write, keep going. + if os.IsPermission(err) { + return nil + } + return err + } + if _, err := io.Copy(tmpFile, bytes.NewBuffer(schemaData)); err != nil { + return err + } + if err := os.Link(tmpFile.Name(), cacheFile); err != nil { + // If we can't write due to file existing, or permission problems, keep going. + if os.IsExist(err) || os.IsPermission(err) { + return nil + } + return err + } + return nil +} + func getSchemaAndValidate(c schemaClient, data []byte, prefix, groupVersion, cacheDir string) (err error) { var schemaData []byte - cacheFile := path.Join(cacheDir, prefix, groupVersion, schemaFileName) + fullDir, err := substituteUserHome(cacheDir) + if err != nil { + return err + } + cacheFile := path.Join(fullDir, prefix, groupVersion, schemaFileName) if len(cacheDir) != 0 { if schemaData, err = ioutil.ReadFile(cacheFile); err != nil && !os.IsNotExist(err) { @@ -326,17 +381,7 @@ func getSchemaAndValidate(c schemaClient, data []byte, prefix, groupVersion, cac return err } if len(cacheDir) != 0 { - if err = os.MkdirAll(path.Join(cacheDir, prefix, groupVersion), 0755); err != nil { - return err - } - tmpFile, err := ioutil.TempFile(cacheDir, "schema") - if err != nil { - return err - } - if _, err := io.Copy(tmpFile, bytes.NewBuffer(schemaData)); err != nil { - return err - } - if err := os.Link(tmpFile.Name(), cacheFile); err != nil && !os.IsExist(err) { + if err := writeSchemaFile(schemaData, fullDir, cacheFile, prefix, groupVersion); err != nil { return err } } diff --git a/pkg/kubectl/cmd/util/factory_test.go b/pkg/kubectl/cmd/util/factory_test.go index 734e13aaabd..beeb73d5939 100644 --- a/pkg/kubectl/cmd/util/factory_test.go +++ b/pkg/kubectl/cmd/util/factory_test.go @@ -22,6 +22,7 @@ import ( "io/ioutil" "net/http" "os" + "os/user" "path" "sort" "strings" @@ -302,3 +303,32 @@ func TestValidateCachesSchema(t *testing.T) { t.Errorf("unexpected cache file error: %v", err) } } + +func TestSubstitueUser(t *testing.T) { + usr, _ := user.Current() + tests := []struct { + input string + expected string + expectErr bool + }{ + {input: "~/foo", expected: path.Join(os.Getenv("HOME"), "foo")}, + {input: "~" + usr.Username + "/bar", expected: usr.HomeDir + "/bar"}, + {input: "/foo/bar", expected: "/foo/bar"}, + {input: "~doesntexit/bar", expectErr: true}, + } + for _, test := range tests { + output, err := substituteUserHome(test.input) + if test.expectErr { + if err == nil { + t.Error("unexpected non-error") + } + continue + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if output != test.expected { + t.Errorf("expected: %s, saw: %s", test.expected, output) + } + } +} diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index c75bc0c5fb8..32a3f89b30c 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -279,7 +279,7 @@ func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration { func AddValidateFlags(cmd *cobra.Command) { cmd.Flags().Bool("validate", true, "If true, use a schema to validate the input before sending it") - cmd.Flags().String("schema-cache-dir", "/tmp/kubectl.schema", "If non-empty, load/store cached API schemas in this directory, default is '/tmp/kubectl.schema'") + cmd.Flags().String("schema-cache-dir", fmt.Sprintf("~/%s/%s", clientcmd.RecommendedHomeDir, clientcmd.RecommendedSchemaName), fmt.Sprintf("If non-empty, load/store cached API schemas in this directory, default is '$HOME/%s/%s'", clientcmd.RecommendedHomeDir, clientcmd.RecommendedSchemaName)) } func ReadConfigDataFromReader(reader io.Reader, source string) ([]byte, error) {