config.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. package kubernetes
  2. import (
  3. "path/filepath"
  4. "regexp"
  5. "strings"
  6. "time"
  7. "github.com/porter-dev/porter/internal/models"
  8. "k8s.io/apimachinery/pkg/api/meta"
  9. "k8s.io/apimachinery/pkg/runtime"
  10. "k8s.io/cli-runtime/pkg/genericclioptions"
  11. "k8s.io/client-go/discovery"
  12. diskcached "k8s.io/client-go/discovery/cached/disk"
  13. "k8s.io/client-go/kubernetes"
  14. "k8s.io/client-go/kubernetes/fake"
  15. "k8s.io/client-go/rest"
  16. "k8s.io/client-go/restmapper"
  17. "k8s.io/client-go/tools/clientcmd"
  18. "k8s.io/client-go/util/homedir"
  19. )
  20. // GetAgentOutOfClusterConfig creates a new Agent using the OutOfClusterConfig
  21. func GetAgentOutOfClusterConfig(conf *OutOfClusterConfig) (*Agent, error) {
  22. restConf, err := conf.ToRESTConfig()
  23. if err != nil {
  24. return nil, err
  25. }
  26. clientset, err := kubernetes.NewForConfig(restConf)
  27. if err != nil {
  28. return nil, err
  29. }
  30. return &Agent{conf, clientset}, nil
  31. }
  32. // GetAgentInClusterConfig uses the service account that kubernetes
  33. // gives to pods to connect
  34. func GetAgentInClusterConfig() (*Agent, error) {
  35. conf, err := rest.InClusterConfig()
  36. if err != nil {
  37. return nil, err
  38. }
  39. restClientGetter := newRESTClientGetterFromInClusterConfig(conf)
  40. clientset, err := kubernetes.NewForConfig(conf)
  41. return &Agent{restClientGetter, clientset}, nil
  42. }
  43. // GetAgentTesting creates a new Agent using an optional existing storage class
  44. func GetAgentTesting(objects ...runtime.Object) *Agent {
  45. return &Agent{&fakeRESTClientGetter{}, fake.NewSimpleClientset(objects...)}
  46. }
  47. // UpdateTokenCacheFunc is a function that updates the token cache
  48. // with a new token and expiry time
  49. type UpdateTokenCacheFunc func(token string, expiry time.Time) error
  50. // OutOfClusterConfig is the set of parameters required for an out-of-cluster connection.
  51. // This implements RESTClientGetter
  52. type OutOfClusterConfig struct {
  53. ServiceAccount *models.ServiceAccount `form:"required"`
  54. ClusterID uint `json:"cluster_id" form:"required"`
  55. UpdateTokenCache UpdateTokenCacheFunc
  56. }
  57. // ToRESTConfig creates a kubernetes REST client factory -- it calls ClientConfig on
  58. // the result of ToRawKubeConfigLoader, and also adds a custom http transport layer
  59. // if necessary (required for GCP auth)
  60. func (conf *OutOfClusterConfig) ToRESTConfig() (*rest.Config, error) {
  61. restConf, err := conf.ToRawKubeConfigLoader().ClientConfig()
  62. if err != nil {
  63. return nil, err
  64. }
  65. // if conf.ServiceAccount.AuthMechanism == models.GCP {
  66. // creds, err := google.CredentialsFromJSON(
  67. // context.Background(),
  68. // conf.ServiceAccount.KeyData,
  69. // "https://www.googleapis.com/auth/cloud-platform",
  70. // )
  71. // if err != nil {
  72. // return nil, err
  73. // }
  74. // restConf.Transport = &oauth2.Transport{
  75. // Source: creds.TokenSource,
  76. // }
  77. // }
  78. rest.SetKubernetesDefaults(restConf)
  79. return restConf, nil
  80. }
  81. // ToRawKubeConfigLoader creates a clientcmd.ClientConfig from the raw kubeconfig found in
  82. // the OutOfClusterConfig. It does not implement loading rules or overrides.
  83. func (conf *OutOfClusterConfig) ToRawKubeConfigLoader() clientcmd.ClientConfig {
  84. cmdConf, _ := GetClientConfigFromServiceAccount(
  85. conf.ServiceAccount,
  86. conf.ClusterID,
  87. conf.UpdateTokenCache,
  88. )
  89. return cmdConf
  90. }
  91. // ToDiscoveryClient returns a CachedDiscoveryInterface using a computed RESTConfig
  92. // It's required to implement the interface genericclioptions.RESTClientGetter
  93. func (conf *OutOfClusterConfig) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
  94. // From: k8s.io/cli-runtime/pkg/genericclioptions/config_flags.go > func (*configFlags) ToDiscoveryClient()
  95. restConf, err := conf.ToRESTConfig()
  96. if err != nil {
  97. return nil, err
  98. }
  99. restConf.Burst = 100
  100. defaultHTTPCacheDir := filepath.Join(homedir.HomeDir(), ".kube", "http-cache")
  101. // takes the parentDir and the host and comes up with a "usually non-colliding" name for the discoveryCacheDir
  102. parentDir := filepath.Join(homedir.HomeDir(), ".kube", "cache", "discovery")
  103. // strip the optional scheme from host if its there:
  104. schemelessHost := strings.Replace(strings.Replace(restConf.Host, "https://", "", 1), "http://", "", 1)
  105. // now do a simple collapse of non-AZ09 characters. Collisions are possible but unlikely. Even if we do collide the problem is short lived
  106. safeHost := regexp.MustCompile(`[^(\w/\.)]`).ReplaceAllString(schemelessHost, "_")
  107. discoveryCacheDir := filepath.Join(parentDir, safeHost)
  108. return diskcached.NewCachedDiscoveryClientForConfig(restConf, discoveryCacheDir, defaultHTTPCacheDir, time.Duration(10*time.Minute))
  109. }
  110. // ToRESTMapper returns a mapper
  111. func (conf *OutOfClusterConfig) ToRESTMapper() (meta.RESTMapper, error) {
  112. // From: k8s.io/cli-runtime/pkg/genericclioptions/config_flags.go > func (*configFlags) ToRESTMapper()
  113. discoveryClient, err := conf.ToDiscoveryClient()
  114. if err != nil {
  115. return nil, err
  116. }
  117. mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
  118. expander := restmapper.NewShortcutExpander(mapper, discoveryClient)
  119. return expander, nil
  120. }
  121. // newRESTClientGetterFromInClusterConfig returns a RESTClientGetter using
  122. // default values set from the *rest.Config
  123. func newRESTClientGetterFromInClusterConfig(conf *rest.Config) genericclioptions.RESTClientGetter {
  124. cfs := genericclioptions.NewConfigFlags(false)
  125. cfs.ClusterName = &conf.ServerName
  126. cfs.Insecure = &conf.Insecure
  127. cfs.APIServer = &conf.Host
  128. cfs.CAFile = &conf.CAFile
  129. cfs.KeyFile = &conf.KeyFile
  130. cfs.CertFile = &conf.CertFile
  131. cfs.BearerToken = &conf.BearerToken
  132. cfs.Timeout = stringptr(conf.Timeout.String())
  133. cfs.Impersonate = &conf.Impersonate.UserName
  134. cfs.ImpersonateGroup = &conf.Impersonate.Groups
  135. cfs.Username = &conf.Username
  136. cfs.Password = &conf.Password
  137. return cfs
  138. }
  139. func stringptr(val string) *string {
  140. return &val
  141. }
  142. type fakeRESTClientGetter struct{}
  143. func (f *fakeRESTClientGetter) ToRESTConfig() (*rest.Config, error) {
  144. return nil, nil
  145. }
  146. func (f *fakeRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
  147. return nil
  148. }
  149. func (f *fakeRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
  150. return nil, nil
  151. }
  152. func (f *fakeRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
  153. return nil, nil
  154. }