many lint fixes and optimizations #17
| @@ -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