github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/runtime/kubernetes/api/request.go (about)

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