From 27ca024bb78747837dd26c9ed53813ca5faba1b3 Mon Sep 17 00:00:00 2001 From: zryfish Date: Wed, 27 May 2020 18:11:27 +0800 Subject: [PATCH] add version api (#2127) add cluster validation api --- .dockerignore | 1 - build/ks-apiserver/Dockerfile | 10 +- go.mod | 1 + pkg/apiserver/apiserver.go | 4 +- pkg/controller/cluster/cluster_controller.go | 2 +- pkg/kapis/cluster/v1alpha1/handler.go | 116 ++++++++++++++++++- pkg/kapis/cluster/v1alpha1/handler_test.go | 101 +++++++++++++++- pkg/kapis/cluster/v1alpha1/register.go | 6 + pkg/kapis/version/register.go | 35 ++++++ pkg/simple/client/k8s/kubernetes.go | 5 + pkg/version/version.go | 41 ++++++- 11 files changed, 308 insertions(+), 14 deletions(-) create mode 100644 pkg/kapis/version/register.go diff --git a/.dockerignore b/.dockerignore index e1d2b5f5..50609239 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,2 @@ tmp/ -.git .github diff --git a/build/ks-apiserver/Dockerfile b/build/ks-apiserver/Dockerfile index a2f60675..551c6cda 100644 --- a/build/ks-apiserver/Dockerfile +++ b/build/ks-apiserver/Dockerfile @@ -7,7 +7,15 @@ FROM golang:1.12 as ks-apiserver-builder COPY / /go/src/kubesphere.io/kubesphere WORKDIR /go/src/kubesphere.io/kubesphere -RUN CGO_ENABLED=0 GO111MODULE=on GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor go build -i -ldflags '-w -s' -o ks-apiserver cmd/ks-apiserver/apiserver.go +RUN GIT_VERSION=$(git describe --always --dirty) && \ + GIT_HASH=$(git rev-parse HEAD) && \ + BUILDDATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') && \ + CGO_ENABLED=0 GO111MODULE=on GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor \ + go build -i -ldflags \ + '-w -s -X kubesphere.io/kubesphere/pkg/version.version=$(GIT_VERSION) \ + -X kubesphere.io/kubesphere/pkg/version.gitCommit=$(GIT_HASH) \ + -X kubesphere.io/kubesphere/pkg/version.buildDate=$(BUILDDATE)' \ + -o ks-apiserver cmd/ks-apiserver/apiserver.go FROM alpine:3.9 RUN apk add --update ca-certificates && update-ca-certificates diff --git a/go.mod b/go.mod index 79bce0c1..eee1cfee 100644 --- a/go.mod +++ b/go.mod @@ -107,6 +107,7 @@ require ( sigs.k8s.io/controller-runtime v0.5.0 sigs.k8s.io/controller-tools v0.2.4 sigs.k8s.io/kubefed v0.2.0-alpha.1 + sigs.k8s.io/testing_frameworks v0.1.2 ) replace ( diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 83ec0a06..4cc56489 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -60,6 +60,7 @@ import ( servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2" tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2" terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2" + "kubesphere.io/kubesphere/pkg/kapis/version" "kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/im" "kubesphere.io/kubesphere/pkg/simple/client/cache" @@ -202,6 +203,7 @@ func (s *APIServer) installKubeSphereAPIs() { s.InformerFactory.KubernetesSharedInformerFactory())) urlruntime.Must(notificationv1.AddToContainer(s.container, s.Config.NotificationOptions.Endpoint)) urlruntime.Must(alertingv1.AddToContainer(s.container, s.Config.AlertingOptions.Endpoint)) + urlruntime.Must(version.AddToContainer(s.container, s.KubernetesClient.Discovery())) } func (s *APIServer) Run(stopCh <-chan struct{}) (err error) { @@ -253,7 +255,7 @@ func (s *APIServer) buildHandlerChain() { default: fallthrough case authorizationoptions.RBAC: - excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*"} + excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*", "/kapis/version"} pathAuthorizer, _ := path.NewAuthorizer(excludedPaths) amOperator := am.NewReadOnlyOperator(s.InformerFactory) authorizers = unionauthorizer.New(pathAuthorizer, authorizerfactory.NewRBACAuthorizer(amOperator)) diff --git a/pkg/controller/cluster/cluster_controller.go b/pkg/controller/cluster/cluster_controller.go index 7a762370..18a08809 100644 --- a/pkg/controller/cluster/cluster_controller.go +++ b/pkg/controller/cluster/cluster_controller.go @@ -352,7 +352,7 @@ func (c *ClusterController) syncCluster(key string) error { clientSet, err = kubernetes.NewForConfig(clusterConfig) if err != nil { klog.Errorf("Failed to create ClientSet from config, %#v", err) - return nil + return err } if !cluster.Spec.JoinFederation { // trying to unJoin federation diff --git a/pkg/kapis/cluster/v1alpha1/handler.go b/pkg/kapis/cluster/v1alpha1/handler.go index 9c95eee4..d34a7a57 100644 --- a/pkg/kapis/cluster/v1alpha1/handler.go +++ b/pkg/kapis/cluster/v1alpha1/handler.go @@ -2,6 +2,7 @@ package v1alpha1 import ( "bytes" + "encoding/json" "fmt" "github.com/emicklei/go-restful" "io" @@ -10,20 +11,30 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" v1 "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1" clusterlister "kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1" + "kubesphere.io/kubesphere/pkg/version" + "net/http" + "net/url" "strings" + "time" "k8s.io/cli-runtime/pkg/printers" ) const ( defaultAgentImage = "kubesphere/tower:v1.0" + defaultTimeout = 5 * time.Second ) -var ErrClusterConnectionIsNotProxy = fmt.Errorf("cluster is not using proxy connection") +var errClusterConnectionIsNotProxy = fmt.Errorf("cluster is not using proxy connection") +var errNon200Response = fmt.Errorf("non-200 response returned from endpoint") +var errInvalidResponse = fmt.Errorf("invalid response from kubesphere apiserver") type handler struct { serviceLister v1.ServiceLister @@ -87,7 +98,6 @@ func (h *handler) GenerateAgentDeployment(request *restful.Request, response *re } response.Write(buf.Bytes()) - } // @@ -138,7 +148,7 @@ func (h *handler) populateProxyAddress() error { func (h *handler) generateDefaultDeployment(cluster *v1alpha1.Cluster, w io.Writer) error { if cluster.Spec.Connection.Type == v1alpha1.ConnectionTypeDirect { - return ErrClusterConnectionIsNotProxy + return errClusterConnectionIsNotProxy } agent := appsv1.Deployment{ @@ -174,6 +184,7 @@ func (h *handler) generateDefaultDeployment(cluster *v1alpha1.Cluster, w io.Writ fmt.Sprintf("--name=%s", cluster.Name), fmt.Sprintf("--token=%s", cluster.Spec.Connection.Token), fmt.Sprintf("--proxy-server=%s", h.proxyAddress), + fmt.Sprintf("--keepalive=30s"), fmt.Sprintf("--kubesphere-service=ks-apiserver.kubesphere-system.svc:80"), fmt.Sprintf("--kubernetes-service=kubernetes.default.svc:443"), }, @@ -198,3 +209,102 @@ func (h *handler) generateDefaultDeployment(cluster *v1alpha1.Cluster, w io.Writ return h.yamlPrinter.PrintObj(&agent, w) } + +// ValidateCluster validate cluster kubeconfig and kubesphere apiserver address, check their accessibility +func (h *handler) ValidateCluster(request *restful.Request, response *restful.Response) { + var cluster v1alpha1.Cluster + + err := request.ReadEntity(&cluster) + if err != nil { + api.HandleBadRequest(response, request, err) + return + } + + if cluster.Spec.Connection.Type != v1alpha1.ConnectionTypeDirect { + api.HandleBadRequest(response, request, fmt.Errorf("cluster connection type is not direct")) + return + } + + if len(cluster.Spec.Connection.KubeConfig) == 0 || len(cluster.Spec.Connection.KubeSphereAPIEndpoint) == 0 { + api.HandleBadRequest(response, request, fmt.Errorf("cluster kubeconfig and kubesphere endpoint should not be empty")) + return + } + + err = validateKubeConfig(cluster.Spec.Connection.KubeConfig) + if err != nil { + api.HandleBadRequest(response, request, err) + return + } + + _, err = validateKubeSphereAPIServer(cluster.Spec.Connection.KubeSphereAPIEndpoint) + if err != nil { + api.HandleBadRequest(response, request, fmt.Errorf("unable validate kubesphere endpoint, %v", err)) + return + } + + response.WriteHeader(http.StatusOK) +} + +// validateKubeConfig takes base64 encoded kubeconfig and check its validity +func validateKubeConfig(kubeconfig []byte) error { + config, err := loadKubeConfigFromBytes(kubeconfig) + if err != nil { + return err + } + + config.Timeout = defaultTimeout + + clientSet, err := kubernetes.NewForConfig(config) + if err != nil { + return err + } + + _, err = clientSet.Discovery().ServerVersion() + + return err +} + +func loadKubeConfigFromBytes(kubeconfig []byte) (*rest.Config, error) { + clientConfig, err := clientcmd.NewClientConfigFromBytes(kubeconfig) + if err != nil { + return nil, err + } + + config, err := clientConfig.ClientConfig() + if err != nil { + return nil, err + } + + return config, nil +} + +// validateKubeSphereAPIServer uses version api to check the accessibility +func validateKubeSphereAPIServer(ksEndpoint string) (*version.Info, error) { + _, err := url.Parse(ksEndpoint) + if err != nil { + return nil, err + } + + path := fmt.Sprintf("%s/kapis/version", ksEndpoint) + + client := http.Client{ + Timeout: defaultTimeout, + } + + response, err := client.Get(path) + if err != nil { + return nil, err + } + + if response.StatusCode != http.StatusOK { + return nil, errNon200Response + } + + ver := version.Info{} + err = json.NewDecoder(response.Body).Decode(&ver) + if err != nil { + return nil, errInvalidResponse + } + + return &ver, nil +} diff --git a/pkg/kapis/cluster/v1alpha1/handler_test.go b/pkg/kapis/cluster/v1alpha1/handler_test.go index f6ef4d4a..4da3ec6c 100644 --- a/pkg/kapis/cluster/v1alpha1/handler_test.go +++ b/pkg/kapis/cluster/v1alpha1/handler_test.go @@ -2,14 +2,22 @@ package v1alpha1 import ( "bytes" + "encoding/json" + "fmt" "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/printers" - fake2 "k8s.io/client-go/kubernetes/fake" + k8sfake "k8s.io/client-go/kubernetes/fake" "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1" "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake" "kubesphere.io/kubesphere/pkg/informers" + "kubesphere.io/kubesphere/pkg/version" + "net/http" + "net/http/httptest" + "net/url" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/testing_frameworks/integration" "testing" ) @@ -81,6 +89,7 @@ spec: - --name=gondor - --token=randomtoken - --proxy-server=http://139.198.121.121:8080 + - --keepalive=30s - --kubesphere-service=ks-apiserver.kubesphere-system.svc:80 - --kubernetes-service=kubernetes.default.svc:443 image: kubesphere/tower:v1.0 @@ -97,7 +106,7 @@ status: {} ` func TestGeranteAgentDeployment(t *testing.T) { - k8sclient := fake2.NewSimpleClientset(service) + k8sclient := k8sfake.NewSimpleClientset(service) ksclient := fake.NewSimpleClientset(cluster) informersFactory := informers.NewInformerFactories(k8sclient, ksclient, nil, nil, nil, nil) @@ -124,7 +133,7 @@ func TestGeranteAgentDeployment(t *testing.T) { { description: "test direct connection cluster", expectingError: true, - expectedError: ErrClusterConnectionIsNotProxy, + expectedError: errClusterConnectionIsNotProxy, cluster: directConnectionCluster, }, } @@ -159,7 +168,6 @@ func TestGeranteAgentDeployment(t *testing.T) { } }) } - } func TestInnerGenerateAgentDeployment(t *testing.T) { @@ -177,10 +185,91 @@ func TestInnerGenerateAgentDeployment(t *testing.T) { t.Error(err) } - t.Log(buf.String()) - if diff := cmp.Diff(buf.String(), expected); len(diff) != 0 { t.Error(diff) } } + +var base64EncodedKubeConfig = ` +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKYzNCb1pYSmxNQjRYRFRJd01EVXlOekEzTWpFME1Gb1hEVE13TURVeU5UQTNNakUwTUZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYTndhR1Z5WlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTWVxCjRwNldVQVJmeUtRcEpZM1ZGcEVhT3Y5REZBQkZhQXBpbUswQTBBL05KV25yaGl0MjhnL3ZXMno5bmxkeHQwZzgKc1J0ZEp4TUxmOHF5WkEramZudU5jUDBLUTJ5a3VxZE41c29MdE1TUmt1K1dHZFNkWlJvTVpEakdKbHRSUEdVRQpnOHd6OE9zdWRMcmZ5Zlcxdy8vUFRPWnRLNmsrNUhQZGtuWU5KcU9UZGJDTEVma2RZbFB1ZXNZTTFKamRacXlNClJQRDM1RXpUSEowR05jYlBzbUlyZ05WZGR4Nmh5RmIxTFZ0QXRqa0tWY2lNR1k1UlFTQWlQdVVGaGQreUcrOHUKUWlqQlgvV09ESlFOelJCQWFPdWRWdURqSkhIZ0lBV3FzcC9qSllWQkdRYnNad3djTTRUSEJPb2k3N1krYVR3SAphcHRaMitVMzgwbFY4d0tJR3NFQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDVjY0VVpCZkcxc1d4ampJWTZ5Vm0rdzhxTFgKS0ZYanR3VElGb1Y3WENOT2RDbEQ0a2NsN1pyTnl5UEtUaTFOV3BDbUVzSXZPVjRSRXV6a2ZWZ2Rzc0tHL2dYVwpGNGV2dStZTFh1UEk1RHJFejNGaW5OMGxVcFNZMVg3b1Y5N2JyU1lmdE53aWJQbUVFTEVHbWJvMnNHS3VoL21BCjRJZ0tmZklqWVdSYjE3TlZLb2s0am1WTnhkMmNCL09GeFlsY2xndlc2THpxc1BDdnpWbDdDRWErRElyNHZLamgKRlhvOERXejMzclUySCs0RjNMOThzWGE1OWNITWZPb1kzZ3pzUE5LYnQwbWsxeTNOUmZocTZYTTJXemkrUFZkcAppbUVqUlY4UUl5c3Zhc25sTjFIY1FtcWtMaFdLYlRoOEc5MGhiTzlwdTFRNE0wMmZ3STg0ZVlSUWZNUT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + server: 127.0.0.1:6578 + name: kubernetes +contexts: +- context: + cluster: kubernetes + user: kubernetes-admin + name: kubernetes-admin@kubernetes +current-context: kubernetes-admin@kubernetes +kind: Config +preferences: {} +users: +- name: kubernetes-admin + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJZkcyYVlKYnRYT2N3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWE53YUdWeVpUQWVGdzB5TURBMU1qY3dOekl4TkRCYUZ3MHpNREExTWpVd056SXhOREJhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXRiR3JpeFBxOWF6QU1wNGsKM0VsaElmanhOZXhkY2VORGJENkFMMXVGSWJUMk1uVnRXeFFXOU5ueVFwVU9NMzN4clNJMkJlVW1uU0U5RlR6cAo1bTBmaGVBOGhtRHRSZWVOckNxVnBJQy9kNUV2eDJ3NTJ2NXVCYUNXd09ZUGpQMi9uL0Y3THQwMmN3TkFXWE5pCmFRTi9uRGl6TjJrRXFoSmZiL0tyNGx3eEEzTDExVXJhMDNTRUp0U3FXSzBKQ1pnL0lzUnRFNFFqZXp1WWhiVWkKcWU0TmdqZjN1ZFBMMXQyeVpCK3hSTE1sNTFqenhYaXQ0U2pHSFJ2UEt1VHlkc1AxdEtINXdYdnhqaEZTZjN3UQpMSHFQd3hQWXVKSG5MWlhPaElnTnEzNnk4Rlp0WkdQRUhDVDFlUEh5cWhQaEFZZGlBRlRXT3pRN2FlS1puZWJzClpSblJMUUlEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFCZGNMN2I4akVuYjRSSndqZ3ZrVUxSQXJLVGk1WFhRSzllUwpsMlNkNEF3Ris5QWg3WElUazJFQ3J0WVR2WEM0K2trS1BxK2FuYjlOOFptenA0R0ovd3N5VEN2dHo5eGlQMTd3ClFCUXREdFA3eS9venlLc24rUzYvSDg2Y0JqdGZGT0dMYm5CekRBY3J0S3YyeUxxY3pyNlYzSDBnZDI3MVdlSkQKcU81U3czSEZoTHhERDRXSVVSQnFLOTJPVUhnSzVQOWRHaWdkK2MvQ0xWMFdJS1kzN3JGR2MrU3VUa2JOQXNmaQpmVmhBYXRsYlpQdE1QekJoV1hkM0JWcTMxTmtBM1F4aWUzdWc4Tm9OZ0czUHFPanJkZHA2aEs0Sk5wMGtkSGFvClZHRXMxcUdzOGJjekxNTTVzeUdkSndNbXVNUEdnaGxheUZrZlJ2RmpZWDlwbllJUStpTT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdGJHcml4UHE5YXpBTXA0azNFbGhJZmp4TmV4ZGNlTkRiRDZBTDF1RkliVDJNblZ0Cld4UVc5Tm55UXBVT00zM3hyU0kyQmVVbW5TRTlGVHpwNW0wZmhlQThobUR0UmVlTnJDcVZwSUMvZDVFdngydzUKMnY1dUJhQ1d3T1lQalAyL24vRjdMdDAyY3dOQVdYTmlhUU4vbkRpek4ya0VxaEpmYi9LcjRsd3hBM0wxMVVyYQowM1NFSnRTcVdLMEpDWmcvSXNSdEU0UWplenVZaGJVaXFlNE5namYzdWRQTDF0MnlaQit4UkxNbDUxanp4WGl0CjRTakdIUnZQS3VUeWRzUDF0S0g1d1h2eGpoRlNmM3dRTEhxUHd4UFl1SkhuTFpYT2hJZ05xMzZ5OEZadFpHUEUKSENUMWVQSHlxaFBoQVlkaUFGVFdPelE3YWVLWm5lYnNaUm5STFFJREFRQUJBb0lCQVFDSDFnQ054YUpQY1l0dgpURlA2Yk5HMWVFdTlLS3pqekNoSDhLSWN4YXRPZTkvajhXNkVQUXk4bVlSSXl1OEhDQTE2aHEwazB5Qi9NSzVlCkJtQkg2U1U4RFZ5eWloeFp1cmRzRTVvMGxoeU80M2g0K3l4MTBPbW9RMXJ4ZEE0RU5tRGd6c1J0VU95NEo2SWcKUGVkQTQyQ3dCcVBWdFNuTGpGalZkUE9VRTZDQkZsa01nTVR2R1I5SE9EaVovdmhqRXhFQWg0UFFmMkl3ZXZhVwpxTXFsOVJNVFprVEUyVUJEbHZMWVRNcG5pdGlSSWdLdEdmSk1WTW81WWR4SWpOMXRnUHcwZmN5ZXkrM2JhdDZQCkVUSjk5K21KNDJMZGYzaTlNNkNvTDVaa1BjcEE0ckVSR1RKamtaT3FESzhPMVZXSDNtQ21WYTFyVGN0bmpJbVMKaGRGWElJWUJBb0dCQU84R25uekhzSVFXaExBSkw5U3VWWjhBODl5WHZuWHBFYUUxaHBRdEViRFlEaTBPcGg1UAp0cVVISlZ0T0ViaTYvai9tY1pwTnNNVzR2QitiV3hkbDA4U0g1Qnp2eW9qOEkvbUlVNDFCQkYya3hxMk8yNlJRCjRkdFQrN3NWUUFCT1ArUE0vcWtMOWVKdFI3blYwamRGd3BvcmtzcFo5cE9BZHNnUGVnUzlxUHFOQW9HQkFNS1kKeU5OQWdYRjhBdEV3ZEk5OVB2ZTVoYzN6QjZUclVOMStYNVNUZWNYWnFVSUNwMlJGTFNtL3RiVkRuay9VaXBXeQpYSjgzT084VGFIUjIrYk5OZU5NK2REK0diakVWdDNVMk1XSzN2azFjcy9oWjZrK0F2aE9iSXBrLzR2VEVRUW1FCjh4bkl5bkpPNWJleFQybkEyUWVYblVPbjZzdWtxVnk3YlBLSnNOa2hBb0dBV1YzNUxhQWZvQk1uUXdYOFN5RnYKUThiQVptNlp1RTRPMkY1QjFlN1AyWFcrUHh4bUFaaytLWTkxYVNEVVFXUXdvVVdRbmVlRU96aXBwWXVaVURNegpMUnk5cmcvOWdwLzY5MVlBSHlUNjgrUWlvRXQwVllna0diUFp2NFhmYXYzV3AxNUNySU9iU0RBaGpCcWt3U09rCjhhMXU4WmNYT09qa0FFTEJGVHF3RGhVQ2dZQVVqQjFvY1A4NkJHWW53SDRPU0tORmRRbHozWjJKQkcvZGMyS1UKUlo0dURmV1pTcjV5RC92YzFLbFRJbmlzNVR4YzRpQjFqMWNycDFqNE16ZmFmdXVySW9VVDBCWUNpTkIrUitLZgpFZGUrUTNPZFhhRW9FK2YrR2Z0bFF5R3J4cTAzWEJwdk5veHAxWHJjRXBUWURjemN5RjJLcjBoVGlHZDVxekN0Cnkyd3BBUUtCZ1FDWTFKdlp0YkFlZll2RXQ4c3JETVZsb1FOWVVKTURxWnBLenpqR0w5S3FqdlFwbjJOL2EwNmsKUzNsMndWWExXNy8xb0RMV2gxZGZLbUJlUlJhZUxJMXIvS3FyeTRjUStiVEIzTzBwS2R3WjFFYXNQajBDTnlaUAp0YnFkOEFWa09pRkFNYlRWaVZuR3RzSnJ3c1V2N1NSTUU4ckloMVJva0ZtRGRpN0J1UVl5emc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= +` + +func TestValidateKubeConfig(t *testing.T) { + config, err := loadKubeConfigFromBytes([]byte(base64EncodedKubeConfig)) + if err != nil { + t.Fatal(err) + } + + // config.Host is schemaless, we need to add schema manually + u, err := url.Parse(fmt.Sprintf("http://%s", config.Host)) + if err != nil { + t.Fatal(err) + } + + // we need to specify apiserver port to match above kubeconfig + env := &envtest.Environment{ + Config: config, + ControlPlane: integration.ControlPlane{ + APIServer: &integration.APIServer{ + Args: envtest.DefaultKubeAPIServerFlags, + URL: u, + }, + }, + } + + cfg, err := env.Start() + if err != nil { + t.Log(cfg) + t.Fatal(err) + } + + defer func() { + _ = env.Stop() + }() + + err = validateKubeConfig([]byte(base64EncodedKubeConfig)) + if err != nil { + t.Fatal(err) + } +} + +var ver = version.Get() + +func endpoint(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(ver) +} + +func TestValidateKubeSphereEndpoint(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(endpoint)) + defer svr.Close() + + got, err := validateKubeSphereAPIServer(svr.URL) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(&ver, got); len(diff) != 0 { + t.Errorf("%T +got, -expected %v", ver, diff) + } + +} diff --git a/pkg/kapis/cluster/v1alpha1/register.go b/pkg/kapis/cluster/v1alpha1/register.go index 5012b7a4..05e785b2 100644 --- a/pkg/kapis/cluster/v1alpha1/register.go +++ b/pkg/kapis/cluster/v1alpha1/register.go @@ -33,6 +33,12 @@ func AddToContainer(container *restful.Container, To(h.GenerateAgentDeployment). Returns(http.StatusOK, api.StatusOK, nil)) + webservice.Route(webservice.POST("/clusters/validation"). + Doc(""). + Param(webservice.BodyParameter("cluster", "cluster specification")). + To(h.ValidateCluster). + Returns(http.StatusOK, api.StatusOK, nil)) + container.Add(webservice) return nil diff --git a/pkg/kapis/version/register.go b/pkg/kapis/version/register.go new file mode 100644 index 00000000..7acec73d --- /dev/null +++ b/pkg/kapis/version/register.go @@ -0,0 +1,35 @@ +package version + +import ( + "github.com/emicklei/go-restful" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/klog" + "kubesphere.io/kubesphere/pkg/apiserver/runtime" + "kubesphere.io/kubesphere/pkg/version" +) + +func AddToContainer(container *restful.Container, k8sDiscovery discovery.DiscoveryInterface) error { + webservice := runtime.NewWebService(schema.GroupVersion{}) + + webservice.Route(webservice.GET("/version"). + To(func(request *restful.Request, response *restful.Response) { + ksVersion := version.Get() + + if k8sDiscovery != nil { + k8sVersion, err := k8sDiscovery.ServerVersion() + if err == nil { + ksVersion.Kubernetes = k8sVersion + } else { + klog.Errorf("Failed to get kubernetes version, error %v", err) + } + } + + response.WriteAsJson(ksVersion) + })). + Doc("KubeSphere version") + + container.Add(webservice) + + return nil +} diff --git a/pkg/simple/client/k8s/kubernetes.go b/pkg/simple/client/k8s/kubernetes.go index 3e324de0..243f461e 100644 --- a/pkg/simple/client/k8s/kubernetes.go +++ b/pkg/simple/client/k8s/kubernetes.go @@ -98,6 +98,11 @@ func NewKubernetesClient(options *KubernetesOptions) (Client, error) { return nil, err } + k.discoveryClient, err = discovery.NewDiscoveryClientForConfig(config) + if err != nil { + return nil, err + } + k.ks, err = kubesphere.NewForConfig(config) if err != nil { return nil, err diff --git a/pkg/version/version.go b/pkg/version/version.go index 6cf015f6..4b16a4c2 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -16,4 +16,43 @@ limitations under the License. package version -var Version = "v0.0.0" +import ( + "fmt" + "runtime" + + apimachineryversion "k8s.io/apimachinery/pkg/version" +) + +var ( + version = "v0.0.0" + gitCommit = "unknown" + gitTreeState = "unknown" + buildDate = "unknown" +) + +type Info struct { + Version string `json:"gitVersion"` + GitCommit string `json:"gitCommit"` + GitTreeState string `json:"gitTreeState"` + BuildDate string `json:"buildDate"` + GoVersion string `json:"goVersion"` + Compiler string `json:"compiler"` + Platform string `json:"platform"` + Kubernetes *apimachineryversion.Info `json:"kubernetes,omitempty"` +} + +// Get returns the overall codebase version. It's for +// detecting what code a binary was built from. +func Get() Info { + // These variables typically come from -ldflags settings and + // in their absence fallback to the default settings + return Info{ + Version: version, + GitCommit: gitCommit, + GitTreeState: gitTreeState, + BuildDate: buildDate, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } +} -- GitLab