diff --git a/cmd/osc/osc.go b/cmd/osc/osc.go index b4157c43ae02..8c48957fe7d8 100644 --- a/cmd/osc/osc.go +++ b/cmd/osc/osc.go @@ -73,6 +73,8 @@ func NewCmdOpenShiftClient(name string) *cobra.Command { cmd.PersistentFlags().String("client-certificate", "", "Path to a client certificate for TLS.") cmd.PersistentFlags().String("client-key", "", "Path to a client key file for TLS.") cmd.PersistentFlags().Bool("insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.") + cmd.PersistentFlags().String("ns-path", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace info file that holds the namespace context to use for CLI requests.") + cmd.PersistentFlags().StringP("namespace", "n", "", "If present, the namespace scope for this CLI request.") f := kubectl.NewFactory() out := os.Stdout diff --git a/pkg/cmd/client/client.go b/pkg/cmd/client/client.go index 3ec6376b4eba..c576c310020a 100644 --- a/pkg/cmd/client/client.go +++ b/pkg/cmd/client/client.go @@ -83,6 +83,8 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, cmds.PersistentFlags().String("client-certificate", "", "Path to a client certificate for TLS.") cmds.PersistentFlags().String("client-key", "", "Path to a client key file for TLS.") cmds.PersistentFlags().Bool("insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.") + cmds.PersistentFlags().String("ns-path", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace info file that holds the namespace context to use for CLI requests.") + cmds.PersistentFlags().StringP("namespace", "n", "", "If present, the namespace scope for this CLI request.") f := kubectl.NewFactory() out := os.Stdout @@ -93,6 +95,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, cmds.AddCommand(f.NewCmdCreate(out)) cmds.AddCommand(f.NewCmdUpdate(out)) cmds.AddCommand(f.NewCmdDelete(out)) + cmds.AddCommand(f.NewCmdNamespace(out)) return cmds } diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index d154dd901f0d..6941e28eb35a 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -86,6 +86,8 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, cmds.PersistentFlags().String("client-certificate", "", "Path to a client certificate for TLS.") cmds.PersistentFlags().String("client-key", "", "Path to a client key file for TLS.") cmds.PersistentFlags().Bool("insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.") + cmds.PersistentFlags().String("ns-path", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace info file that holds the namespace context to use for CLI requests.") + cmds.PersistentFlags().StringP("namespace", "n", "", "If present, the namespace scope for this CLI request.") cmds.AddCommand(NewCmdVersion(out)) cmds.AddCommand(NewCmdProxy(out)) @@ -94,6 +96,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, cmds.AddCommand(f.NewCmdCreate(out)) cmds.AddCommand(f.NewCmdUpdate(out)) cmds.AddCommand(f.NewCmdDelete(out)) + cmds.AddCommand(f.NewCmdNamespace(out)) if err := cmds.Execute(); err != nil { os.Exit(1) @@ -169,6 +172,23 @@ func getFlagInt(cmd *cobra.Command, flag string) int { return v } +func getKubeNamespace(cmd *cobra.Command) string { + result := api.NamespaceDefault + if ns := getFlagString(cmd, "namespace"); len(ns) > 0 { + result = ns + glog.V(2).Infof("Using namespace from -ns flag") + } else { + nsPath := getFlagString(cmd, "ns-path") + nsInfo, err := kubectl.LoadNamespaceInfo(nsPath) + if err != nil { + glog.Fatalf("Error loading current namespace: %v", err) + } + result = nsInfo.Namespace + } + glog.V(2).Infof("Using namespace %s", result) + return result +} + func getKubeConfig(cmd *cobra.Command) *client.Config { config := &client.Config{} diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index a262169652ba..c7790111ffa3 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -44,6 +44,7 @@ Examples: usageError(cmd, "Must specify filename to create") } mapping, namespace, name, data := ResourceFromFile(filename, f.Typer, f.Mapper) + namespace = getKubeNamespace(cmd) client, err := f.Client(cmd, mapping) checkErr(err) err = kubectl.NewRESTHelper(client, mapping).Create(namespace, data) diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index aa5ffe0dd568..17f385e2d930 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -51,6 +51,7 @@ Examples: Run: func(cmd *cobra.Command, args []string) { filename := getFlagString(cmd, "filename") mapping, namespace, name := ResourceFromArgsOrFile(cmd, args, filename, f.Typer, f.Mapper) + namespace = getKubeNamespace(cmd) client, err := f.Client(cmd, mapping) checkErr(err) err = kubectl.NewRESTHelper(client, mapping).Delete(namespace, name) diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index d0f974a88774..323cc0f781a9 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -33,6 +33,7 @@ This command joins many API calls together to form a detailed description of a given resource.`, Run: func(cmd *cobra.Command, args []string) { mapping, namespace, name := ResourceFromArgs(cmd, args, f.Mapper) + namespace = getKubeNamespace(cmd) describe, err := f.Describe(cmd, mapping) checkErr(err) diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 3daa360b0db1..c7d239a13c56 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -46,6 +46,7 @@ Examples: `, Run: func(cmd *cobra.Command, args []string) { mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, f.Mapper) + namespace = getKubeNamespace(cmd) selector := getFlagString(cmd, "selector") labels, err := labels.ParseSelector(selector) diff --git a/pkg/kubectl/cmd/namespace.go b/pkg/kubectl/cmd/namespace.go new file mode 100644 index 000000000000..1fcb3ce69f16 --- /dev/null +++ b/pkg/kubectl/cmd/namespace.go @@ -0,0 +1,60 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "io" + + "fmt" + "github.com/openshift/origin/pkg/kubectl" + "github.com/spf13/cobra" +) + +func (f *Factory) NewCmdNamespace(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "namespace []", + Short: "Set and view the current Kubernetes namespace", + Long: `Set and view the current Kubernetes namespace scope for command line requests. + +A Kubernetes namespace subdivides the cluster into groups of logically related pods, services, and replication controllers. + +Examples: + $ kubectl namespace + Using namespace default + + $ kubectl namespace other + Set current namespace to other`, + Run: func(cmd *cobra.Command, args []string) { + nsPath := getFlagString(cmd, "ns-path") + var err error + var ns *kubectl.NamespaceInfo + switch len(args) { + case 0: + ns, err = kubectl.LoadNamespaceInfo(nsPath) + fmt.Printf("Using namespace %s\n", ns.Namespace) + case 1: + ns = &kubectl.NamespaceInfo{Namespace: args[0]} + err = kubectl.SaveNamespaceInfo(nsPath, ns) + fmt.Printf("Set current namespace to %s\n", ns.Namespace) + default: + usageError(cmd, "kubectl namespace []") + } + checkErr(err) + }, + } + return cmd +} diff --git a/pkg/kubectl/cmd/update.go b/pkg/kubectl/cmd/update.go index 01ee8cfa86ec..705cee2f356a 100644 --- a/pkg/kubectl/cmd/update.go +++ b/pkg/kubectl/cmd/update.go @@ -44,6 +44,7 @@ Examples: usageError(cmd, "Must specify filename to create") } mapping, namespace, name, data := ResourceFromFile(filename, f.Typer, f.Mapper) + namespace = getKubeNamespace(cmd) client, err := f.Client(cmd, mapping) checkErr(err) err = kubectl.NewRESTHelper(client, mapping).Update(namespace, name, true, data) diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go index 05ce572bb5b4..911cff9f15f0 100644 --- a/pkg/kubectl/kubectl.go +++ b/pkg/kubectl/kubectl.go @@ -26,6 +26,7 @@ import ( "reflect" "strings" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" api "github.com/openshift/origin/pkg/api2" client "github.com/openshift/origin/pkg/client2" "github.com/openshift/origin/pkg/labels" @@ -65,6 +66,39 @@ type AuthInfo struct { Insecure *bool } +type NamespaceInfo struct { + Namespace string +} + +// LoadNamespaceInfo parses a NamespaceInfo object from a file path. It creates a file at the specified path if it doesn't exist with the default namespace. +func LoadNamespaceInfo(path string) (*NamespaceInfo, error) { + var ns NamespaceInfo + if _, err := os.Stat(path); os.IsNotExist(err) { + ns.Namespace = api.NamespaceDefault + err = SaveNamespaceInfo(path, &ns) + return &ns, err + } + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &ns) + if err != nil { + return nil, err + } + return &ns, err +} + +// SaveNamespaceInfo saves a NamespaceInfo object at the specified file path. +func SaveNamespaceInfo(path string, ns *NamespaceInfo) error { + if !util.IsDNSLabel(ns.Namespace) { + return fmt.Errorf("Namespace %s is not a valid DNS Label", ns.Namespace) + } + data, err := json.Marshal(ns) + err = ioutil.WriteFile(path, data, 0600) + return err +} + // LoadAuthInfo parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist. func LoadAuthInfo(path string, r io.Reader) (*AuthInfo, error) { var auth AuthInfo diff --git a/pkg/kubectl/kubectl_test.go b/pkg/kubectl/kubectl_test.go index 3bea97bf03c7..13adba59fcd5 100644 --- a/pkg/kubectl/kubectl_test.go +++ b/pkg/kubectl/kubectl_test.go @@ -33,6 +33,58 @@ func validateAction(expectedAction, actualAction client.FakeAction, t *testing.T } } +func TestLoadNamespaceInfo(t *testing.T) { + loadNamespaceInfoTests := []struct { + nsData string + nsInfo *NamespaceInfo + }{ + { + `{"Namespace":"test"}`, + &NamespaceInfo{Namespace: "test"}, + }, + { + "", nil, + }, + { + "missing", + &NamespaceInfo{Namespace: "default"}, + }, + } + for _, loadNamespaceInfoTest := range loadNamespaceInfoTests { + tt := loadNamespaceInfoTest + nsfile, err := ioutil.TempFile("", "testNamespaceInfo") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if tt.nsData != "missing" { + defer os.Remove(nsfile.Name()) + defer nsfile.Close() + _, err := nsfile.WriteString(tt.nsData) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } else { + nsfile.Close() + os.Remove(nsfile.Name()) + } + nsInfo, err := LoadNamespaceInfo(nsfile.Name()) + if len(tt.nsData) == 0 && tt.nsData != "missing" { + if err == nil { + t.Error("LoadNamespaceInfo didn't fail on an empty file") + } + continue + } + if tt.nsData != "missing" { + if err != nil { + t.Errorf("Unexpected error: %v, %v", tt.nsData, err) + } + if !reflect.DeepEqual(nsInfo, tt.nsInfo) { + t.Errorf("Expected %v, got %v", tt.nsInfo, nsInfo) + } + } + } +} + func TestLoadAuthInfo(t *testing.T) { loadAuthInfoTests := []struct { authData string