github.com/micro/go-micro/v2@v2.9.1/util/kubernetes/api/request.go (about)

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"net/url"
    12  
    13  	"github.com/micro/go-micro/v2/logger"
    14  )
    15  
    16  // Request is used to construct a http request for the k8s API.
    17  type Request struct {
    18  	// the request context
    19  	context   context.Context
    20  	client    *http.Client
    21  	header    http.Header
    22  	params    url.Values
    23  	method    string
    24  	host      string
    25  	namespace string
    26  
    27  	resource     string
    28  	resourceName *string
    29  	subResource  *string
    30  	body         io.Reader
    31  
    32  	err error
    33  }
    34  
    35  // Params is the object to pass in to set parameters
    36  // on a request.
    37  type Params struct {
    38  	LabelSelector map[string]string
    39  	Annotations   map[string]string
    40  	Additional    map[string]string
    41  }
    42  
    43  // verb sets method
    44  func (r *Request) verb(method string) *Request {
    45  	r.method = method
    46  	return r
    47  }
    48  
    49  func (r *Request) Context(ctx context.Context) {
    50  	r.context = ctx
    51  }
    52  
    53  // Get request
    54  func (r *Request) Get() *Request {
    55  	return r.verb("GET")
    56  }
    57  
    58  // Post request
    59  func (r *Request) Post() *Request {
    60  	return r.verb("POST")
    61  }
    62  
    63  // Put request
    64  func (r *Request) Put() *Request {
    65  	return r.verb("PUT")
    66  }
    67  
    68  // Patch request
    69  func (r *Request) Patch() *Request {
    70  	return r.verb("PATCH")
    71  }
    72  
    73  // Delete request
    74  func (r *Request) Delete() *Request {
    75  	return r.verb("DELETE")
    76  }
    77  
    78  // Namespace is to set the namespace to operate on
    79  func (r *Request) Namespace(s string) *Request {
    80  	if len(s) > 0 {
    81  		r.namespace = s
    82  	}
    83  	return r
    84  }
    85  
    86  // Resource is the type of resource the operation is
    87  // for, such as "services", "endpoints" or "pods"
    88  func (r *Request) Resource(s string) *Request {
    89  	r.resource = s
    90  	return r
    91  }
    92  
    93  // SubResource sets a subresource on a resource,
    94  // e.g. pods/log for pod logs
    95  func (r *Request) SubResource(s string) *Request {
    96  	r.subResource = &s
    97  	return r
    98  }
    99  
   100  // Name is for targeting a specific resource by id
   101  func (r *Request) Name(s string) *Request {
   102  	r.resourceName = &s
   103  	return r
   104  }
   105  
   106  // Body pass in a body to set, this is for POST, PUT and PATCH requests
   107  func (r *Request) Body(in interface{}) *Request {
   108  	b := new(bytes.Buffer)
   109  	// if we're not sending YAML request, we encode to JSON
   110  	if r.header.Get("Content-Type") != "application/yaml" {
   111  		if err := json.NewEncoder(b).Encode(&in); err != nil {
   112  			r.err = err
   113  			return r
   114  		}
   115  		r.body = b
   116  		return r
   117  	}
   118  
   119  	// if application/yaml is set, we assume we get a raw bytes so we just copy over
   120  	body, ok := in.(io.Reader)
   121  	if !ok {
   122  		r.err = errors.New("invalid data")
   123  		return r
   124  	}
   125  	// copy over data to the bytes buffer
   126  	if _, err := io.Copy(b, body); err != nil {
   127  		r.err = err
   128  		return r
   129  	}
   130  
   131  	r.body = b
   132  	return r
   133  }
   134  
   135  // Params isused to set paramters on a request
   136  func (r *Request) Params(p *Params) *Request {
   137  	for k, v := range p.LabelSelector {
   138  		// create new key=value pair
   139  		value := fmt.Sprintf("%s=%s", k, v)
   140  		// check if there's an existing value
   141  		if label := r.params.Get("labelSelector"); len(label) > 0 {
   142  			value = fmt.Sprintf("%s,%s", label, value)
   143  		}
   144  		// set and overwrite the value
   145  		r.params.Set("labelSelector", value)
   146  	}
   147  	for k, v := range p.Additional {
   148  		r.params.Set(k, v)
   149  	}
   150  
   151  	return r
   152  }
   153  
   154  // SetHeader sets a header on a request with
   155  // a `key` and `value`
   156  func (r *Request) SetHeader(key, value string) *Request {
   157  	r.header.Add(key, value)
   158  	return r
   159  }
   160  
   161  // request builds the http.Request from the options
   162  func (r *Request) request() (*http.Request, error) {
   163  	var url string
   164  	switch r.resource {
   165  	case "namespace":
   166  		// /api/v1/namespaces/
   167  		url = fmt.Sprintf("%s/api/v1/namespaces/", r.host)
   168  	case "deployment":
   169  		// /apis/apps/v1/namespaces/{namespace}/deployments/{name}
   170  		url = fmt.Sprintf("%s/apis/apps/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
   171  	default:
   172  		// /api/v1/namespaces/{namespace}/{resource}
   173  		url = fmt.Sprintf("%s/api/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
   174  	}
   175  
   176  	// append resourceName if it is present
   177  	if r.resourceName != nil {
   178  		url += *r.resourceName
   179  		if r.subResource != nil {
   180  			url += "/" + *r.subResource
   181  		}
   182  	}
   183  
   184  	// append any query params
   185  	if len(r.params) > 0 {
   186  		url += "?" + r.params.Encode()
   187  	}
   188  
   189  	var req *http.Request
   190  	var err error
   191  
   192  	// build request
   193  	if r.context != nil {
   194  		req, err = http.NewRequestWithContext(r.context, r.method, url, r.body)
   195  	} else {
   196  		req, err = http.NewRequest(r.method, url, r.body)
   197  	}
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	// set headers on request
   203  	req.Header = r.header
   204  	return req, nil
   205  }
   206  
   207  // Do builds and triggers the request
   208  func (r *Request) Do() *Response {
   209  	if r.err != nil {
   210  		return &Response{
   211  			err: r.err,
   212  		}
   213  	}
   214  
   215  	req, err := r.request()
   216  	if err != nil {
   217  		return &Response{
   218  			err: err,
   219  		}
   220  	}
   221  
   222  	logger.Debugf("[Kubernetes] %v %v", req.Method, req.URL.String())
   223  	res, err := r.client.Do(req)
   224  	if err != nil {
   225  		return &Response{
   226  			err: err,
   227  		}
   228  	}
   229  
   230  	// return res, err
   231  	return newResponse(res, err)
   232  }
   233  
   234  // Raw performs a Raw HTTP request to the Kubernetes API
   235  func (r *Request) Raw() (*http.Response, error) {
   236  	req, err := r.request()
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	res, err := r.client.Do(req)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	return res, nil
   246  }
   247  
   248  // Options ...
   249  type Options struct {
   250  	Host        string
   251  	Namespace   string
   252  	BearerToken *string
   253  	Client      *http.Client
   254  }
   255  
   256  // NewRequest creates a k8s api request
   257  func NewRequest(opts *Options) *Request {
   258  	req := &Request{
   259  		header:    make(http.Header),
   260  		params:    make(url.Values),
   261  		client:    opts.Client,
   262  		namespace: opts.Namespace,
   263  		host:      opts.Host,
   264  	}
   265  
   266  	if opts.BearerToken != nil {
   267  		req.SetHeader("Authorization", "Bearer "+*opts.BearerToken)
   268  	}
   269  
   270  	return req
   271  }