Kubernetes Registry (#1064)

* add teh k8s registry

* add k8s reg config/cmd

* go mod update
This commit is contained in:
Asim Aslam
2019-12-27 20:08:46 +00:00
committed by GitHub
parent 22aa7d14b3
commit 61cde4a9f4
14 changed files with 950 additions and 236 deletions

View File

@@ -2,6 +2,7 @@ package api
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
@@ -12,6 +13,8 @@ import (
// Request is used to construct a http request for the k8s API.
type Request struct {
// the request context
context context.Context
client *http.Client
header http.Header
params url.Values
@@ -41,6 +44,10 @@ func (r *Request) verb(method string) *Request {
return r
}
func (r *Request) Context(ctx context.Context) {
r.context = ctx
}
// Get request
func (r *Request) Get() *Request {
return r.verb("GET")
@@ -172,8 +179,15 @@ func (r *Request) request() (*http.Request, error) {
url += "?" + r.params.Encode()
}
var req *http.Request
var err error
// build request
req, err := http.NewRequest(r.method, url, r.body)
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
}

View File

@@ -58,6 +58,10 @@ func (r *Response) Into(data interface{}) error {
return r.err
}
func (r *Response) Close() error {
return r.res.Body.Close()
}
func newResponse(res *http.Response, err error) *Response {
r := &Response{
res: res,

View File

@@ -10,7 +10,6 @@ import (
"net/http"
"os"
"path"
"strconv"
"strings"
"github.com/micro/go-micro/util/kubernetes/api"
@@ -45,6 +44,8 @@ type Client interface {
List(*Resource) error
// Log gets log for a pod
Log(*Resource, ...LogOption) (io.ReadCloser, error)
// Watch for events
Watch(*Resource, ...WatchOption) (Watcher, error)
}
func detectNamespace() (string, error) {
@@ -132,6 +133,8 @@ func (c *client) Update(r *Resource) error {
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")
}
@@ -157,6 +160,34 @@ func (c *client) List(r *Resource) error {
return c.Get(r, labels)
}
// Watch returns an event stream
func (c *client) Watch(r *Resource, opts ...WatchOption) (Watcher, error) {
var options WatchOptions
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).
Params(params)
return newWatcher(req)
}
// NewService returns default micro kubernetes service definition
func NewService(name, version, typ string) *Service {
log.Tracef("kubernetes default service: name: %s, version: %s", name, version)
@@ -252,28 +283,22 @@ func NewDeployment(name, version, typ string) *Deployment {
}
}
// NewLocalDevClient returns a client that can be used with `kubectl proxy` on an optional port
func NewLocalDevClient(port ...int) *client {
var p int
if len(port) > 1 {
log.Fatal("Expected 0 or 1 port parameters")
}
if len(port) == 0 {
p = 8001
} else {
p = port[0]
// 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: "http://localhost:" + strconv.Itoa(p),
Host: hosts[0],
Namespace: "default",
},
}
}
// NewClientInCluster creates a Kubernetes client for use from within a k8s pod.
func NewClientInCluster() *client {
// 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)

View File

@@ -4,7 +4,12 @@ type LogOptions struct {
Params map[string]string
}
type WatchOptions struct {
Params map[string]string
}
type LogOption func(*LogOptions)
type WatchOption func(*WatchOptions)
// LogParams provides additional params for logs
func LogParams(p map[string]string) LogOption {
@@ -12,3 +17,10 @@ func LogParams(p map[string]string) LogOption {
l.Params = p
}
}
// WatchParams used for watch params
func WatchParams(p map[string]string) WatchOption {
return func(w *WatchOptions) {
w.Params = p
}
}

View File

@@ -1,61 +1,5 @@
package client
// Resource is API resource
type Resource struct {
Name string
Kind string
Value interface{}
}
// Metadata defines api object metadata
type Metadata struct {
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
Version string `json:"version,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
}
// ServicePort configures service ports
type ServicePort struct {
Name string `json:"name,omitempty"`
Port int `json:"port"`
Protocol string `json:"protocol,omitempty"`
}
// ServiceSpec provides service configuration
type ServiceSpec struct {
Type string `json:"type,omitempty"`
Selector map[string]string `json:"selector,omitempty"`
Ports []ServicePort `json:"ports,omitempty"`
}
type LoadBalancerIngress struct {
IP string `json:"ip,omitempty"`
Hostname string `json:"hostname,omitempty"`
}
type LoadBalancerStatus struct {
Ingress []LoadBalancerIngress `json:"ingress,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"`
}
// ContainerPort
type ContainerPort struct {
Name string `json:"name,omitempty"`
@@ -79,23 +23,6 @@ type Container struct {
Ports []ContainerPort `json:"ports,omitempty"`
}
// PodSpec is a pod
type PodSpec struct {
Containers []Container `json:"containers"`
}
// Template is micro deployment template
type Template struct {
Metadata *Metadata `json:"metadata,omitempty"`
PodSpec *PodSpec `json:"spec,omitempty"`
}
// 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"`
}
// DeploymentSpec defines micro deployment spec
type DeploymentSpec struct {
Replicas int `json:"replicas,omitempty"`
@@ -132,6 +59,93 @@ type DeploymentList struct {
Items []Deployment `json:"items"`
}
type PodList struct {
Items []Template `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 {
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
Version string `json:"version,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
}
// PodSpec is a pod
type PodSpec struct {
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"`
}
// Resource is API resource
type Resource struct {
Name string
Kind string
Value interface{}
}
// ServicePort configures service ports
type ServicePort struct {
Name string `json:"name,omitempty"`
Port int `json:"port"`
Protocol string `json:"protocol,omitempty"`
}
// ServiceSpec provides service configuration
type ServiceSpec struct {
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"`
}

View File

@@ -0,0 +1,122 @@
package client
import (
"bufio"
"context"
"encoding/json"
"errors"
"net/http"
"github.com/micro/go-micro/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 {
return nil, err
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
// 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
}