Commit 2dbfb803 authored by deads2k's avatar deads2k

add utility for binding flags and building api server clients

parent ff1e9f4c
...@@ -19,9 +19,11 @@ package main ...@@ -19,9 +19,11 @@ package main
import ( import (
"os" "os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
) )
func main() { func main() {
cmd.NewFactory().Run(os.Stdout) clientBuilder := clientcmd.NewBuilder(clientcmd.NewPromptingAuthLoader(os.Stdin))
cmd.NewFactory(clientBuilder).Run(os.Stdout)
} }
/*
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 clientcmd
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
)
// AuthLoaders are used to build clientauth.Info objects.
type AuthLoader interface {
// LoadAuth takes a path to a config file and can then do anything it needs in order to return a valid clientauth.Info
LoadAuth(path string) (*clientauth.Info, error)
}
// default implementation of an AuthLoader
type defaultAuthLoader struct{}
// LoadAuth for defaultAuthLoader simply delegates to clientauth.LoadFromFile
func (*defaultAuthLoader) LoadAuth(path string) (*clientauth.Info, error) {
return clientauth.LoadFromFile(path)
}
type promptingAuthLoader struct {
reader io.Reader
}
// LoadAuth parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
func (a *promptingAuthLoader) LoadAuth(path string) (*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", a.reader)
auth.Password = promptForString("Password", a.reader)
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
}
// NewDefaultAuthLoader is an AuthLoader that parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
func NewPromptingAuthLoader(reader io.Reader) AuthLoader {
return &promptingAuthLoader{reader}
}
// NewDefaultAuthLoader returns a default implementation of an AuthLoader that only reads from a config file
func NewDefaultAuthLoader() AuthLoader {
return &defaultAuthLoader{}
}
/*
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 clientcmd
import (
"fmt"
"os"
"reflect"
"github.com/spf13/pflag"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
)
// Builder are used to bind and interpret command line flags to make it easy to get an api server client
type Builder interface {
// BindFlags must bind and keep track of all the flags required to build a client config object
BindFlags(flags *pflag.FlagSet)
// Config uses the values of the bound flags and builds a complete client config
Config() (*client.Config, error)
// Client calls BuildConfig under the covers and uses that config to return a client
Client() (*client.Client, error)
}
// cmdAuthInfo is used to track whether flags have been set
type cmdAuthInfo struct {
User StringFlag
Password StringFlag
CAFile StringFlag
CertFile StringFlag
KeyFile StringFlag
BearerToken StringFlag
Insecure BoolFlag
}
// builder is a default implementation of a Builder
type builder struct {
authLoader AuthLoader
cmdAuthInfo cmdAuthInfo
authPath string
apiserver string
apiVersion string
matchApiVersion bool
}
// NewBuilder returns a valid Builder that uses the passed authLoader. If authLoader is nil, the NewDefaultAuthLoader is used.
func NewBuilder(authLoader AuthLoader) Builder {
if authLoader == nil {
authLoader = NewDefaultAuthLoader()
}
return &builder{
authLoader: authLoader,
}
}
const (
FlagApiServer = "server"
FlagMatchApiVersion = "match-server-version"
FlagApiVersion = "api-version"
FlagAuthPath = "auth-path"
FlagInsecure = "insecure-skip-tls-verify"
FlagCertFile = "client-certificate"
FlagKeyFile = "client-key"
FlagCAFile = "certificate-authority"
FlagBearerToken = "token"
)
// BindFlags implements Builder
func (builder *builder) BindFlags(flags *pflag.FlagSet) {
flags.StringVarP(&builder.apiserver, FlagApiServer, "s", builder.apiserver, "The address of the Kubernetes API server")
flags.BoolVar(&builder.matchApiVersion, FlagMatchApiVersion, false, "Require server version to match client version")
flags.StringVar(&builder.apiVersion, FlagApiVersion, latest.Version, "The API version to use when talking to the server")
flags.StringVarP(&builder.authPath, FlagAuthPath, "a", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file. If missing, prompt the user. Only used if using https.")
flags.Var(&builder.cmdAuthInfo.Insecure, FlagInsecure, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.")
flags.Var(&builder.cmdAuthInfo.CertFile, FlagCertFile, "Path to a client key file for TLS.")
flags.Var(&builder.cmdAuthInfo.KeyFile, FlagKeyFile, "Path to a client key file for TLS.")
flags.Var(&builder.cmdAuthInfo.CAFile, FlagCAFile, "Path to a cert. file for the certificate authority.")
flags.Var(&builder.cmdAuthInfo.BearerToken, FlagBearerToken, "Bearer token for authentication to the API server.")
}
// Client implements Builder
func (builder *builder) Client() (*client.Client, error) {
clientConfig, err := builder.Config()
if err != nil {
return nil, err
}
c, err := client.New(clientConfig)
if err != nil {
return nil, err
}
if builder.matchApiVersion {
clientVersion := version.Get()
serverVersion, err := c.ServerVersion()
if err != nil {
return nil, fmt.Errorf("couldn't read version from server: %v\n", err)
}
if s := *serverVersion; !reflect.DeepEqual(clientVersion, s) {
return nil, fmt.Errorf("server version (%#v) differs from client version (%#v)!\n", s, clientVersion)
}
}
return c, nil
}
// Config implements Builder
func (builder *builder) Config() (*client.Config, error) {
clientConfig := client.Config{}
if len(builder.apiserver) > 0 {
clientConfig.Host = builder.apiserver
} else if len(os.Getenv("KUBERNETES_MASTER")) > 0 {
clientConfig.Host = os.Getenv("KUBERNETES_MASTER")
} else {
// TODO: eventually apiserver should start on 443 and be secure by default
clientConfig.Host = "http://localhost:8080"
}
clientConfig.Version = builder.apiVersion
// only try to read the auth information if we are secure
if client.IsConfigTransportTLS(&clientConfig) {
authInfoFileFound := true
authInfo, err := builder.authLoader.LoadAuth(builder.authPath)
if authInfo == nil && err != nil { // only consider failing if we don't have any auth info
if os.IsNotExist(err) { // if it's just a case of a missing file, simply flag the auth as not found and use the command line arguments
authInfoFileFound = false
authInfo = &clientauth.Info{}
} else {
return nil, err
}
}
// If provided, the command line options override options from the auth file
if !authInfoFileFound || builder.cmdAuthInfo.User.Provided() {
authInfo.User = builder.cmdAuthInfo.User.Value
}
if !authInfoFileFound || builder.cmdAuthInfo.Password.Provided() {
authInfo.Password = builder.cmdAuthInfo.Password.Value
}
if !authInfoFileFound || builder.cmdAuthInfo.CAFile.Provided() {
authInfo.CAFile = builder.cmdAuthInfo.CAFile.Value
}
if !authInfoFileFound || builder.cmdAuthInfo.CertFile.Provided() {
authInfo.CertFile = builder.cmdAuthInfo.CertFile.Value
}
if !authInfoFileFound || builder.cmdAuthInfo.KeyFile.Provided() {
authInfo.KeyFile = builder.cmdAuthInfo.KeyFile.Value
}
if !authInfoFileFound || builder.cmdAuthInfo.BearerToken.Provided() {
authInfo.BearerToken = builder.cmdAuthInfo.BearerToken.Value
}
if !authInfoFileFound || builder.cmdAuthInfo.Insecure.Provided() {
authInfo.Insecure = &builder.cmdAuthInfo.Insecure.Value
}
clientConfig, err = authInfo.MergeWithConfig(clientConfig)
if err != nil {
return nil, err
}
}
return &clientConfig, nil
}
/*
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 provides one stop shopping for a command line executable to bind the correct flags,
build the client config, and create a working client. The code for usage looks like this:
clientBuilder := clientcmd.NewBuilder(clientcmd.NewDefaultAuthLoader())
clientBuilder.BindFlags(cmds.PersistentFlags())
apiClient, err := clientBuilder.Client()
*/
package clientcmd
/*
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 clientcmd
import (
"fmt"
"strconv"
"github.com/spf13/pflag"
)
// FlagProvider adds a check for whether .Set was called on this flag variable
type FlagProvider interface {
// Provided returns true iff .Set was called on this flag
Provided() bool
pflag.Value
}
// StringFlag implements FlagProvider
type StringFlag struct {
Default string
Value string
WasProvided bool
}
// SetDefault sets a default value for a flag while keeping Provided() false
func (flag *StringFlag) SetDefault(value string) {
flag.Value = value
flag.WasProvided = false
}
func (flag *StringFlag) Set(value string) error {
flag.Value = value
flag.WasProvided = true
return nil
}
func (flag *StringFlag) Type() string {
return "string"
}
func (flag *StringFlag) Provided() bool {
return flag.WasProvided
}
func (flag *StringFlag) String() string {
return flag.Value
}
// BoolFlag implements FlagProvider
type BoolFlag struct {
Default bool
Value bool
WasProvided bool
}
// SetDefault sets a default value for a flag while keeping Provided() false
func (flag *BoolFlag) SetDefault(value bool) {
flag.Value = value
flag.WasProvided = false
}
func (flag *BoolFlag) Set(value string) error {
boolValue, err := strconv.ParseBool(value)
if err != nil {
return err
}
flag.Value = boolValue
flag.WasProvided = true
return nil
}
func (flag *BoolFlag) Type() string {
return "bool"
}
func (flag *BoolFlag) Provided() bool {
return flag.WasProvided
}
func (flag *BoolFlag) String() string {
return fmt.Sprintf("%s", flag.Value)
}
...@@ -25,6 +25,7 @@ type FlagSet interface { ...@@ -25,6 +25,7 @@ type FlagSet interface {
} }
// BindClientConfigFlags registers a standard set of CLI flags for connecting to a Kubernetes API server. // BindClientConfigFlags registers a standard set of CLI flags for connecting to a Kubernetes API server.
// TODO this method is superceded by pkg/client/clientcmd/client_builder.go
func BindClientConfigFlags(flags FlagSet, config *Config) { func BindClientConfigFlags(flags FlagSet, config *Config) {
flags.StringVar(&config.Host, "master", config.Host, "The address of the Kubernetes API server") flags.StringVar(&config.Host, "master", config.Host, "The address of the Kubernetes API server")
flags.StringVar(&config.Version, "api_version", config.Version, "The API version to use when talking to the server") flags.StringVar(&config.Version, "api_version", config.Version, "The API version to use when talking to the server")
......
...@@ -26,14 +26,18 @@ import ( ...@@ -26,14 +26,18 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
type RESTClientPoster interface {
Post() *client.Request
}
// ClientFunc returns the RESTClient defined for given resource // ClientFunc returns the RESTClient defined for given resource
type ClientFunc func(mapping *meta.RESTMapping) (*client.RESTClient, error) type ClientPosterFunc func(mapping *meta.RESTMapping) (RESTClientPoster, error)
// CreateObjects creates bulk of resources provided by items list. Each item must // CreateObjects creates bulk of resources provided by items list. Each item must
// be valid API type. It requires ObjectTyper to parse the Version and Kind and // be valid API type. It requires ObjectTyper to parse the Version and Kind and
// RESTMapper to get the resource URI and REST client that knows how to create // RESTMapper to get the resource URI and REST client that knows how to create
// given type // given type
func CreateObjects(typer runtime.ObjectTyper, mapper meta.RESTMapper, clientFor ClientFunc, objects []runtime.Object) util.ErrorList { func CreateObjects(typer runtime.ObjectTyper, mapper meta.RESTMapper, clientFor ClientPosterFunc, objects []runtime.Object) util.ErrorList {
allErrors := util.ErrorList{} allErrors := util.ErrorList{}
for i, obj := range objects { for i, obj := range objects {
version, kind, err := typer.ObjectVersionAndKind(obj) version, kind, err := typer.ObjectVersionAndKind(obj)
...@@ -65,7 +69,7 @@ func CreateObjects(typer runtime.ObjectTyper, mapper meta.RESTMapper, clientFor ...@@ -65,7 +69,7 @@ func CreateObjects(typer runtime.ObjectTyper, mapper meta.RESTMapper, clientFor
// CreateObject creates the obj using the provided clients and the resource URI // CreateObject creates the obj using the provided clients and the resource URI
// mapping. It reports ValidationError when the object is missing the Metadata // mapping. It reports ValidationError when the object is missing the Metadata
// or the Name and it will report any error occured during create REST call // or the Name and it will report any error occured during create REST call
func CreateObject(client *client.RESTClient, mapping *meta.RESTMapping, obj runtime.Object) *errs.ValidationError { func CreateObject(client RESTClientPoster, mapping *meta.RESTMapping, obj runtime.Object) *errs.ValidationError {
name, err := mapping.MetadataAccessor.Name(obj) name, err := mapping.MetadataAccessor.Name(obj)
if err != nil || name == "" { if err != nil || name == "" {
return errs.NewFieldRequired("name", err) return errs.NewFieldRequired("name", err)
......
...@@ -35,7 +35,7 @@ func getTyperAndMapper() (runtime.ObjectTyper, meta.RESTMapper) { ...@@ -35,7 +35,7 @@ func getTyperAndMapper() (runtime.ObjectTyper, meta.RESTMapper) {
return api.Scheme, latest.RESTMapper return api.Scheme, latest.RESTMapper
} }
func getFakeClient(t *testing.T, validURLs []string) (ClientFunc, *httptest.Server) { func getFakeClient(t *testing.T, validURLs []string) (ClientPosterFunc, *httptest.Server) {
handlerFunc := func(w http.ResponseWriter, r *http.Request) { handlerFunc := func(w http.ResponseWriter, r *http.Request) {
for _, u := range validURLs { for _, u := range validURLs {
if u == r.RequestURI { if u == r.RequestURI {
...@@ -45,7 +45,7 @@ func getFakeClient(t *testing.T, validURLs []string) (ClientFunc, *httptest.Serv ...@@ -45,7 +45,7 @@ func getFakeClient(t *testing.T, validURLs []string) (ClientFunc, *httptest.Serv
t.Errorf("Unexpected HTTP request: %s, expected %v", r.RequestURI, validURLs) t.Errorf("Unexpected HTTP request: %s, expected %v", r.RequestURI, validURLs)
} }
server := httptest.NewServer(http.HandlerFunc(handlerFunc)) server := httptest.NewServer(http.HandlerFunc(handlerFunc))
return func(mapping *meta.RESTMapping) (*client.RESTClient, error) { return func(mapping *meta.RESTMapping) (RESTClientPoster, error) {
fakeCodec := runtime.CodecFor(api.Scheme, "v1beta1") fakeCodec := runtime.CodecFor(api.Scheme, "v1beta1")
fakeUri, _ := url.Parse(server.URL + "/api/v1beta1") fakeUri, _ := url.Parse(server.URL + "/api/v1beta1")
return client.NewRESTClient(fakeUri, fakeCodec), nil return client.NewRESTClient(fakeUri, fakeCodec), nil
...@@ -134,7 +134,7 @@ func TestCreateNoClientItems(t *testing.T) { ...@@ -134,7 +134,7 @@ func TestCreateNoClientItems(t *testing.T) {
typer, mapper := getTyperAndMapper() typer, mapper := getTyperAndMapper()
_, s := getFakeClient(t, []string{"/api/v1beta1/pods", "/api/v1beta1/services"}) _, s := getFakeClient(t, []string{"/api/v1beta1/pods", "/api/v1beta1/services"})
noClientFunc := func(mapping *meta.RESTMapping) (*client.RESTClient, error) { noClientFunc := func(mapping *meta.RESTMapping) (RESTClientPoster, error) {
return nil, fmt.Errorf("no client") return nil, fmt.Errorf("no client")
} }
......
...@@ -24,9 +24,10 @@ import ( ...@@ -24,9 +24,10 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "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/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
...@@ -34,23 +35,29 @@ import ( ...@@ -34,23 +35,29 @@ import (
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types // Factory provides abstractions that allow the Kubectl command to be extended across multiple types
// of resources and different API sets. // of resources and different API sets.
type Factory struct { type Factory struct {
ClientBuilder clientcmd.Builder
Mapper meta.RESTMapper Mapper meta.RESTMapper
Typer runtime.ObjectTyper Typer runtime.ObjectTyper
Client func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error) Client func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error)
Describer func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, 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) Printer func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error)
} }
// NewFactory creates a factory with the default Kubernetes resources defined // NewFactory creates a factory with the default Kubernetes resources defined
func NewFactory() *Factory { func NewFactory(clientBuilder clientcmd.Builder) *Factory {
return &Factory{ return &Factory{
ClientBuilder: clientBuilder,
Mapper: latest.RESTMapper, Mapper: latest.RESTMapper,
Typer: api.Scheme, Typer: api.Scheme,
Client: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) { 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: 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 { if !ok {
return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind) 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.`, ...@@ -73,23 +80,17 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
Run: runHelp, Run: runHelp,
} }
f.ClientBuilder.BindFlags(cmds.PersistentFlags())
// Globally persistent flags across all subcommands. // Globally persistent flags across all subcommands.
// TODO Change flag names to consts to allow safer lookup from 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 // TODO Add a verbose flag that turns on glog logging. Probably need a way
// to do that automatically for every subcommand. // 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().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.PersistentFlags().StringP("namespace", "n", "", "If present, the namespace scope for this CLI request.")
cmds.AddCommand(NewCmdVersion(out)) cmds.AddCommand(f.NewCmdVersion(out))
cmds.AddCommand(NewCmdProxy(out)) cmds.AddCommand(f.NewCmdProxy(out))
cmds.AddCommand(f.NewCmdGet(out)) cmds.AddCommand(f.NewCmdGet(out))
cmds.AddCommand(f.NewCmdDescribe(out)) cmds.AddCommand(f.NewCmdDescribe(out))
...@@ -99,7 +100,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, ...@@ -99,7 +100,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
cmds.AddCommand(f.NewCmdDelete(out)) cmds.AddCommand(f.NewCmdDelete(out))
cmds.AddCommand(NewCmdNamespace(out)) cmds.AddCommand(NewCmdNamespace(out))
cmds.AddCommand(NewCmdLog(out)) cmds.AddCommand(f.NewCmdLog(out))
if err := cmds.Execute(); err != nil { if err := cmds.Execute(); err != nil {
os.Exit(1) os.Exit(1)
...@@ -150,69 +151,3 @@ func getExplicitKubeNamespace(cmd *cobra.Command) (string, bool) { ...@@ -150,69 +151,3 @@ func getExplicitKubeNamespace(cmd *cobra.Command) (string, bool) {
// value and return its value and true. // value and return its value and true.
return "", false 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 ( ...@@ -21,7 +21,6 @@ import (
"io" "io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/config" "github.com/GoogleCloudPlatform/kubernetes/pkg/config"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
...@@ -80,8 +79,10 @@ Examples: ...@@ -80,8 +79,10 @@ Examples:
$ cat config.json | kubectl apply -f - $ cat config.json | kubectl apply -f -
<creates all resources listed in config.json>`, <creates all resources listed in config.json>`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
clientFunc := func(*meta.RESTMapping) (*client.RESTClient, error) { clientFunc := func(mapper *meta.RESTMapping) (config.RESTClientPoster, error) {
return getKubeClient(cmd).RESTClient, nil client, err := f.Client(cmd, mapper)
checkErr(err)
return client, nil
} }
filename := GetFlagString(cmd, "filename") filename := GetFlagString(cmd, "filename")
......
...@@ -21,7 +21,7 @@ import ( ...@@ -21,7 +21,7 @@ import (
"io" "io"
) )
func NewCmdLog(out io.Writer) *cobra.Command { func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "log <pod> <container>", Use: "log <pod> <container>",
Short: "Print the logs for a container in a pod", Short: "Print the logs for a container in a pod",
...@@ -32,7 +32,8 @@ func NewCmdLog(out io.Writer) *cobra.Command { ...@@ -32,7 +32,8 @@ func NewCmdLog(out io.Writer) *cobra.Command {
namespace := getKubeNamespace(cmd) namespace := getKubeNamespace(cmd)
client := getKubeClient(cmd) client, err := f.ClientBuilder.Client()
checkErr(err)
pod, err := client.Pods(namespace).Get(args[0]) pod, err := client.Pods(namespace).Get(args[0])
checkErr(err) checkErr(err)
......
...@@ -24,7 +24,7 @@ import ( ...@@ -24,7 +24,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func NewCmdProxy(out io.Writer) *cobra.Command { func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "proxy", Use: "proxy",
Short: "Run a proxy to the Kubernetes API server", Short: "Run a proxy to the Kubernetes API server",
...@@ -32,7 +32,11 @@ func NewCmdProxy(out io.Writer) *cobra.Command { ...@@ -32,7 +32,11 @@ func NewCmdProxy(out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
port := GetFlagInt(cmd, "port") port := GetFlagInt(cmd, "port")
glog.Infof("Starting to serve on localhost:%d", 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) checkErr(err)
glog.Fatal(server.Serve()) glog.Fatal(server.Serve())
}, },
......
...@@ -23,7 +23,7 @@ import ( ...@@ -23,7 +23,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func NewCmdVersion(out io.Writer) *cobra.Command { func (f *Factory) NewCmdVersion(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "version", Use: "version",
Short: "Print version of client and server", Short: "Print version of client and server",
...@@ -31,7 +31,10 @@ func NewCmdVersion(out io.Writer) *cobra.Command { ...@@ -31,7 +31,10 @@ func NewCmdVersion(out io.Writer) *cobra.Command {
if GetFlagBool(cmd, "client") { if GetFlagBool(cmd, "client") {
kubectl.GetClientVersion(out) kubectl.GetClientVersion(out)
} else { } else {
kubectl.GetVersion(out, getKubeClient(cmd)) client, err := f.ClientBuilder.Client()
checkErr(err)
kubectl.GetVersion(out, client)
} }
}, },
} }
......
...@@ -20,7 +20,6 @@ package kubectl ...@@ -20,7 +20,6 @@ package kubectl
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect" "reflect"
...@@ -28,7 +27,6 @@ import ( ...@@ -28,7 +27,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version" "github.com/GoogleCloudPlatform/kubernetes/pkg/version"
...@@ -90,34 +88,6 @@ func SaveNamespaceInfo(path string, ns *NamespaceInfo) error { ...@@ -90,34 +88,6 @@ func SaveNamespaceInfo(path string, ns *NamespaceInfo) error {
return err 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. // TODO Move to labels package.
func formatLabels(labelMap map[string]string) string { func formatLabels(labelMap map[string]string) string {
l := labels.Set(labelMap).String() l := labels.Set(labelMap).String()
......
...@@ -17,15 +17,12 @@ limitations under the License. ...@@ -17,15 +17,12 @@ limitations under the License.
package kubectl package kubectl
import ( import (
"bytes"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect" "reflect"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
) )
func validateAction(expectedAction, actualAction client.FakeAction, t *testing.T) { func validateAction(expectedAction, actualAction client.FakeAction, t *testing.T) {
...@@ -85,56 +82,3 @@ func TestLoadNamespaceInfo(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)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment