mirror of https://github.com/k3s-io/k3s
Kubectl namespace support
Add unit test for load namespace info Different message on display of namespace versus setting namespacepull/6/head
parent
78df01172a
commit
5a7aced7b0
|
@ -25,6 +25,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||
|
@ -55,6 +56,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))
|
||||
|
@ -63,6 +66,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
|
|||
cmds.AddCommand(NewCmdCreate(out))
|
||||
cmds.AddCommand(NewCmdUpdate(out))
|
||||
cmds.AddCommand(NewCmdDelete(out))
|
||||
cmds.AddCommand(NewCmdNamespace(out))
|
||||
|
||||
if err := cmds.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
|
@ -138,6 +142,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{}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ Examples:
|
|||
data, err := readConfigData(filename)
|
||||
checkErr(err)
|
||||
|
||||
err = kubectl.Modify(out, getKubeClient(cmd).RESTClient, kubectl.ModifyCreate, data)
|
||||
err = kubectl.Modify(out, getKubeClient(cmd).RESTClient, getKubeNamespace(cmd), kubectl.ModifyCreate, data)
|
||||
checkErr(err)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ Examples:
|
|||
}
|
||||
|
||||
// TODO Add ability to require a resource-version check for delete.
|
||||
err = kubectl.Modify(out, getKubeClient(cmd).RESTClient, kubectl.ModifyDelete, data)
|
||||
err = kubectl.Modify(out, getKubeClient(cmd).RESTClient, getKubeNamespace(cmd), kubectl.ModifyDelete, data)
|
||||
checkErr(err)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ Examples:
|
|||
outputFormat := getFlagString(cmd, "output")
|
||||
templateFile := getFlagString(cmd, "template")
|
||||
selector := getFlagString(cmd, "selector")
|
||||
err := kubectl.Get(out, getKubeClient(cmd).RESTClient, resource, id, selector, outputFormat, getFlagBool(cmd, "no-headers"), templateFile)
|
||||
err := kubectl.Get(out, getKubeClient(cmd).RESTClient, getKubeNamespace(cmd), resource, id, selector, outputFormat, getFlagBool(cmd, "no-headers"), templateFile)
|
||||
checkErr(err)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdNamespace(out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "namespace [<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 [<namespace>]")
|
||||
}
|
||||
checkErr(err)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
|
@ -46,7 +46,7 @@ Examples:
|
|||
data, err := readConfigData(filename)
|
||||
checkErr(err)
|
||||
|
||||
err = kubectl.Modify(out, getKubeClient(cmd).RESTClient, kubectl.ModifyUpdate, data)
|
||||
err = kubectl.Modify(out, getKubeClient(cmd).RESTClient, getKubeNamespace(cmd), kubectl.ModifyUpdate, data)
|
||||
checkErr(err)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -23,13 +23,13 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
)
|
||||
|
||||
func Get(w io.Writer, c *client.RESTClient, resource string, id string, selector string, format string, noHeaders bool, templateFile string) error {
|
||||
func Get(w io.Writer, c *client.RESTClient, namespace string, resource string, id string, selector string, format string, noHeaders bool, templateFile string) error {
|
||||
path, err := resolveResource(resolveToPath, resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := c.Verb("GET").Path(path)
|
||||
r := c.Verb("GET").Namespace(namespace).Path(path)
|
||||
if len(id) > 0 {
|
||||
r.Path(id)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||
"gopkg.in/v1/yaml"
|
||||
)
|
||||
|
@ -68,6 +69,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -33,7 +33,7 @@ const (
|
|||
ModifyDelete = ModifyAction("delete")
|
||||
)
|
||||
|
||||
func Modify(w io.Writer, c *client.RESTClient, action ModifyAction, data []byte) error {
|
||||
func Modify(w io.Writer, c *client.RESTClient, namespace string, action ModifyAction, data []byte) error {
|
||||
if action != ModifyCreate && action != ModifyUpdate && action != ModifyDelete {
|
||||
return fmt.Errorf("Action not recognized")
|
||||
}
|
||||
|
@ -64,11 +64,11 @@ func Modify(w io.Writer, c *client.RESTClient, action ModifyAction, data []byte)
|
|||
var id string
|
||||
switch action {
|
||||
case "create":
|
||||
id, err = doCreate(c, resource, data)
|
||||
id, err = doCreate(c, namespace, resource, data)
|
||||
case "update":
|
||||
id, err = doUpdate(c, resource, obj)
|
||||
id, err = doUpdate(c, namespace, resource, obj)
|
||||
case "delete":
|
||||
id, err = doDelete(c, resource, obj)
|
||||
id, err = doDelete(c, namespace, resource, obj)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -80,8 +80,8 @@ func Modify(w io.Writer, c *client.RESTClient, action ModifyAction, data []byte)
|
|||
}
|
||||
|
||||
// Creates the object then returns the ID of the newly created object.
|
||||
func doCreate(c *client.RESTClient, resource string, data []byte) (string, error) {
|
||||
obj, err := c.Post().Path(resource).Body(data).Do().Get()
|
||||
func doCreate(c *client.RESTClient, namespace string, resource string, data []byte) (string, error) {
|
||||
obj, err := c.Post().Namespace(namespace).Path(resource).Body(data).Do().Get()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ func doCreate(c *client.RESTClient, resource string, data []byte) (string, error
|
|||
}
|
||||
|
||||
// Creates the object then returns the ID of the newly created object.
|
||||
func doUpdate(c *client.RESTClient, resource string, obj runtime.Object) (string, error) {
|
||||
func doUpdate(c *client.RESTClient, namespace string, resource string, obj runtime.Object) (string, error) {
|
||||
// Figure out the ID of the object to update by introspecting into the
|
||||
// object.
|
||||
id, err := getIDFromObj(obj)
|
||||
|
@ -99,7 +99,7 @@ func doUpdate(c *client.RESTClient, resource string, obj runtime.Object) (string
|
|||
|
||||
// Get the object from the server to find out its current resource
|
||||
// version to prevent race conditions in updating the object.
|
||||
serverObj, err := c.Get().Path(resource).Path(id).Do().Get()
|
||||
serverObj, err := c.Get().Namespace(namespace).Path(resource).Path(id).Do().Get()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Item Name %s does not exist for update: %v", id, err)
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ func doUpdate(c *client.RESTClient, resource string, obj runtime.Object) (string
|
|||
}
|
||||
|
||||
// Do the update.
|
||||
err = c.Put().Path(resource).Path(id).Body(data).Do().Error()
|
||||
err = c.Put().Namespace(namespace).Path(resource).Path(id).Body(data).Do().Error()
|
||||
fmt.Printf("r: %q, i: %q, d: %s", resource, id, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -132,7 +132,7 @@ func doUpdate(c *client.RESTClient, resource string, obj runtime.Object) (string
|
|||
return id, nil
|
||||
}
|
||||
|
||||
func doDelete(c *client.RESTClient, resource string, obj runtime.Object) (string, error) {
|
||||
func doDelete(c *client.RESTClient, namespace string, resource string, obj runtime.Object) (string, error) {
|
||||
id, err := getIDFromObj(obj)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Name not retrievable from object for delete: %v", err)
|
||||
|
@ -141,7 +141,7 @@ func doDelete(c *client.RESTClient, resource string, obj runtime.Object) (string
|
|||
return "", fmt.Errorf("The supplied resource has no Name and cannot be deleted")
|
||||
}
|
||||
|
||||
err = c.Delete().Path(resource).Path(id).Do().Error()
|
||||
err = c.Delete().Namespace(namespace).Path(resource).Path(id).Do().Error()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue