Skip to content

Commit

Permalink
feature: client support crd, issue TencentBlueKing#269
Browse files Browse the repository at this point in the history
  • Loading branch information
DeveloperJim committed Dec 2, 2019
1 parent 901fba9 commit aa18ec3
Show file tree
Hide file tree
Showing 15 changed files with 710 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ import (
"net/http/httputil"
"net/url"
"path/filepath"
"strconv"
"strings"

simplejson "github.com/bitly/go-simplejson"
restful "github.com/emicklei/go-restful"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
Expand All @@ -38,12 +41,15 @@ const (
//default custom resource definition apiVersion we use
defaultAPIVersion = "apiextensions.k8s.io/v1beta1"
defaultMesosVersion = "v4"
defaultNamespaceURL = "/api/v1/namespaces"
)

//kubeProxy proxy for custom resource
type kubeProxy struct {
//kube config details
config *rest.Config
//client for namespace check
client *http.Client
//custom resource proxy
crsProxy *httputil.ReverseProxy
//custom resource definition proxy
Expand All @@ -57,8 +63,11 @@ func (proxy *kubeProxy) init() error {
blog.Errorf("bcs-mesos-driver initialize kube proxy transport failed, message: %s, config object: %v", err.Error(), proxy.config)
return fmt.Errorf("bcs-mesos-driver create CustomResource transport failed")
}
proxy.client = &http.Client{
Transport: httpRoundTripper,
}
proxy.crsProxy = &httputil.ReverseProxy{
Director: func(req *http.Request) {},
Director: proxy.customResourceNamespaceValidate,
Transport: httpRoundTripper,
}
proxy.crdsProxy = &httputil.ReverseProxy{
Expand All @@ -70,6 +79,94 @@ func (proxy *kubeProxy) init() error {
return nil
}

func (proxy *kubeProxy) isNamespaceActive(namespace string) bool {
reqURL := fmt.Sprintf("%s/%s/%s", proxy.config.Host, defaultNamespaceURL, namespace)
nsReq, _ := http.NewRequest(http.MethodGet, reqURL, nil)
resp, err := proxy.client.Do(nsReq)
if err != nil {
blog.Errorf("check %s failed, %s", reqURL, err.Error())
return false
}
if resp.StatusCode == http.StatusNotFound {
blog.Infof("%s, namespace Not found", reqURL)
return false
}
if resp.StatusCode == http.StatusOK {
blog.Infof("%s, namespace %s is Found!", reqURL, namespace)
return true
}
//others
return false
}

func (proxy *kubeProxy) createNamespace(namespace string) error {
reqURL := fmt.Sprintf("%s/%s", proxy.config.Host, defaultNamespaceURL)
ns := &corev1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}
nsBody, _ := json.Marshal(ns)
nsReq, _ := http.NewRequest(http.MethodPost, reqURL, bytes.NewBuffer(nsBody))
nsReq.Header.Add("Content-Type", "application/json")
resp, err := proxy.client.Do(nsReq)
if err != nil {
blog.Errorf("create %s namespace failed. request body: %s", reqURL, string(nsBody))
return err
}
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusAccepted {
blog.Infof("create namespace %s [%s] success.", reqURL, namespace)
return nil
}
//others
return fmt.Errorf("unknow reason for creation ns failed: %d", resp.StatusCode)
}

func (proxy *kubeProxy) customResourceNamespaceValidate(req *http.Request) {
if req.Method != http.MethodPost {
return
}
//validate namespace exist
allBytes, err := ioutil.ReadAll(req.Body)
if err != nil {
blog.Errorf("Reading custom resource Request for namespace validation failed, %s. URL: %s", err.Error(), req.URL.String())
return
}
buffer := bytes.NewBuffer(allBytes)
req.Body = ioutil.NopCloser(buffer)
req.ContentLength = int64(buffer.Len())
req.GetBody = func() (io.ReadCloser, error) {
r := bytes.NewReader(allBytes)
return ioutil.NopCloser(r), nil
}
jsonObj, err := simplejson.NewJson(allBytes)
if err != nil {
blog.Errorf("Custom Resource POST data is not expected json, %s. URL: %s", err.Error(), req.URL.String())
return
}
meta := jsonObj.Get("metadata")
namespace, _ := meta.Get("namespace").String()
if len(namespace) == 0 {
blog.Errorf("Custom Resource POST to %s lost Namespace. %s", req.URL.String(), err.Error())
return
}
if proxy.isNamespaceActive(namespace) {
return
}
blog.Infof("namespace %s do not exist, create first...", namespace)
if err := proxy.createNamespace(namespace); err != nil {
blog.Errorf("create Namespace for %s failed, %s", req.URL.String(), err.Error())
return
}
blog.Infof("create Namespace for %s success.", req.URL.String())
}

//apiVersionReqConvert convert all request
//attention: all json key is
func (proxy *kubeProxy) apiVersionReqConvert(req *http.Request) {
if req.Method == http.MethodGet || req.Method == http.MethodDelete {
blog.V(3).Infof("bcs-mesos-driver skip %s convertion. Method: %s", req.URL.Path, req.Method)
Expand All @@ -84,19 +181,19 @@ func (proxy *kubeProxy) apiVersionReqConvert(req *http.Request) {
blog.Errorf("bcs-mesos-driver get empty body when in POST or PUT request, URL: %s, Method: %s", req.URL.String(), req.Method)
return
}
crd := &apiextensions.CustomResourceDefinition{}
if err = json.Unmarshal(allBytes, crd); err != nil {
blog.Errorf("bcs-mesos-driver custom resource definition Request json Unmarshal failed, %s. URL: %s", err.Error(), req.URL.Path)
jsonObj, err := simplejson.NewJson(allBytes)
if err != nil {
blog.Errorf("bcs-mesos-driver decode json json failed, %s. details request body: %s", err.Error(), string(allBytes))
return
}
blog.V(3).Infof("request body: %s", string(allBytes))
crd.APIVersion = defaultAPIVersion
newBody, err := json.Marshal(crd)
blog.V(3).Infof("forwarding URL %s old body: %s", req.URL.String(), string(allBytes))
jsonObj.Set("apiVersion", defaultAPIVersion)
newBody, err := jsonObj.MarshalJSON()
if err != nil {
blog.Errorf("bcs-mesos-driver new custom resource definition Request json Marshal failed, %s. URL: %s", err.Error(), req.URL.Path)
return
}
blog.V(3).Infof("forwarding new body: %s", string(newBody))
blog.V(3).Infof("forwarding URL %s new body: %s", req.URL.String(), string(newBody))
buffer := bytes.NewBuffer(newBody)
req.Body = ioutil.NopCloser(buffer)
req.ContentLength = int64(buffer.Len())
Expand All @@ -118,6 +215,8 @@ func (proxy *kubeProxy) apiVersionResConvert(resp *http.Response) error {
buffer := bytes.NewBuffer([]byte(newStr))
resp.Body = ioutil.NopCloser(buffer)
resp.ContentLength = int64(buffer.Len())
//setting header for contentLength
resp.Header.Set("Content-Length", strconv.Itoa(buffer.Len()))
blog.Infof("mesos-driver convert custom resource definition [%s] response success", resp.Request.URL.String())
return nil
}
Expand Down
5 changes: 1 addition & 4 deletions bcs-services/bcs-client/cmd/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ func NewCreateCommand() cli.Command {
},
},
Action: func(c *cli.Context) error {
if err := create(utils.NewClientContext(c)); err != nil {
return err
}
return nil
return create(utils.NewClientContext(c))
},
}
}
Expand Down
18 changes: 13 additions & 5 deletions bcs-services/bcs-client/cmd/create/customresourcedenition.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func createCustomResourceDefinition(c *utils.ClientContext) error {
}

func createCustomResource(c *utils.ClientContext) error {
if err := c.MustSpecified(utils.OptionClusterID); err != nil {
if err := c.MustSpecified(utils.OptionClusterID, utils.OptionType); err != nil {
return err
}

Expand All @@ -60,13 +60,21 @@ func createCustomResource(c *utils.ClientContext) error {
if err != nil {
return err
}

namespace, name, err := utils.ParseNamespaceNameFromJSON(data)
if err != nil {
return err
}
scheduler := v4.NewBcsScheduler(utils.GetClientOption())
err = scheduler.CreateCustomResourceDefinition(c.ClusterID(), data)
//validate command line option type
plural, err := utils.ValidateCustomResourceType(scheduler, c.ClusterID(), version, kind, c.String(utils.OptionType))
if err != nil {
return fmt.Errorf("failed to create CustomResourceDefinition: %v", err)
return err
}
err = scheduler.CreateCustomResource(c.ClusterID(), version, plural, namespace, data)
if err != nil {
return fmt.Errorf("failed to create %s: %v", plural, err)
}

fmt.Printf("success to create CustomResourceDefinition.\n")
fmt.Printf("success to create %s: %s\n", plural, name)
return nil
}
30 changes: 28 additions & 2 deletions bcs-services/bcs-client/cmd/inspect/customresourcedefinition.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package inspect
import (
"bk-bcs/bcs-services/bcs-client/cmd/utils"
v4 "bk-bcs/bcs-services/bcs-client/pkg/scheduler/v4"
"bytes"
"encoding/json"
"fmt"
)

Expand All @@ -26,8 +28,32 @@ func inspectCustomResourceDefinition(c *utils.ClientContext) error {
scheduler := v4.NewBcsScheduler(utils.GetClientOption())
crd, err := scheduler.GetCustomResourceDefinition(c.ClusterID(), c.String(utils.OptionName))
if err != nil {
return fmt.Errorf("failed to Get CustomResourceDefinition: %v", err)
return fmt.Errorf("failed to Get CustomResourceDefinition: %s", err.Error())
}

return printInspect(crd)
}

func inspectCustomResource(c *utils.ClientContext) error {
if err := c.MustSpecified(utils.OptionClusterID, utils.OptionNamespace, utils.OptionName); err != nil {
return err
}
namespace := c.String(utils.OptionNamespace)
name := c.String(utils.OptionName)
scheduler := v4.NewBcsScheduler(utils.GetClientOption())
//validate command line option type
apiVersion, plural, err := utils.GetCustomResourceType(scheduler, c.ClusterID(), c.String(utils.OptionType))
if err != nil {
return err
}
crd, err := scheduler.GetCustomResource(c.ClusterID(), apiVersion, plural, namespace, name)
if err != nil {
return fmt.Errorf("failed to Get %s: %v", plural, err)
}
utils.DebugPrintf("original CustomResource: %s", string(crd))
var buffer bytes.Buffer
if err := json.Indent(&buffer, crd, "", " "); err != nil {
return fmt.Errorf("pretty print CustomResource failed, %s", err.Error())
}
fmt.Println(buffer.String())
return nil
}
7 changes: 2 additions & 5 deletions bcs-services/bcs-client/cmd/inspect/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@ func NewInspectCommand() cli.Command {
},
},
Action: func(c *cli.Context) error {
if err := inspect(utils.NewClientContext(c)); err != nil {
return err
}
return nil
return inspect(utils.NewClientContext(c))
},
}
}
Expand Down Expand Up @@ -82,7 +79,7 @@ func inspect(c *utils.ClientContext) error {
return inspectCustomResourceDefinition(c)
default:
//unkown type, try Custom Resource
return fmt.Errorf("invalid type: %s", resourceType)
return inspectCustomResource(c)
}
}

Expand Down
71 changes: 61 additions & 10 deletions bcs-services/bcs-client/cmd/list/customresourcedefinition.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ package list

import (
"fmt"
"strings"

"bk-bcs/bcs-services/bcs-client/cmd/utils"
v4 "bk-bcs/bcs-services/bcs-client/pkg/scheduler/v4"

simplejson "github.com/bitly/go-simplejson"
)

//listCustomResourceDefinition list all CRDs from mesos-driver
Expand All @@ -31,27 +32,22 @@ func listCustomResourceDefinition(c *utils.ClientContext) error {
if err != nil {
return fmt.Errorf("failed to List all CustomResourceDefinition: %v", err)
}
if len(crdList.Items) == 0 {
fmt.Printf("Found no customresourcedefinition\n")
return nil
}
//print all datas
//Name - ShortName - apiVersion - Kind - CreatedTime
fmt.Printf(
"%-50s %-10s %-50s %-20s %-21s\n",
"%-50s %-20s %-25s %-20s %-21s\n",
"NAME",
"SHORTNAME",
"CMDTYPE",
"APIVERSION",
"KIND",
"CRAETEDTIME",
)
for _, item := range crdList.Items {
apiVersion := item.Spec.Group + "/" + item.Spec.Version
shortNames := strings.Join(item.Spec.Names.ShortNames, ",")
fmt.Printf(
"%-50s %-10s %-50s %-20s\n",
"%-50s %-20s %-25s %-20s %-21s\n",
item.GetName(),
shortNames,
item.Spec.Names.Singular,
apiVersion,
item.Spec.Names.Kind,
item.GetCreationTimestamp(),
Expand All @@ -61,5 +57,60 @@ func listCustomResourceDefinition(c *utils.ClientContext) error {
}

func listCustomResource(c *utils.ClientContext) error {
if err := c.MustSpecified(utils.OptionClusterID, utils.OptionType); err != nil {
return err
}
namespace := c.String(utils.OptionNamespace)
allNamespaces := c.Bool(utils.OptionAllNamespace)
if namespace == "" && !allNamespaces {
return fmt.Errorf("namespace or all-namespace must be specified")
}
if allNamespaces {
namespace = v4.AllNamespace
}
scheduler := v4.NewBcsScheduler(utils.GetClientOption())
//validate command line option type
apiVersion, plural, err := utils.GetCustomResourceType(scheduler, c.ClusterID(), c.String(utils.OptionType))
if err != nil {
return err
}
allBytes, err := scheduler.ListCustomResource(c.ClusterID(), apiVersion, plural, namespace)
if err != nil {
return fmt.Errorf("failed to List %s, %v", plural, err)
}
//parse item list formation
json, err := simplejson.NewJson(allBytes)
if err != nil {
return fmt.Errorf("list %s failed, response is not expected json format: %s", plural, err.Error())
}
items := json.Get("items")
dataList, err := items.Array()
if err != nil {
return fmt.Errorf("list %s failed, No items array response, %s", plural, err.Error())
}
len := len(dataList)
if len == 0 {
fmt.Printf("Found No Resources\n")
return nil
}
//print simple information
fmt.Printf(
"%-30s %-20s %-25s\n",
"NAME",
"NAMESPACE",
"CRAETEDTIME",
)
for i := 0; i < len; i++ {
meta := items.GetIndex(i).Get("metadata")
destName, _ := meta.Get("name").String()
destNS, _ := meta.Get("namespace").String()
createdTime, _ := meta.Get("creationTimestamp").String()
fmt.Printf(
"%-30s %-20s %-25s\n",
destName,
destNS,
createdTime,
)
}
return nil
}
Loading

0 comments on commit aa18ec3

Please sign in to comment.