mirror of
https://github.com/go-micro/go-micro.git
synced 2025-06-30 22:33:49 +02:00
Move runtime/kubernetes/client to util/kubernetes/client
This commit is contained in:
169
util/kubernetes/client/api/api_test.go
Normal file
169
util/kubernetes/client/api/api_test.go
Normal file
@ -0,0 +1,169 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testcase struct {
|
||||
Token string
|
||||
ReqFn func(opts *Options) *Request
|
||||
Method string
|
||||
URI string
|
||||
Body interface{}
|
||||
Header map[string]string
|
||||
Assert func(req *http.Request) bool
|
||||
}
|
||||
|
||||
type assertFn func(req *http.Request) bool
|
||||
|
||||
var tests = []testcase{
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("service")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/api/v1/namespaces/default/services/",
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("service").Name("foo")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/api/v1/namespaces/default/services/foo",
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("service").Namespace("test").Name("bar")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/api/v1/namespaces/test/services/bar",
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("deployment").Name("foo")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/apis/apps/v1/namespaces/default/deployments/foo",
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("deployment").Namespace("test").Name("foo")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/apis/apps/v1/namespaces/test/deployments/foo",
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("pod").Params(&Params{LabelSelector: map[string]string{"foo": "bar"}})
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/api/v1/namespaces/default/pods/?labelSelector=foo%3Dbar",
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Post().Resource("service").Name("foo").Body(map[string]string{"foo": "bar"})
|
||||
},
|
||||
Method: "POST",
|
||||
URI: "/api/v1/namespaces/default/services/foo",
|
||||
Body: map[string]string{"foo": "bar"},
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Post().Resource("deployment").Namespace("test").Name("foo").Body(map[string]string{"foo": "bar"})
|
||||
},
|
||||
Method: "POST",
|
||||
URI: "/apis/apps/v1/namespaces/test/deployments/foo",
|
||||
Body: map[string]string{"foo": "bar"},
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Put().Resource("endpoint").Name("baz").Body(map[string]string{"bam": "bar"})
|
||||
},
|
||||
Method: "PUT",
|
||||
URI: "/api/v1/namespaces/default/endpoints/baz",
|
||||
Body: map[string]string{"bam": "bar"},
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Patch().Resource("endpoint").Name("baz").Body(map[string]string{"bam": "bar"})
|
||||
},
|
||||
Method: "PATCH",
|
||||
URI: "/api/v1/namespaces/default/endpoints/baz",
|
||||
Body: map[string]string{"bam": "bar"},
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Patch().Resource("endpoint").Name("baz").SetHeader("foo", "bar")
|
||||
},
|
||||
Method: "PATCH",
|
||||
URI: "/api/v1/namespaces/default/endpoints/baz",
|
||||
Header: map[string]string{"foo": "bar"},
|
||||
},
|
||||
testcase{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Patch().Resource("deployment").Name("baz").SetHeader("foo", "bar")
|
||||
},
|
||||
Method: "PATCH",
|
||||
URI: "/apis/apps/v1/namespaces/default/deployments/baz",
|
||||
Header: map[string]string{"foo": "bar"},
|
||||
},
|
||||
}
|
||||
|
||||
var wrappedHandler = func(test *testcase, t *testing.T) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if len(test.Token) > 0 && (len(auth) == 0 || auth != "Bearer "+test.Token) {
|
||||
t.Errorf("test case token (%s) did not match expected token (%s)", "Bearer "+test.Token, auth)
|
||||
}
|
||||
|
||||
if len(test.Method) > 0 && test.Method != r.Method {
|
||||
t.Errorf("test case Method (%s) did not match expected Method (%s)", test.Method, r.Method)
|
||||
}
|
||||
|
||||
if len(test.URI) > 0 && test.URI != r.URL.RequestURI() {
|
||||
t.Errorf("test case URI (%s) did not match expected URI (%s)", test.URI, r.URL.RequestURI())
|
||||
}
|
||||
|
||||
if test.Body != nil {
|
||||
var res map[string]string
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
if err := decoder.Decode(&res); err != nil {
|
||||
t.Errorf("decoding body failed: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(res, test.Body) {
|
||||
t.Error("body did not match")
|
||||
}
|
||||
}
|
||||
|
||||
if test.Header != nil {
|
||||
for k, v := range test.Header {
|
||||
if r.Header.Get(k) != v {
|
||||
t.Error("header did not exist")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRequest(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
ts := httptest.NewServer(wrappedHandler(&test, t))
|
||||
req := test.ReqFn(&Options{
|
||||
Host: ts.URL,
|
||||
Client: &http.Client{},
|
||||
BearerToken: &test.Token,
|
||||
Namespace: "default",
|
||||
})
|
||||
res := req.Do()
|
||||
if res.Error() != nil {
|
||||
t.Errorf("request failed with %v", res.Error())
|
||||
}
|
||||
ts.Close()
|
||||
}
|
||||
}
|
228
util/kubernetes/client/api/request.go
Normal file
228
util/kubernetes/client/api/request.go
Normal file
@ -0,0 +1,228 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/micro/go-micro/util/log"
|
||||
)
|
||||
|
||||
// Request is used to construct a http request for the k8s API.
|
||||
type Request struct {
|
||||
client *http.Client
|
||||
header http.Header
|
||||
params url.Values
|
||||
method string
|
||||
host string
|
||||
namespace string
|
||||
|
||||
resource string
|
||||
resourceName *string
|
||||
body io.Reader
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
// Params is the object to pass in to set paramaters
|
||||
// on a request.
|
||||
type Params struct {
|
||||
LabelSelector map[string]string
|
||||
Annotations map[string]string
|
||||
}
|
||||
|
||||
// verb sets method
|
||||
func (r *Request) verb(method string) *Request {
|
||||
r.method = method
|
||||
return r
|
||||
}
|
||||
|
||||
// Get request
|
||||
func (r *Request) Get() *Request {
|
||||
return r.verb("GET")
|
||||
}
|
||||
|
||||
// Post request
|
||||
func (r *Request) Post() *Request {
|
||||
return r.verb("POST")
|
||||
}
|
||||
|
||||
// Put request
|
||||
func (r *Request) Put() *Request {
|
||||
return r.verb("PUT")
|
||||
}
|
||||
|
||||
// Patch request
|
||||
func (r *Request) Patch() *Request {
|
||||
return r.verb("PATCH")
|
||||
}
|
||||
|
||||
// Delete request
|
||||
func (r *Request) Delete() *Request {
|
||||
return r.verb("DELETE")
|
||||
}
|
||||
|
||||
// Namespace is to set the namespace to operate on
|
||||
func (r *Request) Namespace(s string) *Request {
|
||||
r.namespace = s
|
||||
return r
|
||||
}
|
||||
|
||||
// Resource is the type of resource the operation is
|
||||
// for, such as "services", "endpoints" or "pods"
|
||||
func (r *Request) Resource(s string) *Request {
|
||||
r.resource = s
|
||||
return r
|
||||
}
|
||||
|
||||
// Name is for targeting a specific resource by id
|
||||
func (r *Request) Name(s string) *Request {
|
||||
r.resourceName = &s
|
||||
return r
|
||||
}
|
||||
|
||||
// Body pass in a body to set, this is for POST, PUT and PATCH requests
|
||||
func (r *Request) Body(in interface{}) *Request {
|
||||
b := new(bytes.Buffer)
|
||||
// if we're not sending YAML request, we encode to JSON
|
||||
if r.header.Get("Content-Type") != "application/yaml" {
|
||||
if err := json.NewEncoder(b).Encode(&in); err != nil {
|
||||
r.err = err
|
||||
return r
|
||||
}
|
||||
log.Debugf("Request body: %v", b)
|
||||
r.body = b
|
||||
return r
|
||||
}
|
||||
|
||||
// if application/yaml is set, we assume we get a raw bytes so we just copy over
|
||||
body, ok := in.(io.Reader)
|
||||
if !ok {
|
||||
r.err = errors.New("invalid data")
|
||||
return r
|
||||
}
|
||||
// copy over data to the bytes buffer
|
||||
if _, err := io.Copy(b, body); err != nil {
|
||||
r.err = err
|
||||
return r
|
||||
}
|
||||
|
||||
log.Debugf("Request body: %v", b)
|
||||
r.body = b
|
||||
return r
|
||||
}
|
||||
|
||||
// Params isused to set paramters on a request
|
||||
func (r *Request) Params(p *Params) *Request {
|
||||
for k, v := range p.LabelSelector {
|
||||
// create new key=value pair
|
||||
value := fmt.Sprintf("%s=%s", k, v)
|
||||
// check if there's an existing value
|
||||
if label := r.params.Get("labelSelector"); len(label) > 0 {
|
||||
value = fmt.Sprintf("%s,%s", label, value)
|
||||
}
|
||||
// set and overwrite the value
|
||||
r.params.Set("labelSelector", value)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// SetHeader sets a header on a request with
|
||||
// a `key` and `value`
|
||||
func (r *Request) SetHeader(key, value string) *Request {
|
||||
r.header.Add(key, value)
|
||||
return r
|
||||
}
|
||||
|
||||
// request builds the http.Request from the options
|
||||
func (r *Request) request() (*http.Request, error) {
|
||||
var url string
|
||||
switch r.resource {
|
||||
case "pod", "service", "endpoint":
|
||||
// /api/v1/namespaces/{namespace}/pods
|
||||
url = fmt.Sprintf("%s/api/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
|
||||
case "deployment":
|
||||
// /apis/apps/v1/namespaces/{namespace}/deployments/{name}
|
||||
url = fmt.Sprintf("%s/apis/apps/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
|
||||
}
|
||||
|
||||
// append resourceName if it is present
|
||||
if r.resourceName != nil {
|
||||
url += *r.resourceName
|
||||
}
|
||||
|
||||
// append any query params
|
||||
if len(r.params) > 0 {
|
||||
url += "?" + r.params.Encode()
|
||||
}
|
||||
|
||||
// build request
|
||||
req, err := http.NewRequest(r.method, url, r.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set headers on request
|
||||
req.Header = r.header
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Do builds and triggers the request
|
||||
func (r *Request) Do() *Response {
|
||||
if r.err != nil {
|
||||
return &Response{
|
||||
err: r.err,
|
||||
}
|
||||
}
|
||||
|
||||
req, err := r.request()
|
||||
if err != nil {
|
||||
return &Response{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("kubernetes api request: %v", req)
|
||||
|
||||
res, err := r.client.Do(req)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("kubernetes api response: %v", res)
|
||||
|
||||
// return res, err
|
||||
return newResponse(res, err)
|
||||
}
|
||||
|
||||
// Options ...
|
||||
type Options struct {
|
||||
Host string
|
||||
Namespace string
|
||||
BearerToken *string
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
// NewRequest creates a k8s api request
|
||||
func NewRequest(opts *Options) *Request {
|
||||
req := &Request{
|
||||
header: make(http.Header),
|
||||
params: make(url.Values),
|
||||
client: opts.Client,
|
||||
namespace: opts.Namespace,
|
||||
host: opts.Host,
|
||||
}
|
||||
|
||||
if opts.BearerToken != nil {
|
||||
req.SetHeader("Authorization", "Bearer "+*opts.BearerToken)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
95
util/kubernetes/client/api/response.go
Normal file
95
util/kubernetes/client/api/response.go
Normal file
@ -0,0 +1,95 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/micro/go-micro/util/log"
|
||||
)
|
||||
|
||||
// Errors ...
|
||||
var (
|
||||
ErrNotFound = errors.New("kubernetes: resource not found")
|
||||
ErrDecode = errors.New("kubernetes: error decoding")
|
||||
ErrUnknown = errors.New("kubernetes: unknown error")
|
||||
)
|
||||
|
||||
// Status is an object that is returned when a request
|
||||
// failed or delete succeeded.
|
||||
// type Status struct {
|
||||
// Kind string `json:"kind"`
|
||||
// Status string `json:"status"`
|
||||
// Message string `json:"message"`
|
||||
// Reason string `json:"reason"`
|
||||
// Code int `json:"code"`
|
||||
// }
|
||||
|
||||
// Response ...
|
||||
type Response struct {
|
||||
res *http.Response
|
||||
err error
|
||||
|
||||
body []byte
|
||||
}
|
||||
|
||||
// Error returns an error
|
||||
func (r *Response) Error() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
// StatusCode returns status code for response
|
||||
func (r *Response) StatusCode() int {
|
||||
return r.res.StatusCode
|
||||
}
|
||||
|
||||
// Into decode body into `data`
|
||||
func (r *Response) Into(data interface{}) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
|
||||
defer r.res.Body.Close()
|
||||
decoder := json.NewDecoder(r.res.Body)
|
||||
err := decoder.Decode(&data)
|
||||
if err != nil {
|
||||
return ErrDecode
|
||||
}
|
||||
|
||||
return r.err
|
||||
}
|
||||
|
||||
func newResponse(res *http.Response, err error) *Response {
|
||||
r := &Response{
|
||||
res: res,
|
||||
err: err,
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return r
|
||||
}
|
||||
|
||||
if r.res.StatusCode == http.StatusOK ||
|
||||
r.res.StatusCode == http.StatusCreated ||
|
||||
r.res.StatusCode == http.StatusNoContent {
|
||||
// Non error status code
|
||||
return r
|
||||
}
|
||||
|
||||
if r.res.StatusCode == http.StatusNotFound {
|
||||
r.err = ErrNotFound
|
||||
return r
|
||||
}
|
||||
|
||||
log.Logf("kubernetes: request failed with code %v", r.res.StatusCode)
|
||||
|
||||
b, err := ioutil.ReadAll(r.res.Body)
|
||||
if err == nil {
|
||||
log.Log("kubernetes: request failed with body:")
|
||||
log.Log(string(b))
|
||||
}
|
||||
r.err = ErrUnknown
|
||||
|
||||
return r
|
||||
}
|
Reference in New Issue
Block a user