Skip to content

Commit

Permalink
Refactor client constructors
Browse files Browse the repository at this point in the history
- improve naming of types and functions
- enable use of raw REST client
  • Loading branch information
errordeveloper committed Mar 7, 2019
1 parent 1606a35 commit ba5f04e
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 84 deletions.
6 changes: 3 additions & 3 deletions pkg/ctl/create/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,13 +490,13 @@ func doCreateCluster(p *api.ProviderConfig, cfg *api.ClusterConfig, nameArg stri
var kubeconfigContextName string

if writeKubeconfig {
clientConfig, err := ctl.NewClientConfig(cfg, false)
client, err := ctl.NewClient(cfg, false)
if err != nil {
return err
}
kubeconfigContextName = clientConfig.ContextName
kubeconfigContextName = client.ContextName

kubeconfigPath, err = kubeconfig.Write(kubeconfigPath, *clientConfig.Client, setContext)
kubeconfigPath, err = kubeconfig.Write(kubeconfigPath, *client.Config, setContext)
if err != nil {
return errors.Wrap(err, "writing kubeconfig")
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/ctl/utils/write_kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ func doWriteKubeconfigCmd(p *api.ProviderConfig, cfg *api.ClusterConfig, nameArg
return err
}

config, err := ctl.NewClientConfig(cfg, false)
client, err := ctl.NewClient(cfg, false)
if err != nil {
return err
}

filename, err := kubeconfig.Write(writeKubeconfigOutputPath, *config.Client, writeKubeconfigSetContext)
filename, err := kubeconfig.Write(writeKubeconfigOutputPath, *client.Config, writeKubeconfigSetContext)
if err != nil {
return errors.Wrap(err, "writing kubeconfig")
}
Expand Down
77 changes: 0 additions & 77 deletions pkg/eks/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,11 @@ import (

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/sts"

"github.com/kubernetes-sigs/aws-iam-authenticator/pkg/token"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kops/pkg/pki"

api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha4"
"github.com/weaveworks/eksctl/pkg/utils"
"github.com/weaveworks/eksctl/pkg/utils/kubeconfig"
)

func (c *ClusterProvider) getKeyPairName(clusterName string, ng *api.NodeGroup, fingerprint *string) string {
Expand Down Expand Up @@ -150,74 +144,3 @@ func (c *ClusterProvider) getUsername() string {
}
return "iam-root-account"
}

// ClientConfig stores information about the client config
type ClientConfig struct {
Client *clientcmdapi.Config
ContextName string
}

// NewClientConfig creates a new client config, if withEmbeddedToken is true
// it will embed the STS token, otherwise it will use authenticator exec plugin
// and ensures that AWS_PROFILE environment variable gets set also
func (c *ClusterProvider) NewClientConfig(spec *api.ClusterConfig, withEmbeddedToken bool) (*ClientConfig, error) {
client, _, contextName := kubeconfig.New(spec, c.getUsername(), "")

config := &ClientConfig{
Client: client,
ContextName: contextName,
}

if withEmbeddedToken {
if err := config.useEmbeddedToken(spec, c.Provider.STS().(*sts.STS)); err != nil {
return nil, err
}
} else {
kubeconfig.AppendAuthenticator(config.Client, spec, utils.DetectAuthenticator(), c.Provider.Profile())
}

return config, nil
}

func (c *ClientConfig) useEmbeddedToken(spec *api.ClusterConfig, sts *sts.STS) error {
gen, err := token.NewGenerator(true)
if err != nil {
return errors.Wrap(err, "could not get token generator")
}

tok, err := gen.GetWithSTS(spec.Metadata.Name, sts)
if err != nil {
return errors.Wrap(err, "could not get token")
}

c.Client.AuthInfos[c.ContextName].Token = tok
return nil
}

// NewClientSet creates a new API client
func (c *ClientConfig) NewClientSet() (*kubernetes.Clientset, error) {
clientConfig, err := clientcmd.NewDefaultClientConfig(*c.Client, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to create API client configuration from client config")
}

client, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
return nil, errors.Wrap(err, "failed to create API client")
}
return client, nil
}

// NewStdClientSet creates a new API client in one go with an embedded STS token, this is most commonly used option
func (c *ClusterProvider) NewStdClientSet(spec *api.ClusterConfig) (*kubernetes.Clientset, error) {
clientConfig, err := c.NewClientConfig(spec, true)
if err != nil {
return nil, errors.Wrap(err, "creating Kubernetes client config with embedded token")
}

clientSet, err := clientConfig.NewClientSet()
if err != nil {
return nil, errors.Wrap(err, "creating Kubernetes client")
}
return clientSet, nil
}
4 changes: 2 additions & 2 deletions pkg/eks/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ var _ = Describe("eks auth helpers", func() {
}

It("should create config with authenticator", func() {
clientConfig, err := ctl.NewClientConfig(cfg, false)
clientConfig, err := ctl.NewClient(cfg, false)

Expect(err).To(Not(HaveOccurred()))

Expand Down Expand Up @@ -88,7 +88,7 @@ var _ = Describe("eks auth helpers", func() {
})

It("should create clientset", func() {
clientConfig, err := ctl.NewClientConfig(cfg, false)
clientConfig, err := ctl.NewClient(cfg, false)

Expect(err).To(Not(HaveOccurred()))
Expect(clientConfig).To(Not(BeNil()))
Expand Down
182 changes: 182 additions & 0 deletions pkg/eks/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package eks

import (
"github.com/kris-nova/logger"
"github.com/pkg/errors"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

"github.com/aws/aws-sdk-go/service/sts"
"github.com/kubernetes-sigs/aws-iam-authenticator/pkg/token"

api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha4"
"github.com/weaveworks/eksctl/pkg/utils"
"github.com/weaveworks/eksctl/pkg/utils/kubeconfig"
)

// Client stores information about the client config
type Client struct {
Config *clientcmdapi.Config
ContextName string

rawConfig *restclient.Config
}

// RawClient stores information about the client config
type RawClient struct {
mapper meta.RESTMapper
config *restclient.Config
ClientSet kubernetes.Interface
}

// NewClient creates a new client config, if withEmbeddedToken is true
// it will embed the STS token, otherwise it will use authenticator exec plugin
// and ensures that AWS_PROFILE environment variable gets set also
func (c *ClusterProvider) NewClient(spec *api.ClusterConfig, withEmbeddedToken bool) (*Client, error) {
clientConfig, _, contextName := kubeconfig.New(spec, c.getUsername(), "")

config := &Client{
Config: clientConfig,
ContextName: contextName,
}

return config.new(spec, withEmbeddedToken, c.Provider.STS().(*sts.STS), c.Provider.Profile())
}

func (c *Client) new(spec *api.ClusterConfig, withEmbeddedToken bool, sts *sts.STS, profile string) (*Client, error) {
if withEmbeddedToken {
if err := c.useEmbeddedToken(spec, sts); err != nil {
return nil, err
}
} else {
kubeconfig.AppendAuthenticator(c.Config, spec, utils.DetectAuthenticator(), profile)
}

rawConfig, err := clientcmd.NewDefaultClientConfig(*c.Config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to create API client configuration from client config")
}
c.rawConfig = rawConfig

return c, nil
}

func (c *Client) useEmbeddedToken(spec *api.ClusterConfig, sts *sts.STS) error {
gen, err := token.NewGenerator(true)
if err != nil {
return errors.Wrap(err, "could not get token generator")
}

tok, err := gen.GetWithSTS(spec.Metadata.Name, sts)
if err != nil {
return errors.Wrap(err, "could not get token")
}

c.Config.AuthInfos[c.ContextName].Token = tok
return nil
}

// NewClientSet creates a new API client
func (c *Client) NewClientSet() (*kubernetes.Clientset, error) {
client, err := kubernetes.NewForConfig(c.rawConfig)
if err != nil {
return nil, errors.Wrap(err, "failed to create API client")
}
return client, nil
}

// NewStdClientSet creates a new API client in one go with an embedded STS token, this is most commonly used option
func (c *ClusterProvider) NewStdClientSet(spec *api.ClusterConfig) (*kubernetes.Clientset, error) {
_, clientSet, err := c.newClientSetWithEmbeddedToken(spec)
if err != nil {
return nil, err
}

return clientSet, nil
}

func (c *ClusterProvider) newClientSetWithEmbeddedToken(spec *api.ClusterConfig) (*Client, *kubernetes.Clientset, error) {
client, err := c.NewClient(spec, true)
if err != nil {
return nil, nil, errors.Wrap(err, "creating Kubernetes client config with embedded token")
}

clientSet, err := client.NewClientSet()
if err != nil {
return nil, nil, errors.Wrap(err, "creating Kubernetes client")
}

return client, clientSet, nil
}

// NewRawClient creates a new raw REST client in one go with an embedded STS token
func (c *ClusterProvider) NewRawClient(spec *api.ClusterConfig) (*RawClient, error) {
client, clientSet, err := c.newClientSetWithEmbeddedToken(spec)
if err != nil {
return nil, err
}
rawClient := &RawClient{
config: client.rawConfig,
ClientSet: clientSet,
}
return rawClient.new()
}

func (c *RawClient) new() (*RawClient, error) {
apiGroupResources, err := restmapper.GetAPIGroupResources(c.ClientSet.Discovery())
if err != nil {
return nil, errors.Wrap(err, "getting list of API resources for raw REST client")
}

for i, r := range apiGroupResources {
logger.Debug("apiGroupResources[%d] = %#v", i, *r)
}

c.mapper = restmapper.NewDiscoveryRESTMapper(apiGroupResources)

if c.config.APIPath == "" {
c.config.APIPath = "/api"
}
if c.config.NegotiatedSerializer == nil {
c.config.NegotiatedSerializer = &serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
}
if err := restclient.SetKubernetesDefaults(c.config); err != nil {
return nil, errors.Wrap(err, "applying defaults for REST client")
}
return c, nil
}

// NewFor construct a resource type-specific client for a give gvk
// (it's based on k8s.io/kubernetes/pkg/kubectl/cmd/util/factory_client_access.go)
func (c *RawClient) NewFor(gvk schema.GroupVersionKind) (*resource.Helper, error) {
mapping, err := c.mapper.RESTMapping(gvk.GroupKind(), gvk.GroupVersion().Version, "")
if err != nil {
return nil, errors.Wrapf(err, "constructing REST client mapping for %s", gvk.String())
}

switch gvk.Group {
case corev1.GroupName:
c.config.APIPath = "/api"
default:
c.config.APIPath = "/apis"
}
gv := gvk.GroupVersion()
c.config.GroupVersion = &gv

client, err := restclient.RESTClientFor(c.config)
if err != nil {
return nil, errors.Wrapf(err, "constructing REST client for %s", gvk.String())
}

return resource.NewHelper(client, mapping), nil
}

0 comments on commit ba5f04e

Please sign in to comment.