linkerd2/pkg/k8s/api.go

122 lines
4.0 KiB
Go

package k8s
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"github.com/runconduit/conduit/pkg/healthcheck"
"github.com/runconduit/conduit/pkg/shell"
"k8s.io/client-go/rest"
)
const (
kubernetesConfigFilePathEnvVariable = "KUBECONFIG"
KubeapiSubsystemName = "kubernetes-api"
KubeapiClientCheckDescription = "can initialize the client"
KubeapiAccessCheckDescription = "can query the Kubernetes API"
)
type KubernetesApi interface {
UrlFor(namespace string, extraPathStartingWithSlash string) (*url.URL, error)
NewClient() (*http.Client, error)
healthcheck.StatusChecker
}
type kubernetesApi struct {
config *rest.Config
apiSchemeHostAndPort string
}
func (kubeapi *kubernetesApi) NewClient() (*http.Client, error) {
secureTransport, err := rest.TransportFor(kubeapi.config)
if err != nil {
return nil, fmt.Errorf("error instantiating Kubernetes API client: %v", err)
}
return &http.Client{
Transport: secureTransport,
}, nil
}
func (kubeapi *kubernetesApi) SelfCheck() ([]healthcheck.CheckResult, error) {
apiConnectivityCheck := healthcheck.CheckResult{
Status: healthcheck.CheckError,
SubsystemName: KubeapiSubsystemName,
CheckDescription: KubeapiClientCheckDescription,
}
client, err := kubeapi.NewClient()
if err != nil {
apiConnectivityCheck.Status = healthcheck.CheckError
apiConnectivityCheck.FriendlyMessageToUser = fmt.Sprintf("Error connecting to the API. Error message is [%s]", err.Error())
} else {
apiConnectivityCheck.Status = healthcheck.CheckOk
}
apiAccessCheck := healthcheck.CheckResult{
Status: healthcheck.CheckError,
SubsystemName: KubeapiSubsystemName,
CheckDescription: KubeapiAccessCheckDescription,
}
endpointToCheck, err := generateBaseKubernetesApiUrl(kubeapi.apiSchemeHostAndPort)
if err != nil {
apiAccessCheck.Status = healthcheck.CheckError
apiAccessCheck.FriendlyMessageToUser = fmt.Sprintf("Error querying Kubernetes API. Configured host is [%s], error message is [%s]", kubeapi.apiSchemeHostAndPort, err.Error())
} else {
if client != nil {
resp, err := client.Get(endpointToCheck.String())
if err != nil {
apiAccessCheck.Status = healthcheck.CheckError
apiAccessCheck.FriendlyMessageToUser = fmt.Sprintf("HTTP GET request to endpoint [%s] resulted in error: [%s]", endpointToCheck, err.Error())
} else {
statusCodeReturnedIsWithinSuccessRange := resp.StatusCode < 400
if statusCodeReturnedIsWithinSuccessRange {
apiAccessCheck.Status = healthcheck.CheckOk
} else {
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
apiAccessCheck.Status = healthcheck.CheckError
apiAccessCheck.FriendlyMessageToUser = fmt.Sprintf("HTTP GET request to endpoint [%s] resulted in invalid response: [%v]", endpointToCheck, resp)
} else {
body := string(bytes)
apiAccessCheck.Status = healthcheck.CheckFailed
apiAccessCheck.FriendlyMessageToUser = fmt.Sprintf("HTTP GET request to endpoint [%s] resulted in Status: [%s], body: [%s]", endpointToCheck, resp.Status, body)
}
}
}
}
}
results := []healthcheck.CheckResult{
apiConnectivityCheck,
apiAccessCheck,
}
return results, nil
}
func (kubeapi *kubernetesApi) UrlFor(namespace string, extraPathStartingWithSlash string) (*url.URL, error) {
return generateKubernetesApiBaseUrlFor(kubeapi.apiSchemeHostAndPort, namespace, extraPathStartingWithSlash)
}
func NewK8sAPi(shell shell.Shell, k8sConfigFilesystemPathOverride string, apiHostAndPortOverride string) (KubernetesApi, error) {
kubeconfigEnvVar := os.Getenv(kubernetesConfigFilePathEnvVariable)
config, err := parseK8SConfig(findK8sConfigFile(k8sConfigFilesystemPathOverride, kubeconfigEnvVar, shell.HomeDir()))
if err != nil {
return nil, fmt.Errorf("error configuring Kubernetes API client: %v", err)
}
if apiHostAndPortOverride == "" {
apiHostAndPortOverride = config.Host
}
return &kubernetesApi{
apiSchemeHostAndPort: apiHostAndPortOverride,
config: config,
}, nil
}