Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add framework to init config in a plugin #182

Merged
merged 3 commits into from
Jan 24, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions pkg/pluginutils/framework_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pluginutils_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"testing"
)

func TestFramework(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Framework Suite")
}
176 changes: 176 additions & 0 deletions pkg/pluginutils/plugin_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
Copyright 2018 The Kubernetes Authors.

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 pluginutils

import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"time"

restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

// InitConfig uses the KUBECONFIG environment variable to create a new config
// object based on the existing kubectl config and options passed from the
// calling plugin framework
func InitConfig() (*restclient.Config, error) {
// resolve kubeconfig location, prioritizing the --config global flag,
// then the value of the KUBECONFIG env var (if any), and defaulting
// to ~/.kube/config as a last resort.
home := os.Getenv("HOME")
if runtime.GOOS == "windows" {
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
}
kubeconfig := filepath.Join(home, ".kube", "config")

kubeconfigEnv := os.Getenv("KUBECONFIG")
if len(kubeconfigEnv) > 0 {
kubeconfig = kubeconfigEnv
}

configFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONFIG")
kubeConfigFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_KUBECONFIG")
if len(configFile) > 0 {
kubeconfig = configFile
} else if len(kubeConfigFile) > 0 {
kubeconfig = kubeConfigFile
}

if len(kubeconfig) == 0 {
return nil, errors.New(fmt.Sprintf("error iniializing config. The KUBECONFIG environment variable must be defined."))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/iniializing/initializing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors.New(fmt.Sprintf) ---> fmt.Errorf(....) ?

}

clientConfig, _, err := clientFromConfig(kubeconfig)
if err != nil {
return nil, errors.New(fmt.Sprintf("error obtaining kubectl config: %v", err))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fmt.Errorf(...) ?

}

err = applyGlobalOptionsToConfig(clientConfig)
if err != nil {
return nil, errors.New(fmt.Sprintf("error processing global plugin options: %v", err))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fmt.Errorf(...) ?

}

return clientConfig, nil
}

func clientFromConfig(path string) (*restclient.Config, string, error) {
rules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: path}
credentials, err := rules.Load()
if err != nil {
return nil, "", fmt.Errorf("the provided credentials %q could not be loaded: %v", path, err)
}

cfg := clientcmd.NewDefaultClientConfig(*credentials, &clientcmd.ConfigOverrides{})
config, err := cfg.ClientConfig()
if err != nil {
return nil, "", fmt.Errorf("the provided credentials %q could not be used: %v", path, err)
}

namespace, _, _ := cfg.Namespace()
return config, namespace, nil
}

func applyGlobalOptionsToConfig(config *restclient.Config) error {
// impersonation config
impersonateUser := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS")
if len(impersonateUser) > 0 {
config.Impersonate.UserName = impersonateUser
}

impersonateGroup := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS_GROUP")
if len(impersonateGroup) > 0 {
impersonateGroupJSON := []string{}
err := json.Unmarshal([]byte(impersonateGroup), &impersonateGroupJSON)
if err != nil {
return errors.New(fmt.Sprintf("error parsing global option %q: %v", "--as-group", err))
}
if len(impersonateGroupJSON) > 0 {
config.Impersonate.Groups = impersonateGroupJSON
}
}

// tls config
caFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CERTIFICATE_AUTHORITY")
if len(caFile) > 0 {
config.TLSClientConfig.CAFile = caFile
}

clientCertFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_CERTIFICATE")
if len(clientCertFile) > 0 {
config.TLSClientConfig.CertFile = clientCertFile
}

clientKey := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_KEY")
if len(clientKey) > 0 {
config.TLSClientConfig.KeyFile = clientKey
}

cluster := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLUSTER")
if len(cluster) > 0 {
// TODO(jvallejo): figure out how to override kubeconfig options
}

context := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONTEXT")
if len(context) > 0 {
// TODO(jvallejo): figure out how to override kubeconfig options
}

user := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_USER")
if len(user) > 0 {
// TODO(jvallejo): figure out how to override kubeconfig options
}

// user / misc request config
requestTimeout := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_REQUEST_TIMEOUT")
if len(requestTimeout) > 0 {
t, err := time.ParseDuration(requestTimeout)
if err != nil {
return errors.New(fmt.Sprintf("%v", err))
}
config.Timeout = t
}

server := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_SERVER")
if len(server) > 0 {
config.ServerName = server
}

token := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_TOKEN")
if len(token) > 0 {
config.BearerToken = token
}

username := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_USERNAME")
if len(username) > 0 {
config.Username = username
}

password := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_PASSWORD")
if len(password) > 0 {
config.Password = password
}

return nil
}
67 changes: 67 additions & 0 deletions pkg/pluginutils/plugin_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package pluginutils_test

import (
"os"
"time"

"k8s.io/kubectl/pkg/pluginutils"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("InitConfig", func() {
BeforeEach(func() {
os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_KUBECONFIG", "testdata/config")
})

Describe("InitConfig", func() {
Context("When nothing is overridden by the calling framework", func() {
It("finds and parses the preexisting config", func() {
config, err := pluginutils.InitConfig()
Expect(err).NotTo(HaveOccurred())

Expect(config.Host).To(Equal("https://notreal.com:1234"))
Expect(config.Username).To(Equal("foo"))
Expect(config.Password).To(Equal("bar"))
})
})

Context("When the calling plugin framework sets env vars", func() {
BeforeEach(func() {
os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS", "apple")
os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS_GROUP", "[\"banana\",\"cherry\"]")

os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CERTIFICATE_AUTHORITY", "testdata/apiserver_ca.crt")
os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_CERTIFICATE", "testdata/client.crt")
os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_KEY", "testdata/client.key")

os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_REQUEST_TIMEOUT", "45s")
os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_SERVER", "some-other-server.com")
os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_TOKEN", "bearer notreal")
os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_USERNAME", "date")
os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_PASSWORD", "elderberry")

os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLUSTER", "")
})
It("overrides the config settings with the passed in settings", func() {
config, err := pluginutils.InitConfig()
Expect(err).NotTo(HaveOccurred())

Expect(config.Impersonate.UserName).To(Equal("apple"))
Expect(config.Impersonate.Groups).Should(ConsistOf("banana", "cherry"))

Expect(config.CertFile).To(Equal("testdata/client.crt"))
Expect(config.KeyFile).To(Equal("testdata/client.key"))
Expect(config.CAFile).To(Equal("testdata/apiserver_ca.crt"))

Expect(config.Timeout).To(Equal(45 * time.Second))
Expect(config.ServerName).To(Equal("some-other-server.com"))
Expect(config.BearerToken).To(Equal("bearer notreal"))

Expect(config.Username).To(Equal("date"))
Expect(config.Password).To(Equal("elderberry"))
})
})
})
})
23 changes: 23 additions & 0 deletions pkg/pluginutils/testdata/apiserver_ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIUKlswr/x5/FFyyL7mL7kCPi27dSMwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCeHgxCjAIBgNVBAgMAXgxCjAIBgNVBAcMAXgxCjAIBgNV
BAoMAXgxCjAIBgNVBAsMAXgxCzAJBgNVBAMMAmNhMRAwDgYJKoZIhvcNAQkBFgF4
MB4XDTE3MTIxMjE5MzAwMFoXDTIyMTIxMTE5MzAwMFowHTEbMBkGA1UEAxMSa3Vi
ZXJuZXRlcy5kZWZhdWx0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
0Y4WVfNk1cjwiiPQmK6ro0UDnf3qAEZc55Xqa+txVAstpA9pJWpAKAxQGP8Nbl1B
GAqeqYOib7/2Lr5Ui0LNi9rvN4lNNNuZmeHfIqpDxgv/a6zrMeOkixRTP9yWJxkD
QX+ZVKJ8Dwa7cpZFhnzGMchs24q4GAKNEl1dzBO5QKWSsUjfBW9i+bXCaAO3WGi/
9IPFt2ol6vFML7pSHl/cWkK1qqytQxuJPybeeDDc2qQBOKO1FSmQTaFuAvjO2WtB
zrnu0t7tgsVbE7dT/rgvmZYsf0PN2Ly6u8K0MCZQPBAAAv6/BKQTJt7qyXL2fV5V
g6hJ4XT9rW5HYj2PehJ0lwIDAQABo4G7MIG4MA4GA1UdDwEB/wQEAwIFoDATBgNV
HSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRXlgiiXDCv
BqR3cnlFLOR3luTRqjAfBgNVHSMEGDAWgBSkSamcYNBiEpi2xNQ93XiuUC+OfDBD
BgNVHREEPDA6ghZrdWJlcm5ldGVzLmRlZmF1bHQuc3Zjgglsb2NhbGhvc3SCCWxv
Y2FsaG9zdIcEfwAAAYcECgAAATANBgkqhkiG9w0BAQsFAAOCAQEAm215zaH5dwRr
AnSQQ1x0QnTiMbTgmZucYpHWgZ8QrNsC5ioypHtwbSwEbjzSxmddFu2ZEXNcDTtj
aWU9M8j0JMSOSykxltOTIcnG5aCuH2IXsG5l0F/6Q5iXUUQu+nKw89xY396DxSGe
jvvrXgKu3iTadv5/N0QuNmj8+bPk9B9ELYNFiYzk64kbmr0/elXaMaPA6g/1XvqB
WjWmyvFisfZpuBuNGYLAi21qKIKUEItH71w120AbuR14x8YkK/Afp1+mh67gDgdL
Rcggy/IJOIuqJLvynmfb17iED+7HVw/4ujkg5LbLSC1iQLO8HjlNUu/dvSIg6oHQ
0UUz9d+Yzg==
-----END CERTIFICATE-----
22 changes: 22 additions & 0 deletions pkg/pluginutils/testdata/client.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDnjCCAoagAwIBAgIUfTrvesXVMf9SrMMdZlwpRbgzyW8wDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCeHgxCjAIBgNVBAgMAXgxCjAIBgNVBAcMAXgxCjAIBgNV
BAoMAXgxCjAIBgNVBAsMAXgxCzAJBgNVBAMMAmNhMRAwDgYJKoZIhvcNAQkBFgF4
MB4XDTE3MTIxMjE5MzAwMFoXDTIyMTIxMTE5MzAwMFowMDEXMBUGA1UEChMOc3lz
dGVtOm1hc3RlcnMxFTATBgNVBAMTDHN5c3RlbTphZG1pbjCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMkSvo6lpMtWhot8C73BZK+pYGKV3uvQY+ooAVxE
PV20wkWpoM5qWIn5nVJeE2aL/BQTyYibykjNiRUJM0SS3+PnxQvcLkPrVsR8oxon
IL32Dybi9vOywknWq30yLmfQeTiIanzkadaqlwyz0ZkuOVAqtrOE5j2pY+PQcx/P
lsb5ECZ0AHz3BJMiXr1hgvXQiwoSwbQJ8T2chN/BwOaeYvRqYmOUSAebi8qrZiE/
rUJsnDFzMR4CRNfDzEfcC6G89tUiCy8Jc+fQ/32S0++suF8PFzitUCn0VR00hGqz
kO8KvnKaT3yCagVHeCz2kanpQGtrXAcubIk9RWUAxkVnNIECAwEAAaOBgzCBgDAO
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIw
ADAdBgNVHQ4EFgQUilZN91cPegCWddZPZqb+UVCbrmAwHwYDVR0jBBgwFoAU+9WT
XLaQKwimk00vR6wI1B0VWRIwCwYDVR0RBAQwAoIAMA0GCSqGSIb3DQEBCwUAA4IB
AQAV+ObptlNalJjk9kr6/zIwy53ZMA3L+G2uGYqXYW/kZJoBfeLYrg6mJEJHvwlt
IfsOIm/DDk7LWTiDaGyVHOd6P8IohGOCIUHQH2rWMHNUk6FuQvwDVbcjsYBkB8tS
G/0VnIduDCnmH+lYs1lOCs7R/Q/0Zvj22Yjn2YhDKA4fSLQU2Yz6sXja22fGfpGf
dpYw+p8tH/3ZY+RRD6B0xHlPLXuxjl/vz5rHaSVta4jigtPUJGAy81noevkwYdhq
HKK2EKK7+Q8lEYxMUm0KremgwettiVUBO+FR1xRRrsMcKX6sWNRhbH8oxfv++EmU
0h+zLBSC8GVsba27MVpjd8sy
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions pkg/pluginutils/testdata/client.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAyRK+jqWky1aGi3wLvcFkr6lgYpXe69Bj6igBXEQ9XbTCRamg
zmpYifmdUl4TZov8FBPJiJvKSM2JFQkzRJLf4+fFC9wuQ+tWxHyjGicgvfYPJuL2
87LCSdarfTIuZ9B5OIhqfORp1qqXDLPRmS45UCq2s4TmPalj49BzH8+WxvkQJnQA
fPcEkyJevWGC9dCLChLBtAnxPZyE38HA5p5i9GpiY5RIB5uLyqtmIT+tQmycMXMx
HgJE18PMR9wLobz21SILLwlz59D/fZLT76y4Xw8XOK1QKfRVHTSEarOQ7wq+cppP
fIJqBUd4LPaRqelAa2tcBy5siT1FZQDGRWc0gQIDAQABAoIBAFZYXUpGaYkEJAzb
/PLEjKc+dex/7VWYjH9uJH9psmx+BfsCR9K8Kj892MgZK/2aWWqJwL6QIAK5eId3
BvQImmp1CdH5AEkcU4tW9ndxnAbXFlSdnGy0M+ifbc76cKEWDigvtILjV2ven7TN
t1EvX19EqGZYyMJr7kBTMEzVySqx4L8C7L3UUrmvwBUbcEUSL1dsjNPkPlrLtIhr
Hu4ZsJzTjcpELnlwtxeFE3DyryNW+dFQbaFbru9fJzG9FIDH8oXMGhEh3H3uSYiD
im/3RbdHRd9ViInQ7cVvoIgOkbt/9jGEXJiu19IMg4HQFuZbn38gT6/gKE4rX4VJ
RqaRSu0CgYEA26x14/58IaCmf16kTuaFI6g41eQxC+avZ35WBxkkXzcJoSDqj1co
KmgWvkCyYeyTIPLEISIDqeASKRMTmbwYcniz5E+G9ztJP9o1/SH5Jkc49U+rxmf2
EqAYzF01uTBtdpfnWMzWqnRUWhynEuqogsGZIPgGWjsFD2JvKExXVjMCgYEA6lLc
5xBlEXv7EdHsC+BVtNB5O7OaLts28n++BIu2jcOLNhig9saP9NqR3RBHlCSJcyok
VW/z3nGz/1g4VmizEYodpmJhWixWMj7YxNVhpsFPxKX648pwg6bpZeDyEcLD3tKp
cp/P1pcWMntu8/yMfOiK6qeIILq+GDjwmi5eDnsCgYA/NPNC49GBt4DQu0GZtjhu
1xNrb9ow63Ji2/YS2sgdYW+y3g6/qbtT4FlS6rio9nIrE88dHXViZqezC9si6/04
ysQwGDXkYzmjVr0cFa9jtTCNXRw23WjvWQNpohQ4Mdf9PJ4DbgUCTLCMsRvdlIlh
/iR6WQMy6TJ+h7smJDkpAwKBgQCDufVpB/RpMdNgnywdOQ21SgT9JOgmMyHejSVb
FNeZNjZKQOosEE/ZDA43wfEbPLbwQN6QERF9GVpgz01MditETuqAIIot0QLb2Cyv
6mys+7tGBzDVYXaC7BAFL8GlbmICH1cWkvSZ4/Gci3tCOdUYWvnNFEUYcSJwJ4JK
iophhQKBgH/oS7xi2AFrGrscIVGL8V0M2GeACAhR7VKEw8PNbloH2HayfTY7fMlb
hY+kHxp52Ycinj1xiaCRIdceI/cXxENcEDyIrKny0Xs4shO3NXFzW6JhMB3hjDY7
2dPsYPzdEQTldmWHGS45XmKBMNgIOPNTXVl8i8UEZqtHIQohI1Wj
-----END RSA PRIVATE KEY-----
20 changes: 20 additions & 0 deletions pkg/pluginutils/testdata/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority:
server: https://notreal.com:1234
name: local
contexts:
- context:
cluster: local
user: myself
name: local
current-context: local
kind: Config
preferences: {}
users:
- name: myself
user:
as-user-extra: {}
username: foo
password: bar