mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
add utility for binding flags and building api server clients
This commit is contained in:
parent
ff1e9f4c19
commit
2dbfb80349
@ -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)
|
||||||
}
|
}
|
||||||
|
82
pkg/client/clientcmd/auth_loaders.go
Normal file
82
pkg/client/clientcmd/auth_loaders.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
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{}
|
||||||
|
}
|
181
pkg/client/clientcmd/client_builder.go
Normal file
181
pkg/client/clientcmd/client_builder.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
321
pkg/client/clientcmd/client_builder_test.go
Normal file
321
pkg/client/clientcmd/client_builder_test.go
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetAllArgumentsOnly(t *testing.T) {
|
||||||
|
flags := pflag.NewFlagSet("test-flags", pflag.ContinueOnError)
|
||||||
|
clientBuilder := NewBuilder(nil)
|
||||||
|
clientBuilder.BindFlags(flags)
|
||||||
|
|
||||||
|
args := argValues{"https://localhost:8080", "v1beta1", "/auth-path", "cert-file", "key-file", "ca-file", "bearer-token", true, true}
|
||||||
|
flags.Parse(strings.Split(args.toArguments(), " "))
|
||||||
|
|
||||||
|
castBuilder, ok := clientBuilder.(*builder)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Got unexpected cast result: %#v", castBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchStringArg(args.server, castBuilder.apiserver, t)
|
||||||
|
matchStringArg(args.apiVersion, castBuilder.apiVersion, t)
|
||||||
|
matchStringArg(args.authPath, castBuilder.authPath, t)
|
||||||
|
matchStringArg(args.certFile, castBuilder.cmdAuthInfo.CertFile.Value, t)
|
||||||
|
matchStringArg(args.keyFile, castBuilder.cmdAuthInfo.KeyFile.Value, t)
|
||||||
|
matchStringArg(args.caFile, castBuilder.cmdAuthInfo.CAFile.Value, t)
|
||||||
|
matchStringArg(args.bearerToken, castBuilder.cmdAuthInfo.BearerToken.Value, t)
|
||||||
|
matchBoolArg(args.insecure, castBuilder.cmdAuthInfo.Insecure.Value, t)
|
||||||
|
matchBoolArg(args.matchApiVersion, castBuilder.matchApiVersion, t)
|
||||||
|
|
||||||
|
clientConfig, err := clientBuilder.Config()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchStringArg(args.server, clientConfig.Host, t)
|
||||||
|
matchStringArg(args.apiVersion, clientConfig.Version, t)
|
||||||
|
matchStringArg(args.certFile, clientConfig.CertFile, t)
|
||||||
|
matchStringArg(args.keyFile, clientConfig.KeyFile, t)
|
||||||
|
matchStringArg(args.caFile, clientConfig.CAFile, t)
|
||||||
|
matchStringArg(args.bearerToken, clientConfig.BearerToken, t)
|
||||||
|
matchBoolArg(args.insecure, clientConfig.Insecure, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetInsecureArgumentsOnly(t *testing.T) {
|
||||||
|
flags := pflag.NewFlagSet("test-flags", pflag.ContinueOnError)
|
||||||
|
clientBuilder := NewBuilder(nil)
|
||||||
|
clientBuilder.BindFlags(flags)
|
||||||
|
|
||||||
|
args := argValues{"http://localhost:8080", "v1beta1", "/auth-path", "cert-file", "key-file", "ca-file", "bearer-token", true, true}
|
||||||
|
flags.Parse(strings.Split(args.toArguments(), " "))
|
||||||
|
|
||||||
|
clientConfig, err := clientBuilder.Config()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchStringArg(args.server, clientConfig.Host, t)
|
||||||
|
matchStringArg(args.apiVersion, clientConfig.Version, t)
|
||||||
|
|
||||||
|
// all security related params should be empty in the resulting config even though we set them because we're using http transport
|
||||||
|
matchStringArg("", clientConfig.CertFile, t)
|
||||||
|
matchStringArg("", clientConfig.KeyFile, t)
|
||||||
|
matchStringArg("", clientConfig.CAFile, t)
|
||||||
|
matchStringArg("", clientConfig.BearerToken, t)
|
||||||
|
matchBoolArg(false, clientConfig.Insecure, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadAuthFile(t *testing.T) {
|
||||||
|
flags := pflag.NewFlagSet("test-flags", pflag.ContinueOnError)
|
||||||
|
clientBuilder := NewBuilder(nil)
|
||||||
|
clientBuilder.BindFlags(flags)
|
||||||
|
authFileContents := fmt.Sprintf(`{"user": "alfa-user", "password": "bravo-password", "cAFile": "charlie", "certFile": "delta", "keyFile": "echo", "bearerToken": "foxtrot"}`)
|
||||||
|
authFile := writeTempAuthFile(authFileContents, t)
|
||||||
|
|
||||||
|
args := argValues{"https://localhost:8080", "v1beta1", authFile, "", "", "", "", true, true}
|
||||||
|
flags.Parse(strings.Split(args.toArguments(), " "))
|
||||||
|
|
||||||
|
castBuilder, ok := clientBuilder.(*builder)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Got unexpected cast result: %#v", castBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchStringArg(args.server, castBuilder.apiserver, t)
|
||||||
|
matchStringArg(args.apiVersion, castBuilder.apiVersion, t)
|
||||||
|
matchStringArg(args.authPath, castBuilder.authPath, t)
|
||||||
|
matchStringArg(args.certFile, castBuilder.cmdAuthInfo.CertFile.Value, t)
|
||||||
|
matchStringArg(args.keyFile, castBuilder.cmdAuthInfo.KeyFile.Value, t)
|
||||||
|
matchStringArg(args.caFile, castBuilder.cmdAuthInfo.CAFile.Value, t)
|
||||||
|
matchStringArg(args.bearerToken, castBuilder.cmdAuthInfo.BearerToken.Value, t)
|
||||||
|
matchBoolArg(args.insecure, castBuilder.cmdAuthInfo.Insecure.Value, t)
|
||||||
|
matchBoolArg(args.matchApiVersion, castBuilder.matchApiVersion, t)
|
||||||
|
|
||||||
|
clientConfig, err := clientBuilder.Config()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchStringArg(args.server, clientConfig.Host, t)
|
||||||
|
matchStringArg(args.apiVersion, clientConfig.Version, t)
|
||||||
|
matchStringArg("delta", clientConfig.CertFile, t)
|
||||||
|
matchStringArg("echo", clientConfig.KeyFile, t)
|
||||||
|
matchStringArg("charlie", clientConfig.CAFile, t)
|
||||||
|
matchStringArg("foxtrot", clientConfig.BearerToken, t)
|
||||||
|
matchStringArg("alfa-user", clientConfig.Username, t)
|
||||||
|
matchStringArg("bravo-password", clientConfig.Password, t)
|
||||||
|
matchBoolArg(args.insecure, clientConfig.Insecure, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthFileOverridden(t *testing.T) {
|
||||||
|
flags := pflag.NewFlagSet("test-flags", pflag.ContinueOnError)
|
||||||
|
clientBuilder := NewBuilder(nil)
|
||||||
|
clientBuilder.BindFlags(flags)
|
||||||
|
authFileContents := fmt.Sprintf(`{"user": "alfa-user", "password": "bravo-password", "cAFile": "charlie", "certFile": "delta", "keyFile": "echo", "bearerToken": "foxtrot"}`)
|
||||||
|
authFile := writeTempAuthFile(authFileContents, t)
|
||||||
|
|
||||||
|
args := argValues{"https://localhost:8080", "v1beta1", authFile, "cert-file", "key-file", "ca-file", "bearer-token", true, true}
|
||||||
|
flags.Parse(strings.Split(args.toArguments(), " "))
|
||||||
|
|
||||||
|
castBuilder, ok := clientBuilder.(*builder)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Got unexpected cast result: %#v", castBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchStringArg(args.server, castBuilder.apiserver, t)
|
||||||
|
matchStringArg(args.apiVersion, castBuilder.apiVersion, t)
|
||||||
|
matchStringArg(args.authPath, castBuilder.authPath, t)
|
||||||
|
matchStringArg(args.certFile, castBuilder.cmdAuthInfo.CertFile.Value, t)
|
||||||
|
matchStringArg(args.keyFile, castBuilder.cmdAuthInfo.KeyFile.Value, t)
|
||||||
|
matchStringArg(args.caFile, castBuilder.cmdAuthInfo.CAFile.Value, t)
|
||||||
|
matchStringArg(args.bearerToken, castBuilder.cmdAuthInfo.BearerToken.Value, t)
|
||||||
|
matchBoolArg(args.insecure, castBuilder.cmdAuthInfo.Insecure.Value, t)
|
||||||
|
matchBoolArg(args.matchApiVersion, castBuilder.matchApiVersion, t)
|
||||||
|
|
||||||
|
clientConfig, err := clientBuilder.Config()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchStringArg(args.server, clientConfig.Host, t)
|
||||||
|
matchStringArg(args.apiVersion, clientConfig.Version, t)
|
||||||
|
matchStringArg(args.certFile, clientConfig.CertFile, t)
|
||||||
|
matchStringArg(args.keyFile, clientConfig.KeyFile, t)
|
||||||
|
matchStringArg(args.caFile, clientConfig.CAFile, t)
|
||||||
|
matchStringArg(args.bearerToken, clientConfig.BearerToken, t)
|
||||||
|
matchStringArg("alfa-user", clientConfig.Username, t)
|
||||||
|
matchStringArg("bravo-password", clientConfig.Password, t)
|
||||||
|
matchBoolArg(args.insecure, clientConfig.Insecure, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseDefaultArgumentsOnly(t *testing.T) {
|
||||||
|
flags := pflag.NewFlagSet("test-flags", pflag.ContinueOnError)
|
||||||
|
clientBuilder := NewBuilder(nil)
|
||||||
|
clientBuilder.BindFlags(flags)
|
||||||
|
|
||||||
|
flags.Parse(strings.Split("", " "))
|
||||||
|
|
||||||
|
castBuilder, ok := clientBuilder.(*builder)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Got unexpected cast result: %#v", castBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchStringArg("", castBuilder.apiserver, t)
|
||||||
|
matchStringArg(latest.Version, castBuilder.apiVersion, t)
|
||||||
|
matchStringArg(os.Getenv("HOME")+"/.kubernetes_auth", castBuilder.authPath, t)
|
||||||
|
matchStringArg("", castBuilder.cmdAuthInfo.CertFile.Value, t)
|
||||||
|
matchStringArg("", castBuilder.cmdAuthInfo.KeyFile.Value, t)
|
||||||
|
matchStringArg("", castBuilder.cmdAuthInfo.CAFile.Value, t)
|
||||||
|
matchStringArg("", castBuilder.cmdAuthInfo.BearerToken.Value, t)
|
||||||
|
matchBoolArg(false, castBuilder.matchApiVersion, 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())
|
||||||
|
}
|
||||||
|
prompter := NewPromptingAuthLoader(tt.r)
|
||||||
|
authInfo, err := prompter.LoadAuth(aifile.Name())
|
||||||
|
if len(tt.authData) == 0 && tt.authData != "missing" {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("LoadAuth 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchStringArg(expected, got string, t *testing.T) {
|
||||||
|
if expected != got {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchBoolArg(expected, got bool, t *testing.T) {
|
||||||
|
if expected != got {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTempAuthFile(contents string, t *testing.T) string {
|
||||||
|
file, err := ioutil.TempFile("", "testAuthInfo")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to write config file. Test cannot continue due to: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
_, err = file.WriteString(contents)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
return file.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
type argValues struct {
|
||||||
|
server string
|
||||||
|
apiVersion string
|
||||||
|
authPath string
|
||||||
|
certFile string
|
||||||
|
keyFile string
|
||||||
|
caFile string
|
||||||
|
bearerToken string
|
||||||
|
insecure bool
|
||||||
|
matchApiVersion bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *argValues) toArguments() string {
|
||||||
|
args := ""
|
||||||
|
if len(a.server) > 0 {
|
||||||
|
args += "--" + FlagApiServer + "=" + a.server + " "
|
||||||
|
}
|
||||||
|
if len(a.apiVersion) > 0 {
|
||||||
|
args += "--" + FlagApiVersion + "=" + a.apiVersion + " "
|
||||||
|
}
|
||||||
|
if len(a.authPath) > 0 {
|
||||||
|
args += "--" + FlagAuthPath + "=" + a.authPath + " "
|
||||||
|
}
|
||||||
|
if len(a.certFile) > 0 {
|
||||||
|
args += "--" + FlagCertFile + "=" + a.certFile + " "
|
||||||
|
}
|
||||||
|
if len(a.keyFile) > 0 {
|
||||||
|
args += "--" + FlagKeyFile + "=" + a.keyFile + " "
|
||||||
|
}
|
||||||
|
if len(a.caFile) > 0 {
|
||||||
|
args += "--" + FlagCAFile + "=" + a.caFile + " "
|
||||||
|
}
|
||||||
|
if len(a.bearerToken) > 0 {
|
||||||
|
args += "--" + FlagBearerToken + "=" + a.bearerToken + " "
|
||||||
|
}
|
||||||
|
args += "--" + FlagInsecure + "=" + fmt.Sprintf("%v", a.insecure) + " "
|
||||||
|
args += "--" + FlagMatchApiVersion + "=" + fmt.Sprintf("%v", a.matchApiVersion) + " "
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
25
pkg/client/clientcmd/doc.go
Normal file
25
pkg/client/clientcmd/doc.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
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
|
100
pkg/client/clientcmd/provided_flags.go
Normal file
100
pkg/client/clientcmd/provided_flags.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 (
|
|||||||
"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
|
|||||||
// 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) {
|
|||||||
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
|
|||||||
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) {
|
|||||||
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 (
|
|||||||
"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 (
|
|||||||
// 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 {
|
||||||
Mapper meta.RESTMapper
|
ClientBuilder clientcmd.Builder
|
||||||
Typer runtime.ObjectTyper
|
Mapper meta.RESTMapper
|
||||||
Client func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error)
|
Typer runtime.ObjectTyper
|
||||||
Describer func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error)
|
Client func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error)
|
||||||
Printer func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, 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
|
// NewFactory creates a factory with the default Kubernetes resources defined
|
||||||
func NewFactory() *Factory {
|
func NewFactory(clientBuilder clientcmd.Builder) *Factory {
|
||||||
return &Factory{
|
return &Factory{
|
||||||
Mapper: latest.RESTMapper,
|
ClientBuilder: clientBuilder,
|
||||||
Typer: api.Scheme,
|
Mapper: latest.RESTMapper,
|
||||||
|
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.`,
|
|||||||
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.`,
|
|||||||
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) {
|
|||||||
// 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 (
|
|||||||
"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:
|
|||||||
$ 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 (
|
|||||||
"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 {
|
|||||||
|
|
||||||
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 (
|
|||||||
"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 {
|
|||||||
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 (
|
|||||||
"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 {
|
|||||||
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
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -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 {
|
|||||||
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.
|
|||||||
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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user