kubernetes: drop stale files
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
		| @@ -1,190 +0,0 @@ | |||||||
| // Package kubernetes is a logger implementing (github.com/unistack-org/micro/v3/debug/log).Log |  | ||||||
| package kubernetes |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bufio" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"os" |  | ||||||
| 	"sort" |  | ||||||
| 	"strconv" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/debug/log" |  | ||||||
| 	"github.com/unistack-org/micro/v3/metadata" |  | ||||||
| 	"github.com/unistack-org/micro/v3/util/kubernetes/client" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type klog struct { |  | ||||||
| 	client client.Client |  | ||||||
|  |  | ||||||
| 	log.Options |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (k *klog) podLogStream(podName string, stream *kubeStream) { |  | ||||||
| 	p := make(map[string]string) |  | ||||||
| 	p["follow"] = "true" |  | ||||||
|  |  | ||||||
| 	// get the logs for the pod |  | ||||||
| 	body, err := k.client.Log(&client.Resource{ |  | ||||||
| 		Name: podName, |  | ||||||
| 		Kind: "pod", |  | ||||||
| 	}, client.LogParams(p)) |  | ||||||
|  |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Fprintf(os.Stderr, "%v", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	s := bufio.NewScanner(body) |  | ||||||
| 	defer body.Close() |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		select { |  | ||||||
| 		case <-stream.stop: |  | ||||||
| 			return |  | ||||||
| 		default: |  | ||||||
| 			if s.Scan() { |  | ||||||
| 				record := k.parse(s.Text()) |  | ||||||
| 				stream.stream <- record |  | ||||||
| 			} else { |  | ||||||
| 				// TODO: is there a blocking call |  | ||||||
| 				// rather than a sleep loop? |  | ||||||
| 				time.Sleep(time.Second) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (k *klog) getMatchingPods() ([]string, error) { |  | ||||||
| 	r := &client.Resource{ |  | ||||||
| 		Kind:  "pod", |  | ||||||
| 		Value: new(client.PodList), |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	l := make(map[string]string) |  | ||||||
|  |  | ||||||
| 	l["name"] = client.Format(k.Options.Name) |  | ||||||
| 	// TODO: specify micro:service |  | ||||||
| 	// l["micro"] = "service" |  | ||||||
|  |  | ||||||
| 	if err := k.client.Get(r, client.GetLabels(l)); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var matches []string |  | ||||||
|  |  | ||||||
| 	for _, p := range r.Value.(*client.PodList).Items { |  | ||||||
| 		// find labels that match the name |  | ||||||
| 		if p.Metadata.Labels["name"] == client.Format(k.Options.Name) { |  | ||||||
| 			matches = append(matches, p.Metadata.Name) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return matches, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (k *klog) parse(line string) log.Record { |  | ||||||
| 	record := log.Record{} |  | ||||||
|  |  | ||||||
| 	if err := json.Unmarshal([]byte(line), &record); err != nil { |  | ||||||
| 		record.Timestamp = time.Now().UTC() |  | ||||||
| 		record.Message = line |  | ||||||
| 		record.Metadata = metadata.New(1) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	record.Metadata["service"] = k.Options.Name |  | ||||||
|  |  | ||||||
| 	return record |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (k *klog) Read(options ...log.ReadOption) ([]log.Record, error) { |  | ||||||
| 	opts := &log.ReadOptions{} |  | ||||||
| 	for _, o := range options { |  | ||||||
| 		o(opts) |  | ||||||
| 	} |  | ||||||
| 	pods, err := k.getMatchingPods() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var records []log.Record |  | ||||||
|  |  | ||||||
| 	for _, pod := range pods { |  | ||||||
| 		logParams := make(map[string]string) |  | ||||||
|  |  | ||||||
| 		if !opts.Since.Equal(time.Time{}) { |  | ||||||
| 			logParams["sinceSeconds"] = strconv.Itoa(int(time.Since(opts.Since).Seconds())) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if opts.Count != 0 { |  | ||||||
| 			logParams["tailLines"] = strconv.Itoa(opts.Count) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if opts.Stream { |  | ||||||
| 			logParams["follow"] = "true" |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		logs, err := k.client.Log(&client.Resource{ |  | ||||||
| 			Name: pod, |  | ||||||
| 			Kind: "pod", |  | ||||||
| 		}, client.LogParams(logParams)) |  | ||||||
|  |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		defer logs.Close() |  | ||||||
|  |  | ||||||
| 		s := bufio.NewScanner(logs) |  | ||||||
|  |  | ||||||
| 		for s.Scan() { |  | ||||||
| 			record := k.parse(s.Text()) |  | ||||||
| 			record.Metadata["pod"] = pod |  | ||||||
| 			records = append(records, record) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// sort the records |  | ||||||
| 	sort.Slice(records, func(i, j int) bool { return records[i].Timestamp.Before(records[j].Timestamp) }) |  | ||||||
|  |  | ||||||
| 	return records, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (k *klog) Write(l log.Record) error { |  | ||||||
| 	return write(l) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (k *klog) Stream() (log.Stream, error) { |  | ||||||
| 	// find the matching pods |  | ||||||
| 	pods, err := k.getMatchingPods() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	stream := &kubeStream{ |  | ||||||
| 		stream: make(chan log.Record), |  | ||||||
| 		stop:   make(chan bool), |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// stream from the individual pods |  | ||||||
| 	for _, pod := range pods { |  | ||||||
| 		go k.podLogStream(pod, stream) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return stream, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewLog returns a configured Kubernetes logger |  | ||||||
| func NewLog(opts ...log.Option) log.Log { |  | ||||||
| 	klog := &klog{} |  | ||||||
| 	for _, o := range opts { |  | ||||||
| 		o(&klog.Options) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(os.Getenv("KUBERNETES_SERVICE_HOST")) > 0 { |  | ||||||
| 		klog.client = client.NewClusterClient() |  | ||||||
| 	} else { |  | ||||||
| 		klog.client = client.NewLocalClient() |  | ||||||
| 	} |  | ||||||
| 	return klog |  | ||||||
| } |  | ||||||
| @@ -1,71 +0,0 @@ | |||||||
| package kubernetes |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" |  | ||||||
| 	"github.com/unistack-org/micro/v3/debug/log" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func TestKubernetes(t *testing.T) { |  | ||||||
| 	if len(os.Getenv("INTEGRATION_TESTS")) > 0 { |  | ||||||
| 		t.Skip() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	k := NewLog(log.Name("micro-network")) |  | ||||||
|  |  | ||||||
| 	r, w, err := os.Pipe() |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	s := os.Stderr |  | ||||||
| 	os.Stderr = w |  | ||||||
| 	meta := make(map[string]string) |  | ||||||
|  |  | ||||||
| 	write := log.Record{ |  | ||||||
| 		Timestamp: time.Unix(0, 0).UTC(), |  | ||||||
| 		Message:   "Test log entry", |  | ||||||
| 		Metadata:  meta, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	meta["foo"] = "bar" |  | ||||||
|  |  | ||||||
| 	k.Write(write) |  | ||||||
| 	b := &bytes.Buffer{} |  | ||||||
| 	w.Close() |  | ||||||
| 	io.Copy(b, r) |  | ||||||
| 	os.Stderr = s |  | ||||||
|  |  | ||||||
| 	var read log.Record |  | ||||||
|  |  | ||||||
| 	if err := json.Unmarshal(b.Bytes(), &read); err != nil { |  | ||||||
| 		t.Fatalf("json.Unmarshal failed: %s", err.Error()) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	assert.Equal(t, write, read, "Write was not equal") |  | ||||||
|  |  | ||||||
| 	records, err := k.Read() |  | ||||||
| 	assert.Nil(t, err, "Read should not error") |  | ||||||
| 	assert.NotNil(t, records, "Read should return records") |  | ||||||
|  |  | ||||||
| 	stream, err := k.Stream() |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	records = nil |  | ||||||
|  |  | ||||||
| 	go stream.Stop() |  | ||||||
|  |  | ||||||
| 	for s := range stream.Chan() { |  | ||||||
| 		records = append(records, s) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	assert.Equal(t, 0, len(records), "Stream should return nothing") |  | ||||||
| } |  | ||||||
| @@ -1,44 +0,0 @@ | |||||||
| package kubernetes |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"os" |  | ||||||
| 	"sync" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/debug/log" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func write(l log.Record) error { |  | ||||||
| 	m, err := json.Marshal(l) |  | ||||||
| 	if err == nil { |  | ||||||
| 		_, err := fmt.Fprintf(os.Stderr, "%s", m) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type kubeStream struct { |  | ||||||
| 	// the k8s log stream |  | ||||||
| 	stream chan log.Record |  | ||||||
| 	sync.Mutex |  | ||||||
| 	// the stop chan |  | ||||||
| 	stop chan bool |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (k *kubeStream) Chan() <-chan log.Record { |  | ||||||
| 	return k.stream |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (k *kubeStream) Stop() error { |  | ||||||
| 	k.Lock() |  | ||||||
| 	defer k.Unlock() |  | ||||||
| 	select { |  | ||||||
| 	case <-k.stop: |  | ||||||
| 		return nil |  | ||||||
| 	default: |  | ||||||
| 		close(k.stop) |  | ||||||
| 		close(k.stream) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| @@ -1,178 +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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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,271 +0,0 @@ | |||||||
| package api |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"context" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/url" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/logger" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // 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 |  | ||||||
| 	method    string |  | ||||||
| 	host      string |  | ||||||
| 	namespace string |  | ||||||
|  |  | ||||||
| 	resource     string |  | ||||||
| 	resourceName *string |  | ||||||
| 	subResource  *string |  | ||||||
| 	body         io.Reader |  | ||||||
|  |  | ||||||
| 	err error |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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 sub resource 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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Params is used to set parameters on a request |  | ||||||
| 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.Debug(context.TODO(), "[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 { |  | ||||||
| 	Host        string |  | ||||||
| 	Namespace   string |  | ||||||
| 	BearerToken *string |  | ||||||
| 	Client      *http.Client |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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,94 +0,0 @@ | |||||||
| package api |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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 { |  | ||||||
| 	return r.res.Body.Close() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 := ioutil.ReadAll(r.res.Body) |  | ||||||
| 	if err == nil { |  | ||||||
| 		r.err = errors.New(string(b)) |  | ||||||
| 		return r |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	r.err = ErrUnknown |  | ||||||
|  |  | ||||||
| 	return r |  | ||||||
| } |  | ||||||
| @@ -1,401 +0,0 @@ | |||||||
| // Package client provides an implementation of a restricted subset of kubernetes API client |  | ||||||
| package client |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"context" |  | ||||||
| 	"crypto/tls" |  | ||||||
| 	"errors" |  | ||||||
| 	"io" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os" |  | ||||||
| 	"path" |  | ||||||
| 	"regexp" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/logger" |  | ||||||
| 	"github.com/unistack-org/micro/v3/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/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 resources |  | ||||||
| 	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 |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	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, 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") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return req.Do().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) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	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, 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.Trace(context.TODO(), "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{{ |  | ||||||
| 			"service-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.Trace(context.TODO(), "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{ |  | ||||||
| 				Containers: []Container{{ |  | ||||||
| 					Name:    name, |  | ||||||
| 					Image:   DefaultImage, |  | ||||||
| 					Env:     []EnvVar{env}, |  | ||||||
| 					Command: []string{}, |  | ||||||
| 					Ports: []ContainerPort{{ |  | ||||||
| 						Name:          "service-port", |  | ||||||
| 						ContainerPort: 8080, |  | ||||||
| 					}}, |  | ||||||
| 					ReadinessProbe: &Probe{ |  | ||||||
| 						TCPSocket: TCPSocketAction{ |  | ||||||
| 							Port: 8080, |  | ||||||
| 						}, |  | ||||||
| 						PeriodSeconds:       10, |  | ||||||
| 						InitialDelaySeconds: 10, |  | ||||||
| 					}, |  | ||||||
| 				}}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return &Deployment{ |  | ||||||
| 		Metadata: Metadata, |  | ||||||
| 		Spec:     Spec, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewLocalClient returns a client that can be used with `kubectl proxy` |  | ||||||
| func NewLocalClient(hosts ...string) *client { |  | ||||||
| 	c := &client{ |  | ||||||
| 		opts: &api.Options{ |  | ||||||
| 			Client:    http.DefaultClient, |  | ||||||
| 			Namespace: "default", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(hosts) == 0 { |  | ||||||
| 		c.opts.Host = "http://localhost:8001" |  | ||||||
| 	} else { |  | ||||||
| 		c.opts.Host = hosts[0] |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return c |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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(context.TODO(), err.Error()) |  | ||||||
| 	} |  | ||||||
| 	if s == nil || !s.IsDir() { |  | ||||||
| 		logger.Fatal(context.TODO(), "service account not found") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	token, err := ioutil.ReadFile(path.Join(serviceAccountPath, "token")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		logger.Fatal(context.TODO(), err.Error()) |  | ||||||
| 	} |  | ||||||
| 	t := string(token) |  | ||||||
|  |  | ||||||
| 	crt, err := CertPoolFromFile(path.Join(serviceAccountPath, "ca.crt")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		logger.Fatal(context.TODO(), err.Error()) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	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 { |  | ||||||
| 	Namespace string |  | ||||||
| 	Labels    map[string]string |  | ||||||
| } |  | ||||||
| type UpdateOptions struct { |  | ||||||
| 	Namespace string |  | ||||||
| } |  | ||||||
| type DeleteOptions struct { |  | ||||||
| 	Namespace string |  | ||||||
| } |  | ||||||
| type ListOptions struct { |  | ||||||
| 	Namespace string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type LogOptions struct { |  | ||||||
| 	Namespace string |  | ||||||
| 	Params    map[string]string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type WatchOptions struct { |  | ||||||
| 	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) |  | ||||||
|  |  | ||||||
| // 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,227 +0,0 @@ | |||||||
| package client |  | ||||||
|  |  | ||||||
| var templates = map[string]string{ |  | ||||||
| 	"deployment":     deploymentTmpl, |  | ||||||
| 	"service":        serviceTmpl, |  | ||||||
| 	"namespace":      namespaceTmpl, |  | ||||||
| 	"secret":         secretTmpl, |  | ||||||
| 	"serviceaccount": serviceAccountTmpl, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 }}" |  | ||||||
|           {{- if .ValueFrom }} |  | ||||||
|           {{- with .ValueFrom }} |  | ||||||
|             valueFrom:  |  | ||||||
|               {{- if .SecretKeyRef }} |  | ||||||
|               {{- with .SecretKeyRef }} |  | ||||||
|               secretKeyRef: |  | ||||||
|                 key: {{ .Key }} |  | ||||||
|                 name: {{ .Name }} |  | ||||||
|                 optional: {{ .Optional }} |  | ||||||
|               {{- end }} |  | ||||||
|               {{- end }} |  | ||||||
|           {{- end }} |  | ||||||
|           {{- end }} |  | ||||||
|           {{- end }} |  | ||||||
|           {{- end }} |  | ||||||
|           args: |  | ||||||
|           {{- range .Args }} |  | ||||||
|           - {{.}} |  | ||||||
|           {{- end }} |  | ||||||
|           command: |  | ||||||
|           {{- range .Command }} |  | ||||||
|           - {{.}} |  | ||||||
|           {{- end }} |  | ||||||
|           image: {{ .Image }} |  | ||||||
|           imagePullPolicy: Always |  | ||||||
|           ports: |  | ||||||
|           {{- with .Ports }} |  | ||||||
|           {{- range . }} |  | ||||||
|           - containerPort: {{ .ContainerPort }} |  | ||||||
|             name: {{ .Name }} |  | ||||||
|           {{- end }} |  | ||||||
|           {{- end }} |  | ||||||
|           {{- if .ReadinessProbe }} |  | ||||||
|           {{- with .ReadinessProbe }} |  | ||||||
|           readinessProbe: |  | ||||||
|             {{- with .TCPSocket }} |  | ||||||
|             tcpSocket: |  | ||||||
|               {{- if .Host }} |  | ||||||
|               host: {{ .Host }} |  | ||||||
|               {{- end }} |  | ||||||
|               port: {{ .Port }} |  | ||||||
|             {{- end }} |  | ||||||
|             initialDelaySeconds: {{ .InitialDelaySeconds }} |  | ||||||
|             periodSeconds: {{ .PeriodSeconds }} |  | ||||||
|           {{- end }} |  | ||||||
|           {{- end }} |  | ||||||
|           {{- if .Resources }} |  | ||||||
|           {{- with .Resources }} |  | ||||||
|           resources: |  | ||||||
|             {{- if .Limits }} |  | ||||||
|             {{- with .Limits }} |  | ||||||
|             limits: |  | ||||||
|               {{- if .Memory }} |  | ||||||
|               memory: {{ .Memory }} |  | ||||||
|               {{- end }} |  | ||||||
|               {{- if .CPU }} |  | ||||||
|               cpu: {{ .CPU }} |  | ||||||
|               {{- end }} |  | ||||||
|               {{- if .EphemeralStorage }} |  | ||||||
|               ephemeral-storage: {{ .EphemeralStorage }} |  | ||||||
|               {{- end }} |  | ||||||
|             {{- end }} |  | ||||||
|             {{- end }} |  | ||||||
|             {{- if .Requests }} |  | ||||||
|             {{- with .Requests }} |  | ||||||
|             requests: |  | ||||||
|               {{- if .Memory }} |  | ||||||
|               memory: {{ .Memory }} |  | ||||||
|               {{- end }} |  | ||||||
|               {{- if .CPU }} |  | ||||||
|               cpu: {{ .CPU }} |  | ||||||
|               {{- end }} |  | ||||||
|               {{- if .EphemeralStorage }} |  | ||||||
|               ephemeral-storage: {{ .EphemeralStorage }} |  | ||||||
|               {{- end }} |  | ||||||
|             {{- end }} |  | ||||||
|             {{- end }} |  | ||||||
|           {{- 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 }} |  | ||||||
| ` |  | ||||||
|  |  | ||||||
| //nolint:gosec |  | ||||||
| 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,250 +0,0 @@ | |||||||
| package client |  | ||||||
|  |  | ||||||
| // ContainerPort |  | ||||||
| type ContainerPort struct { |  | ||||||
| 	Name          string `json:"name,omitempty"` |  | ||||||
| 	HostPort      int    `json:"hostPort,omitempty"` |  | ||||||
| 	ContainerPort int    `json:"containerPort"` |  | ||||||
| 	Protocol      string `json:"protocol,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // EnvVar is environment variable |  | ||||||
| type EnvVar struct { |  | ||||||
| 	Name      string        `json:"name"` |  | ||||||
| 	Value     string        `json:"value,omitempty"` |  | ||||||
| 	ValueFrom *EnvVarSource `json:"valueFrom,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // EnvVarSource represents a source for the value of an EnvVar. |  | ||||||
| type EnvVarSource struct { |  | ||||||
| 	SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SecretKeySelector selects a key of a Secret. |  | ||||||
| type SecretKeySelector struct { |  | ||||||
| 	Key      string `json:"key"` |  | ||||||
| 	Name     string `json:"name"` |  | ||||||
| 	Optional bool   `json:"optional,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"` |  | ||||||
| 	ReadinessProbe *Probe                `json:"readinessProbe,omitempty"` |  | ||||||
| 	Resources      *ResourceRequirements `json:"resources,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // DeploymentSpec defines micro deployment spec |  | ||||||
| type DeploymentSpec struct { |  | ||||||
| 	Replicas int            `json:"replicas,omitempty"` |  | ||||||
| 	Selector *LabelSelector `json:"selector"` |  | ||||||
| 	Template *Template      `json:"template,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 { |  | ||||||
| 	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"` |  | ||||||
| 	Conditions          []DeploymentCondition `json:"conditions,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 { |  | ||||||
| 	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"` |  | ||||||
| 	ServiceAccountName string      `json:"serviceAccountName"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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 { |  | ||||||
| 	Conditions []PodCondition    `json:"conditions,omitempty"` |  | ||||||
| 	Containers []ContainerStatus `json:"containerStatuses"` |  | ||||||
| 	PodIP      string            `json:"podIP"` |  | ||||||
| 	Phase      string            `json:"phase"` |  | ||||||
| 	Reason     string            `json:"reason"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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 { |  | ||||||
| 	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 { |  | ||||||
| 	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 { |  | ||||||
| 	Type     string            `json:"type,omitempty"` |  | ||||||
| 	Data     map[string]string `json:"data"` |  | ||||||
| 	Metadata *Metadata         `json:"metadata,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ServiceAccount |  | ||||||
| type ServiceAccount struct { |  | ||||||
| 	Metadata         *Metadata         `json:"metadata,omitempty"` |  | ||||||
| 	ImagePullSecrets []ImagePullSecret `json:"imagePullSecrets,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic. |  | ||||||
| type Probe struct { |  | ||||||
| 	TCPSocket           TCPSocketAction `json:"tcpSocket,omitempty"` |  | ||||||
| 	PeriodSeconds       int             `json:"periodSeconds"` |  | ||||||
| 	InitialDelaySeconds int             `json:"initialDelaySeconds"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TCPSocketAction describes an action based on opening a socket |  | ||||||
| type TCPSocketAction struct { |  | ||||||
| 	Host string `json:"host,omitempty"` |  | ||||||
| 	Port int    `json:"port,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ResourceRequirements describes the compute resource requirements. |  | ||||||
| type ResourceRequirements struct { |  | ||||||
| 	Limits   *ResourceLimits `json:"limits,omitempty"` |  | ||||||
| 	Requests *ResourceLimits `json:"requests,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ResourceLimits describes the limits for a service |  | ||||||
| type ResourceLimits struct { |  | ||||||
| 	Memory           string `json:"memory,omitempty"` |  | ||||||
| 	CPU              string `json:"cpu,omitempty"` |  | ||||||
| 	EphemeralStorage string `json:"ephemeral-storage,omitempty"` |  | ||||||
| } |  | ||||||
| @@ -1,104 +0,0 @@ | |||||||
| package client |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"crypto/x509" |  | ||||||
| 	"encoding/pem" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"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 := ioutil.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"}, |  | ||||||
| 		{"micro.foo.bar", "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" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/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