pkg/common/hubconfig/hubconfig.go (93 lines of code) (raw):

/* Copyright (c) Microsoft Corporation. Licensed under the MIT license. */ // package hubconfig provides common functionalities for hub configuration. package hubconfig import ( "bufio" "encoding/base64" "errors" "fmt" "io" "net/http" "net/textproto" "os" "strings" "k8s.io/client-go/rest" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" "go.goms.io/fleet-networking/pkg/common/env" "go.goms.io/fleet-networking/pkg/common/httpclient" ) const ( // Environment variable keys for hub config hubServerURLEnvKey = "HUB_SERVER_URL" tokenConfigPathEnvKey = "CONFIG_PATH" //nolint:gosec hubCAEnvKey = "HUB_CERTIFICATE_AUTHORITY" hubKubeHeaderEnvKey = "HUB_KUBE_HEADER" // Naming pattern of member cluster namespace in hub cluster, should be the same as envValue as defined in // https://github.com/Azure/fleet/blob/main/pkg/utils/common.go HubNamespaceNameFormat = "fleet-member-%s" ) // PrepareHubConfig return the config holding attributes for a Kubernetes client to request hub cluster. // Called must make sure all required environment variables are well set. func PrepareHubConfig(tlsClientInsecure bool) (*rest.Config, error) { hubURL, err := env.Lookup(hubServerURLEnvKey) if err != nil { klog.ErrorS(err, "Hub cluster endpoint URL cannot be empty") return nil, err } tokenFilePath, err := env.Lookup(tokenConfigPathEnvKey) if err != nil { klog.ErrorS(err, "Hub token file path cannot be empty") return nil, err } // Retry on obtaining token file as it is created asynchronously by token-refesh container if err := retry.OnError(retry.DefaultRetry, func(_ error) bool { return true }, func() error { // Stat returns file info. It will return an error if there is no file. _, err := os.Stat(tokenFilePath) return err }); err != nil { klog.ErrorS(err, "Cannot retrieve token file from the path %s", tokenFilePath) return nil, err } var hubConfig *rest.Config if tlsClientInsecure { hubConfig = &rest.Config{ BearerTokenFile: tokenFilePath, Host: hubURL, TLSClientConfig: rest.TLSClientConfig{ Insecure: tlsClientInsecure, }, } } else { var caData []byte hubCA, err := env.Lookup(hubCAEnvKey) if err == nil { caData, err = base64.StdEncoding.DecodeString(hubCA) if err != nil { klog.ErrorS(err, "Cannot decode hub cluster certificate authority data") return nil, err } } hubConfig = &rest.Config{ BearerTokenFile: tokenFilePath, Host: hubURL, TLSClientConfig: rest.TLSClientConfig{ Insecure: tlsClientInsecure, CAData: caData, }, } } // Sometime the hub cluster need additional http header for authentication or authorization. // the "HUB_KUBE_HEADER" to allow sending custom header to hub's API Server for authentication and authorization. if header, err := env.Lookup(hubKubeHeaderEnvKey); err == nil { r := textproto.NewReader(bufio.NewReader(strings.NewReader(header))) h, err := r.ReadMIMEHeader() if err != nil && !errors.Is(err, io.EOF) { klog.ErrorS(err, "failed to parse HUB_KUBE_HEADER %q", header) return nil, err } hubConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { return httpclient.NewCustomHeadersRoundTripper(http.Header(h), rt) } } return hubConfig, nil } // FetchMemberClusterNamespace gets the assigned namespace for the member cluster in the hub. func FetchMemberClusterNamespace() (string, error) { mcName, err := env.LookupMemberClusterName() if err != nil { klog.ErrorS(err, "Member cluster name cannot be empty") return "", err } return fmt.Sprintf(HubNamespaceNameFormat, mcName), nil }