github.com/erda-project/erda-infra@v1.0.10-0.20240327085753-f3a249292aeb/providers/kubernetes/kubernetes.go (about)

     1  // Copyright (c) 2021 Terminus, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kubernetes
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"reflect"
    24  	"time"
    25  
    26  	"k8s.io/client-go/kubernetes"
    27  	"k8s.io/client-go/rest"
    28  	"k8s.io/client-go/tools/clientcmd"
    29  	certutil "k8s.io/client-go/util/cert"
    30  
    31  	"github.com/erda-project/erda-infra/base/servicehub"
    32  )
    33  
    34  // Interface .
    35  type Interface interface {
    36  	Client() *kubernetes.Clientset
    37  }
    38  
    39  var clientType = reflect.TypeOf((*kubernetes.Clientset)(nil))
    40  
    41  type config struct {
    42  	MasterURL          string `file:"master_url"`
    43  	ConfigPath         string `file:"config_path"`
    44  	RootCAFile         string `file:"root_ca_file"`
    45  	TokenFile          string `file:"token_file"`
    46  	InsecureSkipVerify bool   `file:"insecure_skip_verify"`
    47  	ConnectionCheck    bool   `file:"connection_check" default:"true"`
    48  }
    49  
    50  // provider .
    51  type provider struct {
    52  	Cfg    *config
    53  	client *kubernetes.Clientset
    54  }
    55  
    56  // Init .
    57  func (p *provider) Init(ctx servicehub.Context) error {
    58  
    59  	config, err := p.createRestConfig()
    60  	if err != nil {
    61  		return fmt.Errorf("create rest config err: %w", err)
    62  	}
    63  
    64  	// create the clientset
    65  	clientset, err := kubernetes.NewForConfig(config)
    66  	if err != nil {
    67  		return fmt.Errorf("create k8s client err: %w", err)
    68  	}
    69  
    70  	if p.Cfg.ConnectionCheck {
    71  		if err := HealthCheck(clientset, 30*time.Second); err != nil {
    72  			return fmt.Errorf("check connection err: %w", err)
    73  		}
    74  	}
    75  
    76  	p.client = clientset
    77  	return nil
    78  }
    79  
    80  func (p *provider) Client() *kubernetes.Clientset { return p.client }
    81  
    82  // Provide .
    83  func (p *provider) Provide(ctx servicehub.DependencyContext, args ...interface{}) interface{} {
    84  	if ctx.Type() == clientType || ctx.Service() == "kube-client" || ctx.Service() == "kubernetes-client" {
    85  		return p.client
    86  	}
    87  	return p
    88  }
    89  
    90  func (p *provider) createRestConfig() (*rest.Config, error) {
    91  	var config *rest.Config
    92  	if p.Cfg.MasterURL != "" {
    93  		if p.Cfg.RootCAFile != "" && p.Cfg.TokenFile != "" {
    94  			tlscfg := rest.TLSClientConfig{
    95  				Insecure: p.Cfg.InsecureSkipVerify,
    96  			}
    97  			if _, err := certutil.NewPool(p.Cfg.RootCAFile); err != nil {
    98  				return nil, fmt.Errorf("expected to load root CA config from %s, but got err: %v", p.Cfg.RootCAFile, err)
    99  			}
   100  			tlscfg.CAFile = p.Cfg.RootCAFile
   101  			token, err := ioutil.ReadFile(p.Cfg.TokenFile)
   102  			if err != nil {
   103  				return nil, err
   104  			}
   105  
   106  			config = &rest.Config{
   107  				TLSClientConfig: tlscfg,
   108  				Host:            p.Cfg.MasterURL,
   109  				BearerTokenFile: p.Cfg.TokenFile,
   110  				BearerToken:     string(token),
   111  			}
   112  		} else {
   113  			if p.Cfg.ConfigPath == "" {
   114  				if home := homeDir(); home != "" {
   115  					p.Cfg.ConfigPath = filepath.Join(home, ".kube", "config")
   116  				}
   117  			}
   118  			if _, err := os.Stat(p.Cfg.ConfigPath); err == nil {
   119  				// use the current context in kubeconfig
   120  				cfg, err := clientcmd.BuildConfigFromFlags(p.Cfg.MasterURL, p.Cfg.ConfigPath)
   121  				if err != nil {
   122  					return nil, fmt.Errorf("fail to build kube config: %s", err)
   123  				}
   124  				config = cfg
   125  			}
   126  		}
   127  	} else {
   128  		cfg, err := rest.InClusterConfig()
   129  		if err != nil {
   130  			return nil, fmt.Errorf("build from inCluster err: %w", err)
   131  		}
   132  		config = cfg
   133  	}
   134  	return config, nil
   135  }
   136  
   137  // HealthCheck check apiserver connection
   138  func HealthCheck(client *kubernetes.Clientset, to time.Duration) error {
   139  	ctx, _ := context.WithTimeout(context.TODO(), to)
   140  	_, err := client.Discovery().RESTClient().Get().AbsPath("/healthz").DoRaw(ctx)
   141  	return err
   142  }
   143  
   144  func homeDir() string {
   145  	if h := os.Getenv("HOME"); h != "" {
   146  		return h
   147  	}
   148  	return os.Getenv("USERPROFILE") // windows
   149  }
   150  
   151  func init() {
   152  	servicehub.Register("kubernetes", &servicehub.Spec{
   153  		Services: []string{"kubernetes", "kubernetes-client", "kube-client"},
   154  		Types: []reflect.Type{
   155  			reflect.TypeOf((*Interface)(nil)).Elem(),
   156  			clientType,
   157  		},
   158  		Description: "kubernetes",
   159  		ConfigFunc:  func() interface{} { return &config{} },
   160  		Creator: func() servicehub.Provider {
   161  			return &provider{}
   162  		},
   163  	})
   164  }