Runtime Namespace (#1547)

* Add context option to runtime; Add dynamic namespace to kubectl client

* Add namespace runtime arg

* Fixes & Debugging

* Pass options in k8s runtime

* Set namespace on k8s resources

* Additional Logging

* More debugging

* Remove Debugging

* Ensure namespace exists

* Add debugging

* Refactor namespaceExists check

* Fix

* Fix

* Fix

* Fix

* Change the way we check for namespace

* Fix

* Tidying Up

* Fix Test

* Fix merge bugs

* Serialize k8s namespaces

* Add namespace to watch

* Serialize namespace when creating k8s namespace

Co-authored-by: Ben Toogood <ben@micro.mu>
Co-authored-by: Asim Aslam <asim@aslam.me>
This commit is contained in:
ben-toogood
2020-04-23 13:53:42 +01:00
committed by GitHub
parent 7345ce9192
commit 692b27578c
14 changed files with 411 additions and 109 deletions

View File

@@ -75,7 +75,9 @@ func (r *Request) Delete() *Request {
// Namespace is to set the namespace to operate on
func (r *Request) Namespace(s string) *Request {
r.namespace = s
if len(s) > 0 {
r.namespace = s
}
return r
}
@@ -158,6 +160,9 @@ func (r *Request) SetHeader(key, value string) *Request {
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 "pod", "service", "endpoint":
// /api/v1/namespaces/{namespace}/pods
url = fmt.Sprintf("%s/api/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"os"
"path"
"regexp"
"strings"
"github.com/micro/go-micro/v2/logger"
@@ -23,6 +24,8 @@ var (
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 ...
@@ -33,41 +36,28 @@ type client struct {
// Kubernetes client
type Client interface {
// Create creates new API resource
Create(*Resource) error
Create(*Resource, ...CreateOption) error
// Get queries API resrouces
Get(*Resource, map[string]string) error
Get(*Resource, ...GetOption) error
// Update patches existing API object
Update(*Resource) error
Update(*Resource, ...UpdateOption) error
// Delete deletes API resource
Delete(*Resource) error
Delete(*Resource, ...DeleteOption) error
// List lists API resources
List(*Resource) error
List(*Resource, ...ListOption) 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) {
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 {
func (c *client) Create(r *Resource, opts ...CreateOption) error {
var options CreateOptions
for _, o := range opts {
o(&options)
}
b := new(bytes.Buffer)
if err := renderTemplate(r.Kind, b, r.Value); err != nil {
return err
@@ -76,18 +66,35 @@ func (c *client) Create(r *Resource) error {
return api.NewRequest(c.opts).
Post().
SetHeader("Content-Type", "application/yaml").
Namespace(options.Namespace).
Resource(r.Kind).
Body(b).
Do().
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, labels map[string]string) error {
func (c *client) Get(r *Resource, opts ...GetOption) error {
var options GetOptions
for _, o := range opts {
o(&options)
}
return api.NewRequest(c.opts).
Get().
Resource(r.Kind).
Params(&api.Params{LabelSelector: labels}).
Namespace(options.Namespace).
Params(&api.Params{LabelSelector: options.Labels}).
Do().
Into(r.Value)
}
@@ -103,7 +110,8 @@ func (c *client) Log(r *Resource, opts ...LogOption) (io.ReadCloser, error) {
Get().
Resource(r.Kind).
SubResource("log").
Name(r.Name)
Name(r.Name).
Namespace(options.Namespace)
if options.Params != nil {
req.Params(&api.Params{Additional: options.Params})
@@ -121,12 +129,18 @@ func (c *client) Log(r *Resource, opts ...LogOption) (io.ReadCloser, error) {
}
// Update updates API object
func (c *client) Update(r *Resource) error {
func (c *client) Update(r *Resource, opts ...UpdateOption) error {
var options UpdateOptions
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)
Name(r.Name).
Namespace(options.Namespace)
switch r.Kind {
case "service":
@@ -143,21 +157,33 @@ func (c *client) Update(r *Resource) error {
}
// Delete removes API object
func (c *client) Delete(r *Resource) error {
func (c *client) Delete(r *Resource, opts ...DeleteOption) error {
var options DeleteOptions
for _, o := range opts {
o(&options)
}
return api.NewRequest(c.opts).
Delete().
Resource(r.Kind).
Name(r.Name).
Namespace(options.Namespace).
Do().
Error()
}
// List lists API objects and stores the result in r
func (c *client) List(r *Resource) error {
func (c *client) List(r *Resource, opts ...ListOption) error {
var options ListOptions
for _, o := range opts {
o(&options)
}
labels := map[string]string{
"micro": "service",
}
return c.Get(r, labels)
return c.Get(r, GetLabels(labels), GetNamespace(options.Namespace))
}
// Watch returns an event stream
@@ -183,13 +209,14 @@ func (c *client) Watch(r *Resource, opts ...WatchOption) (Watcher, error) {
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 string) *Service {
func NewService(name, version, typ, namespace string) *Service {
if logger.V(logger.TraceLevel, logger.DefaultLogger) {
logger.Tracef("kubernetes default service: name: %s, version: %s", name, version)
}
@@ -208,7 +235,7 @@ func NewService(name, version, typ string) *Service {
Metadata := &Metadata{
Name: svcName,
Namespace: "default",
Namespace: SerializeResourceName(namespace),
Version: version,
Labels: Labels,
}
@@ -228,7 +255,7 @@ func NewService(name, version, typ string) *Service {
}
// NewService returns default micro kubernetes deployment definition
func NewDeployment(name, version, typ string) *Deployment {
func NewDeployment(name, version, typ, namespace string) *Deployment {
if logger.V(logger.TraceLevel, logger.DefaultLogger) {
logger.Tracef("kubernetes default deployment: name: %s, version: %s", name, version)
}
@@ -247,7 +274,7 @@ func NewDeployment(name, version, typ string) *Deployment {
Metadata := &Metadata{
Name: depName,
Namespace: "default",
Namespace: SerializeResourceName(namespace),
Version: version,
Labels: Labels,
Annotations: map[string]string{},
@@ -319,11 +346,6 @@ func NewClusterClient() *client {
}
t := string(token)
ns, err := detectNamespace()
if err != nil {
logger.Fatal(err)
}
crt, err := CertPoolFromFile(path.Join(serviceAccountPath, "ca.crt"))
if err != nil {
logger.Fatal(err)
@@ -342,8 +364,8 @@ func NewClusterClient() *client {
opts: &api.Options{
Client: c,
Host: host,
Namespace: ns,
BearerToken: &t,
Namespace: DefaultNamespace,
},
}
}

View File

@@ -1,13 +1,38 @@
package client
type CreateOptions struct {
Namespace string
}
type GetOptions struct {
Namespace string
Labels map[string]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
Params map[string]string
}
type WatchOptions struct {
Params map[string]string
Namespace string
Params map[string]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)
@@ -24,3 +49,59 @@ func WatchParams(p map[string]string) WatchOption {
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)
}
}

View File

@@ -183,3 +183,8 @@ type Template struct {
type Namespace struct {
Metadata *Metadata `json:"metadata,omitempty"`
}
// NamespaceList
type NamespaceList struct {
Items []Namespace `json:"items"`
}

View File

@@ -9,16 +9,17 @@ func TestTemplates(t *testing.T) {
name := "foo"
version := "123"
typ := "service"
namespace := "default"
// Render default service
s := NewService(name, version, typ)
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)
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)