2
0
Эх сурвалжийг харах

kubernetes agent list ns + tests

Alexander Belanger 5 жил өмнө
parent
commit
218d05d560

+ 2 - 2
internal/helm/agent.go

@@ -57,13 +57,13 @@ func (h *Form) ToAgent(
 	}
 
 	// create a kubernetes agent
-	k8sForm := &kubernetes.Form{
+	conf := &kubernetes.OutOfClusterConfig{
 		KubeConfig:      h.KubeConfig,
 		AllowedContexts: h.AllowedContexts,
 		Context:         h.Context,
 	}
 
-	k8sAgent, err := k8sForm.ToAgent()
+	k8sAgent, err := kubernetes.AgentFromOutOfClusterConfig(conf)
 
 	if err != nil {
 		return nil, err

+ 3 - 57
internal/kubernetes/agent.go

@@ -4,76 +4,22 @@ import (
 	"context"
 
 	v1 "k8s.io/api/core/v1"
-	v1Machinery "k8s.io/apimachinery/pkg/apis/meta/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/rest"
 )
 
 // Agent is a Kubernetes agent for performing operations that interact with the
 // api server
 type Agent struct {
 	RESTClientGetter genericclioptions.RESTClientGetter
-	Clientset        *kubernetes.Clientset
-}
-
-// Form represents the options for connecting to a cluster externally and
-// creating an Agent
-type Form struct {
-	KubeConfig      []byte
-	AllowedContexts []string
-	Context         string `json:"context" form:"required"`
-	ConfigFlags     *genericclioptions.ConfigFlags
-}
-
-// ToAgent uses the Form to generate an agent
-func (h *Form) ToAgent() (*Agent, error) {
-	// create a client config using the app's helm/kubernetes agents
-	conf, err := GetRestrictedClientConfigFromBytes(
-		h.KubeConfig,
-		h.Context,
-		h.AllowedContexts,
-	)
-
-	if err != nil {
-		return nil, err
-	}
-
-	clientConf, err := conf.ClientConfig()
-
-	if err != nil {
-		return nil, err
-	}
-
-	restClientGetter := NewRESTClientGetterFromClientConfig(clientConf)
-	clientset, err := kubernetes.NewForConfig(clientConf)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return &Agent{restClientGetter, clientset}, nil
-}
-
-// AgentFromInClusterConfig uses the service account that kubernetes
-// gives to pods to connect
-func AgentFromInClusterConfig() (*Agent, error) {
-	conf, err := rest.InClusterConfig()
-
-	if err != nil {
-		return nil, err
-	}
-
-	restClientGetter := NewRESTClientGetterFromClientConfig(conf)
-	clientset, err := kubernetes.NewForConfig(conf)
-
-	return &Agent{restClientGetter, clientset}, nil
+	Clientset        kubernetes.Interface
 }
 
 // ListNamespaces simply lists namespaces
 func (a *Agent) ListNamespaces() (*v1.NamespaceList, error) {
 	return a.Clientset.CoreV1().Namespaces().List(
 		context.TODO(),
-		v1Machinery.ListOptions{},
+		metav1.ListOptions{},
 	)
 }

+ 66 - 0
internal/kubernetes/agent_test.go

@@ -0,0 +1,66 @@
+package kubernetes_test
+
+import (
+	"testing"
+
+	"github.com/porter-dev/porter/internal/kubernetes"
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/discovery"
+	"k8s.io/client-go/kubernetes/fake"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/clientcmd"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type fakeRESTClientGetter struct{}
+
+func (f *fakeRESTClientGetter) ToRESTConfig() (*rest.Config, error) {
+	return nil, nil
+}
+
+func (f *fakeRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
+	return nil
+}
+
+func (f *fakeRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
+	return nil, nil
+}
+
+func (f *fakeRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
+	return nil, nil
+}
+
+func newAgentFixture(t *testing.T, objects ...runtime.Object) *kubernetes.Agent {
+	t.Helper()
+
+	return &kubernetes.Agent{&fakeRESTClientGetter{}, fake.NewSimpleClientset(objects...)}
+}
+
+func TestOutOfClusterConfig(t *testing.T) {
+	k8sAgent := newAgentFixture(t, &v1.Namespace{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "test-namespace-0",
+		},
+	}, &v1.Namespace{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "test-namespace-1",
+		},
+	})
+
+	namespaces, err := k8sAgent.ListNamespaces()
+
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	names := []string{"test-namespace-0", "test-namespace-1"}
+
+	for i, ns := range namespaces.Items {
+		if names[i] != ns.Name {
+			t.Errorf("Namespace names do not match: expected %s, got %s\n", names[i], ns.Name)
+		}
+	}
+}

+ 145 - 0
internal/kubernetes/config.go

@@ -0,0 +1,145 @@
+package kubernetes
+
+import (
+	"path/filepath"
+	"regexp"
+	"strings"
+	"time"
+
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/cli-runtime/pkg/genericclioptions"
+	"k8s.io/client-go/discovery"
+	diskcached "k8s.io/client-go/discovery/cached/disk"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/restmapper"
+	"k8s.io/client-go/tools/clientcmd"
+	"k8s.io/client-go/util/homedir"
+)
+
+// AgentFromOutOfClusterConfig creates a new Agent using the OutOfClusterConfig
+func AgentFromOutOfClusterConfig(conf *OutOfClusterConfig) (*Agent, error) {
+	restConf, err := conf.ToRESTConfig()
+
+	if err != nil {
+		return nil, err
+	}
+
+	clientset, err := kubernetes.NewForConfig(restConf)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &Agent{conf, clientset}, nil
+}
+
+// AgentFromInClusterConfig uses the service account that kubernetes
+// gives to pods to connect
+func AgentFromInClusterConfig() (*Agent, error) {
+	conf, err := rest.InClusterConfig()
+
+	if err != nil {
+		return nil, err
+	}
+
+	restClientGetter := NewRESTClientGetterFromInClusterConfig(conf)
+	clientset, err := kubernetes.NewForConfig(conf)
+
+	return &Agent{restClientGetter, clientset}, nil
+}
+
+// OutOfClusterConfig is the set of parameters required for an out-of-cluster connection.
+// This implements RESTClientGetter
+type OutOfClusterConfig struct {
+	KubeConfig      []byte
+	AllowedContexts []string
+	Context         string `json:"context" form:"required"`
+}
+
+// ToRESTConfig creates a kubernetes REST client factory -- it simply calls ClientConfig on
+// the result of ToRawKubeConfigLoader
+func (conf *OutOfClusterConfig) ToRESTConfig() (*rest.Config, error) {
+	restConf, err := conf.ToRawKubeConfigLoader().ClientConfig()
+
+	if err != nil {
+		return nil, err
+	}
+
+	rest.SetKubernetesDefaults(restConf)
+	return restConf, nil
+}
+
+// ToRawKubeConfigLoader creates a clientcmd.ClientConfig from the raw kubeconfig found in
+// the OutOfClusterConfig. It does not implement loading rules or overrides.
+func (conf *OutOfClusterConfig) ToRawKubeConfigLoader() clientcmd.ClientConfig {
+	cmdConf, _ := GetRestrictedClientConfigFromBytes(
+		conf.KubeConfig,
+		conf.Context,
+		conf.AllowedContexts,
+	)
+
+	return cmdConf
+}
+
+// ToDiscoveryClient returns a CachedDiscoveryInterface using a computed RESTConfig
+// It's required to implement the interface genericclioptions.RESTClientGetter
+func (conf *OutOfClusterConfig) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
+	// From: k8s.io/cli-runtime/pkg/genericclioptions/config_flags.go > func (*configFlags) ToDiscoveryClient()
+	restConf, err := conf.ToRESTConfig()
+
+	if err != nil {
+		return nil, err
+	}
+
+	restConf.Burst = 100
+	defaultHTTPCacheDir := filepath.Join(homedir.HomeDir(), ".kube", "http-cache")
+
+	// takes the parentDir and the host and comes up with a "usually non-colliding" name for the discoveryCacheDir
+	parentDir := filepath.Join(homedir.HomeDir(), ".kube", "cache", "discovery")
+	// strip the optional scheme from host if its there:
+	schemelessHost := strings.Replace(strings.Replace(restConf.Host, "https://", "", 1), "http://", "", 1)
+	// now do a simple collapse of non-AZ09 characters.  Collisions are possible but unlikely.  Even if we do collide the problem is short lived
+	safeHost := regexp.MustCompile(`[^(\w/\.)]`).ReplaceAllString(schemelessHost, "_")
+	discoveryCacheDir := filepath.Join(parentDir, safeHost)
+
+	return diskcached.NewCachedDiscoveryClientForConfig(restConf, discoveryCacheDir, defaultHTTPCacheDir, time.Duration(10*time.Minute))
+}
+
+// ToRESTMapper returns a mapper
+func (conf *OutOfClusterConfig) ToRESTMapper() (meta.RESTMapper, error) {
+	// From: k8s.io/cli-runtime/pkg/genericclioptions/config_flags.go > func (*configFlags) ToRESTMapper()
+	discoveryClient, err := conf.ToDiscoveryClient()
+	if err != nil {
+		return nil, err
+	}
+
+	mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
+	expander := restmapper.NewShortcutExpander(mapper, discoveryClient)
+	return expander, nil
+}
+
+// NewRESTClientGetterFromInClusterConfig returns a RESTClientGetter using
+// default values set from the *rest.Config
+func NewRESTClientGetterFromInClusterConfig(conf *rest.Config) genericclioptions.RESTClientGetter {
+	cfs := genericclioptions.NewConfigFlags(false)
+
+	cfs.ClusterName = &conf.ServerName
+	cfs.Insecure = &conf.Insecure
+	cfs.APIServer = &conf.Host
+	cfs.CAFile = &conf.CAFile
+	cfs.KeyFile = &conf.KeyFile
+	cfs.CertFile = &conf.CertFile
+	cfs.BearerToken = &conf.BearerToken
+	cfs.Timeout = stringptr(conf.Timeout.String())
+	cfs.Impersonate = &conf.Impersonate.UserName
+	cfs.ImpersonateGroup = &conf.Impersonate.Groups
+	cfs.Username = &conf.Username
+	cfs.Password = &conf.Password
+
+	return cfs
+}
+
+func stringptr(val string) *string {
+	return &val
+}

+ 0 - 23
internal/kubernetes/helpers.go

@@ -1,23 +0,0 @@
-package kubernetes
-
-import (
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"k8s.io/client-go/rest"
-)
-
-// NewRESTClientGetterFromClientConfig returns a RESTClientGetter using
-// default values set from the *rest.Config
-func NewRESTClientGetterFromClientConfig(conf *rest.Config) genericclioptions.RESTClientGetter {
-	cfs := genericclioptions.NewConfigFlags(false)
-
-	cfs.Insecure = &conf.Insecure
-	cfs.APIServer = stringptr(conf.Host)
-	cfs.CAFile = stringptr(conf.CAFile)
-	cfs.BearerToken = stringptr(conf.BearerToken)
-
-	return cfs
-}
-
-func stringptr(val string) *string {
-	return &val
-}