mirror of
https://github.com/go-micro/go-micro.git
synced 2025-07-12 22:41:07 +02:00
k8s not needed
This commit is contained in:
@ -1,180 +0,0 @@
|
||||
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{
|
||||
{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("service")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/api/v1/namespaces/default/services/",
|
||||
},
|
||||
{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("service").Name("foo")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/api/v1/namespaces/default/services/foo",
|
||||
},
|
||||
{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("service").Namespace("test").Name("bar")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/api/v1/namespaces/test/services/bar",
|
||||
},
|
||||
{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).Get().Resource("deployment").Name("foo")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/apis/apps/v1/namespaces/default/deployments/foo",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
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"},
|
||||
},
|
||||
{
|
||||
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"},
|
||||
},
|
||||
{
|
||||
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"},
|
||||
},
|
||||
{
|
||||
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"},
|
||||
},
|
||||
{
|
||||
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"},
|
||||
},
|
||||
{
|
||||
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"},
|
||||
},
|
||||
{
|
||||
ReqFn: func(opts *Options) *Request {
|
||||
return NewRequest(opts).
|
||||
Get().
|
||||
Resource("pod").
|
||||
SubResource("log").
|
||||
Name("foolog")
|
||||
},
|
||||
Method: "GET",
|
||||
URI: "/api/v1/namespaces/default/pods/foolog/log",
|
||||
},
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
@ -1,270 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"go-micro.dev/v5/logger"
|
||||
)
|
||||
|
||||
// Request is used to construct a http request for the k8s API.
|
||||
type Request struct {
|
||||
// the request context
|
||||
context context.Context
|
||||
body io.Reader
|
||||
|
||||
err error
|
||||
client *http.Client
|
||||
header http.Header
|
||||
params url.Values
|
||||
resourceName *string
|
||||
subResource *string
|
||||
method string
|
||||
host string
|
||||
namespace string
|
||||
|
||||
resource string
|
||||
}
|
||||
|
||||
// Params is the object to pass in to set parameters
|
||||
// on a request.
|
||||
type Params struct {
|
||||
LabelSelector map[string]string
|
||||
Annotations map[string]string
|
||||
Additional map[string]string
|
||||
}
|
||||
|
||||
// verb sets method.
|
||||
func (r *Request) verb(method string) *Request {
|
||||
r.method = method
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) Context(ctx context.Context) {
|
||||
r.context = ctx
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if len(s) > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// SubResource sets a subresource on a resource,
|
||||
// e.g. pods/log for pod logs.
|
||||
func (r *Request) SubResource(s string) *Request {
|
||||
r.subResource = &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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
r.body = b
|
||||
return r
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
for k, v := range p.Additional {
|
||||
r.params.Set(k, v)
|
||||
}
|
||||
|
||||
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 "namespace":
|
||||
// /api/v1/namespaces/
|
||||
url = fmt.Sprintf("%s/api/v1/namespaces/", r.host)
|
||||
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)
|
||||
default:
|
||||
// /api/v1/namespaces/{namespace}/{resource}
|
||||
url = fmt.Sprintf("%s/api/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
|
||||
}
|
||||
|
||||
// append resourceName if it is present
|
||||
if r.resourceName != nil {
|
||||
url += *r.resourceName
|
||||
if r.subResource != nil {
|
||||
url += "/" + *r.subResource
|
||||
}
|
||||
}
|
||||
|
||||
// append any query params
|
||||
if len(r.params) > 0 {
|
||||
url += "?" + r.params.Encode()
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
var err error
|
||||
|
||||
// build request
|
||||
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)
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debugf("[Kubernetes] %v %v", req.Method, req.URL.String())
|
||||
res, err := r.client.Do(req)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// return res, err
|
||||
return newResponse(res, err)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Options ...
|
||||
type Options struct {
|
||||
BearerToken *string
|
||||
Client *http.Client
|
||||
Host string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 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)
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
return fmt.Errorf("%v: %v", ErrDecode, err)
|
||||
}
|
||||
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *Response) Close() error {
|
||||
if r.res != nil {
|
||||
return r.res.Body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(r.res.Body)
|
||||
if err == nil {
|
||||
r.err = errors.New(string(b))
|
||||
return r
|
||||
}
|
||||
|
||||
r.err = ErrUnknown
|
||||
|
||||
return r
|
||||
}
|
@ -1,392 +0,0 @@
|
||||
// Package client provides an implementation of a restricted subset of kubernetes API client
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"go-micro.dev/v5/logger"
|
||||
"go-micro.dev/v5/util/kubernetes/api"
|
||||
)
|
||||
|
||||
var (
|
||||
// path to kubernetes service account token.
|
||||
serviceAccountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
// ErrReadNamespace is returned when the names could not be read from service account.
|
||||
ErrReadNamespace = errors.New("could not read namespace from service account secret")
|
||||
// DefaultImage is default micro image.
|
||||
DefaultImage = "micro/go-micro"
|
||||
// DefaultNamespace is the default k8s namespace.
|
||||
DefaultNamespace = "default"
|
||||
)
|
||||
|
||||
// Client ...
|
||||
type client struct {
|
||||
opts *api.Options
|
||||
}
|
||||
|
||||
// Kubernetes client.
|
||||
type Client interface {
|
||||
// Create creates new API resource
|
||||
Create(*Resource, ...CreateOption) error
|
||||
// Get queries API resrouces
|
||||
Get(*Resource, ...GetOption) error
|
||||
// Update patches existing API object
|
||||
Update(*Resource, ...UpdateOption) error
|
||||
// Delete deletes API resource
|
||||
Delete(*Resource, ...DeleteOption) error
|
||||
// List lists API resources
|
||||
List(*Resource, ...ListOption) error
|
||||
// Log gets log for a pod
|
||||
Log(*Resource, ...LogOption) (io.ReadCloser, error)
|
||||
// Watch for events
|
||||
Watch(*Resource, ...WatchOption) (Watcher, error)
|
||||
}
|
||||
|
||||
// Create creates new API object.
|
||||
func (c *client) Create(r *Resource, opts ...CreateOption) error {
|
||||
options := CreateOptions{
|
||||
Namespace: c.opts.Namespace,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
if err := renderTemplate(r.Kind, b, r.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
resp := api.NewRequest(c.opts).
|
||||
Post().
|
||||
SetHeader("Content-Type", "application/yaml").
|
||||
Namespace(options.Namespace).
|
||||
Resource(r.Kind).
|
||||
Body(b).
|
||||
Do()
|
||||
resp.Close()
|
||||
return resp.Error()
|
||||
}
|
||||
|
||||
var (
|
||||
nameRegex = regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||
)
|
||||
|
||||
// SerializeResourceName removes all spacial chars from a string so it
|
||||
// can be used as a k8s resource name.
|
||||
func SerializeResourceName(ns string) string {
|
||||
return nameRegex.ReplaceAllString(ns, "-")
|
||||
}
|
||||
|
||||
// Get queries API objects and stores the result in r.
|
||||
func (c *client) Get(r *Resource, opts ...GetOption) error {
|
||||
options := GetOptions{
|
||||
Namespace: c.opts.Namespace,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return api.NewRequest(c.opts).
|
||||
Get().
|
||||
Resource(r.Kind).
|
||||
Namespace(options.Namespace).
|
||||
Params(&api.Params{LabelSelector: options.Labels}).
|
||||
Do().
|
||||
Into(r.Value)
|
||||
}
|
||||
|
||||
// Log returns logs for a pod.
|
||||
func (c *client) Log(r *Resource, opts ...LogOption) (io.ReadCloser, error) {
|
||||
options := LogOptions{
|
||||
Namespace: c.opts.Namespace,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
req := api.NewRequest(c.opts).
|
||||
Get().
|
||||
Resource(r.Kind).
|
||||
SubResource("log").
|
||||
Name(r.Name).
|
||||
Namespace(options.Namespace)
|
||||
|
||||
if options.Params != nil {
|
||||
req.Params(&api.Params{Additional: options.Params})
|
||||
}
|
||||
|
||||
resp, err := req.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
resp.Body.Close()
|
||||
return nil, errors.New(resp.Request.URL.String() + ": " + resp.Status)
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// Update updates API object.
|
||||
func (c *client) Update(r *Resource, opts ...UpdateOption) error {
|
||||
options := UpdateOptions{
|
||||
Namespace: c.opts.Namespace,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
req := api.NewRequest(c.opts).
|
||||
Patch().
|
||||
SetHeader("Content-Type", "application/strategic-merge-patch+json").
|
||||
Resource(r.Kind).
|
||||
Name(r.Name).
|
||||
Namespace(options.Namespace)
|
||||
|
||||
switch r.Kind {
|
||||
case "service":
|
||||
req.Body(r.Value.(*Service))
|
||||
case "deployment":
|
||||
req.Body(r.Value.(*Deployment))
|
||||
case "pod":
|
||||
req.Body(r.Value.(*Pod))
|
||||
default:
|
||||
return errors.New("unsupported resource")
|
||||
}
|
||||
resp := req.Do()
|
||||
resp.Close()
|
||||
return resp.Error()
|
||||
}
|
||||
|
||||
// Delete removes API object.
|
||||
func (c *client) Delete(r *Resource, opts ...DeleteOption) error {
|
||||
options := DeleteOptions{
|
||||
Namespace: c.opts.Namespace,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
resp := api.NewRequest(c.opts).
|
||||
Delete().
|
||||
Resource(r.Kind).
|
||||
Name(r.Name).
|
||||
Namespace(options.Namespace).
|
||||
Do()
|
||||
resp.Close()
|
||||
return resp.Error()
|
||||
}
|
||||
|
||||
// List lists API objects and stores the result in r.
|
||||
func (c *client) List(r *Resource, opts ...ListOption) error {
|
||||
options := ListOptions{
|
||||
Namespace: c.opts.Namespace,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return c.Get(r, GetNamespace(options.Namespace))
|
||||
}
|
||||
|
||||
// Watch returns an event stream.
|
||||
func (c *client) Watch(r *Resource, opts ...WatchOption) (Watcher, error) {
|
||||
options := WatchOptions{
|
||||
Namespace: c.opts.Namespace,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// set the watch param
|
||||
params := &api.Params{Additional: map[string]string{
|
||||
"watch": "true",
|
||||
}}
|
||||
|
||||
// get options params
|
||||
if options.Params != nil {
|
||||
for k, v := range options.Params {
|
||||
params.Additional[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
req := api.NewRequest(c.opts).
|
||||
Get().
|
||||
Resource(r.Kind).
|
||||
Name(r.Name).
|
||||
Namespace(options.Namespace).
|
||||
Params(params)
|
||||
|
||||
return newWatcher(req)
|
||||
}
|
||||
|
||||
// NewService returns default micro kubernetes service definition.
|
||||
func NewService(name, version, typ, namespace string) *Service {
|
||||
if logger.V(logger.TraceLevel, logger.DefaultLogger) {
|
||||
logger.Logf(logger.TraceLevel, "kubernetes default service: name: %s, version: %s", name, version)
|
||||
}
|
||||
|
||||
Labels := map[string]string{
|
||||
"name": name,
|
||||
"version": version,
|
||||
"micro": typ,
|
||||
}
|
||||
|
||||
svcName := name
|
||||
if len(version) > 0 {
|
||||
// API service object name joins name and version over "-"
|
||||
svcName = strings.Join([]string{name, version}, "-")
|
||||
}
|
||||
|
||||
if len(namespace) == 0 {
|
||||
namespace = DefaultNamespace
|
||||
}
|
||||
|
||||
Metadata := &Metadata{
|
||||
Name: svcName,
|
||||
Namespace: SerializeResourceName(namespace),
|
||||
Version: version,
|
||||
Labels: Labels,
|
||||
}
|
||||
|
||||
Spec := &ServiceSpec{
|
||||
Type: "ClusterIP",
|
||||
Selector: Labels,
|
||||
Ports: []ServicePort{{
|
||||
Name: "service-port",
|
||||
Protocol: "",
|
||||
Port: 8080,
|
||||
}},
|
||||
}
|
||||
|
||||
return &Service{
|
||||
Metadata: Metadata,
|
||||
Spec: Spec,
|
||||
}
|
||||
}
|
||||
|
||||
// NewService returns default micro kubernetes deployment definition.
|
||||
func NewDeployment(name, version, typ, namespace string) *Deployment {
|
||||
if logger.V(logger.TraceLevel, logger.DefaultLogger) {
|
||||
logger.Logf(logger.TraceLevel, "kubernetes default deployment: name: %s, version: %s", name, version)
|
||||
}
|
||||
|
||||
Labels := map[string]string{
|
||||
"name": name,
|
||||
"version": version,
|
||||
"micro": typ,
|
||||
}
|
||||
|
||||
depName := name
|
||||
if len(version) > 0 {
|
||||
// API deployment object name joins name and version over "-"
|
||||
depName = strings.Join([]string{name, version}, "-")
|
||||
}
|
||||
|
||||
if len(namespace) == 0 {
|
||||
namespace = DefaultNamespace
|
||||
}
|
||||
|
||||
Metadata := &Metadata{
|
||||
Name: depName,
|
||||
Namespace: SerializeResourceName(namespace),
|
||||
Version: version,
|
||||
Labels: Labels,
|
||||
Annotations: map[string]string{},
|
||||
}
|
||||
|
||||
// enable go modules by default
|
||||
env := EnvVar{
|
||||
Name: "GO111MODULE",
|
||||
Value: "on",
|
||||
}
|
||||
|
||||
Spec := &DeploymentSpec{
|
||||
Replicas: 1,
|
||||
Selector: &LabelSelector{
|
||||
MatchLabels: Labels,
|
||||
},
|
||||
Template: &Template{
|
||||
Metadata: Metadata,
|
||||
PodSpec: &PodSpec{
|
||||
ServiceAccountName: namespace,
|
||||
Containers: []Container{{
|
||||
Name: name,
|
||||
Image: DefaultImage,
|
||||
Env: []EnvVar{env},
|
||||
Command: []string{"go", "run", "."},
|
||||
Ports: []ContainerPort{{
|
||||
Name: "service-port",
|
||||
ContainerPort: 8080,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &Deployment{
|
||||
Metadata: Metadata,
|
||||
Spec: Spec,
|
||||
}
|
||||
}
|
||||
|
||||
// NewLocalClient returns a client that can be used with `kubectl proxy`.
|
||||
func NewLocalClient(hosts ...string) *client {
|
||||
if len(hosts) == 0 {
|
||||
hosts[0] = "http://localhost:8001"
|
||||
}
|
||||
return &client{
|
||||
opts: &api.Options{
|
||||
Client: http.DefaultClient,
|
||||
Host: hosts[0],
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewClusterClient creates a Kubernetes client for use from within a k8s pod.
|
||||
func NewClusterClient() *client {
|
||||
host := "https://" + os.Getenv("KUBERNETES_SERVICE_HOST") + ":" + os.Getenv("KUBERNETES_SERVICE_PORT")
|
||||
|
||||
s, err := os.Stat(serviceAccountPath)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
if s == nil || !s.IsDir() {
|
||||
logger.Fatal(errors.New("service account not found"))
|
||||
}
|
||||
|
||||
token, err := os.ReadFile(path.Join(serviceAccountPath, "token"))
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
t := string(token)
|
||||
|
||||
crt, err := CertPoolFromFile(path.Join(serviceAccountPath, "ca.crt"))
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
c := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: crt,
|
||||
},
|
||||
DisableCompression: true,
|
||||
},
|
||||
}
|
||||
|
||||
return &client{
|
||||
opts: &api.Options{
|
||||
Client: c,
|
||||
Host: host,
|
||||
BearerToken: &t,
|
||||
Namespace: DefaultNamespace,
|
||||
},
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package client
|
||||
|
||||
type CreateOptions struct {
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type GetOptions struct {
|
||||
Labels map[string]string
|
||||
Namespace string
|
||||
}
|
||||
type UpdateOptions struct {
|
||||
Namespace string
|
||||
}
|
||||
type DeleteOptions struct {
|
||||
Namespace string
|
||||
}
|
||||
type ListOptions struct {
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type LogOptions struct {
|
||||
Params map[string]string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type WatchOptions struct {
|
||||
Params map[string]string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type CreateOption func(*CreateOptions)
|
||||
type GetOption func(*GetOptions)
|
||||
type UpdateOption func(*UpdateOptions)
|
||||
type DeleteOption func(*DeleteOptions)
|
||||
type ListOption func(*ListOptions)
|
||||
type LogOption func(*LogOptions)
|
||||
type WatchOption func(*WatchOptions)
|
||||
|
||||
// LogParams provides additional params for logs.
|
||||
func LogParams(p map[string]string) LogOption {
|
||||
return func(l *LogOptions) {
|
||||
l.Params = p
|
||||
}
|
||||
}
|
||||
|
||||
// WatchParams used for watch params.
|
||||
func WatchParams(p map[string]string) WatchOption {
|
||||
return func(w *WatchOptions) {
|
||||
w.Params = p
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNamespace sets the namespace for creating a resource.
|
||||
func CreateNamespace(ns string) CreateOption {
|
||||
return func(o *CreateOptions) {
|
||||
o.Namespace = SerializeResourceName(ns)
|
||||
}
|
||||
}
|
||||
|
||||
// GetNamespace sets the namespace for getting a resource.
|
||||
func GetNamespace(ns string) GetOption {
|
||||
return func(o *GetOptions) {
|
||||
o.Namespace = SerializeResourceName(ns)
|
||||
}
|
||||
}
|
||||
|
||||
// GetLabels sets the labels for when getting a resource.
|
||||
func GetLabels(ls map[string]string) GetOption {
|
||||
return func(o *GetOptions) {
|
||||
o.Labels = ls
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateNamespace sets the namespace for updating a resource.
|
||||
func UpdateNamespace(ns string) UpdateOption {
|
||||
return func(o *UpdateOptions) {
|
||||
o.Namespace = SerializeResourceName(ns)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteNamespace sets the namespace for deleting a resource.
|
||||
func DeleteNamespace(ns string) DeleteOption {
|
||||
return func(o *DeleteOptions) {
|
||||
o.Namespace = SerializeResourceName(ns)
|
||||
}
|
||||
}
|
||||
|
||||
// ListNamespace sets the namespace for listing resources.
|
||||
func ListNamespace(ns string) ListOption {
|
||||
return func(o *ListOptions) {
|
||||
o.Namespace = SerializeResourceName(ns)
|
||||
}
|
||||
}
|
||||
|
||||
// LogNamespace sets the namespace for logging a resource.
|
||||
func LogNamespace(ns string) LogOption {
|
||||
return func(o *LogOptions) {
|
||||
o.Namespace = SerializeResourceName(ns)
|
||||
}
|
||||
}
|
||||
|
||||
// WatchNamespace sets the namespace for watching a resource.
|
||||
func WatchNamespace(ns string) WatchOption {
|
||||
return func(o *WatchOptions) {
|
||||
o.Namespace = SerializeResourceName(ns)
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
package client
|
||||
|
||||
var templates = map[string]string{
|
||||
"deployment": deploymentTmpl,
|
||||
"service": serviceTmpl,
|
||||
"namespace": namespaceTmpl,
|
||||
"secret": secretTmpl,
|
||||
"serviceaccount": serviceAccountTmpl,
|
||||
}
|
||||
|
||||
// stripped image pull policy always
|
||||
// imagePullPolicy: Always.
|
||||
var deploymentTmpl = `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: "{{ .Metadata.Name }}"
|
||||
namespace: "{{ .Metadata.Namespace }}"
|
||||
labels:
|
||||
{{- with .Metadata.Labels }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
{{- with .Metadata.Annotations }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
replicas: {{ .Spec.Replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- with .Spec.Selector.MatchLabels }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- with .Spec.Template.Metadata.Labels }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
{{- with .Spec.Template.Metadata.Annotations }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
serviceAccountName: {{ .Spec.Template.PodSpec.ServiceAccountName }}
|
||||
containers:
|
||||
{{- with .Spec.Template.PodSpec.Containers }}
|
||||
{{- range . }}
|
||||
- name: {{ .Name }}
|
||||
env:
|
||||
{{- with .Env }}
|
||||
{{- range . }}
|
||||
- name: "{{ .Name }}"
|
||||
value: "{{ .Value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
args:
|
||||
{{- range .Args }}
|
||||
- {{.}}
|
||||
{{- end }}
|
||||
command:
|
||||
{{- range .Command }}
|
||||
- {{.}}
|
||||
{{- end }}
|
||||
image: {{ .Image }}
|
||||
ports:
|
||||
{{- with .Ports }}
|
||||
{{- range . }}
|
||||
- containerPort: {{ .ContainerPort }}
|
||||
name: {{ .Name }}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{- end }}
|
||||
{{- end}}
|
||||
`
|
||||
|
||||
var serviceTmpl = `
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: "{{ .Metadata.Name }}"
|
||||
namespace: "{{ .Metadata.Namespace }}"
|
||||
labels:
|
||||
{{- with .Metadata.Labels }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
selector:
|
||||
{{- with .Spec.Selector }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
ports:
|
||||
{{- with .Spec.Ports }}
|
||||
{{- range . }}
|
||||
- name: "{{ .Name }}"
|
||||
port: {{ .Port }}
|
||||
protocol: {{ .Protocol }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
var namespaceTmpl = `
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ .Metadata.Name }}"
|
||||
labels:
|
||||
{{- with .Metadata.Labels }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
var secretTmpl = `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: "{{ .Type }}"
|
||||
metadata:
|
||||
name: "{{ .Metadata.Name }}"
|
||||
namespace: "{{ .Metadata.Namespace }}"
|
||||
labels:
|
||||
{{- with .Metadata.Labels }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
data:
|
||||
{{- with .Data }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
var serviceAccountTmpl = `
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: "{{ .Metadata.Name }}"
|
||||
labels:
|
||||
{{- with .Metadata.Labels }}
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: "{{ $value }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
imagePullSecrets:
|
||||
{{- with .ImagePullSecrets }}
|
||||
{{- range . }}
|
||||
- name: "{{ .Name }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
@ -1,209 +0,0 @@
|
||||
package client
|
||||
|
||||
// ContainerPort.
|
||||
type ContainerPort struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
HostPort int `json:"hostPort,omitempty"`
|
||||
ContainerPort int `json:"containerPort"`
|
||||
}
|
||||
|
||||
// EnvVar is environment variable.
|
||||
type EnvVar struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Condition struct {
|
||||
Started string `json:"startedAt,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Container defined container runtime values.
|
||||
type Container struct {
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
Env []EnvVar `json:"env,omitempty"`
|
||||
Command []string `json:"command,omitempty"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
Ports []ContainerPort `json:"ports,omitempty"`
|
||||
}
|
||||
|
||||
// DeploymentSpec defines micro deployment spec.
|
||||
type DeploymentSpec struct {
|
||||
Selector *LabelSelector `json:"selector"`
|
||||
Template *Template `json:"template,omitempty"`
|
||||
Replicas int `json:"replicas,omitempty"`
|
||||
}
|
||||
|
||||
// DeploymentCondition describes the state of deployment.
|
||||
type DeploymentCondition struct {
|
||||
LastUpdateTime string `json:"lastUpdateTime"`
|
||||
Type string `json:"type"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// DeploymentStatus is returned when querying deployment.
|
||||
type DeploymentStatus struct {
|
||||
Conditions []DeploymentCondition `json:"conditions,omitempty"`
|
||||
Replicas int `json:"replicas,omitempty"`
|
||||
UpdatedReplicas int `json:"updatedReplicas,omitempty"`
|
||||
ReadyReplicas int `json:"readyReplicas,omitempty"`
|
||||
AvailableReplicas int `json:"availableReplicas,omitempty"`
|
||||
UnavailableReplicas int `json:"unavailableReplicas,omitempty"`
|
||||
}
|
||||
|
||||
// Deployment is Kubernetes deployment.
|
||||
type Deployment struct {
|
||||
Metadata *Metadata `json:"metadata"`
|
||||
Spec *DeploymentSpec `json:"spec,omitempty"`
|
||||
Status *DeploymentStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// DeploymentList.
|
||||
type DeploymentList struct {
|
||||
Items []Deployment `json:"items"`
|
||||
}
|
||||
|
||||
// LabelSelector is a label query over a set of resources
|
||||
// NOTE: we do not support MatchExpressions at the moment.
|
||||
type LabelSelector struct {
|
||||
MatchLabels map[string]string `json:"matchLabels,omitempty"`
|
||||
}
|
||||
|
||||
type LoadBalancerIngress struct {
|
||||
IP string `json:"ip,omitempty"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
}
|
||||
|
||||
type LoadBalancerStatus struct {
|
||||
Ingress []LoadBalancerIngress `json:"ingress,omitempty"`
|
||||
}
|
||||
|
||||
// Metadata defines api object metadata.
|
||||
type Metadata struct {
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// PodSpec is a pod.
|
||||
type PodSpec struct {
|
||||
ServiceAccountName string `json:"serviceAccountName"`
|
||||
Containers []Container `json:"containers"`
|
||||
}
|
||||
|
||||
// PodList.
|
||||
type PodList struct {
|
||||
Items []Pod `json:"items"`
|
||||
}
|
||||
|
||||
// Pod is the top level item for a pod.
|
||||
type Pod struct {
|
||||
Metadata *Metadata `json:"metadata"`
|
||||
Spec *PodSpec `json:"spec,omitempty"`
|
||||
Status *PodStatus `json:"status"`
|
||||
}
|
||||
|
||||
// PodStatus.
|
||||
type PodStatus struct {
|
||||
PodIP string `json:"podIP"`
|
||||
Phase string `json:"phase"`
|
||||
Reason string `json:"reason"`
|
||||
Conditions []PodCondition `json:"conditions,omitempty"`
|
||||
Containers []ContainerStatus `json:"containerStatuses"`
|
||||
}
|
||||
|
||||
// PodCondition describes the state of pod.
|
||||
type PodCondition struct {
|
||||
Type string `json:"type"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type ContainerStatus struct {
|
||||
State ContainerState `json:"state"`
|
||||
}
|
||||
|
||||
type ContainerState struct {
|
||||
Running *Condition `json:"running"`
|
||||
Terminated *Condition `json:"terminated"`
|
||||
Waiting *Condition `json:"waiting"`
|
||||
}
|
||||
|
||||
// Resource is API resource.
|
||||
type Resource struct {
|
||||
Value interface{}
|
||||
Name string
|
||||
Kind string
|
||||
}
|
||||
|
||||
// ServicePort configures service ports.
|
||||
type ServicePort struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
// ServiceSpec provides service configuration.
|
||||
type ServiceSpec struct {
|
||||
ClusterIP string `json:"clusterIP"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Selector map[string]string `json:"selector,omitempty"`
|
||||
Ports []ServicePort `json:"ports,omitempty"`
|
||||
}
|
||||
|
||||
// ServiceStatus.
|
||||
type ServiceStatus struct {
|
||||
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
|
||||
}
|
||||
|
||||
// Service is kubernetes service.
|
||||
type Service struct {
|
||||
Metadata *Metadata `json:"metadata"`
|
||||
Spec *ServiceSpec `json:"spec,omitempty"`
|
||||
Status *ServiceStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// ServiceList.
|
||||
type ServiceList struct {
|
||||
Items []Service `json:"items"`
|
||||
}
|
||||
|
||||
// Template is micro deployment template.
|
||||
type Template struct {
|
||||
Metadata *Metadata `json:"metadata,omitempty"`
|
||||
PodSpec *PodSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// Namespace is a Kubernetes Namespace.
|
||||
type Namespace struct {
|
||||
Metadata *Metadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// NamespaceList.
|
||||
type NamespaceList struct {
|
||||
Items []Namespace `json:"items"`
|
||||
}
|
||||
|
||||
// ImagePullSecret.
|
||||
type ImagePullSecret struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Secret.
|
||||
type Secret struct {
|
||||
Data map[string]string `json:"data"`
|
||||
Metadata *Metadata `json:"metadata"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// ServiceAccount.
|
||||
type ServiceAccount struct {
|
||||
Metadata *Metadata `json:"metadata,omitempty"`
|
||||
ImagePullSecrets []ImagePullSecret `json:"imagePullSecrets,omitempty"`
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// renderTemplateFile renders template for a given resource into writer w.
|
||||
func renderTemplate(resource string, w io.Writer, data interface{}) error {
|
||||
t := template.Must(template.New("kubernetes").Parse(templates[resource]))
|
||||
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// COPIED FROM
|
||||
// https://github.com/kubernetes/kubernetes/blob/7a725418af4661067b56506faabc2d44c6d7703a/pkg/util/crypto/crypto.go
|
||||
|
||||
// CertPoolFromFile returns an x509.CertPool containing the certificates in the given PEM-encoded file.
|
||||
// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates.
|
||||
func CertPoolFromFile(filename string) (*x509.CertPool, error) {
|
||||
certs, err := certificatesFromFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
for _, cert := range certs {
|
||||
pool.AddCert(cert)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// certificatesFromFile returns the x509.Certificates contained in the given PEM-encoded file.
|
||||
// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates.
|
||||
func certificatesFromFile(file string) ([]*x509.Certificate, error) {
|
||||
if len(file) == 0 {
|
||||
return nil, errors.New("error reading certificates from an empty filename")
|
||||
}
|
||||
pemBlock, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs, err := CertsFromPEM(pemBlock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", file, err)
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// CertsFromPEM returns the x509.Certificates contained in the given PEM-encoded byte array
|
||||
// Returns an error if a certificate could not be parsed, or if the data does not contain any certificates.
|
||||
func CertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) {
|
||||
ok := false
|
||||
certs := []*x509.Certificate{}
|
||||
for len(pemCerts) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemCerts = pem.Decode(pemCerts)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
// Only use PEM "CERTIFICATE" blocks without extra headers
|
||||
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return certs, err
|
||||
}
|
||||
|
||||
certs = append(certs, cert)
|
||||
ok = true
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return certs, errors.New("could not read any certificates")
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// Format is used to format a string value into a k8s valid name.
|
||||
func Format(v string) string {
|
||||
// to lower case
|
||||
v = strings.ToLower(v)
|
||||
// / to dashes
|
||||
v = strings.ReplaceAll(v, "/", "-")
|
||||
// dots to dashes
|
||||
v = strings.ReplaceAll(v, ".", "-")
|
||||
// limit to 253 chars
|
||||
if len(v) > 253 {
|
||||
v = v[:253]
|
||||
}
|
||||
// return new name
|
||||
return v
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTemplates(t *testing.T) {
|
||||
name := "foo"
|
||||
version := "123"
|
||||
typ := "service"
|
||||
namespace := "default"
|
||||
|
||||
// Render default service
|
||||
s := NewService(name, version, typ, namespace)
|
||||
bs := new(bytes.Buffer)
|
||||
if err := renderTemplate(templates["service"], bs, s); err != nil {
|
||||
t.Errorf("Failed to render kubernetes service: %v", err)
|
||||
}
|
||||
|
||||
// Render default deployment
|
||||
d := NewDeployment(name, version, typ, namespace)
|
||||
bd := new(bytes.Buffer)
|
||||
if err := renderTemplate(templates["deployment"], bd, d); err != nil {
|
||||
t.Errorf("Failed to render kubernetes deployment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
expect string
|
||||
}{
|
||||
{"foobar", "foobar"},
|
||||
{"foo-bar", "foo-bar"},
|
||||
{"foo.bar", "foo-bar"},
|
||||
{"Foo.Bar", "foo-bar"},
|
||||
{"go.micro.foo.bar", "go-micro-foo-bar"},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
v := Format(test.name)
|
||||
if v != test.expect {
|
||||
t.Fatalf("Expected name %s for %s got: %s", test.expect, test.name, v)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"go-micro.dev/v5/util/kubernetes/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// EventTypes used.
|
||||
Added EventType = "ADDED"
|
||||
Modified EventType = "MODIFIED"
|
||||
Deleted EventType = "DELETED"
|
||||
Error EventType = "ERROR"
|
||||
)
|
||||
|
||||
// Watcher is used to watch for events.
|
||||
type Watcher interface {
|
||||
// A channel of events
|
||||
Chan() <-chan Event
|
||||
// Stop the watcher
|
||||
Stop()
|
||||
}
|
||||
|
||||
// EventType defines the possible types of events.
|
||||
type EventType string
|
||||
|
||||
// Event represents a single event to a watched resource.
|
||||
type Event struct {
|
||||
Type EventType `json:"type"`
|
||||
Object json.RawMessage `json:"object"`
|
||||
}
|
||||
|
||||
// bodyWatcher scans the body of a request for chunks.
|
||||
type bodyWatcher struct {
|
||||
results chan Event
|
||||
cancel func()
|
||||
stop chan bool
|
||||
res *http.Response
|
||||
req *api.Request
|
||||
}
|
||||
|
||||
// Changes returns the results channel.
|
||||
func (wr *bodyWatcher) Chan() <-chan Event {
|
||||
return wr.results
|
||||
}
|
||||
|
||||
// Stop cancels the request.
|
||||
func (wr *bodyWatcher) Stop() {
|
||||
select {
|
||||
case <-wr.stop:
|
||||
return
|
||||
default:
|
||||
// cancel the request
|
||||
wr.cancel()
|
||||
// stop the watcher
|
||||
close(wr.stop)
|
||||
}
|
||||
}
|
||||
|
||||
func (wr *bodyWatcher) stream() {
|
||||
reader := bufio.NewReader(wr.res.Body)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
// read a line
|
||||
b, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// send the event
|
||||
var event Event
|
||||
if err := json.Unmarshal(b, &event); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case <-wr.stop:
|
||||
return
|
||||
case wr.results <- event:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// newWatcher creates a k8s body watcher for
|
||||
// a given http request.
|
||||
func newWatcher(req *api.Request) (Watcher, error) {
|
||||
// set request context so we can cancel the request
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
req.Context(ctx)
|
||||
|
||||
// do the raw request
|
||||
res, err := req.Raw()
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
cancel()
|
||||
// close the response body
|
||||
res.Body.Close()
|
||||
// return an error
|
||||
return nil, errors.New(res.Request.URL.String() + ": " + res.Status)
|
||||
}
|
||||
|
||||
wr := &bodyWatcher{
|
||||
results: make(chan Event),
|
||||
stop: make(chan bool),
|
||||
cancel: cancel,
|
||||
req: req,
|
||||
res: res,
|
||||
}
|
||||
|
||||
go wr.stream()
|
||||
|
||||
return wr, nil
|
||||
}
|
Reference in New Issue
Block a user