2019-11-02 16:25:10 +03:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2019-12-27 23:08:46 +03:00
|
|
|
"context"
|
2019-11-02 16:25:10 +03:00
|
|
|
"encoding/json"
|
2019-11-15 16:41:40 +03:00
|
|
|
"errors"
|
2019-11-02 16:25:10 +03:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2020-05-07 13:35:56 +03:00
|
|
|
|
2020-08-19 17:47:17 +03:00
|
|
|
"github.com/unistack-org/micro/v3/logger"
|
2019-11-02 16:25:10 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Request is used to construct a http request for the k8s API.
|
|
|
|
type Request struct {
|
2019-12-27 23:08:46 +03:00
|
|
|
// the request context
|
|
|
|
context context.Context
|
2019-11-02 16:25:10 +03:00
|
|
|
client *http.Client
|
|
|
|
header http.Header
|
|
|
|
params url.Values
|
|
|
|
method string
|
|
|
|
host string
|
|
|
|
namespace string
|
|
|
|
|
|
|
|
resource string
|
|
|
|
resourceName *string
|
2019-12-17 19:09:51 +03:00
|
|
|
subResource *string
|
2019-11-02 16:25:10 +03:00
|
|
|
body io.Reader
|
|
|
|
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
2020-05-29 19:49:22 +03:00
|
|
|
// Params is the object to pass in to set parameters
|
2019-11-02 16:25:10 +03:00
|
|
|
// on a request.
|
|
|
|
type Params struct {
|
|
|
|
LabelSelector map[string]string
|
|
|
|
Annotations map[string]string
|
2019-12-21 02:16:05 +03:00
|
|
|
Additional map[string]string
|
2019-11-02 16:25:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// verb sets method
|
|
|
|
func (r *Request) verb(method string) *Request {
|
|
|
|
r.method = method
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2019-12-27 23:08:46 +03:00
|
|
|
func (r *Request) Context(ctx context.Context) {
|
|
|
|
r.context = ctx
|
|
|
|
}
|
|
|
|
|
2019-11-02 16:25:10 +03:00
|
|
|
// 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 {
|
2019-11-15 16:41:40 +03:00
|
|
|
return r.verb("PATCH")
|
2019-11-02 16:25:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2020-04-23 15:53:42 +03:00
|
|
|
if len(s) > 0 {
|
|
|
|
r.namespace = s
|
|
|
|
}
|
2019-11-02 16:25:10 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-07-16 18:33:11 +03:00
|
|
|
// SubResource sets a sub resource on a resource,
|
2019-12-17 19:09:51 +03:00
|
|
|
// e.g. pods/log for pod logs
|
|
|
|
func (r *Request) SubResource(s string) *Request {
|
|
|
|
r.subResource = &s
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2019-11-02 16:25:10 +03:00
|
|
|
// Name is for targeting a specific resource by id
|
|
|
|
func (r *Request) Name(s string) *Request {
|
|
|
|
r.resourceName = &s
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2019-11-15 16:41:40 +03:00
|
|
|
// Body pass in a body to set, this is for POST, PUT and PATCH requests
|
2019-11-02 16:25:10 +03:00
|
|
|
func (r *Request) Body(in interface{}) *Request {
|
|
|
|
b := new(bytes.Buffer)
|
2019-11-15 16:41:40 +03:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
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 {
|
2019-11-02 16:25:10 +03:00
|
|
|
r.err = err
|
|
|
|
return r
|
|
|
|
}
|
2019-11-15 16:41:40 +03:00
|
|
|
|
2019-11-02 16:25:10 +03:00
|
|
|
r.body = b
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2020-07-16 18:33:11 +03:00
|
|
|
// Params is used to set parameters on a request
|
2019-11-02 16:25:10 +03:00
|
|
|
func (r *Request) Params(p *Params) *Request {
|
|
|
|
for k, v := range p.LabelSelector {
|
2019-11-27 01:28:08 +03:00
|
|
|
// 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)
|
2019-11-02 16:25:10 +03:00
|
|
|
}
|
2019-12-21 02:16:05 +03:00
|
|
|
for k, v := range p.Additional {
|
|
|
|
r.params.Set(k, v)
|
|
|
|
}
|
2019-11-02 16:25:10 +03:00
|
|
|
|
|
|
|
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 {
|
2020-04-23 15:53:42 +03:00
|
|
|
case "namespace":
|
|
|
|
// /api/v1/namespaces/
|
|
|
|
url = fmt.Sprintf("%s/api/v1/namespaces/", r.host)
|
2019-11-15 16:41:40 +03:00
|
|
|
case "deployment":
|
2019-11-02 16:25:10 +03:00
|
|
|
// /apis/apps/v1/namespaces/{namespace}/deployments/{name}
|
2019-11-15 16:41:40 +03:00
|
|
|
url = fmt.Sprintf("%s/apis/apps/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
|
2020-04-27 16:37:28 +03:00
|
|
|
default:
|
|
|
|
// /api/v1/namespaces/{namespace}/{resource}
|
|
|
|
url = fmt.Sprintf("%s/api/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
|
2019-11-02 16:25:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// append resourceName if it is present
|
|
|
|
if r.resourceName != nil {
|
|
|
|
url += *r.resourceName
|
2019-12-17 19:09:51 +03:00
|
|
|
if r.subResource != nil {
|
|
|
|
url += "/" + *r.subResource
|
|
|
|
}
|
2019-11-02 16:25:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// append any query params
|
|
|
|
if len(r.params) > 0 {
|
|
|
|
url += "?" + r.params.Encode()
|
|
|
|
}
|
|
|
|
|
2019-12-27 23:08:46 +03:00
|
|
|
var req *http.Request
|
|
|
|
var err error
|
|
|
|
|
2019-11-02 16:25:10 +03:00
|
|
|
// build request
|
2019-12-27 23:08:46 +03:00
|
|
|
if r.context != nil {
|
|
|
|
req, err = http.NewRequestWithContext(r.context, r.method, url, r.body)
|
|
|
|
} else {
|
|
|
|
req, err = http.NewRequest(r.method, url, r.body)
|
|
|
|
}
|
2019-11-02 16:25:10 +03:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-07 13:35:56 +03:00
|
|
|
logger.Debugf("[Kubernetes] %v %v", req.Method, req.URL.String())
|
2019-11-02 16:25:10 +03:00
|
|
|
res, err := r.client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return &Response{
|
|
|
|
err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// return res, err
|
|
|
|
return newResponse(res, err)
|
|
|
|
}
|
|
|
|
|
2019-12-17 19:09:51 +03:00
|
|
|
// Raw performs a Raw HTTP request to the Kubernetes API
|
|
|
|
func (r *Request) Raw() (*http.Response, error) {
|
|
|
|
req, err := r.request()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := r.client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2019-11-02 16:25:10 +03:00
|
|
|
// 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
|
|
|
|
}
|