Milos Gajdos 97c1300f53 [WIP] Micro Runtime (#947)
* Add Get() and GetOptions.

* Removed watcher. Outline of client. YAML templates

* Added default service and deployment templates and types

* Added API tests and cleaned up errors.

* Small refactoring. Template package is no more.

* Ripped out existing code in preparation to small rework

* Reshuffled the source code to make it organized better

* Create service and deployment in kubernetes runtime

* Major cleanup and refactoring of Kubernetes runtime

* Service now handles low level K8s API calls across both K8s deployment
an service API objects
* Runtime has a task queue that serves for queueing runtime action
requests
* General refactoring

* No need for Lock in k8s service

* Added kubernetes runtime env var to default deployment

* Enable running different versions of the same service

* Can't delete services through labels

* Proto cruft. Added runtime.CreateOptions implementation in proto

* Removed proxy service from default env variables

* Make service name mandatory param to Get method

* Get Delete changes from https://github.com/micro/go-micro/pull/945

* Replaced template files with global variables

* Validate service names before sending K8s API request

* Refactored Kubernetes API client. Fixed typos.

* Added client.Resource to make API resources more explicit in code
2019-11-15 13:41:40 +00:00

162 lines
3.3 KiB
Go

package client
import (
"bytes"
"crypto/tls"
"errors"
"io/ioutil"
"net/http"
"os"
"path"
"github.com/micro/go-micro/runtime/kubernetes/client/api"
"github.com/micro/go-micro/util/log"
)
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")
)
// Client ...
type client struct {
opts *api.Options
}
// NewClientInCluster creates a Kubernetes client for use from within a k8s pod.
func NewClientInCluster() *client {
host := "https://" + os.Getenv("KUBERNETES_SERVICE_HOST") + ":" + os.Getenv("KUBERNETES_SERVICE_PORT")
s, err := os.Stat(serviceAccountPath)
if err != nil {
log.Fatal(err)
}
if s == nil || !s.IsDir() {
log.Fatal(errors.New("service account not found"))
}
token, err := ioutil.ReadFile(path.Join(serviceAccountPath, "token"))
if err != nil {
log.Fatal(err)
}
t := string(token)
ns, err := detectNamespace()
if err != nil {
log.Fatal(err)
}
crt, err := CertPoolFromFile(path.Join(serviceAccountPath, "ca.crt"))
if err != nil {
log.Fatal(err)
}
c := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: crt,
},
DisableCompression: true,
},
}
return &client{
opts: &api.Options{
Client: c,
Host: host,
Namespace: ns,
BearerToken: &t,
},
}
}
func detectNamespace() (string, error) {
nsPath := path.Join(serviceAccountPath, "namespace")
// Make sure it's a file and we can read it
if s, e := os.Stat(nsPath); e != nil {
return "", e
} else if s.IsDir() {
return "", ErrReadNamespace
}
// Read the file, and cast to a string
if ns, e := ioutil.ReadFile(nsPath); e != nil {
return string(ns), e
} else {
return string(ns), nil
}
}
// Create creates new API object
func (c *client) Create(r *Resource) error {
b := new(bytes.Buffer)
if err := renderTemplate(r.Kind, b, r.Value); err != nil {
return err
}
return api.NewRequest(c.opts).
Post().
SetHeader("Content-Type", "application/yaml").
Resource(r.Kind).
Body(b).
Do().
Error()
}
// Get queries API objects and stores the result in r
func (c *client) Get(r *Resource, labels map[string]string) error {
return api.NewRequest(c.opts).
Get().
Resource(r.Kind).
Params(&api.Params{LabelSelector: labels}).
Do().
Into(r.Value)
}
// Update updates API object
func (c *client) Update(r *Resource) error {
req := api.NewRequest(c.opts).
Patch().
SetHeader("Content-Type", "application/strategic-merge-patch+json").
Resource(r.Kind).
Name(r.Name)
switch r.Kind {
case "service":
req.Body(r.Value.(*Service).Spec)
case "deployment":
req.Body(r.Value.(*Deployment).Spec)
default:
return errors.New("unsupported resource")
}
return req.Do().Error()
}
// Delete removes API object
func (c *client) Delete(r *Resource) error {
return api.NewRequest(c.opts).
Delete().
Resource(r.Kind).
Name(r.Name).
Do().
Error()
}
// List lists API objects and stores the result in r
func (c *client) List(r *Resource) error {
labels := map[string]string{
"micro": "service",
}
return api.NewRequest(c.opts).
Get().
Resource(r.Kind).
Params(&api.Params{LabelSelector: labels}).
Do().
Into(r.Value)
}