mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-10 21:50:05 +00:00
add utility for binding flags and building api server clients
This commit is contained in:
@@ -24,9 +24,10 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -34,23 +35,29 @@ import (
|
||||
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types
|
||||
// of resources and different API sets.
|
||||
type Factory struct {
|
||||
Mapper meta.RESTMapper
|
||||
Typer runtime.ObjectTyper
|
||||
Client func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error)
|
||||
Describer func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error)
|
||||
Printer func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error)
|
||||
ClientBuilder clientcmd.Builder
|
||||
Mapper meta.RESTMapper
|
||||
Typer runtime.ObjectTyper
|
||||
Client func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error)
|
||||
Describer func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error)
|
||||
Printer func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error)
|
||||
}
|
||||
|
||||
// NewFactory creates a factory with the default Kubernetes resources defined
|
||||
func NewFactory() *Factory {
|
||||
func NewFactory(clientBuilder clientcmd.Builder) *Factory {
|
||||
return &Factory{
|
||||
Mapper: latest.RESTMapper,
|
||||
Typer: api.Scheme,
|
||||
ClientBuilder: clientBuilder,
|
||||
Mapper: latest.RESTMapper,
|
||||
Typer: api.Scheme,
|
||||
Client: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) {
|
||||
return getKubeClient(cmd), nil
|
||||
return clientBuilder.Client()
|
||||
},
|
||||
Describer: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error) {
|
||||
describer, ok := kubectl.DescriberFor(mapping.Kind, getKubeClient(cmd))
|
||||
client, err := clientBuilder.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
describer, ok := kubectl.DescriberFor(mapping.Kind, client)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind)
|
||||
}
|
||||
@@ -73,23 +80,17 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
|
||||
Run: runHelp,
|
||||
}
|
||||
|
||||
f.ClientBuilder.BindFlags(cmds.PersistentFlags())
|
||||
|
||||
// Globally persistent flags across all subcommands.
|
||||
// TODO Change flag names to consts to allow safer lookup from subcommands.
|
||||
// TODO Add a verbose flag that turns on glog logging. Probably need a way
|
||||
// to do that automatically for every subcommand.
|
||||
cmds.PersistentFlags().StringP("server", "s", "", "Kubernetes apiserver to connect to")
|
||||
cmds.PersistentFlags().StringP("auth-path", "a", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file. If missing, prompt the user. Only used if using https.")
|
||||
cmds.PersistentFlags().Bool("match-server-version", false, "Require server version to match client version")
|
||||
cmds.PersistentFlags().String("api-version", latest.Version, "The version of the API to use against the server")
|
||||
cmds.PersistentFlags().String("certificate-authority", "", "Path to a certificate file for the certificate authority")
|
||||
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))
|
||||
cmds.AddCommand(f.NewCmdVersion(out))
|
||||
cmds.AddCommand(f.NewCmdProxy(out))
|
||||
|
||||
cmds.AddCommand(f.NewCmdGet(out))
|
||||
cmds.AddCommand(f.NewCmdDescribe(out))
|
||||
@@ -99,7 +100,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
|
||||
cmds.AddCommand(f.NewCmdDelete(out))
|
||||
|
||||
cmds.AddCommand(NewCmdNamespace(out))
|
||||
cmds.AddCommand(NewCmdLog(out))
|
||||
cmds.AddCommand(f.NewCmdLog(out))
|
||||
|
||||
if err := cmds.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
@@ -150,69 +151,3 @@ func getExplicitKubeNamespace(cmd *cobra.Command) (string, bool) {
|
||||
// value and return its value and true.
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetKubeConfig returns a config used for the Kubernetes client with CLI
|
||||
// options parsed.
|
||||
func GetKubeConfig(cmd *cobra.Command) *client.Config {
|
||||
config := &client.Config{}
|
||||
|
||||
var host string
|
||||
if hostFlag := GetFlagString(cmd, "server"); len(hostFlag) > 0 {
|
||||
host = hostFlag
|
||||
glog.V(2).Infof("Using server from -s flag: %s", host)
|
||||
} else if len(os.Getenv("KUBERNETES_MASTER")) > 0 {
|
||||
host = os.Getenv("KUBERNETES_MASTER")
|
||||
glog.V(2).Infof("Using server from env var KUBERNETES_MASTER: %s", host)
|
||||
} else {
|
||||
// TODO: eventually apiserver should start on 443 and be secure by default
|
||||
host = "http://localhost:8080"
|
||||
glog.V(2).Infof("No server found in flag or env var, using default: %s", host)
|
||||
}
|
||||
config.Host = host
|
||||
|
||||
if client.IsConfigTransportTLS(config) {
|
||||
// Get the values from the file on disk (or from the user at the
|
||||
// command line). Override them with the command line parameters, if
|
||||
// provided.
|
||||
authPath := GetFlagString(cmd, "auth-path")
|
||||
authInfo, err := kubectl.LoadClientAuthInfoOrPrompt(authPath, os.Stdin)
|
||||
// TODO: handle the case where the file could not be written but
|
||||
// we still got a user/pass from prompting.
|
||||
if err != nil {
|
||||
glog.Fatalf("Error loading auth: %v", err)
|
||||
}
|
||||
|
||||
config.Username = authInfo.User
|
||||
config.Password = authInfo.Password
|
||||
// First priority is flag, then file.
|
||||
config.CAFile = FirstNonEmptyString(GetFlagString(cmd, "certificate-authority"), authInfo.CAFile)
|
||||
config.CertFile = FirstNonEmptyString(GetFlagString(cmd, "client-certificate"), authInfo.CertFile)
|
||||
config.KeyFile = FirstNonEmptyString(GetFlagString(cmd, "client-key"), authInfo.KeyFile)
|
||||
config.BearerToken = authInfo.BearerToken
|
||||
// For config.Insecure, the command line ALWAYS overrides the authInfo
|
||||
// file, regardless of its setting.
|
||||
if insecureFlag := GetFlagBoolPtr(cmd, "insecure-skip-tls-verify"); insecureFlag != nil {
|
||||
config.Insecure = *insecureFlag
|
||||
} else if authInfo.Insecure != nil {
|
||||
config.Insecure = *authInfo.Insecure
|
||||
}
|
||||
}
|
||||
|
||||
// The API version (e.g. v1beta1), not the binary version.
|
||||
config.Version = GetFlagString(cmd, "api-version")
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func getKubeClient(cmd *cobra.Command) *client.Client {
|
||||
config := GetKubeConfig(cmd)
|
||||
|
||||
// The binary version.
|
||||
matchVersion := GetFlagBool(cmd, "match-server-version")
|
||||
|
||||
c, err := kubectl.GetKubeClient(config, matchVersion)
|
||||
if err != nil {
|
||||
glog.Fatalf("Error creating kubernetes client: %v", err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
@@ -21,7 +21,6 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/config"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
@@ -80,8 +79,10 @@ Examples:
|
||||
$ cat config.json | kubectl apply -f -
|
||||
<creates all resources listed in config.json>`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clientFunc := func(*meta.RESTMapping) (*client.RESTClient, error) {
|
||||
return getKubeClient(cmd).RESTClient, nil
|
||||
clientFunc := func(mapper *meta.RESTMapping) (config.RESTClientPoster, error) {
|
||||
client, err := f.Client(cmd, mapper)
|
||||
checkErr(err)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
filename := GetFlagString(cmd, "filename")
|
||||
|
@@ -21,7 +21,7 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func NewCmdLog(out io.Writer) *cobra.Command {
|
||||
func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "log <pod> <container>",
|
||||
Short: "Print the logs for a container in a pod",
|
||||
@@ -32,7 +32,8 @@ func NewCmdLog(out io.Writer) *cobra.Command {
|
||||
|
||||
namespace := getKubeNamespace(cmd)
|
||||
|
||||
client := getKubeClient(cmd)
|
||||
client, err := f.ClientBuilder.Client()
|
||||
checkErr(err)
|
||||
pod, err := client.Pods(namespace).Get(args[0])
|
||||
checkErr(err)
|
||||
|
||||
|
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdProxy(out io.Writer) *cobra.Command {
|
||||
func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "proxy",
|
||||
Short: "Run a proxy to the Kubernetes API server",
|
||||
@@ -32,7 +32,11 @@ func NewCmdProxy(out io.Writer) *cobra.Command {
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
port := GetFlagInt(cmd, "port")
|
||||
glog.Infof("Starting to serve on localhost:%d", port)
|
||||
server, err := kubectl.NewProxyServer(GetFlagString(cmd, "www"), GetKubeConfig(cmd), port)
|
||||
|
||||
clientConfig, err := f.ClientBuilder.Config()
|
||||
checkErr(err)
|
||||
|
||||
server, err := kubectl.NewProxyServer(GetFlagString(cmd, "www"), clientConfig, port)
|
||||
checkErr(err)
|
||||
glog.Fatal(server.Serve())
|
||||
},
|
||||
|
@@ -23,7 +23,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdVersion(out io.Writer) *cobra.Command {
|
||||
func (f *Factory) NewCmdVersion(out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print version of client and server",
|
||||
@@ -31,7 +31,10 @@ func NewCmdVersion(out io.Writer) *cobra.Command {
|
||||
if GetFlagBool(cmd, "client") {
|
||||
kubectl.GetClientVersion(out)
|
||||
} else {
|
||||
kubectl.GetVersion(out, getKubeClient(cmd))
|
||||
client, err := f.ClientBuilder.Client()
|
||||
checkErr(err)
|
||||
|
||||
kubectl.GetVersion(out, client)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@@ -20,7 +20,6 @@ package kubectl
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -28,7 +27,6 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||
@@ -90,34 +88,6 @@ func SaveNamespaceInfo(path string, ns *NamespaceInfo) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadClientAuthInfoOrPrompt parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
|
||||
func LoadClientAuthInfoOrPrompt(path string, r io.Reader) (*clientauth.Info, error) {
|
||||
var auth clientauth.Info
|
||||
// Prompt for user/pass and write a file if none exists.
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
auth.User = promptForString("Username", r)
|
||||
auth.Password = promptForString("Password", r)
|
||||
data, err := json.Marshal(auth)
|
||||
if err != nil {
|
||||
return &auth, err
|
||||
}
|
||||
err = ioutil.WriteFile(path, data, 0600)
|
||||
return &auth, err
|
||||
}
|
||||
authPtr, err := clientauth.LoadFromFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return authPtr, nil
|
||||
}
|
||||
|
||||
func promptForString(field string, r io.Reader) string {
|
||||
fmt.Printf("Please enter %s: ", field)
|
||||
var result string
|
||||
fmt.Fscan(r, &result)
|
||||
return result
|
||||
}
|
||||
|
||||
// TODO Move to labels package.
|
||||
func formatLabels(labelMap map[string]string) string {
|
||||
l := labels.Set(labelMap).String()
|
||||
|
@@ -17,15 +17,12 @@ limitations under the License.
|
||||
package kubectl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
|
||||
)
|
||||
|
||||
func validateAction(expectedAction, actualAction client.FakeAction, t *testing.T) {
|
||||
@@ -85,56 +82,3 @@ func TestLoadNamespaceInfo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadClientAuthInfoOrPrompt(t *testing.T) {
|
||||
loadAuthInfoTests := []struct {
|
||||
authData string
|
||||
authInfo *clientauth.Info
|
||||
r io.Reader
|
||||
}{
|
||||
{
|
||||
`{"user": "user", "password": "pass"}`,
|
||||
&clientauth.Info{User: "user", Password: "pass"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"", nil, nil,
|
||||
},
|
||||
{
|
||||
"missing",
|
||||
&clientauth.Info{User: "user", Password: "pass"},
|
||||
bytes.NewBufferString("user\npass"),
|
||||
},
|
||||
}
|
||||
for _, loadAuthInfoTest := range loadAuthInfoTests {
|
||||
tt := loadAuthInfoTest
|
||||
aifile, err := ioutil.TempFile("", "testAuthInfo")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if tt.authData != "missing" {
|
||||
defer os.Remove(aifile.Name())
|
||||
defer aifile.Close()
|
||||
_, err = aifile.WriteString(tt.authData)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
} else {
|
||||
aifile.Close()
|
||||
os.Remove(aifile.Name())
|
||||
}
|
||||
authInfo, err := LoadClientAuthInfoOrPrompt(aifile.Name(), tt.r)
|
||||
if len(tt.authData) == 0 && tt.authData != "missing" {
|
||||
if err == nil {
|
||||
t.Error("LoadClientAuthInfoOrPrompt didn't fail on empty file")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(authInfo, tt.authInfo) {
|
||||
t.Errorf("Expected %#v, got %#v", tt.authInfo, authInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user